diff options
Diffstat (limited to 'server')
186 files changed, 2511 insertions, 1810 deletions
diff --git a/server/controllers/activitypub/client.ts b/server/controllers/activitypub/client.ts index 1b4acc234..1982e171d 100644 --- a/server/controllers/activitypub/client.ts +++ b/server/controllers/activitypub/client.ts | |||
@@ -30,7 +30,7 @@ import { videoFileRedundancyGetValidator, videoPlaylistRedundancyGetValidator } | |||
30 | import { videoPlaylistElementAPGetValidator, videoPlaylistsGetValidator } from '../../middlewares/validators/videos/video-playlists' | 30 | import { videoPlaylistElementAPGetValidator, videoPlaylistsGetValidator } from '../../middlewares/validators/videos/video-playlists' |
31 | import { AccountModel } from '../../models/account/account' | 31 | import { AccountModel } from '../../models/account/account' |
32 | import { AccountVideoRateModel } from '../../models/account/account-video-rate' | 32 | import { AccountVideoRateModel } from '../../models/account/account-video-rate' |
33 | import { ActorFollowModel } from '../../models/activitypub/actor-follow' | 33 | import { ActorFollowModel } from '../../models/actor/actor-follow' |
34 | import { VideoModel } from '../../models/video/video' | 34 | import { VideoModel } from '../../models/video/video' |
35 | import { VideoCaptionModel } from '../../models/video/video-caption' | 35 | import { VideoCaptionModel } from '../../models/video/video-caption' |
36 | import { VideoCommentModel } from '../../models/video/video-comment' | 36 | import { VideoCommentModel } from '../../models/video/video-comment' |
diff --git a/server/controllers/activitypub/utils.ts b/server/controllers/activitypub/utils.ts index 599cf48ab..19bdd58eb 100644 --- a/server/controllers/activitypub/utils.ts +++ b/server/controllers/activitypub/utils.ts | |||
@@ -3,7 +3,6 @@ import * as express from 'express' | |||
3 | function activityPubResponse (data: any, res: express.Response) { | 3 | function activityPubResponse (data: any, res: express.Response) { |
4 | return res.type('application/activity+json; charset=utf-8') | 4 | return res.type('application/activity+json; charset=utf-8') |
5 | .json(data) | 5 | .json(data) |
6 | .end() | ||
7 | } | 6 | } |
8 | 7 | ||
9 | export { | 8 | export { |
diff --git a/server/controllers/api/config.ts b/server/controllers/api/config.ts index 2ddb73519..1f2a5f2da 100644 --- a/server/controllers/api/config.ts +++ b/server/controllers/api/config.ts | |||
@@ -1,8 +1,8 @@ | |||
1 | import { ServerConfigManager } from '@server/lib/server-config-manager' | ||
1 | import * as express from 'express' | 2 | import * as express from 'express' |
2 | import { remove, writeJSON } from 'fs-extra' | 3 | import { remove, writeJSON } from 'fs-extra' |
3 | import { snakeCase } from 'lodash' | 4 | import { snakeCase } from 'lodash' |
4 | import validator from 'validator' | 5 | import validator from 'validator' |
5 | import { getServerConfig } from '@server/lib/config' | ||
6 | import { UserRight } from '../../../shared' | 6 | import { UserRight } from '../../../shared' |
7 | import { About } from '../../../shared/models/server/about.model' | 7 | import { About } from '../../../shared/models/server/about.model' |
8 | import { CustomConfig } from '../../../shared/models/server/custom-config.model' | 8 | import { CustomConfig } from '../../../shared/models/server/custom-config.model' |
@@ -18,6 +18,7 @@ const configRouter = express.Router() | |||
18 | const auditLogger = auditLoggerFactory('config') | 18 | const auditLogger = auditLoggerFactory('config') |
19 | 19 | ||
20 | configRouter.get('/about', getAbout) | 20 | configRouter.get('/about', getAbout) |
21 | |||
21 | configRouter.get('/', | 22 | configRouter.get('/', |
22 | asyncMiddleware(getConfig) | 23 | asyncMiddleware(getConfig) |
23 | ) | 24 | ) |
@@ -27,12 +28,14 @@ configRouter.get('/custom', | |||
27 | ensureUserHasRight(UserRight.MANAGE_CONFIGURATION), | 28 | ensureUserHasRight(UserRight.MANAGE_CONFIGURATION), |
28 | getCustomConfig | 29 | getCustomConfig |
29 | ) | 30 | ) |
31 | |||
30 | configRouter.put('/custom', | 32 | configRouter.put('/custom', |
31 | authenticate, | 33 | authenticate, |
32 | ensureUserHasRight(UserRight.MANAGE_CONFIGURATION), | 34 | ensureUserHasRight(UserRight.MANAGE_CONFIGURATION), |
33 | customConfigUpdateValidator, | 35 | customConfigUpdateValidator, |
34 | asyncMiddleware(updateCustomConfig) | 36 | asyncMiddleware(updateCustomConfig) |
35 | ) | 37 | ) |
38 | |||
36 | configRouter.delete('/custom', | 39 | configRouter.delete('/custom', |
37 | authenticate, | 40 | authenticate, |
38 | ensureUserHasRight(UserRight.MANAGE_CONFIGURATION), | 41 | ensureUserHasRight(UserRight.MANAGE_CONFIGURATION), |
@@ -40,7 +43,7 @@ configRouter.delete('/custom', | |||
40 | ) | 43 | ) |
41 | 44 | ||
42 | async function getConfig (req: express.Request, res: express.Response) { | 45 | async function getConfig (req: express.Request, res: express.Response) { |
43 | const json = await getServerConfig(req.ip) | 46 | const json = await ServerConfigManager.Instance.getServerConfig(req.ip) |
44 | 47 | ||
45 | return res.json(json) | 48 | return res.json(json) |
46 | } | 49 | } |
@@ -67,13 +70,13 @@ function getAbout (req: express.Request, res: express.Response) { | |||
67 | } | 70 | } |
68 | } | 71 | } |
69 | 72 | ||
70 | return res.json(about).end() | 73 | return res.json(about) |
71 | } | 74 | } |
72 | 75 | ||
73 | function getCustomConfig (req: express.Request, res: express.Response) { | 76 | function getCustomConfig (req: express.Request, res: express.Response) { |
74 | const data = customConfig() | 77 | const data = customConfig() |
75 | 78 | ||
76 | return res.json(data).end() | 79 | return res.json(data) |
77 | } | 80 | } |
78 | 81 | ||
79 | async function deleteCustomConfig (req: express.Request, res: express.Response) { | 82 | async function deleteCustomConfig (req: express.Request, res: express.Response) { |
@@ -171,7 +174,8 @@ function customConfig (): CustomConfig { | |||
171 | signup: { | 174 | signup: { |
172 | enabled: CONFIG.SIGNUP.ENABLED, | 175 | enabled: CONFIG.SIGNUP.ENABLED, |
173 | limit: CONFIG.SIGNUP.LIMIT, | 176 | limit: CONFIG.SIGNUP.LIMIT, |
174 | requiresEmailVerification: CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION | 177 | requiresEmailVerification: CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION, |
178 | minimumAge: CONFIG.SIGNUP.MINIMUM_AGE | ||
175 | }, | 179 | }, |
176 | admin: { | 180 | admin: { |
177 | email: CONFIG.ADMIN.EMAIL | 181 | email: CONFIG.ADMIN.EMAIL |
diff --git a/server/controllers/api/custom-page.ts b/server/controllers/api/custom-page.ts new file mode 100644 index 000000000..3c47f7b9a --- /dev/null +++ b/server/controllers/api/custom-page.ts | |||
@@ -0,0 +1,42 @@ | |||
1 | import * as express from 'express' | ||
2 | import { ServerConfigManager } from '@server/lib/server-config-manager' | ||
3 | import { ActorCustomPageModel } from '@server/models/account/actor-custom-page' | ||
4 | import { HttpStatusCode } from '@shared/core-utils' | ||
5 | import { UserRight } from '@shared/models' | ||
6 | import { asyncMiddleware, authenticate, ensureUserHasRight } from '../../middlewares' | ||
7 | |||
8 | const customPageRouter = express.Router() | ||
9 | |||
10 | customPageRouter.get('/homepage/instance', | ||
11 | asyncMiddleware(getInstanceHomepage) | ||
12 | ) | ||
13 | |||
14 | customPageRouter.put('/homepage/instance', | ||
15 | authenticate, | ||
16 | ensureUserHasRight(UserRight.MANAGE_INSTANCE_CUSTOM_PAGE), | ||
17 | asyncMiddleware(updateInstanceHomepage) | ||
18 | ) | ||
19 | |||
20 | // --------------------------------------------------------------------------- | ||
21 | |||
22 | export { | ||
23 | customPageRouter | ||
24 | } | ||
25 | |||
26 | // --------------------------------------------------------------------------- | ||
27 | |||
28 | async function getInstanceHomepage (req: express.Request, res: express.Response) { | ||
29 | const page = await ActorCustomPageModel.loadInstanceHomepage() | ||
30 | if (!page) return res.sendStatus(HttpStatusCode.NOT_FOUND_404) | ||
31 | |||
32 | return res.json(page.toFormattedJSON()) | ||
33 | } | ||
34 | |||
35 | async function updateInstanceHomepage (req: express.Request, res: express.Response) { | ||
36 | const content = req.body.content | ||
37 | |||
38 | await ActorCustomPageModel.updateInstanceHomepage(content) | ||
39 | ServerConfigManager.Instance.updateHomepageState(content) | ||
40 | |||
41 | return res.sendStatus(HttpStatusCode.NO_CONTENT_204) | ||
42 | } | ||
diff --git a/server/controllers/api/index.ts b/server/controllers/api/index.ts index 7ade1df3a..28378654a 100644 --- a/server/controllers/api/index.ts +++ b/server/controllers/api/index.ts | |||
@@ -8,6 +8,7 @@ import { abuseRouter } from './abuse' | |||
8 | import { accountsRouter } from './accounts' | 8 | import { accountsRouter } from './accounts' |
9 | import { bulkRouter } from './bulk' | 9 | import { bulkRouter } from './bulk' |
10 | import { configRouter } from './config' | 10 | import { configRouter } from './config' |
11 | import { customPageRouter } from './custom-page' | ||
11 | import { jobsRouter } from './jobs' | 12 | import { jobsRouter } from './jobs' |
12 | import { oauthClientsRouter } from './oauth-clients' | 13 | import { oauthClientsRouter } from './oauth-clients' |
13 | import { overviewsRouter } from './overviews' | 14 | import { overviewsRouter } from './overviews' |
@@ -47,6 +48,7 @@ apiRouter.use('/jobs', jobsRouter) | |||
47 | apiRouter.use('/search', searchRouter) | 48 | apiRouter.use('/search', searchRouter) |
48 | apiRouter.use('/overviews', overviewsRouter) | 49 | apiRouter.use('/overviews', overviewsRouter) |
49 | apiRouter.use('/plugins', pluginRouter) | 50 | apiRouter.use('/plugins', pluginRouter) |
51 | apiRouter.use('/custom-pages', customPageRouter) | ||
50 | apiRouter.use('/ping', pong) | 52 | apiRouter.use('/ping', pong) |
51 | apiRouter.use('/*', badRequest) | 53 | apiRouter.use('/*', badRequest) |
52 | 54 | ||
diff --git a/server/controllers/api/plugins.ts b/server/controllers/api/plugins.ts index a186de010..e18eed332 100644 --- a/server/controllers/api/plugins.ts +++ b/server/controllers/api/plugins.ts | |||
@@ -1,16 +1,18 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { getFormattedObjects } from '../../helpers/utils' | 2 | import { logger } from '@server/helpers/logger' |
3 | import { getFormattedObjects } from '@server/helpers/utils' | ||
4 | import { listAvailablePluginsFromIndex } from '@server/lib/plugins/plugin-index' | ||
5 | import { PluginManager } from '@server/lib/plugins/plugin-manager' | ||
3 | import { | 6 | import { |
4 | asyncMiddleware, | 7 | asyncMiddleware, |
5 | authenticate, | 8 | authenticate, |
9 | availablePluginsSortValidator, | ||
6 | ensureUserHasRight, | 10 | ensureUserHasRight, |
7 | paginationValidator, | 11 | paginationValidator, |
12 | pluginsSortValidator, | ||
8 | setDefaultPagination, | 13 | setDefaultPagination, |
9 | setDefaultSort | 14 | setDefaultSort |
10 | } from '../../middlewares' | 15 | } from '@server/middlewares' |
11 | import { availablePluginsSortValidator, pluginsSortValidator } from '../../middlewares/validators' | ||
12 | import { PluginModel } from '../../models/server/plugin' | ||
13 | import { UserRight } from '../../../shared/models/users' | ||
14 | import { | 16 | import { |
15 | existingPluginValidator, | 17 | existingPluginValidator, |
16 | installOrUpdatePluginValidator, | 18 | installOrUpdatePluginValidator, |
@@ -18,16 +20,17 @@ import { | |||
18 | listPluginsValidator, | 20 | listPluginsValidator, |
19 | uninstallPluginValidator, | 21 | uninstallPluginValidator, |
20 | updatePluginSettingsValidator | 22 | updatePluginSettingsValidator |
21 | } from '../../middlewares/validators/plugins' | 23 | } from '@server/middlewares/validators/plugins' |
22 | import { PluginManager } from '../../lib/plugins/plugin-manager' | 24 | import { PluginModel } from '@server/models/server/plugin' |
23 | import { InstallOrUpdatePlugin } from '../../../shared/models/plugins/install-plugin.model' | 25 | import { HttpStatusCode } from '@shared/core-utils' |
24 | import { ManagePlugin } from '../../../shared/models/plugins/manage-plugin.model' | 26 | import { |
25 | import { logger } from '../../helpers/logger' | 27 | InstallOrUpdatePlugin, |
26 | import { listAvailablePluginsFromIndex } from '../../lib/plugins/plugin-index' | 28 | ManagePlugin, |
27 | import { PeertubePluginIndexList } from '../../../shared/models/plugins/peertube-plugin-index-list.model' | 29 | PeertubePluginIndexList, |
28 | import { RegisteredServerSettings } from '../../../shared/models/plugins/register-server-setting.model' | 30 | PublicServerSetting, |
29 | import { PublicServerSetting } from '../../../shared/models/plugins/public-server.setting' | 31 | RegisteredServerSettings, |
30 | import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' | 32 | UserRight |
33 | } from '@shared/models' | ||
31 | 34 | ||
32 | const pluginRouter = express.Router() | 35 | const pluginRouter = express.Router() |
33 | 36 | ||
diff --git a/server/controllers/api/server/follows.ts b/server/controllers/api/server/follows.ts index 80025bc5b..daeef22de 100644 --- a/server/controllers/api/server/follows.ts +++ b/server/controllers/api/server/follows.ts | |||
@@ -1,9 +1,15 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { getServerActor } from '@server/models/application/application' | ||
3 | import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' | ||
2 | import { UserRight } from '../../../../shared/models/users' | 4 | import { UserRight } from '../../../../shared/models/users' |
3 | import { logger } from '../../../helpers/logger' | 5 | import { logger } from '../../../helpers/logger' |
4 | import { getFormattedObjects } from '../../../helpers/utils' | 6 | import { getFormattedObjects } from '../../../helpers/utils' |
5 | import { SERVER_ACTOR_NAME } from '../../../initializers/constants' | 7 | import { SERVER_ACTOR_NAME } from '../../../initializers/constants' |
8 | import { sequelizeTypescript } from '../../../initializers/database' | ||
9 | import { autoFollowBackIfNeeded } from '../../../lib/activitypub/follow' | ||
6 | import { sendAccept, sendReject, sendUndoFollow } from '../../../lib/activitypub/send' | 10 | import { sendAccept, sendReject, sendUndoFollow } from '../../../lib/activitypub/send' |
11 | import { JobQueue } from '../../../lib/job-queue' | ||
12 | import { removeRedundanciesOfServer } from '../../../lib/redundancy' | ||
7 | import { | 13 | import { |
8 | asyncMiddleware, | 14 | asyncMiddleware, |
9 | authenticate, | 15 | authenticate, |
@@ -19,16 +25,10 @@ import { | |||
19 | followingSortValidator, | 25 | followingSortValidator, |
20 | followValidator, | 26 | followValidator, |
21 | getFollowerValidator, | 27 | getFollowerValidator, |
22 | removeFollowingValidator, | 28 | listFollowsValidator, |
23 | listFollowsValidator | 29 | removeFollowingValidator |
24 | } from '../../../middlewares/validators' | 30 | } from '../../../middlewares/validators' |
25 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' | 31 | import { ActorFollowModel } from '../../../models/actor/actor-follow' |
26 | import { JobQueue } from '../../../lib/job-queue' | ||
27 | import { removeRedundanciesOfServer } from '../../../lib/redundancy' | ||
28 | import { sequelizeTypescript } from '../../../initializers/database' | ||
29 | import { autoFollowBackIfNeeded } from '../../../lib/activitypub/follow' | ||
30 | import { getServerActor } from '@server/models/application/application' | ||
31 | import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' | ||
32 | 32 | ||
33 | const serverFollowsRouter = express.Router() | 33 | const serverFollowsRouter = express.Router() |
34 | serverFollowsRouter.get('/following', | 34 | serverFollowsRouter.get('/following', |
diff --git a/server/controllers/api/server/server-blocklist.ts b/server/controllers/api/server/server-blocklist.ts index 6e341c0fb..a86bc7d19 100644 --- a/server/controllers/api/server/server-blocklist.ts +++ b/server/controllers/api/server/server-blocklist.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import 'multer' | 1 | import 'multer' |
2 | import * as express from 'express' | 2 | import * as express from 'express' |
3 | import { logger } from '@server/helpers/logger' | 3 | import { logger } from '@server/helpers/logger' |
4 | import { UserNotificationModel } from '@server/models/account/user-notification' | 4 | import { UserNotificationModel } from '@server/models/user/user-notification' |
5 | import { getServerActor } from '@server/models/application/application' | 5 | import { getServerActor } from '@server/models/application/application' |
6 | import { UserRight } from '../../../../shared/models/users' | 6 | import { UserRight } from '../../../../shared/models/users' |
7 | import { getFormattedObjects } from '../../../helpers/utils' | 7 | import { getFormattedObjects } from '../../../helpers/utils' |
diff --git a/server/controllers/api/users/index.ts b/server/controllers/api/users/index.ts index e2b1ea7cd..f384f0f28 100644 --- a/server/controllers/api/users/index.ts +++ b/server/controllers/api/users/index.ts | |||
@@ -45,7 +45,7 @@ import { | |||
45 | usersResetPasswordValidator, | 45 | usersResetPasswordValidator, |
46 | usersVerifyEmailValidator | 46 | usersVerifyEmailValidator |
47 | } from '../../../middlewares/validators' | 47 | } from '../../../middlewares/validators' |
48 | import { UserModel } from '../../../models/account/user' | 48 | import { UserModel } from '../../../models/user/user' |
49 | import { meRouter } from './me' | 49 | import { meRouter } from './me' |
50 | import { myAbusesRouter } from './my-abuses' | 50 | import { myAbusesRouter } from './my-abuses' |
51 | import { myBlocklistRouter } from './my-blocklist' | 51 | import { myBlocklistRouter } from './my-blocklist' |
@@ -323,14 +323,20 @@ async function updateUser (req: express.Request, res: express.Response) { | |||
323 | const oldUserAuditView = new UserAuditView(userToUpdate.toFormattedJSON()) | 323 | const oldUserAuditView = new UserAuditView(userToUpdate.toFormattedJSON()) |
324 | const roleChanged = body.role !== undefined && body.role !== userToUpdate.role | 324 | const roleChanged = body.role !== undefined && body.role !== userToUpdate.role |
325 | 325 | ||
326 | if (body.password !== undefined) userToUpdate.password = body.password | 326 | const keysToUpdate: (keyof UserUpdate)[] = [ |
327 | if (body.email !== undefined) userToUpdate.email = body.email | 327 | 'password', |
328 | if (body.emailVerified !== undefined) userToUpdate.emailVerified = body.emailVerified | 328 | 'email', |
329 | if (body.videoQuota !== undefined) userToUpdate.videoQuota = body.videoQuota | 329 | 'emailVerified', |
330 | if (body.videoQuotaDaily !== undefined) userToUpdate.videoQuotaDaily = body.videoQuotaDaily | 330 | 'videoQuota', |
331 | if (body.role !== undefined) userToUpdate.role = body.role | 331 | 'videoQuotaDaily', |
332 | if (body.adminFlags !== undefined) userToUpdate.adminFlags = body.adminFlags | 332 | 'role', |
333 | if (body.pluginAuth !== undefined) userToUpdate.pluginAuth = body.pluginAuth | 333 | 'adminFlags', |
334 | 'pluginAuth' | ||
335 | ] | ||
336 | |||
337 | for (const key of keysToUpdate) { | ||
338 | if (body[key] !== undefined) userToUpdate.set(key, body[key]) | ||
339 | } | ||
334 | 340 | ||
335 | const user = await userToUpdate.save() | 341 | const user = await userToUpdate.save() |
336 | 342 | ||
diff --git a/server/controllers/api/users/me.ts b/server/controllers/api/users/me.ts index 0763d1900..a609abaa6 100644 --- a/server/controllers/api/users/me.ts +++ b/server/controllers/api/users/me.ts | |||
@@ -28,9 +28,10 @@ import { deleteMeValidator, videoImportsSortValidator, videosSortValidator } fro | |||
28 | import { updateAvatarValidator } from '../../../middlewares/validators/actor-image' | 28 | import { updateAvatarValidator } from '../../../middlewares/validators/actor-image' |
29 | import { AccountModel } from '../../../models/account/account' | 29 | import { AccountModel } from '../../../models/account/account' |
30 | import { AccountVideoRateModel } from '../../../models/account/account-video-rate' | 30 | import { AccountVideoRateModel } from '../../../models/account/account-video-rate' |
31 | import { UserModel } from '../../../models/account/user' | 31 | import { UserModel } from '../../../models/user/user' |
32 | import { VideoModel } from '../../../models/video/video' | 32 | import { VideoModel } from '../../../models/video/video' |
33 | import { VideoImportModel } from '../../../models/video/video-import' | 33 | import { VideoImportModel } from '../../../models/video/video-import' |
34 | import { AttributesOnly } from '@shared/core-utils' | ||
34 | 35 | ||
35 | const auditLogger = auditLoggerFactory('users') | 36 | const auditLogger = auditLoggerFactory('users') |
36 | 37 | ||
@@ -191,17 +192,23 @@ async function updateMe (req: express.Request, res: express.Response) { | |||
191 | 192 | ||
192 | const user = res.locals.oauth.token.user | 193 | const user = res.locals.oauth.token.user |
193 | 194 | ||
194 | if (body.password !== undefined) user.password = body.password | 195 | const keysToUpdate: (keyof UserUpdateMe & keyof AttributesOnly<UserModel>)[] = [ |
195 | if (body.nsfwPolicy !== undefined) user.nsfwPolicy = body.nsfwPolicy | 196 | 'password', |
196 | if (body.webTorrentEnabled !== undefined) user.webTorrentEnabled = body.webTorrentEnabled | 197 | 'nsfwPolicy', |
197 | if (body.autoPlayVideo !== undefined) user.autoPlayVideo = body.autoPlayVideo | 198 | 'webTorrentEnabled', |
198 | if (body.autoPlayNextVideo !== undefined) user.autoPlayNextVideo = body.autoPlayNextVideo | 199 | 'autoPlayVideo', |
199 | if (body.autoPlayNextVideoPlaylist !== undefined) user.autoPlayNextVideoPlaylist = body.autoPlayNextVideoPlaylist | 200 | 'autoPlayNextVideo', |
200 | if (body.videosHistoryEnabled !== undefined) user.videosHistoryEnabled = body.videosHistoryEnabled | 201 | 'autoPlayNextVideoPlaylist', |
201 | if (body.videoLanguages !== undefined) user.videoLanguages = body.videoLanguages | 202 | 'videosHistoryEnabled', |
202 | if (body.theme !== undefined) user.theme = body.theme | 203 | 'videoLanguages', |
203 | if (body.noInstanceConfigWarningModal !== undefined) user.noInstanceConfigWarningModal = body.noInstanceConfigWarningModal | 204 | 'theme', |
204 | if (body.noWelcomeModal !== undefined) user.noWelcomeModal = body.noWelcomeModal | 205 | 'noInstanceConfigWarningModal', |
206 | 'noWelcomeModal' | ||
207 | ] | ||
208 | |||
209 | for (const key of keysToUpdate) { | ||
210 | if (body[key] !== undefined) user.set(key, body[key]) | ||
211 | } | ||
205 | 212 | ||
206 | if (body.email !== undefined) { | 213 | if (body.email !== undefined) { |
207 | if (CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION) { | 214 | if (CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION) { |
@@ -215,15 +222,15 @@ async function updateMe (req: express.Request, res: express.Response) { | |||
215 | await sequelizeTypescript.transaction(async t => { | 222 | await sequelizeTypescript.transaction(async t => { |
216 | await user.save({ transaction: t }) | 223 | await user.save({ transaction: t }) |
217 | 224 | ||
218 | if (body.displayName !== undefined || body.description !== undefined) { | 225 | if (body.displayName === undefined && body.description === undefined) return |
219 | const userAccount = await AccountModel.load(user.Account.id, t) | ||
220 | 226 | ||
221 | if (body.displayName !== undefined) userAccount.name = body.displayName | 227 | const userAccount = await AccountModel.load(user.Account.id, t) |
222 | if (body.description !== undefined) userAccount.description = body.description | ||
223 | await userAccount.save({ transaction: t }) | ||
224 | 228 | ||
225 | await sendUpdateActor(userAccount, t) | 229 | if (body.displayName !== undefined) userAccount.name = body.displayName |
226 | } | 230 | if (body.description !== undefined) userAccount.description = body.description |
231 | await userAccount.save({ transaction: t }) | ||
232 | |||
233 | await sendUpdateActor(userAccount, t) | ||
227 | }) | 234 | }) |
228 | 235 | ||
229 | if (sendVerificationEmail === true) { | 236 | if (sendVerificationEmail === true) { |
diff --git a/server/controllers/api/users/my-blocklist.ts b/server/controllers/api/users/my-blocklist.ts index faaef3ac0..a1561b751 100644 --- a/server/controllers/api/users/my-blocklist.ts +++ b/server/controllers/api/users/my-blocklist.ts | |||
@@ -20,7 +20,7 @@ import { | |||
20 | import { AccountBlocklistModel } from '../../../models/account/account-blocklist' | 20 | import { AccountBlocklistModel } from '../../../models/account/account-blocklist' |
21 | import { addAccountInBlocklist, addServerInBlocklist, removeAccountFromBlocklist, removeServerFromBlocklist } from '../../../lib/blocklist' | 21 | import { addAccountInBlocklist, addServerInBlocklist, removeAccountFromBlocklist, removeServerFromBlocklist } from '../../../lib/blocklist' |
22 | import { ServerBlocklistModel } from '../../../models/server/server-blocklist' | 22 | import { ServerBlocklistModel } from '../../../models/server/server-blocklist' |
23 | import { UserNotificationModel } from '@server/models/account/user-notification' | 23 | import { UserNotificationModel } from '@server/models/user/user-notification' |
24 | import { logger } from '@server/helpers/logger' | 24 | import { logger } from '@server/helpers/logger' |
25 | import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' | 25 | import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' |
26 | 26 | ||
diff --git a/server/controllers/api/users/my-history.ts b/server/controllers/api/users/my-history.ts index 72c7da373..cff1697ab 100644 --- a/server/controllers/api/users/my-history.ts +++ b/server/controllers/api/users/my-history.ts | |||
@@ -9,7 +9,7 @@ import { | |||
9 | userHistoryRemoveValidator | 9 | userHistoryRemoveValidator |
10 | } from '../../../middlewares' | 10 | } from '../../../middlewares' |
11 | import { getFormattedObjects } from '../../../helpers/utils' | 11 | import { getFormattedObjects } from '../../../helpers/utils' |
12 | import { UserVideoHistoryModel } from '../../../models/account/user-video-history' | 12 | import { UserVideoHistoryModel } from '../../../models/user/user-video-history' |
13 | import { sequelizeTypescript } from '../../../initializers/database' | 13 | import { sequelizeTypescript } from '../../../initializers/database' |
14 | import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' | 14 | import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' |
15 | 15 | ||
diff --git a/server/controllers/api/users/my-notifications.ts b/server/controllers/api/users/my-notifications.ts index 0a9101a46..2909770da 100644 --- a/server/controllers/api/users/my-notifications.ts +++ b/server/controllers/api/users/my-notifications.ts | |||
@@ -1,5 +1,9 @@ | |||
1 | import * as express from 'express' | ||
2 | import 'multer' | 1 | import 'multer' |
2 | import * as express from 'express' | ||
3 | import { UserNotificationModel } from '@server/models/user/user-notification' | ||
4 | import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' | ||
5 | import { UserNotificationSetting } from '../../../../shared/models/users' | ||
6 | import { getFormattedObjects } from '../../../helpers/utils' | ||
3 | import { | 7 | import { |
4 | asyncMiddleware, | 8 | asyncMiddleware, |
5 | asyncRetryTransactionMiddleware, | 9 | asyncRetryTransactionMiddleware, |
@@ -9,17 +13,13 @@ import { | |||
9 | setDefaultSort, | 13 | setDefaultSort, |
10 | userNotificationsSortValidator | 14 | userNotificationsSortValidator |
11 | } from '../../../middlewares' | 15 | } from '../../../middlewares' |
12 | import { getFormattedObjects } from '../../../helpers/utils' | ||
13 | import { UserNotificationModel } from '../../../models/account/user-notification' | ||
14 | import { meRouter } from './me' | ||
15 | import { | 16 | import { |
16 | listUserNotificationsValidator, | 17 | listUserNotificationsValidator, |
17 | markAsReadUserNotificationsValidator, | 18 | markAsReadUserNotificationsValidator, |
18 | updateNotificationSettingsValidator | 19 | updateNotificationSettingsValidator |
19 | } from '../../../middlewares/validators/user-notifications' | 20 | } from '../../../middlewares/validators/user-notifications' |
20 | import { UserNotificationSetting } from '../../../../shared/models/users' | 21 | import { UserNotificationSettingModel } from '../../../models/user/user-notification-setting' |
21 | import { UserNotificationSettingModel } from '../../../models/account/user-notification-setting' | 22 | import { meRouter } from './me' |
22 | import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' | ||
23 | 23 | ||
24 | const myNotificationsRouter = express.Router() | 24 | const myNotificationsRouter = express.Router() |
25 | 25 | ||
diff --git a/server/controllers/api/users/my-subscriptions.ts b/server/controllers/api/users/my-subscriptions.ts index 56b93276f..46a73d49e 100644 --- a/server/controllers/api/users/my-subscriptions.ts +++ b/server/controllers/api/users/my-subscriptions.ts | |||
@@ -27,7 +27,7 @@ import { | |||
27 | userSubscriptionsSortValidator, | 27 | userSubscriptionsSortValidator, |
28 | videosSortValidator | 28 | videosSortValidator |
29 | } from '../../../middlewares/validators' | 29 | } from '../../../middlewares/validators' |
30 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' | 30 | import { ActorFollowModel } from '../../../models/actor/actor-follow' |
31 | import { VideoModel } from '../../../models/video/video' | 31 | import { VideoModel } from '../../../models/video/video' |
32 | 32 | ||
33 | const mySubscriptionsRouter = express.Router() | 33 | const mySubscriptionsRouter = express.Router() |
diff --git a/server/controllers/api/video-channel.ts b/server/controllers/api/video-channel.ts index a755d7e57..859d8b3c0 100644 --- a/server/controllers/api/video-channel.ts +++ b/server/controllers/api/video-channel.ts | |||
@@ -162,6 +162,7 @@ async function updateVideoChannelBanner (req: express.Request, res: express.Resp | |||
162 | 162 | ||
163 | return res.json({ banner: banner.toFormattedJSON() }) | 163 | return res.json({ banner: banner.toFormattedJSON() }) |
164 | } | 164 | } |
165 | |||
165 | async function updateVideoChannelAvatar (req: express.Request, res: express.Response) { | 166 | async function updateVideoChannelAvatar (req: express.Request, res: express.Response) { |
166 | const avatarPhysicalFile = req.files['avatarfile'][0] | 167 | const avatarPhysicalFile = req.files['avatarfile'][0] |
167 | const videoChannel = res.locals.videoChannel | 168 | const videoChannel = res.locals.videoChannel |
@@ -221,10 +222,6 @@ async function updateVideoChannel (req: express.Request, res: express.Response) | |||
221 | 222 | ||
222 | try { | 223 | try { |
223 | await sequelizeTypescript.transaction(async t => { | 224 | await sequelizeTypescript.transaction(async t => { |
224 | const sequelizeOptions = { | ||
225 | transaction: t | ||
226 | } | ||
227 | |||
228 | if (videoChannelInfoToUpdate.displayName !== undefined) videoChannelInstance.name = videoChannelInfoToUpdate.displayName | 225 | if (videoChannelInfoToUpdate.displayName !== undefined) videoChannelInstance.name = videoChannelInfoToUpdate.displayName |
229 | if (videoChannelInfoToUpdate.description !== undefined) videoChannelInstance.description = videoChannelInfoToUpdate.description | 226 | if (videoChannelInfoToUpdate.description !== undefined) videoChannelInstance.description = videoChannelInfoToUpdate.description |
230 | 227 | ||
@@ -238,7 +235,7 @@ async function updateVideoChannel (req: express.Request, res: express.Response) | |||
238 | } | 235 | } |
239 | } | 236 | } |
240 | 237 | ||
241 | const videoChannelInstanceUpdated = await videoChannelInstance.save(sequelizeOptions) as MChannelBannerAccountDefault | 238 | const videoChannelInstanceUpdated = await videoChannelInstance.save({ transaction: t }) as MChannelBannerAccountDefault |
242 | await sendUpdateActor(videoChannelInstanceUpdated, t) | 239 | await sendUpdateActor(videoChannelInstanceUpdated, t) |
243 | 240 | ||
244 | auditLogger.update( | 241 | auditLogger.update( |
diff --git a/server/controllers/api/video-playlist.ts b/server/controllers/api/video-playlist.ts index aab16533d..b8613699b 100644 --- a/server/controllers/api/video-playlist.ts +++ b/server/controllers/api/video-playlist.ts | |||
@@ -202,7 +202,7 @@ async function addVideoPlaylist (req: express.Request, res: express.Response) { | |||
202 | id: videoPlaylistCreated.id, | 202 | id: videoPlaylistCreated.id, |
203 | uuid: videoPlaylistCreated.uuid | 203 | uuid: videoPlaylistCreated.uuid |
204 | } | 204 | } |
205 | }).end() | 205 | }) |
206 | } | 206 | } |
207 | 207 | ||
208 | async function updateVideoPlaylist (req: express.Request, res: express.Response) { | 208 | async function updateVideoPlaylist (req: express.Request, res: express.Response) { |
diff --git a/server/controllers/api/videos/comment.ts b/server/controllers/api/videos/comment.ts index f1f53d354..cfdf2773f 100644 --- a/server/controllers/api/videos/comment.ts +++ b/server/controllers/api/videos/comment.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' | 2 | import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' |
3 | import { ResultList, ThreadsResultList, UserRight } from '../../../../shared/models' | 3 | import { ResultList, ThreadsResultList, UserRight } from '../../../../shared/models' |
4 | import { VideoCommentCreate } from '../../../../shared/models/videos/video-comment.model' | 4 | import { VideoCommentCreate } from '../../../../shared/models/videos/comment/video-comment.model' |
5 | import { auditLoggerFactory, CommentAuditView, getAuditIdFromRes } from '../../../helpers/audit-logger' | 5 | import { auditLoggerFactory, CommentAuditView, getAuditIdFromRes } from '../../../helpers/audit-logger' |
6 | import { getFormattedObjects } from '../../../helpers/utils' | 6 | import { getFormattedObjects } from '../../../helpers/utils' |
7 | import { sequelizeTypescript } from '../../../initializers/database' | 7 | import { sequelizeTypescript } from '../../../initializers/database' |
diff --git a/server/controllers/api/videos/import.ts b/server/controllers/api/videos/import.ts index 3b9b887e2..0d5d7a962 100644 --- a/server/controllers/api/videos/import.ts +++ b/server/controllers/api/videos/import.ts | |||
@@ -3,7 +3,9 @@ import { move, readFile } from 'fs-extra' | |||
3 | import * as magnetUtil from 'magnet-uri' | 3 | import * as magnetUtil from 'magnet-uri' |
4 | import * as parseTorrent from 'parse-torrent' | 4 | import * as parseTorrent from 'parse-torrent' |
5 | import { join } from 'path' | 5 | import { join } from 'path' |
6 | import { ServerConfigManager } from '@server/lib/server-config-manager' | ||
6 | import { setVideoTags } from '@server/lib/video' | 7 | import { setVideoTags } from '@server/lib/video' |
8 | import { FilteredModelAttributes } from '@server/types' | ||
7 | import { | 9 | import { |
8 | MChannelAccountDefault, | 10 | MChannelAccountDefault, |
9 | MThumbnail, | 11 | MThumbnail, |
@@ -14,17 +16,17 @@ import { | |||
14 | MVideoThumbnail, | 16 | MVideoThumbnail, |
15 | MVideoWithBlacklistLight | 17 | MVideoWithBlacklistLight |
16 | } from '@server/types/models' | 18 | } from '@server/types/models' |
17 | import { MVideoImport, MVideoImportFormattable } from '@server/types/models/video/video-import' | 19 | import { MVideoImportFormattable } from '@server/types/models/video/video-import' |
18 | import { VideoImportCreate, VideoImportState, VideoPrivacy, VideoState } from '../../../../shared' | 20 | import { ServerErrorCode, VideoImportCreate, VideoImportState, VideoPrivacy, VideoState } from '../../../../shared' |
19 | import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' | 21 | import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' |
20 | import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type' | 22 | import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type' |
21 | import { auditLoggerFactory, getAuditIdFromRes, VideoImportAuditView } from '../../../helpers/audit-logger' | 23 | import { auditLoggerFactory, getAuditIdFromRes, VideoImportAuditView } from '../../../helpers/audit-logger' |
22 | import { moveAndProcessCaptionFile } from '../../../helpers/captions-utils' | 24 | import { moveAndProcessCaptionFile } from '../../../helpers/captions-utils' |
23 | import { isArray } from '../../../helpers/custom-validators/misc' | 25 | import { isArray } from '../../../helpers/custom-validators/misc' |
24 | import { createReqFiles } from '../../../helpers/express-utils' | 26 | import { cleanUpReqFiles, createReqFiles } from '../../../helpers/express-utils' |
25 | import { logger } from '../../../helpers/logger' | 27 | import { logger } from '../../../helpers/logger' |
26 | import { getSecureTorrentName } from '../../../helpers/utils' | 28 | import { getSecureTorrentName } from '../../../helpers/utils' |
27 | import { getYoutubeDLInfo, getYoutubeDLSubs, YoutubeDLInfo } from '../../../helpers/youtube-dl' | 29 | import { YoutubeDL, YoutubeDLInfo } from '../../../helpers/youtube-dl' |
28 | import { CONFIG } from '../../../initializers/config' | 30 | import { CONFIG } from '../../../initializers/config' |
29 | import { MIMETYPES } from '../../../initializers/constants' | 31 | import { MIMETYPES } from '../../../initializers/constants' |
30 | import { sequelizeTypescript } from '../../../initializers/database' | 32 | import { sequelizeTypescript } from '../../../initializers/database' |
@@ -81,22 +83,15 @@ async function addTorrentImport (req: express.Request, res: express.Response, to | |||
81 | let magnetUri: string | 83 | let magnetUri: string |
82 | 84 | ||
83 | if (torrentfile) { | 85 | if (torrentfile) { |
84 | torrentName = torrentfile.originalname | 86 | const result = await processTorrentOrAbortRequest(req, res, torrentfile) |
87 | if (!result) return | ||
85 | 88 | ||
86 | // Rename the torrent to a secured name | 89 | videoName = result.name |
87 | const newTorrentPath = join(CONFIG.STORAGE.TORRENTS_DIR, getSecureTorrentName(torrentName)) | 90 | torrentName = result.torrentName |
88 | await move(torrentfile.path, newTorrentPath) | ||
89 | torrentfile.path = newTorrentPath | ||
90 | |||
91 | const buf = await readFile(torrentfile.path) | ||
92 | const parsedTorrent = parseTorrent(buf) | ||
93 | |||
94 | videoName = isArray(parsedTorrent.name) ? parsedTorrent.name[0] : parsedTorrent.name as string | ||
95 | } else { | 91 | } else { |
96 | magnetUri = body.magnetUri | 92 | const result = processMagnetURI(body) |
97 | 93 | magnetUri = result.magnetUri | |
98 | const parsed = magnetUtil.decode(magnetUri) | 94 | videoName = result.name |
99 | videoName = isArray(parsed.name) ? parsed.name[0] : parsed.name as string | ||
100 | } | 95 | } |
101 | 96 | ||
102 | const video = buildVideo(res.locals.videoChannel.id, body, { name: videoName }) | 97 | const video = buildVideo(res.locals.videoChannel.id, body, { name: videoName }) |
@@ -104,26 +99,26 @@ async function addTorrentImport (req: express.Request, res: express.Response, to | |||
104 | const thumbnailModel = await processThumbnail(req, video) | 99 | const thumbnailModel = await processThumbnail(req, video) |
105 | const previewModel = await processPreview(req, video) | 100 | const previewModel = await processPreview(req, video) |
106 | 101 | ||
107 | const tags = body.tags || undefined | ||
108 | const videoImportAttributes = { | ||
109 | magnetUri, | ||
110 | torrentName, | ||
111 | state: VideoImportState.PENDING, | ||
112 | userId: user.id | ||
113 | } | ||
114 | const videoImport = await insertIntoDB({ | 102 | const videoImport = await insertIntoDB({ |
115 | video, | 103 | video, |
116 | thumbnailModel, | 104 | thumbnailModel, |
117 | previewModel, | 105 | previewModel, |
118 | videoChannel: res.locals.videoChannel, | 106 | videoChannel: res.locals.videoChannel, |
119 | tags, | 107 | tags: body.tags || undefined, |
120 | videoImportAttributes, | 108 | user, |
121 | user | 109 | videoImportAttributes: { |
110 | magnetUri, | ||
111 | torrentName, | ||
112 | state: VideoImportState.PENDING, | ||
113 | userId: user.id | ||
114 | } | ||
122 | }) | 115 | }) |
123 | 116 | ||
124 | // Create job to import the video | 117 | // Create job to import the video |
125 | const payload = { | 118 | const payload = { |
126 | type: torrentfile ? 'torrent-file' as 'torrent-file' : 'magnet-uri' as 'magnet-uri', | 119 | type: torrentfile |
120 | ? 'torrent-file' as 'torrent-file' | ||
121 | : 'magnet-uri' as 'magnet-uri', | ||
127 | videoImportId: videoImport.id, | 122 | videoImportId: videoImport.id, |
128 | magnetUri | 123 | magnetUri |
129 | } | 124 | } |
@@ -139,10 +134,12 @@ async function addYoutubeDLImport (req: express.Request, res: express.Response) | |||
139 | const targetUrl = body.targetUrl | 134 | const targetUrl = body.targetUrl |
140 | const user = res.locals.oauth.token.User | 135 | const user = res.locals.oauth.token.User |
141 | 136 | ||
137 | const youtubeDL = new YoutubeDL(targetUrl, ServerConfigManager.Instance.getEnabledResolutions('vod')) | ||
138 | |||
142 | // Get video infos | 139 | // Get video infos |
143 | let youtubeDLInfo: YoutubeDLInfo | 140 | let youtubeDLInfo: YoutubeDLInfo |
144 | try { | 141 | try { |
145 | youtubeDLInfo = await getYoutubeDLInfo(targetUrl) | 142 | youtubeDLInfo = await youtubeDL.getYoutubeDLInfo() |
146 | } catch (err) { | 143 | } catch (err) { |
147 | logger.info('Cannot fetch information from import for URL %s.', targetUrl, { err }) | 144 | logger.info('Cannot fetch information from import for URL %s.', targetUrl, { err }) |
148 | 145 | ||
@@ -170,45 +167,22 @@ async function addYoutubeDLImport (req: express.Request, res: express.Response) | |||
170 | previewModel = await processPreviewFromUrl(youtubeDLInfo.thumbnailUrl, video) | 167 | previewModel = await processPreviewFromUrl(youtubeDLInfo.thumbnailUrl, video) |
171 | } | 168 | } |
172 | 169 | ||
173 | const tags = body.tags || youtubeDLInfo.tags | ||
174 | const videoImportAttributes = { | ||
175 | targetUrl, | ||
176 | state: VideoImportState.PENDING, | ||
177 | userId: user.id | ||
178 | } | ||
179 | const videoImport = await insertIntoDB({ | 170 | const videoImport = await insertIntoDB({ |
180 | video, | 171 | video, |
181 | thumbnailModel, | 172 | thumbnailModel, |
182 | previewModel, | 173 | previewModel, |
183 | videoChannel: res.locals.videoChannel, | 174 | videoChannel: res.locals.videoChannel, |
184 | tags, | 175 | tags: body.tags || youtubeDLInfo.tags, |
185 | videoImportAttributes, | 176 | user, |
186 | user | 177 | videoImportAttributes: { |
178 | targetUrl, | ||
179 | state: VideoImportState.PENDING, | ||
180 | userId: user.id | ||
181 | } | ||
187 | }) | 182 | }) |
188 | 183 | ||
189 | // Get video subtitles | 184 | // Get video subtitles |
190 | try { | 185 | await processYoutubeSubtitles(youtubeDL, targetUrl, video.id) |
191 | const subtitles = await getYoutubeDLSubs(targetUrl) | ||
192 | |||
193 | logger.info('Will create %s subtitles from youtube import %s.', subtitles.length, targetUrl) | ||
194 | |||
195 | for (const subtitle of subtitles) { | ||
196 | const videoCaption = new VideoCaptionModel({ | ||
197 | videoId: video.id, | ||
198 | language: subtitle.language, | ||
199 | filename: VideoCaptionModel.generateCaptionName(subtitle.language) | ||
200 | }) as MVideoCaption | ||
201 | |||
202 | // Move physical file | ||
203 | await moveAndProcessCaptionFile(subtitle, videoCaption) | ||
204 | |||
205 | await sequelizeTypescript.transaction(async t => { | ||
206 | await VideoCaptionModel.insertOrReplaceLanguage(videoCaption, t) | ||
207 | }) | ||
208 | } | ||
209 | } catch (err) { | ||
210 | logger.warn('Cannot get video subtitles.', { err }) | ||
211 | } | ||
212 | 186 | ||
213 | // Create job to import the video | 187 | // Create job to import the video |
214 | const payload = { | 188 | const payload = { |
@@ -240,7 +214,9 @@ function buildVideo (channelId: number, body: VideoImportCreate, importData: You | |||
240 | privacy: body.privacy || VideoPrivacy.PRIVATE, | 214 | privacy: body.privacy || VideoPrivacy.PRIVATE, |
241 | duration: 0, // duration will be set by the import job | 215 | duration: 0, // duration will be set by the import job |
242 | channelId: channelId, | 216 | channelId: channelId, |
243 | originallyPublishedAt: body.originallyPublishedAt || importData.originallyPublishedAt | 217 | originallyPublishedAt: body.originallyPublishedAt |
218 | ? new Date(body.originallyPublishedAt) | ||
219 | : importData.originallyPublishedAt | ||
244 | } | 220 | } |
245 | const video = new VideoModel(videoData) | 221 | const video = new VideoModel(videoData) |
246 | video.url = getLocalVideoActivityPubUrl(video) | 222 | video.url = getLocalVideoActivityPubUrl(video) |
@@ -304,7 +280,7 @@ async function insertIntoDB (parameters: { | |||
304 | previewModel: MThumbnail | 280 | previewModel: MThumbnail |
305 | videoChannel: MChannelAccountDefault | 281 | videoChannel: MChannelAccountDefault |
306 | tags: string[] | 282 | tags: string[] |
307 | videoImportAttributes: Partial<MVideoImport> | 283 | videoImportAttributes: FilteredModelAttributes<VideoImportModel> |
308 | user: MUser | 284 | user: MUser |
309 | }): Promise<MVideoImportFormattable> { | 285 | }): Promise<MVideoImportFormattable> { |
310 | const { video, thumbnailModel, previewModel, videoChannel, tags, videoImportAttributes, user } = parameters | 286 | const { video, thumbnailModel, previewModel, videoChannel, tags, videoImportAttributes, user } = parameters |
@@ -342,3 +318,71 @@ async function insertIntoDB (parameters: { | |||
342 | 318 | ||
343 | return videoImport | 319 | return videoImport |
344 | } | 320 | } |
321 | |||
322 | async function processTorrentOrAbortRequest (req: express.Request, res: express.Response, torrentfile: Express.Multer.File) { | ||
323 | const torrentName = torrentfile.originalname | ||
324 | |||
325 | // Rename the torrent to a secured name | ||
326 | const newTorrentPath = join(CONFIG.STORAGE.TORRENTS_DIR, getSecureTorrentName(torrentName)) | ||
327 | await move(torrentfile.path, newTorrentPath, { overwrite: true }) | ||
328 | torrentfile.path = newTorrentPath | ||
329 | |||
330 | const buf = await readFile(torrentfile.path) | ||
331 | const parsedTorrent = parseTorrent(buf) as parseTorrent.Instance | ||
332 | |||
333 | if (parsedTorrent.files.length !== 1) { | ||
334 | cleanUpReqFiles(req) | ||
335 | |||
336 | res.status(HttpStatusCode.BAD_REQUEST_400) | ||
337 | .json({ | ||
338 | code: ServerErrorCode.INCORRECT_FILES_IN_TORRENT, | ||
339 | error: 'Torrents with only 1 file are supported.' | ||
340 | }) | ||
341 | |||
342 | return undefined | ||
343 | } | ||
344 | |||
345 | return { | ||
346 | name: extractNameFromArray(parsedTorrent.name), | ||
347 | torrentName | ||
348 | } | ||
349 | } | ||
350 | |||
351 | function processMagnetURI (body: VideoImportCreate) { | ||
352 | const magnetUri = body.magnetUri | ||
353 | const parsed = magnetUtil.decode(magnetUri) | ||
354 | |||
355 | return { | ||
356 | name: extractNameFromArray(parsed.name), | ||
357 | magnetUri | ||
358 | } | ||
359 | } | ||
360 | |||
361 | function extractNameFromArray (name: string | string[]) { | ||
362 | return isArray(name) ? name[0] : name | ||
363 | } | ||
364 | |||
365 | async function processYoutubeSubtitles (youtubeDL: YoutubeDL, targetUrl: string, videoId: number) { | ||
366 | try { | ||
367 | const subtitles = await youtubeDL.getYoutubeDLSubs() | ||
368 | |||
369 | logger.info('Will create %s subtitles from youtube import %s.', subtitles.length, targetUrl) | ||
370 | |||
371 | for (const subtitle of subtitles) { | ||
372 | const videoCaption = new VideoCaptionModel({ | ||
373 | videoId, | ||
374 | language: subtitle.language, | ||
375 | filename: VideoCaptionModel.generateCaptionName(subtitle.language) | ||
376 | }) as MVideoCaption | ||
377 | |||
378 | // Move physical file | ||
379 | await moveAndProcessCaptionFile(subtitle, videoCaption) | ||
380 | |||
381 | await sequelizeTypescript.transaction(async t => { | ||
382 | await VideoCaptionModel.insertOrReplaceLanguage(videoCaption, t) | ||
383 | }) | ||
384 | } | ||
385 | } catch (err) { | ||
386 | logger.warn('Cannot get video subtitles.', { err }) | ||
387 | } | ||
388 | } | ||
diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts index c32626d30..6483d2e8a 100644 --- a/server/controllers/api/videos/index.ts +++ b/server/controllers/api/videos/index.ts | |||
@@ -1,43 +1,20 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { move } from 'fs-extra' | ||
3 | import { extname } from 'path' | ||
4 | import toInt from 'validator/lib/toInt' | 2 | import toInt from 'validator/lib/toInt' |
5 | import { deleteResumableUploadMetaFile, getResumableUploadPath } from '@server/helpers/upload' | ||
6 | import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent' | ||
7 | import { changeVideoChannelShare } from '@server/lib/activitypub/share' | ||
8 | import { getLocalVideoActivityPubUrl } from '@server/lib/activitypub/url' | ||
9 | import { LiveManager } from '@server/lib/live-manager' | 3 | import { LiveManager } from '@server/lib/live-manager' |
10 | import { addOptimizeOrMergeAudioJob, buildLocalVideoFromReq, buildVideoThumbnailsFromReq, setVideoTags } from '@server/lib/video' | ||
11 | import { generateVideoFilename, getVideoFilePath } from '@server/lib/video-paths' | ||
12 | import { getServerActor } from '@server/models/application/application' | 4 | import { getServerActor } from '@server/models/application/application' |
13 | import { MVideo, MVideoFile, MVideoFullLight } from '@server/types/models' | 5 | import { VideosCommonQuery } from '../../../../shared' |
14 | import { uploadx } from '@uploadx/core' | ||
15 | import { VideoCreate, VideosCommonQuery, VideoState, VideoUpdate } from '../../../../shared' | ||
16 | import { HttpStatusCode } from '../../../../shared/core-utils/miscs' | 6 | import { HttpStatusCode } from '../../../../shared/core-utils/miscs' |
17 | import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger' | 7 | import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger' |
18 | import { resetSequelizeInstance, retryTransactionWrapper } from '../../../helpers/database-utils' | 8 | import { buildNSFWFilter, getCountVideos } from '../../../helpers/express-utils' |
19 | import { buildNSFWFilter, createReqFiles, getCountVideos } from '../../../helpers/express-utils' | 9 | import { logger } from '../../../helpers/logger' |
20 | import { getMetadataFromFile, getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffprobe-utils' | ||
21 | import { logger, loggerTagsFactory } from '../../../helpers/logger' | ||
22 | import { getFormattedObjects } from '../../../helpers/utils' | 10 | import { getFormattedObjects } from '../../../helpers/utils' |
23 | import { CONFIG } from '../../../initializers/config' | 11 | import { VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_PRIVACIES } from '../../../initializers/constants' |
24 | import { | ||
25 | DEFAULT_AUDIO_RESOLUTION, | ||
26 | MIMETYPES, | ||
27 | VIDEO_CATEGORIES, | ||
28 | VIDEO_LANGUAGES, | ||
29 | VIDEO_LICENCES, | ||
30 | VIDEO_PRIVACIES | ||
31 | } from '../../../initializers/constants' | ||
32 | import { sequelizeTypescript } from '../../../initializers/database' | 12 | import { sequelizeTypescript } from '../../../initializers/database' |
33 | import { sendView } from '../../../lib/activitypub/send/send-view' | 13 | import { sendView } from '../../../lib/activitypub/send/send-view' |
34 | import { federateVideoIfNeeded, fetchRemoteVideoDescription } from '../../../lib/activitypub/videos' | 14 | import { fetchRemoteVideoDescription } from '../../../lib/activitypub/videos' |
35 | import { JobQueue } from '../../../lib/job-queue' | 15 | import { JobQueue } from '../../../lib/job-queue' |
36 | import { Notifier } from '../../../lib/notifier' | ||
37 | import { Hooks } from '../../../lib/plugins/hooks' | 16 | import { Hooks } from '../../../lib/plugins/hooks' |
38 | import { Redis } from '../../../lib/redis' | 17 | import { Redis } from '../../../lib/redis' |
39 | import { generateVideoMiniature } from '../../../lib/thumbnail' | ||
40 | import { autoBlacklistVideoIfNeeded } from '../../../lib/video-blacklist' | ||
41 | import { | 18 | import { |
42 | asyncMiddleware, | 19 | asyncMiddleware, |
43 | asyncRetryTransactionMiddleware, | 20 | asyncRetryTransactionMiddleware, |
@@ -49,16 +26,11 @@ import { | |||
49 | setDefaultPagination, | 26 | setDefaultPagination, |
50 | setDefaultVideosSort, | 27 | setDefaultVideosSort, |
51 | videoFileMetadataGetValidator, | 28 | videoFileMetadataGetValidator, |
52 | videosAddLegacyValidator, | ||
53 | videosAddResumableInitValidator, | ||
54 | videosAddResumableValidator, | ||
55 | videosCustomGetValidator, | 29 | videosCustomGetValidator, |
56 | videosGetValidator, | 30 | videosGetValidator, |
57 | videosRemoveValidator, | 31 | videosRemoveValidator, |
58 | videosSortValidator, | 32 | videosSortValidator |
59 | videosUpdateValidator | ||
60 | } from '../../../middlewares' | 33 | } from '../../../middlewares' |
61 | import { ScheduleVideoUpdateModel } from '../../../models/video/schedule-video-update' | ||
62 | import { VideoModel } from '../../../models/video/video' | 34 | import { VideoModel } from '../../../models/video/video' |
63 | import { VideoFileModel } from '../../../models/video/video-file' | 35 | import { VideoFileModel } from '../../../models/video/video-file' |
64 | import { blacklistRouter } from './blacklist' | 36 | import { blacklistRouter } from './blacklist' |
@@ -68,40 +40,12 @@ import { videoImportsRouter } from './import' | |||
68 | import { liveRouter } from './live' | 40 | import { liveRouter } from './live' |
69 | import { ownershipVideoRouter } from './ownership' | 41 | import { ownershipVideoRouter } from './ownership' |
70 | import { rateVideoRouter } from './rate' | 42 | import { rateVideoRouter } from './rate' |
43 | import { updateRouter } from './update' | ||
44 | import { uploadRouter } from './upload' | ||
71 | import { watchingRouter } from './watching' | 45 | import { watchingRouter } from './watching' |
72 | 46 | ||
73 | const lTags = loggerTagsFactory('api', 'video') | ||
74 | const auditLogger = auditLoggerFactory('videos') | 47 | const auditLogger = auditLoggerFactory('videos') |
75 | const videosRouter = express.Router() | 48 | const videosRouter = express.Router() |
76 | const uploadxMiddleware = uploadx.upload({ directory: getResumableUploadPath() }) | ||
77 | |||
78 | const reqVideoFileAdd = createReqFiles( | ||
79 | [ 'videofile', 'thumbnailfile', 'previewfile' ], | ||
80 | Object.assign({}, MIMETYPES.VIDEO.MIMETYPE_EXT, MIMETYPES.IMAGE.MIMETYPE_EXT), | ||
81 | { | ||
82 | videofile: CONFIG.STORAGE.TMP_DIR, | ||
83 | thumbnailfile: CONFIG.STORAGE.TMP_DIR, | ||
84 | previewfile: CONFIG.STORAGE.TMP_DIR | ||
85 | } | ||
86 | ) | ||
87 | |||
88 | const reqVideoFileAddResumable = createReqFiles( | ||
89 | [ 'thumbnailfile', 'previewfile' ], | ||
90 | MIMETYPES.IMAGE.MIMETYPE_EXT, | ||
91 | { | ||
92 | thumbnailfile: getResumableUploadPath(), | ||
93 | previewfile: getResumableUploadPath() | ||
94 | } | ||
95 | ) | ||
96 | |||
97 | const reqVideoFileUpdate = createReqFiles( | ||
98 | [ 'thumbnailfile', 'previewfile' ], | ||
99 | MIMETYPES.IMAGE.MIMETYPE_EXT, | ||
100 | { | ||
101 | thumbnailfile: CONFIG.STORAGE.TMP_DIR, | ||
102 | previewfile: CONFIG.STORAGE.TMP_DIR | ||
103 | } | ||
104 | ) | ||
105 | 49 | ||
106 | videosRouter.use('/', blacklistRouter) | 50 | videosRouter.use('/', blacklistRouter) |
107 | videosRouter.use('/', rateVideoRouter) | 51 | videosRouter.use('/', rateVideoRouter) |
@@ -111,6 +55,8 @@ videosRouter.use('/', videoImportsRouter) | |||
111 | videosRouter.use('/', ownershipVideoRouter) | 55 | videosRouter.use('/', ownershipVideoRouter) |
112 | videosRouter.use('/', watchingRouter) | 56 | videosRouter.use('/', watchingRouter) |
113 | videosRouter.use('/', liveRouter) | 57 | videosRouter.use('/', liveRouter) |
58 | videosRouter.use('/', uploadRouter) | ||
59 | videosRouter.use('/', updateRouter) | ||
114 | 60 | ||
115 | videosRouter.get('/categories', listVideoCategories) | 61 | videosRouter.get('/categories', listVideoCategories) |
116 | videosRouter.get('/licences', listVideoLicences) | 62 | videosRouter.get('/licences', listVideoLicences) |
@@ -127,39 +73,6 @@ videosRouter.get('/', | |||
127 | asyncMiddleware(listVideos) | 73 | asyncMiddleware(listVideos) |
128 | ) | 74 | ) |
129 | 75 | ||
130 | videosRouter.post('/upload', | ||
131 | authenticate, | ||
132 | reqVideoFileAdd, | ||
133 | asyncMiddleware(videosAddLegacyValidator), | ||
134 | asyncRetryTransactionMiddleware(addVideoLegacy) | ||
135 | ) | ||
136 | |||
137 | videosRouter.post('/upload-resumable', | ||
138 | authenticate, | ||
139 | reqVideoFileAddResumable, | ||
140 | asyncMiddleware(videosAddResumableInitValidator), | ||
141 | uploadxMiddleware | ||
142 | ) | ||
143 | |||
144 | videosRouter.delete('/upload-resumable', | ||
145 | authenticate, | ||
146 | uploadxMiddleware | ||
147 | ) | ||
148 | |||
149 | videosRouter.put('/upload-resumable', | ||
150 | authenticate, | ||
151 | uploadxMiddleware, // uploadx doesn't use call next() before the file upload completes | ||
152 | asyncMiddleware(videosAddResumableValidator), | ||
153 | asyncMiddleware(addVideoResumable) | ||
154 | ) | ||
155 | |||
156 | videosRouter.put('/:id', | ||
157 | authenticate, | ||
158 | reqVideoFileUpdate, | ||
159 | asyncMiddleware(videosUpdateValidator), | ||
160 | asyncRetryTransactionMiddleware(updateVideo) | ||
161 | ) | ||
162 | |||
163 | videosRouter.get('/:id/description', | 76 | videosRouter.get('/:id/description', |
164 | asyncMiddleware(videosGetValidator), | 77 | asyncMiddleware(videosGetValidator), |
165 | asyncMiddleware(getVideoDescription) | 78 | asyncMiddleware(getVideoDescription) |
@@ -209,279 +122,7 @@ function listVideoPrivacies (_req: express.Request, res: express.Response) { | |||
209 | res.json(VIDEO_PRIVACIES) | 122 | res.json(VIDEO_PRIVACIES) |
210 | } | 123 | } |
211 | 124 | ||
212 | async function addVideoLegacy (req: express.Request, res: express.Response) { | 125 | async function getVideo (_req: express.Request, res: express.Response) { |
213 | // Uploading the video could be long | ||
214 | // Set timeout to 10 minutes, as Express's default is 2 minutes | ||
215 | req.setTimeout(1000 * 60 * 10, () => { | ||
216 | logger.error('Upload video has timed out.') | ||
217 | return res.sendStatus(HttpStatusCode.REQUEST_TIMEOUT_408) | ||
218 | }) | ||
219 | |||
220 | const videoPhysicalFile = req.files['videofile'][0] | ||
221 | const videoInfo: VideoCreate = req.body | ||
222 | const files = req.files | ||
223 | |||
224 | return addVideo({ res, videoPhysicalFile, videoInfo, files }) | ||
225 | } | ||
226 | |||
227 | async function addVideoResumable (_req: express.Request, res: express.Response) { | ||
228 | const videoPhysicalFile = res.locals.videoFileResumable | ||
229 | const videoInfo = videoPhysicalFile.metadata | ||
230 | const files = { previewfile: videoInfo.previewfile } | ||
231 | |||
232 | // Don't need the meta file anymore | ||
233 | await deleteResumableUploadMetaFile(videoPhysicalFile.path) | ||
234 | |||
235 | return addVideo({ res, videoPhysicalFile, videoInfo, files }) | ||
236 | } | ||
237 | |||
238 | async function addVideo (options: { | ||
239 | res: express.Response | ||
240 | videoPhysicalFile: express.VideoUploadFile | ||
241 | videoInfo: VideoCreate | ||
242 | files: express.UploadFiles | ||
243 | }) { | ||
244 | const { res, videoPhysicalFile, videoInfo, files } = options | ||
245 | const videoChannel = res.locals.videoChannel | ||
246 | const user = res.locals.oauth.token.User | ||
247 | |||
248 | const videoData = buildLocalVideoFromReq(videoInfo, videoChannel.id) | ||
249 | |||
250 | videoData.state = CONFIG.TRANSCODING.ENABLED | ||
251 | ? VideoState.TO_TRANSCODE | ||
252 | : VideoState.PUBLISHED | ||
253 | |||
254 | videoData.duration = videoPhysicalFile.duration // duration was added by a previous middleware | ||
255 | |||
256 | const video = new VideoModel(videoData) as MVideoFullLight | ||
257 | video.VideoChannel = videoChannel | ||
258 | video.url = getLocalVideoActivityPubUrl(video) // We use the UUID, so set the URL after building the object | ||
259 | |||
260 | const videoFile = new VideoFileModel({ | ||
261 | extname: extname(videoPhysicalFile.filename), | ||
262 | size: videoPhysicalFile.size, | ||
263 | videoStreamingPlaylistId: null, | ||
264 | metadata: await getMetadataFromFile(videoPhysicalFile.path) | ||
265 | }) | ||
266 | |||
267 | if (videoFile.isAudio()) { | ||
268 | videoFile.resolution = DEFAULT_AUDIO_RESOLUTION | ||
269 | } else { | ||
270 | videoFile.fps = await getVideoFileFPS(videoPhysicalFile.path) | ||
271 | videoFile.resolution = (await getVideoFileResolution(videoPhysicalFile.path)).videoFileResolution | ||
272 | } | ||
273 | |||
274 | videoFile.filename = generateVideoFilename(video, false, videoFile.resolution, videoFile.extname) | ||
275 | |||
276 | // Move physical file | ||
277 | const destination = getVideoFilePath(video, videoFile) | ||
278 | await move(videoPhysicalFile.path, destination) | ||
279 | // This is important in case if there is another attempt in the retry process | ||
280 | videoPhysicalFile.filename = getVideoFilePath(video, videoFile) | ||
281 | videoPhysicalFile.path = destination | ||
282 | |||
283 | const [ thumbnailModel, previewModel ] = await buildVideoThumbnailsFromReq({ | ||
284 | video, | ||
285 | files, | ||
286 | fallback: type => generateVideoMiniature({ video, videoFile, type }) | ||
287 | }) | ||
288 | |||
289 | const { videoCreated } = await sequelizeTypescript.transaction(async t => { | ||
290 | const sequelizeOptions = { transaction: t } | ||
291 | |||
292 | const videoCreated = await video.save(sequelizeOptions) as MVideoFullLight | ||
293 | |||
294 | await videoCreated.addAndSaveThumbnail(thumbnailModel, t) | ||
295 | await videoCreated.addAndSaveThumbnail(previewModel, t) | ||
296 | |||
297 | // Do not forget to add video channel information to the created video | ||
298 | videoCreated.VideoChannel = res.locals.videoChannel | ||
299 | |||
300 | videoFile.videoId = video.id | ||
301 | await videoFile.save(sequelizeOptions) | ||
302 | |||
303 | video.VideoFiles = [ videoFile ] | ||
304 | |||
305 | await setVideoTags({ video, tags: videoInfo.tags, transaction: t }) | ||
306 | |||
307 | // Schedule an update in the future? | ||
308 | if (videoInfo.scheduleUpdate) { | ||
309 | await ScheduleVideoUpdateModel.create({ | ||
310 | videoId: video.id, | ||
311 | updateAt: videoInfo.scheduleUpdate.updateAt, | ||
312 | privacy: videoInfo.scheduleUpdate.privacy || null | ||
313 | }, { transaction: t }) | ||
314 | } | ||
315 | |||
316 | // Channel has a new content, set as updated | ||
317 | await videoCreated.VideoChannel.setAsUpdated(t) | ||
318 | |||
319 | await autoBlacklistVideoIfNeeded({ | ||
320 | video, | ||
321 | user, | ||
322 | isRemote: false, | ||
323 | isNew: true, | ||
324 | transaction: t | ||
325 | }) | ||
326 | |||
327 | auditLogger.create(getAuditIdFromRes(res), new VideoAuditView(videoCreated.toFormattedDetailsJSON())) | ||
328 | logger.info('Video with name %s and uuid %s created.', videoInfo.name, videoCreated.uuid, lTags(videoCreated.uuid)) | ||
329 | |||
330 | return { videoCreated } | ||
331 | }) | ||
332 | |||
333 | // Create the torrent file in async way because it could be long | ||
334 | createTorrentAndSetInfoHashAsync(video, videoFile) | ||
335 | .catch(err => logger.error('Cannot create torrent file for video %s', video.url, { err, ...lTags(video.uuid) })) | ||
336 | .then(() => VideoModel.loadAndPopulateAccountAndServerAndTags(video.id)) | ||
337 | .then(refreshedVideo => { | ||
338 | if (!refreshedVideo) return | ||
339 | |||
340 | // Only federate and notify after the torrent creation | ||
341 | Notifier.Instance.notifyOnNewVideoIfNeeded(refreshedVideo) | ||
342 | |||
343 | return retryTransactionWrapper(() => { | ||
344 | return sequelizeTypescript.transaction(t => federateVideoIfNeeded(refreshedVideo, true, t)) | ||
345 | }) | ||
346 | }) | ||
347 | .catch(err => logger.error('Cannot federate or notify video creation %s', video.url, { err, ...lTags(video.uuid) })) | ||
348 | |||
349 | if (video.state === VideoState.TO_TRANSCODE) { | ||
350 | await addOptimizeOrMergeAudioJob(videoCreated, videoFile, user) | ||
351 | } | ||
352 | |||
353 | Hooks.runAction('action:api.video.uploaded', { video: videoCreated }) | ||
354 | |||
355 | return res.json({ | ||
356 | video: { | ||
357 | id: videoCreated.id, | ||
358 | uuid: videoCreated.uuid | ||
359 | } | ||
360 | }) | ||
361 | } | ||
362 | |||
363 | async function updateVideo (req: express.Request, res: express.Response) { | ||
364 | const videoInstance = res.locals.videoAll | ||
365 | const videoFieldsSave = videoInstance.toJSON() | ||
366 | const oldVideoAuditView = new VideoAuditView(videoInstance.toFormattedDetailsJSON()) | ||
367 | const videoInfoToUpdate: VideoUpdate = req.body | ||
368 | |||
369 | const wasConfidentialVideo = videoInstance.isConfidential() | ||
370 | const hadPrivacyForFederation = videoInstance.hasPrivacyForFederation() | ||
371 | |||
372 | const [ thumbnailModel, previewModel ] = await buildVideoThumbnailsFromReq({ | ||
373 | video: videoInstance, | ||
374 | files: req.files, | ||
375 | fallback: () => Promise.resolve(undefined), | ||
376 | automaticallyGenerated: false | ||
377 | }) | ||
378 | |||
379 | try { | ||
380 | const videoInstanceUpdated = await sequelizeTypescript.transaction(async t => { | ||
381 | const sequelizeOptions = { transaction: t } | ||
382 | const oldVideoChannel = videoInstance.VideoChannel | ||
383 | |||
384 | if (videoInfoToUpdate.name !== undefined) videoInstance.name = videoInfoToUpdate.name | ||
385 | if (videoInfoToUpdate.category !== undefined) videoInstance.category = videoInfoToUpdate.category | ||
386 | if (videoInfoToUpdate.licence !== undefined) videoInstance.licence = videoInfoToUpdate.licence | ||
387 | if (videoInfoToUpdate.language !== undefined) videoInstance.language = videoInfoToUpdate.language | ||
388 | if (videoInfoToUpdate.nsfw !== undefined) videoInstance.nsfw = videoInfoToUpdate.nsfw | ||
389 | if (videoInfoToUpdate.waitTranscoding !== undefined) videoInstance.waitTranscoding = videoInfoToUpdate.waitTranscoding | ||
390 | if (videoInfoToUpdate.support !== undefined) videoInstance.support = videoInfoToUpdate.support | ||
391 | if (videoInfoToUpdate.description !== undefined) videoInstance.description = videoInfoToUpdate.description | ||
392 | if (videoInfoToUpdate.commentsEnabled !== undefined) videoInstance.commentsEnabled = videoInfoToUpdate.commentsEnabled | ||
393 | if (videoInfoToUpdate.downloadEnabled !== undefined) videoInstance.downloadEnabled = videoInfoToUpdate.downloadEnabled | ||
394 | |||
395 | if (videoInfoToUpdate.originallyPublishedAt !== undefined && videoInfoToUpdate.originallyPublishedAt !== null) { | ||
396 | videoInstance.originallyPublishedAt = new Date(videoInfoToUpdate.originallyPublishedAt) | ||
397 | } | ||
398 | |||
399 | let isNewVideo = false | ||
400 | if (videoInfoToUpdate.privacy !== undefined) { | ||
401 | isNewVideo = videoInstance.isNewVideo(videoInfoToUpdate.privacy) | ||
402 | |||
403 | const newPrivacy = parseInt(videoInfoToUpdate.privacy.toString(), 10) | ||
404 | videoInstance.setPrivacy(newPrivacy) | ||
405 | |||
406 | // Unfederate the video if the new privacy is not compatible with federation | ||
407 | if (hadPrivacyForFederation && !videoInstance.hasPrivacyForFederation()) { | ||
408 | await VideoModel.sendDelete(videoInstance, { transaction: t }) | ||
409 | } | ||
410 | } | ||
411 | |||
412 | const videoInstanceUpdated = await videoInstance.save(sequelizeOptions) as MVideoFullLight | ||
413 | |||
414 | if (thumbnailModel) await videoInstanceUpdated.addAndSaveThumbnail(thumbnailModel, t) | ||
415 | if (previewModel) await videoInstanceUpdated.addAndSaveThumbnail(previewModel, t) | ||
416 | |||
417 | // Video tags update? | ||
418 | if (videoInfoToUpdate.tags !== undefined) { | ||
419 | await setVideoTags({ | ||
420 | video: videoInstanceUpdated, | ||
421 | tags: videoInfoToUpdate.tags, | ||
422 | transaction: t | ||
423 | }) | ||
424 | } | ||
425 | |||
426 | // Video channel update? | ||
427 | if (res.locals.videoChannel && videoInstanceUpdated.channelId !== res.locals.videoChannel.id) { | ||
428 | await videoInstanceUpdated.$set('VideoChannel', res.locals.videoChannel, { transaction: t }) | ||
429 | videoInstanceUpdated.VideoChannel = res.locals.videoChannel | ||
430 | |||
431 | if (hadPrivacyForFederation === true) await changeVideoChannelShare(videoInstanceUpdated, oldVideoChannel, t) | ||
432 | } | ||
433 | |||
434 | // Schedule an update in the future? | ||
435 | if (videoInfoToUpdate.scheduleUpdate) { | ||
436 | await ScheduleVideoUpdateModel.upsert({ | ||
437 | videoId: videoInstanceUpdated.id, | ||
438 | updateAt: videoInfoToUpdate.scheduleUpdate.updateAt, | ||
439 | privacy: videoInfoToUpdate.scheduleUpdate.privacy || null | ||
440 | }, { transaction: t }) | ||
441 | } else if (videoInfoToUpdate.scheduleUpdate === null) { | ||
442 | await ScheduleVideoUpdateModel.deleteByVideoId(videoInstanceUpdated.id, t) | ||
443 | } | ||
444 | |||
445 | await autoBlacklistVideoIfNeeded({ | ||
446 | video: videoInstanceUpdated, | ||
447 | user: res.locals.oauth.token.User, | ||
448 | isRemote: false, | ||
449 | isNew: false, | ||
450 | transaction: t | ||
451 | }) | ||
452 | |||
453 | await federateVideoIfNeeded(videoInstanceUpdated, isNewVideo, t) | ||
454 | |||
455 | auditLogger.update( | ||
456 | getAuditIdFromRes(res), | ||
457 | new VideoAuditView(videoInstanceUpdated.toFormattedDetailsJSON()), | ||
458 | oldVideoAuditView | ||
459 | ) | ||
460 | logger.info('Video with name %s and uuid %s updated.', videoInstance.name, videoInstance.uuid, lTags(videoInstance.uuid)) | ||
461 | |||
462 | return videoInstanceUpdated | ||
463 | }) | ||
464 | |||
465 | if (wasConfidentialVideo) { | ||
466 | Notifier.Instance.notifyOnNewVideoIfNeeded(videoInstanceUpdated) | ||
467 | } | ||
468 | |||
469 | Hooks.runAction('action:api.video.updated', { video: videoInstanceUpdated, body: req.body }) | ||
470 | } catch (err) { | ||
471 | // Force fields we want to update | ||
472 | // If the transaction is retried, sequelize will think the object has not changed | ||
473 | // So it will skip the SQL request, even if the last one was ROLLBACKed! | ||
474 | resetSequelizeInstance(videoInstance, videoFieldsSave) | ||
475 | |||
476 | throw err | ||
477 | } | ||
478 | |||
479 | return res.type('json') | ||
480 | .status(HttpStatusCode.NO_CONTENT_204) | ||
481 | .end() | ||
482 | } | ||
483 | |||
484 | async function getVideo (req: express.Request, res: express.Response) { | ||
485 | // We need more attributes | 126 | // We need more attributes |
486 | const userId: number = res.locals.oauth ? res.locals.oauth.token.User.id : null | 127 | const userId: number = res.locals.oauth ? res.locals.oauth.token.User.id : null |
487 | 128 | ||
@@ -543,13 +184,10 @@ async function viewVideo (req: express.Request, res: express.Response) { | |||
543 | 184 | ||
544 | async function getVideoDescription (req: express.Request, res: express.Response) { | 185 | async function getVideoDescription (req: express.Request, res: express.Response) { |
545 | const videoInstance = res.locals.videoAll | 186 | const videoInstance = res.locals.videoAll |
546 | let description = '' | ||
547 | 187 | ||
548 | if (videoInstance.isOwned()) { | 188 | const description = videoInstance.isOwned() |
549 | description = videoInstance.description | 189 | ? videoInstance.description |
550 | } else { | 190 | : await fetchRemoteVideoDescription(videoInstance) |
551 | description = await fetchRemoteVideoDescription(videoInstance) | ||
552 | } | ||
553 | 191 | ||
554 | return res.json({ description }) | 192 | return res.json({ description }) |
555 | } | 193 | } |
@@ -591,7 +229,7 @@ async function listVideos (req: express.Request, res: express.Response) { | |||
591 | return res.json(getFormattedObjects(resultList.data, resultList.total)) | 229 | return res.json(getFormattedObjects(resultList.data, resultList.total)) |
592 | } | 230 | } |
593 | 231 | ||
594 | async function removeVideo (req: express.Request, res: express.Response) { | 232 | async function removeVideo (_req: express.Request, res: express.Response) { |
595 | const videoInstance = res.locals.videoAll | 233 | const videoInstance = res.locals.videoAll |
596 | 234 | ||
597 | await sequelizeTypescript.transaction(async t => { | 235 | await sequelizeTypescript.transaction(async t => { |
@@ -607,17 +245,3 @@ async function removeVideo (req: express.Request, res: express.Response) { | |||
607 | .status(HttpStatusCode.NO_CONTENT_204) | 245 | .status(HttpStatusCode.NO_CONTENT_204) |
608 | .end() | 246 | .end() |
609 | } | 247 | } |
610 | |||
611 | async function createTorrentAndSetInfoHashAsync (video: MVideo, fileArg: MVideoFile) { | ||
612 | await createTorrentAndSetInfoHash(video, fileArg) | ||
613 | |||
614 | // Refresh videoFile because the createTorrentAndSetInfoHash could be long | ||
615 | const refreshedFile = await VideoFileModel.loadWithVideo(fileArg.id) | ||
616 | // File does not exist anymore, remove the generated torrent | ||
617 | if (!refreshedFile) return fileArg.removeTorrent() | ||
618 | |||
619 | refreshedFile.infoHash = fileArg.infoHash | ||
620 | refreshedFile.torrentFilename = fileArg.torrentFilename | ||
621 | |||
622 | return refreshedFile.save() | ||
623 | } | ||
diff --git a/server/controllers/api/videos/ownership.ts b/server/controllers/api/videos/ownership.ts index a85d7c30b..6102f28dc 100644 --- a/server/controllers/api/videos/ownership.ts +++ b/server/controllers/api/videos/ownership.ts | |||
@@ -99,7 +99,7 @@ async function listVideoOwnership (req: express.Request, res: express.Response) | |||
99 | return res.json(getFormattedObjects(resultList.data, resultList.total)) | 99 | return res.json(getFormattedObjects(resultList.data, resultList.total)) |
100 | } | 100 | } |
101 | 101 | ||
102 | async function acceptOwnership (req: express.Request, res: express.Response) { | 102 | function acceptOwnership (req: express.Request, res: express.Response) { |
103 | return sequelizeTypescript.transaction(async t => { | 103 | return sequelizeTypescript.transaction(async t => { |
104 | const videoChangeOwnership = res.locals.videoChangeOwnership | 104 | const videoChangeOwnership = res.locals.videoChangeOwnership |
105 | const channel = res.locals.videoChannel | 105 | const channel = res.locals.videoChannel |
@@ -126,7 +126,7 @@ async function acceptOwnership (req: express.Request, res: express.Response) { | |||
126 | }) | 126 | }) |
127 | } | 127 | } |
128 | 128 | ||
129 | async function refuseOwnership (req: express.Request, res: express.Response) { | 129 | function refuseOwnership (req: express.Request, res: express.Response) { |
130 | return sequelizeTypescript.transaction(async t => { | 130 | return sequelizeTypescript.transaction(async t => { |
131 | const videoChangeOwnership = res.locals.videoChangeOwnership | 131 | const videoChangeOwnership = res.locals.videoChangeOwnership |
132 | 132 | ||
diff --git a/server/controllers/api/videos/update.ts b/server/controllers/api/videos/update.ts new file mode 100644 index 000000000..2450abd0e --- /dev/null +++ b/server/controllers/api/videos/update.ts | |||
@@ -0,0 +1,191 @@ | |||
1 | import * as express from 'express' | ||
2 | import { Transaction } from 'sequelize/types' | ||
3 | import { changeVideoChannelShare } from '@server/lib/activitypub/share' | ||
4 | import { buildVideoThumbnailsFromReq, setVideoTags } from '@server/lib/video' | ||
5 | import { FilteredModelAttributes } from '@server/types' | ||
6 | import { MVideoFullLight } from '@server/types/models' | ||
7 | import { VideoUpdate } from '../../../../shared' | ||
8 | import { HttpStatusCode } from '../../../../shared/core-utils/miscs' | ||
9 | import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger' | ||
10 | import { resetSequelizeInstance } from '../../../helpers/database-utils' | ||
11 | import { createReqFiles } from '../../../helpers/express-utils' | ||
12 | import { logger, loggerTagsFactory } from '../../../helpers/logger' | ||
13 | import { CONFIG } from '../../../initializers/config' | ||
14 | import { MIMETYPES } from '../../../initializers/constants' | ||
15 | import { sequelizeTypescript } from '../../../initializers/database' | ||
16 | import { federateVideoIfNeeded } from '../../../lib/activitypub/videos' | ||
17 | import { Notifier } from '../../../lib/notifier' | ||
18 | import { Hooks } from '../../../lib/plugins/hooks' | ||
19 | import { autoBlacklistVideoIfNeeded } from '../../../lib/video-blacklist' | ||
20 | import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videosUpdateValidator } from '../../../middlewares' | ||
21 | import { ScheduleVideoUpdateModel } from '../../../models/video/schedule-video-update' | ||
22 | import { VideoModel } from '../../../models/video/video' | ||
23 | |||
24 | const lTags = loggerTagsFactory('api', 'video') | ||
25 | const auditLogger = auditLoggerFactory('videos') | ||
26 | const updateRouter = express.Router() | ||
27 | |||
28 | const reqVideoFileUpdate = createReqFiles( | ||
29 | [ 'thumbnailfile', 'previewfile' ], | ||
30 | MIMETYPES.IMAGE.MIMETYPE_EXT, | ||
31 | { | ||
32 | thumbnailfile: CONFIG.STORAGE.TMP_DIR, | ||
33 | previewfile: CONFIG.STORAGE.TMP_DIR | ||
34 | } | ||
35 | ) | ||
36 | |||
37 | updateRouter.put('/:id', | ||
38 | authenticate, | ||
39 | reqVideoFileUpdate, | ||
40 | asyncMiddleware(videosUpdateValidator), | ||
41 | asyncRetryTransactionMiddleware(updateVideo) | ||
42 | ) | ||
43 | |||
44 | // --------------------------------------------------------------------------- | ||
45 | |||
46 | export { | ||
47 | updateRouter | ||
48 | } | ||
49 | |||
50 | // --------------------------------------------------------------------------- | ||
51 | |||
52 | export async function updateVideo (req: express.Request, res: express.Response) { | ||
53 | const videoInstance = res.locals.videoAll | ||
54 | const videoFieldsSave = videoInstance.toJSON() | ||
55 | const oldVideoAuditView = new VideoAuditView(videoInstance.toFormattedDetailsJSON()) | ||
56 | const videoInfoToUpdate: VideoUpdate = req.body | ||
57 | |||
58 | const wasConfidentialVideo = videoInstance.isConfidential() | ||
59 | const hadPrivacyForFederation = videoInstance.hasPrivacyForFederation() | ||
60 | |||
61 | const [ thumbnailModel, previewModel ] = await buildVideoThumbnailsFromReq({ | ||
62 | video: videoInstance, | ||
63 | files: req.files, | ||
64 | fallback: () => Promise.resolve(undefined), | ||
65 | automaticallyGenerated: false | ||
66 | }) | ||
67 | |||
68 | try { | ||
69 | const videoInstanceUpdated = await sequelizeTypescript.transaction(async t => { | ||
70 | const sequelizeOptions = { transaction: t } | ||
71 | const oldVideoChannel = videoInstance.VideoChannel | ||
72 | |||
73 | const keysToUpdate: (keyof VideoUpdate & FilteredModelAttributes<VideoModel>)[] = [ | ||
74 | 'name', | ||
75 | 'category', | ||
76 | 'licence', | ||
77 | 'language', | ||
78 | 'nsfw', | ||
79 | 'waitTranscoding', | ||
80 | 'support', | ||
81 | 'description', | ||
82 | 'commentsEnabled', | ||
83 | 'downloadEnabled' | ||
84 | ] | ||
85 | |||
86 | for (const key of keysToUpdate) { | ||
87 | if (videoInfoToUpdate[key] !== undefined) videoInstance.set(key, videoInfoToUpdate[key]) | ||
88 | } | ||
89 | |||
90 | if (videoInfoToUpdate.originallyPublishedAt !== undefined && videoInfoToUpdate.originallyPublishedAt !== null) { | ||
91 | videoInstance.originallyPublishedAt = new Date(videoInfoToUpdate.originallyPublishedAt) | ||
92 | } | ||
93 | |||
94 | // Privacy update? | ||
95 | let isNewVideo = false | ||
96 | if (videoInfoToUpdate.privacy !== undefined) { | ||
97 | isNewVideo = await updateVideoPrivacy({ videoInstance, videoInfoToUpdate, hadPrivacyForFederation, transaction: t }) | ||
98 | } | ||
99 | |||
100 | const videoInstanceUpdated = await videoInstance.save(sequelizeOptions) as MVideoFullLight | ||
101 | |||
102 | // Thumbnail & preview updates? | ||
103 | if (thumbnailModel) await videoInstanceUpdated.addAndSaveThumbnail(thumbnailModel, t) | ||
104 | if (previewModel) await videoInstanceUpdated.addAndSaveThumbnail(previewModel, t) | ||
105 | |||
106 | // Video tags update? | ||
107 | if (videoInfoToUpdate.tags !== undefined) { | ||
108 | await setVideoTags({ video: videoInstanceUpdated, tags: videoInfoToUpdate.tags, transaction: t }) | ||
109 | } | ||
110 | |||
111 | // Video channel update? | ||
112 | if (res.locals.videoChannel && videoInstanceUpdated.channelId !== res.locals.videoChannel.id) { | ||
113 | await videoInstanceUpdated.$set('VideoChannel', res.locals.videoChannel, { transaction: t }) | ||
114 | videoInstanceUpdated.VideoChannel = res.locals.videoChannel | ||
115 | |||
116 | if (hadPrivacyForFederation === true) await changeVideoChannelShare(videoInstanceUpdated, oldVideoChannel, t) | ||
117 | } | ||
118 | |||
119 | // Schedule an update in the future? | ||
120 | await updateSchedule(videoInstanceUpdated, videoInfoToUpdate, t) | ||
121 | |||
122 | await autoBlacklistVideoIfNeeded({ | ||
123 | video: videoInstanceUpdated, | ||
124 | user: res.locals.oauth.token.User, | ||
125 | isRemote: false, | ||
126 | isNew: false, | ||
127 | transaction: t | ||
128 | }) | ||
129 | |||
130 | await federateVideoIfNeeded(videoInstanceUpdated, isNewVideo, t) | ||
131 | |||
132 | auditLogger.update( | ||
133 | getAuditIdFromRes(res), | ||
134 | new VideoAuditView(videoInstanceUpdated.toFormattedDetailsJSON()), | ||
135 | oldVideoAuditView | ||
136 | ) | ||
137 | logger.info('Video with name %s and uuid %s updated.', videoInstance.name, videoInstance.uuid, lTags(videoInstance.uuid)) | ||
138 | |||
139 | return videoInstanceUpdated | ||
140 | }) | ||
141 | |||
142 | if (wasConfidentialVideo) { | ||
143 | Notifier.Instance.notifyOnNewVideoIfNeeded(videoInstanceUpdated) | ||
144 | } | ||
145 | |||
146 | Hooks.runAction('action:api.video.updated', { video: videoInstanceUpdated, body: req.body }) | ||
147 | } catch (err) { | ||
148 | // Force fields we want to update | ||
149 | // If the transaction is retried, sequelize will think the object has not changed | ||
150 | // So it will skip the SQL request, even if the last one was ROLLBACKed! | ||
151 | resetSequelizeInstance(videoInstance, videoFieldsSave) | ||
152 | |||
153 | throw err | ||
154 | } | ||
155 | |||
156 | return res.type('json') | ||
157 | .status(HttpStatusCode.NO_CONTENT_204) | ||
158 | .end() | ||
159 | } | ||
160 | |||
161 | async function updateVideoPrivacy (options: { | ||
162 | videoInstance: MVideoFullLight | ||
163 | videoInfoToUpdate: VideoUpdate | ||
164 | hadPrivacyForFederation: boolean | ||
165 | transaction: Transaction | ||
166 | }) { | ||
167 | const { videoInstance, videoInfoToUpdate, hadPrivacyForFederation, transaction } = options | ||
168 | const isNewVideo = videoInstance.isNewVideo(videoInfoToUpdate.privacy) | ||
169 | |||
170 | const newPrivacy = parseInt(videoInfoToUpdate.privacy.toString(), 10) | ||
171 | videoInstance.setPrivacy(newPrivacy) | ||
172 | |||
173 | // Unfederate the video if the new privacy is not compatible with federation | ||
174 | if (hadPrivacyForFederation && !videoInstance.hasPrivacyForFederation()) { | ||
175 | await VideoModel.sendDelete(videoInstance, { transaction }) | ||
176 | } | ||
177 | |||
178 | return isNewVideo | ||
179 | } | ||
180 | |||
181 | function updateSchedule (videoInstance: MVideoFullLight, videoInfoToUpdate: VideoUpdate, transaction: Transaction) { | ||
182 | if (videoInfoToUpdate.scheduleUpdate) { | ||
183 | return ScheduleVideoUpdateModel.upsert({ | ||
184 | videoId: videoInstance.id, | ||
185 | updateAt: new Date(videoInfoToUpdate.scheduleUpdate.updateAt), | ||
186 | privacy: videoInfoToUpdate.scheduleUpdate.privacy || null | ||
187 | }, { transaction }) | ||
188 | } else if (videoInfoToUpdate.scheduleUpdate === null) { | ||
189 | return ScheduleVideoUpdateModel.deleteByVideoId(videoInstance.id, transaction) | ||
190 | } | ||
191 | } | ||
diff --git a/server/controllers/api/videos/upload.ts b/server/controllers/api/videos/upload.ts new file mode 100644 index 000000000..ebc17c760 --- /dev/null +++ b/server/controllers/api/videos/upload.ts | |||
@@ -0,0 +1,269 @@ | |||
1 | import * as express from 'express' | ||
2 | import { move } from 'fs-extra' | ||
3 | import { extname } from 'path' | ||
4 | import { deleteResumableUploadMetaFile, getResumableUploadPath } from '@server/helpers/upload' | ||
5 | import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent' | ||
6 | import { getLocalVideoActivityPubUrl } from '@server/lib/activitypub/url' | ||
7 | import { addOptimizeOrMergeAudioJob, buildLocalVideoFromReq, buildVideoThumbnailsFromReq, setVideoTags } from '@server/lib/video' | ||
8 | import { generateVideoFilename, getVideoFilePath } from '@server/lib/video-paths' | ||
9 | import { MVideo, MVideoFile, MVideoFullLight } from '@server/types/models' | ||
10 | import { uploadx } from '@uploadx/core' | ||
11 | import { VideoCreate, VideoState } from '../../../../shared' | ||
12 | import { HttpStatusCode } from '../../../../shared/core-utils/miscs' | ||
13 | import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger' | ||
14 | import { retryTransactionWrapper } from '../../../helpers/database-utils' | ||
15 | import { createReqFiles } from '../../../helpers/express-utils' | ||
16 | import { getMetadataFromFile, getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffprobe-utils' | ||
17 | import { logger, loggerTagsFactory } from '../../../helpers/logger' | ||
18 | import { CONFIG } from '../../../initializers/config' | ||
19 | import { DEFAULT_AUDIO_RESOLUTION, MIMETYPES } from '../../../initializers/constants' | ||
20 | import { sequelizeTypescript } from '../../../initializers/database' | ||
21 | import { federateVideoIfNeeded } from '../../../lib/activitypub/videos' | ||
22 | import { Notifier } from '../../../lib/notifier' | ||
23 | import { Hooks } from '../../../lib/plugins/hooks' | ||
24 | import { generateVideoMiniature } from '../../../lib/thumbnail' | ||
25 | import { autoBlacklistVideoIfNeeded } from '../../../lib/video-blacklist' | ||
26 | import { | ||
27 | asyncMiddleware, | ||
28 | asyncRetryTransactionMiddleware, | ||
29 | authenticate, | ||
30 | videosAddLegacyValidator, | ||
31 | videosAddResumableInitValidator, | ||
32 | videosAddResumableValidator | ||
33 | } from '../../../middlewares' | ||
34 | import { ScheduleVideoUpdateModel } from '../../../models/video/schedule-video-update' | ||
35 | import { VideoModel } from '../../../models/video/video' | ||
36 | import { VideoFileModel } from '../../../models/video/video-file' | ||
37 | |||
38 | const lTags = loggerTagsFactory('api', 'video') | ||
39 | const auditLogger = auditLoggerFactory('videos') | ||
40 | const uploadRouter = express.Router() | ||
41 | const uploadxMiddleware = uploadx.upload({ directory: getResumableUploadPath() }) | ||
42 | |||
43 | const reqVideoFileAdd = createReqFiles( | ||
44 | [ 'videofile', 'thumbnailfile', 'previewfile' ], | ||
45 | Object.assign({}, MIMETYPES.VIDEO.MIMETYPE_EXT, MIMETYPES.IMAGE.MIMETYPE_EXT), | ||
46 | { | ||
47 | videofile: CONFIG.STORAGE.TMP_DIR, | ||
48 | thumbnailfile: CONFIG.STORAGE.TMP_DIR, | ||
49 | previewfile: CONFIG.STORAGE.TMP_DIR | ||
50 | } | ||
51 | ) | ||
52 | |||
53 | const reqVideoFileAddResumable = createReqFiles( | ||
54 | [ 'thumbnailfile', 'previewfile' ], | ||
55 | MIMETYPES.IMAGE.MIMETYPE_EXT, | ||
56 | { | ||
57 | thumbnailfile: getResumableUploadPath(), | ||
58 | previewfile: getResumableUploadPath() | ||
59 | } | ||
60 | ) | ||
61 | |||
62 | uploadRouter.post('/upload', | ||
63 | authenticate, | ||
64 | reqVideoFileAdd, | ||
65 | asyncMiddleware(videosAddLegacyValidator), | ||
66 | asyncRetryTransactionMiddleware(addVideoLegacy) | ||
67 | ) | ||
68 | |||
69 | uploadRouter.post('/upload-resumable', | ||
70 | authenticate, | ||
71 | reqVideoFileAddResumable, | ||
72 | asyncMiddleware(videosAddResumableInitValidator), | ||
73 | uploadxMiddleware | ||
74 | ) | ||
75 | |||
76 | uploadRouter.delete('/upload-resumable', | ||
77 | authenticate, | ||
78 | uploadxMiddleware | ||
79 | ) | ||
80 | |||
81 | uploadRouter.put('/upload-resumable', | ||
82 | authenticate, | ||
83 | uploadxMiddleware, // uploadx doesn't use call next() before the file upload completes | ||
84 | asyncMiddleware(videosAddResumableValidator), | ||
85 | asyncMiddleware(addVideoResumable) | ||
86 | ) | ||
87 | |||
88 | // --------------------------------------------------------------------------- | ||
89 | |||
90 | export { | ||
91 | uploadRouter | ||
92 | } | ||
93 | |||
94 | // --------------------------------------------------------------------------- | ||
95 | |||
96 | export async function addVideoLegacy (req: express.Request, res: express.Response) { | ||
97 | // Uploading the video could be long | ||
98 | // Set timeout to 10 minutes, as Express's default is 2 minutes | ||
99 | req.setTimeout(1000 * 60 * 10, () => { | ||
100 | logger.error('Upload video has timed out.') | ||
101 | return res.sendStatus(HttpStatusCode.REQUEST_TIMEOUT_408) | ||
102 | }) | ||
103 | |||
104 | const videoPhysicalFile = req.files['videofile'][0] | ||
105 | const videoInfo: VideoCreate = req.body | ||
106 | const files = req.files | ||
107 | |||
108 | return addVideo({ res, videoPhysicalFile, videoInfo, files }) | ||
109 | } | ||
110 | |||
111 | export async function addVideoResumable (_req: express.Request, res: express.Response) { | ||
112 | const videoPhysicalFile = res.locals.videoFileResumable | ||
113 | const videoInfo = videoPhysicalFile.metadata | ||
114 | const files = { previewfile: videoInfo.previewfile } | ||
115 | |||
116 | // Don't need the meta file anymore | ||
117 | await deleteResumableUploadMetaFile(videoPhysicalFile.path) | ||
118 | |||
119 | return addVideo({ res, videoPhysicalFile, videoInfo, files }) | ||
120 | } | ||
121 | |||
122 | async function addVideo (options: { | ||
123 | res: express.Response | ||
124 | videoPhysicalFile: express.VideoUploadFile | ||
125 | videoInfo: VideoCreate | ||
126 | files: express.UploadFiles | ||
127 | }) { | ||
128 | const { res, videoPhysicalFile, videoInfo, files } = options | ||
129 | const videoChannel = res.locals.videoChannel | ||
130 | const user = res.locals.oauth.token.User | ||
131 | |||
132 | const videoData = buildLocalVideoFromReq(videoInfo, videoChannel.id) | ||
133 | |||
134 | videoData.state = CONFIG.TRANSCODING.ENABLED | ||
135 | ? VideoState.TO_TRANSCODE | ||
136 | : VideoState.PUBLISHED | ||
137 | |||
138 | videoData.duration = videoPhysicalFile.duration // duration was added by a previous middleware | ||
139 | |||
140 | const video = new VideoModel(videoData) as MVideoFullLight | ||
141 | video.VideoChannel = videoChannel | ||
142 | video.url = getLocalVideoActivityPubUrl(video) // We use the UUID, so set the URL after building the object | ||
143 | |||
144 | const videoFile = await buildNewFile(video, videoPhysicalFile) | ||
145 | |||
146 | // Move physical file | ||
147 | const destination = getVideoFilePath(video, videoFile) | ||
148 | await move(videoPhysicalFile.path, destination) | ||
149 | // This is important in case if there is another attempt in the retry process | ||
150 | videoPhysicalFile.filename = getVideoFilePath(video, videoFile) | ||
151 | videoPhysicalFile.path = destination | ||
152 | |||
153 | const [ thumbnailModel, previewModel ] = await buildVideoThumbnailsFromReq({ | ||
154 | video, | ||
155 | files, | ||
156 | fallback: type => generateVideoMiniature({ video, videoFile, type }) | ||
157 | }) | ||
158 | |||
159 | const { videoCreated } = await sequelizeTypescript.transaction(async t => { | ||
160 | const sequelizeOptions = { transaction: t } | ||
161 | |||
162 | const videoCreated = await video.save(sequelizeOptions) as MVideoFullLight | ||
163 | |||
164 | await videoCreated.addAndSaveThumbnail(thumbnailModel, t) | ||
165 | await videoCreated.addAndSaveThumbnail(previewModel, t) | ||
166 | |||
167 | // Do not forget to add video channel information to the created video | ||
168 | videoCreated.VideoChannel = res.locals.videoChannel | ||
169 | |||
170 | videoFile.videoId = video.id | ||
171 | await videoFile.save(sequelizeOptions) | ||
172 | |||
173 | video.VideoFiles = [ videoFile ] | ||
174 | |||
175 | await setVideoTags({ video, tags: videoInfo.tags, transaction: t }) | ||
176 | |||
177 | // Schedule an update in the future? | ||
178 | if (videoInfo.scheduleUpdate) { | ||
179 | await ScheduleVideoUpdateModel.create({ | ||
180 | videoId: video.id, | ||
181 | updateAt: new Date(videoInfo.scheduleUpdate.updateAt), | ||
182 | privacy: videoInfo.scheduleUpdate.privacy || null | ||
183 | }, sequelizeOptions) | ||
184 | } | ||
185 | |||
186 | // Channel has a new content, set as updated | ||
187 | await videoCreated.VideoChannel.setAsUpdated(t) | ||
188 | |||
189 | await autoBlacklistVideoIfNeeded({ | ||
190 | video, | ||
191 | user, | ||
192 | isRemote: false, | ||
193 | isNew: true, | ||
194 | transaction: t | ||
195 | }) | ||
196 | |||
197 | auditLogger.create(getAuditIdFromRes(res), new VideoAuditView(videoCreated.toFormattedDetailsJSON())) | ||
198 | logger.info('Video with name %s and uuid %s created.', videoInfo.name, videoCreated.uuid, lTags(videoCreated.uuid)) | ||
199 | |||
200 | return { videoCreated } | ||
201 | }) | ||
202 | |||
203 | createTorrentFederate(video, videoFile) | ||
204 | |||
205 | if (video.state === VideoState.TO_TRANSCODE) { | ||
206 | await addOptimizeOrMergeAudioJob(videoCreated, videoFile, user) | ||
207 | } | ||
208 | |||
209 | Hooks.runAction('action:api.video.uploaded', { video: videoCreated }) | ||
210 | |||
211 | return res.json({ | ||
212 | video: { | ||
213 | id: videoCreated.id, | ||
214 | uuid: videoCreated.uuid | ||
215 | } | ||
216 | }) | ||
217 | } | ||
218 | |||
219 | async function buildNewFile (video: MVideo, videoPhysicalFile: express.VideoUploadFile) { | ||
220 | const videoFile = new VideoFileModel({ | ||
221 | extname: extname(videoPhysicalFile.filename), | ||
222 | size: videoPhysicalFile.size, | ||
223 | videoStreamingPlaylistId: null, | ||
224 | metadata: await getMetadataFromFile(videoPhysicalFile.path) | ||
225 | }) | ||
226 | |||
227 | if (videoFile.isAudio()) { | ||
228 | videoFile.resolution = DEFAULT_AUDIO_RESOLUTION | ||
229 | } else { | ||
230 | videoFile.fps = await getVideoFileFPS(videoPhysicalFile.path) | ||
231 | videoFile.resolution = (await getVideoFileResolution(videoPhysicalFile.path)).videoFileResolution | ||
232 | } | ||
233 | |||
234 | videoFile.filename = generateVideoFilename(video, false, videoFile.resolution, videoFile.extname) | ||
235 | |||
236 | return videoFile | ||
237 | } | ||
238 | |||
239 | async function createTorrentAndSetInfoHashAsync (video: MVideo, fileArg: MVideoFile) { | ||
240 | await createTorrentAndSetInfoHash(video, fileArg) | ||
241 | |||
242 | // Refresh videoFile because the createTorrentAndSetInfoHash could be long | ||
243 | const refreshedFile = await VideoFileModel.loadWithVideo(fileArg.id) | ||
244 | // File does not exist anymore, remove the generated torrent | ||
245 | if (!refreshedFile) return fileArg.removeTorrent() | ||
246 | |||
247 | refreshedFile.infoHash = fileArg.infoHash | ||
248 | refreshedFile.torrentFilename = fileArg.torrentFilename | ||
249 | |||
250 | return refreshedFile.save() | ||
251 | } | ||
252 | |||
253 | function createTorrentFederate (video: MVideoFullLight, videoFile: MVideoFile): void { | ||
254 | // Create the torrent file in async way because it could be long | ||
255 | createTorrentAndSetInfoHashAsync(video, videoFile) | ||
256 | .catch(err => logger.error('Cannot create torrent file for video %s', video.url, { err, ...lTags(video.uuid) })) | ||
257 | .then(() => VideoModel.loadAndPopulateAccountAndServerAndTags(video.id)) | ||
258 | .then(refreshedVideo => { | ||
259 | if (!refreshedVideo) return | ||
260 | |||
261 | // Only federate and notify after the torrent creation | ||
262 | Notifier.Instance.notifyOnNewVideoIfNeeded(refreshedVideo) | ||
263 | |||
264 | return retryTransactionWrapper(() => { | ||
265 | return sequelizeTypescript.transaction(t => federateVideoIfNeeded(refreshedVideo, true, t)) | ||
266 | }) | ||
267 | }) | ||
268 | .catch(err => logger.error('Cannot federate or notify video creation %s', video.url, { err, ...lTags(video.uuid) })) | ||
269 | } | ||
diff --git a/server/controllers/api/videos/watching.ts b/server/controllers/api/videos/watching.ts index 627f12aa9..08190e583 100644 --- a/server/controllers/api/videos/watching.ts +++ b/server/controllers/api/videos/watching.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { UserWatchingVideo } from '../../../../shared' | 2 | import { UserWatchingVideo } from '../../../../shared' |
3 | import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videoWatchingValidator } from '../../../middlewares' | 3 | import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videoWatchingValidator } from '../../../middlewares' |
4 | import { UserVideoHistoryModel } from '../../../models/account/user-video-history' | 4 | import { UserVideoHistoryModel } from '../../../models/user/user-video-history' |
5 | import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' | 5 | import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' |
6 | 6 | ||
7 | const watchingRouter = express.Router() | 7 | const watchingRouter = express.Router() |
diff --git a/server/controllers/bots.ts b/server/controllers/bots.ts index 8d1fa72f3..9e92063d4 100644 --- a/server/controllers/bots.ts +++ b/server/controllers/bots.ts | |||
@@ -75,7 +75,7 @@ async function getSitemapLocalVideoUrls () { | |||
75 | }) | 75 | }) |
76 | 76 | ||
77 | return data.map(v => ({ | 77 | return data.map(v => ({ |
78 | url: WEBSERVER.URL + '/videos/watch/' + v.uuid, | 78 | url: WEBSERVER.URL + '/w/' + v.uuid, |
79 | video: [ | 79 | video: [ |
80 | { | 80 | { |
81 | title: v.name, | 81 | title: v.name, |
diff --git a/server/controllers/client.ts b/server/controllers/client.ts index 022a17ff4..fcccc48e0 100644 --- a/server/controllers/client.ts +++ b/server/controllers/client.ts | |||
@@ -19,10 +19,11 @@ const testEmbedPath = join(distPath, 'standalone', 'videos', 'test-embed.html') | |||
19 | 19 | ||
20 | // Special route that add OpenGraph and oEmbed tags | 20 | // Special route that add OpenGraph and oEmbed tags |
21 | // Do not use a template engine for a so little thing | 21 | // Do not use a template engine for a so little thing |
22 | clientsRouter.use('/videos/watch/playlist/:id', asyncMiddleware(generateWatchPlaylistHtmlPage)) | 22 | clientsRouter.use([ '/w/p/:id', '/videos/watch/playlist/:id' ], asyncMiddleware(generateWatchPlaylistHtmlPage)) |
23 | clientsRouter.use('/videos/watch/:id', asyncMiddleware(generateWatchHtmlPage)) | 23 | clientsRouter.use([ '/w/:id', '/videos/watch/:id' ], asyncMiddleware(generateWatchHtmlPage)) |
24 | clientsRouter.use('/accounts/:nameWithHost', asyncMiddleware(generateAccountHtmlPage)) | 24 | clientsRouter.use([ '/accounts/:nameWithHost', '/a/:nameWithHost' ], asyncMiddleware(generateAccountHtmlPage)) |
25 | clientsRouter.use('/video-channels/:nameWithHost', asyncMiddleware(generateVideoChannelHtmlPage)) | 25 | clientsRouter.use([ '/video-channels/:nameWithHost', '/c/:nameWithHost' ], asyncMiddleware(generateVideoChannelHtmlPage)) |
26 | clientsRouter.use('/@:nameWithHost', asyncMiddleware(generateActorHtmlPage)) | ||
26 | 27 | ||
27 | const embedMiddlewares = [ | 28 | const embedMiddlewares = [ |
28 | CONFIG.CSP.ENABLED | 29 | CONFIG.CSP.ENABLED |
@@ -155,6 +156,12 @@ async function generateVideoChannelHtmlPage (req: express.Request, res: express. | |||
155 | return sendHTML(html, res) | 156 | return sendHTML(html, res) |
156 | } | 157 | } |
157 | 158 | ||
159 | async function generateActorHtmlPage (req: express.Request, res: express.Response) { | ||
160 | const html = await ClientHtml.getActorHTMLPage(req.params.nameWithHost, req, res) | ||
161 | |||
162 | return sendHTML(html, res) | ||
163 | } | ||
164 | |||
158 | async function generateManifest (req: express.Request, res: express.Response) { | 165 | async function generateManifest (req: express.Request, res: express.Response) { |
159 | const manifestPhysicalPath = join(root(), 'client', 'dist', 'manifest.webmanifest') | 166 | const manifestPhysicalPath = join(root(), 'client', 'dist', 'manifest.webmanifest') |
160 | const manifestJson = await readFile(manifestPhysicalPath, 'utf8') | 167 | const manifestJson = await readFile(manifestPhysicalPath, 'utf8') |
diff --git a/server/controllers/feeds.ts b/server/controllers/feeds.ts index f0717bbbc..865f5c2a1 100644 --- a/server/controllers/feeds.ts +++ b/server/controllers/feeds.ts | |||
@@ -293,7 +293,7 @@ function addVideosToFeed (feed, videos: VideoModel[]) { | |||
293 | feed.addItem({ | 293 | feed.addItem({ |
294 | title: video.name, | 294 | title: video.name, |
295 | id: video.url, | 295 | id: video.url, |
296 | link: WEBSERVER.URL + '/videos/watch/' + video.uuid, | 296 | link: WEBSERVER.URL + '/w/' + video.uuid, |
297 | description: video.getTruncatedDescription(), | 297 | description: video.getTruncatedDescription(), |
298 | content: video.description, | 298 | content: video.description, |
299 | author: [ | 299 | author: [ |
diff --git a/server/controllers/lazy-static.ts b/server/controllers/lazy-static.ts index 6f71fdb16..25d3b49b4 100644 --- a/server/controllers/lazy-static.ts +++ b/server/controllers/lazy-static.ts | |||
@@ -7,7 +7,7 @@ import { LAZY_STATIC_PATHS, STATIC_MAX_AGE } from '../initializers/constants' | |||
7 | import { actorImagePathUnsafeCache, pushActorImageProcessInQueue } from '../lib/actor-image' | 7 | import { actorImagePathUnsafeCache, pushActorImageProcessInQueue } from '../lib/actor-image' |
8 | import { VideosCaptionCache, VideosPreviewCache } from '../lib/files-cache' | 8 | import { VideosCaptionCache, VideosPreviewCache } from '../lib/files-cache' |
9 | import { asyncMiddleware } from '../middlewares' | 9 | import { asyncMiddleware } from '../middlewares' |
10 | import { ActorImageModel } from '../models/account/actor-image' | 10 | import { ActorImageModel } from '../models/actor/actor-image' |
11 | 11 | ||
12 | const lazyStaticRouter = express.Router() | 12 | const lazyStaticRouter = express.Router() |
13 | 13 | ||
diff --git a/server/controllers/services.ts b/server/controllers/services.ts index 189e1651b..8c0af9ff7 100644 --- a/server/controllers/services.ts +++ b/server/controllers/services.ts | |||
@@ -78,17 +78,18 @@ function buildOEmbed (options: { | |||
78 | const maxWidth = parseInt(req.query.maxwidth, 10) | 78 | const maxWidth = parseInt(req.query.maxwidth, 10) |
79 | 79 | ||
80 | const embedUrl = webserverUrl + embedPath | 80 | const embedUrl = webserverUrl + embedPath |
81 | let embedWidth = EMBED_SIZE.width | ||
82 | let embedHeight = EMBED_SIZE.height | ||
83 | const embedTitle = escapeHTML(title) | 81 | const embedTitle = escapeHTML(title) |
84 | 82 | ||
85 | let thumbnailUrl = previewPath | 83 | let thumbnailUrl = previewPath |
86 | ? webserverUrl + previewPath | 84 | ? webserverUrl + previewPath |
87 | : undefined | 85 | : undefined |
88 | 86 | ||
89 | if (maxHeight < embedHeight) embedHeight = maxHeight | 87 | let embedWidth = EMBED_SIZE.width |
90 | if (maxWidth < embedWidth) embedWidth = maxWidth | 88 | if (maxWidth < embedWidth) embedWidth = maxWidth |
91 | 89 | ||
90 | let embedHeight = EMBED_SIZE.height | ||
91 | if (maxHeight < embedHeight) embedHeight = maxHeight | ||
92 | |||
92 | // Our thumbnail is too big for the consumer | 93 | // Our thumbnail is too big for the consumer |
93 | if ( | 94 | if ( |
94 | (maxHeight !== undefined && maxHeight < previewSize.height) || | 95 | (maxHeight !== undefined && maxHeight < previewSize.height) || |
diff --git a/server/controllers/static.ts b/server/controllers/static.ts index 8d9003a3e..3870ebfe9 100644 --- a/server/controllers/static.ts +++ b/server/controllers/static.ts | |||
@@ -2,9 +2,9 @@ import * as cors from 'cors' | |||
2 | import * as express from 'express' | 2 | import * as express from 'express' |
3 | import { join } from 'path' | 3 | import { join } from 'path' |
4 | import { serveIndexHTML } from '@server/lib/client-html' | 4 | import { serveIndexHTML } from '@server/lib/client-html' |
5 | import { getRegisteredPlugins, getRegisteredThemes } from '@server/lib/config' | 5 | import { ServerConfigManager } from '@server/lib/server-config-manager' |
6 | import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes' | 6 | import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes' |
7 | import { HttpNodeinfoDiasporaSoftwareNsSchema20 } from '../../shared/models/nodeinfo' | 7 | import { HttpNodeinfoDiasporaSoftwareNsSchema20 } from '../../shared/models/nodeinfo/nodeinfo.model' |
8 | import { root } from '../helpers/core-utils' | 8 | import { root } from '../helpers/core-utils' |
9 | import { CONFIG, isEmailEnabled } from '../initializers/config' | 9 | import { CONFIG, isEmailEnabled } from '../initializers/config' |
10 | import { | 10 | import { |
@@ -18,10 +18,9 @@ import { | |||
18 | WEBSERVER | 18 | WEBSERVER |
19 | } from '../initializers/constants' | 19 | } from '../initializers/constants' |
20 | import { getThemeOrDefault } from '../lib/plugins/theme-utils' | 20 | import { getThemeOrDefault } from '../lib/plugins/theme-utils' |
21 | import { getEnabledResolutions } from '../lib/video-transcoding' | ||
22 | import { asyncMiddleware } from '../middlewares' | 21 | import { asyncMiddleware } from '../middlewares' |
23 | import { cacheRoute } from '../middlewares/cache' | 22 | import { cacheRoute } from '../middlewares/cache' |
24 | import { UserModel } from '../models/account/user' | 23 | import { UserModel } from '../models/user/user' |
25 | import { VideoModel } from '../models/video/video' | 24 | import { VideoModel } from '../models/video/video' |
26 | import { VideoCommentModel } from '../models/video/video-comment' | 25 | import { VideoCommentModel } from '../models/video/video-comment' |
27 | 26 | ||
@@ -204,10 +203,10 @@ async function generateNodeinfo (req: express.Request, res: express.Response) { | |||
204 | } | 203 | } |
205 | }, | 204 | }, |
206 | plugin: { | 205 | plugin: { |
207 | registered: getRegisteredPlugins() | 206 | registered: ServerConfigManager.Instance.getRegisteredPlugins() |
208 | }, | 207 | }, |
209 | theme: { | 208 | theme: { |
210 | registered: getRegisteredThemes(), | 209 | registered: ServerConfigManager.Instance.getRegisteredThemes(), |
211 | default: getThemeOrDefault(CONFIG.THEME.DEFAULT, DEFAULT_THEME_NAME) | 210 | default: getThemeOrDefault(CONFIG.THEME.DEFAULT, DEFAULT_THEME_NAME) |
212 | }, | 211 | }, |
213 | email: { | 212 | email: { |
@@ -223,13 +222,13 @@ async function generateNodeinfo (req: express.Request, res: express.Response) { | |||
223 | webtorrent: { | 222 | webtorrent: { |
224 | enabled: CONFIG.TRANSCODING.WEBTORRENT.ENABLED | 223 | enabled: CONFIG.TRANSCODING.WEBTORRENT.ENABLED |
225 | }, | 224 | }, |
226 | enabledResolutions: getEnabledResolutions('vod') | 225 | enabledResolutions: ServerConfigManager.Instance.getEnabledResolutions('vod') |
227 | }, | 226 | }, |
228 | live: { | 227 | live: { |
229 | enabled: CONFIG.LIVE.ENABLED, | 228 | enabled: CONFIG.LIVE.ENABLED, |
230 | transcoding: { | 229 | transcoding: { |
231 | enabled: CONFIG.LIVE.TRANSCODING.ENABLED, | 230 | enabled: CONFIG.LIVE.TRANSCODING.ENABLED, |
232 | enabledResolutions: getEnabledResolutions('live') | 231 | enabledResolutions: ServerConfigManager.Instance.getEnabledResolutions('live') |
233 | } | 232 | } |
234 | }, | 233 | }, |
235 | import: { | 234 | import: { |
diff --git a/server/helpers/actor.ts b/server/helpers/actor.ts index a60d3ed5d..5f742505b 100644 --- a/server/helpers/actor.ts +++ b/server/helpers/actor.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | 1 | ||
2 | import { ActorModel } from '../models/activitypub/actor' | 2 | import { ActorModel } from '../models/actor/actor' |
3 | import { MActorAccountChannelId, MActorFull } from '../types/models' | 3 | import { MActorAccountChannelId, MActorFull } from '../types/models' |
4 | 4 | ||
5 | type ActorFetchByUrlType = 'all' | 'association-ids' | 5 | type ActorFetchByUrlType = 'all' | 'association-ids' |
diff --git a/server/helpers/audit-logger.ts b/server/helpers/audit-logger.ts index 6aae5e821..884bd187d 100644 --- a/server/helpers/audit-logger.ts +++ b/server/helpers/audit-logger.ts | |||
@@ -7,7 +7,7 @@ import * as winston from 'winston' | |||
7 | import { AUDIT_LOG_FILENAME } from '@server/initializers/constants' | 7 | import { AUDIT_LOG_FILENAME } from '@server/initializers/constants' |
8 | import { AdminAbuse, User, VideoChannel, VideoDetails, VideoImport } from '../../shared' | 8 | import { AdminAbuse, User, VideoChannel, VideoDetails, VideoImport } from '../../shared' |
9 | import { CustomConfig } from '../../shared/models/server/custom-config.model' | 9 | import { CustomConfig } from '../../shared/models/server/custom-config.model' |
10 | import { VideoComment } from '../../shared/models/videos/video-comment.model' | 10 | import { VideoComment } from '../../shared/models/videos/comment/video-comment.model' |
11 | import { CONFIG } from '../initializers/config' | 11 | import { CONFIG } from '../initializers/config' |
12 | import { jsonLoggerFormat, labelFormatter } from './logger' | 12 | import { jsonLoggerFormat, labelFormatter } from './logger' |
13 | 13 | ||
diff --git a/server/helpers/custom-validators/misc.ts b/server/helpers/custom-validators/misc.ts index fd3b45804..229e9f03c 100644 --- a/server/helpers/custom-validators/misc.ts +++ b/server/helpers/custom-validators/misc.ts | |||
@@ -14,7 +14,7 @@ function isSafePath (p: string) { | |||
14 | }) | 14 | }) |
15 | } | 15 | } |
16 | 16 | ||
17 | function isArray (value: any) { | 17 | function isArray (value: any): value is any[] { |
18 | return Array.isArray(value) | 18 | return Array.isArray(value) |
19 | } | 19 | } |
20 | 20 | ||
diff --git a/server/helpers/database-utils.ts b/server/helpers/database-utils.ts index f9cb33aca..7befa2c49 100644 --- a/server/helpers/database-utils.ts +++ b/server/helpers/database-utils.ts | |||
@@ -68,7 +68,7 @@ function transactionRetryer <T> (func: (err: any, data: T) => any) { | |||
68 | }) | 68 | }) |
69 | } | 69 | } |
70 | 70 | ||
71 | function updateInstanceWithAnother <T extends Model<T>> (instanceToUpdate: Model<T>, baseInstance: Model<T>) { | 71 | function updateInstanceWithAnother <M, T extends U, U extends Model<M>> (instanceToUpdate: T, baseInstance: U) { |
72 | const obj = baseInstance.toJSON() | 72 | const obj = baseInstance.toJSON() |
73 | 73 | ||
74 | for (const key of Object.keys(obj)) { | 74 | for (const key of Object.keys(obj)) { |
@@ -88,7 +88,7 @@ function afterCommitIfTransaction (t: Transaction, fn: Function) { | |||
88 | return fn() | 88 | return fn() |
89 | } | 89 | } |
90 | 90 | ||
91 | function deleteNonExistingModels <T extends { hasSameUniqueKeysThan (other: T): boolean } & Model<T>> ( | 91 | function deleteNonExistingModels <T extends { hasSameUniqueKeysThan (other: T): boolean } & Pick<Model, 'destroy'>> ( |
92 | fromDatabase: T[], | 92 | fromDatabase: T[], |
93 | newModels: T[], | 93 | newModels: T[], |
94 | t: Transaction | 94 | t: Transaction |
diff --git a/server/helpers/express-utils.ts b/server/helpers/express-utils.ts index ede22a3cc..010c6961a 100644 --- a/server/helpers/express-utils.ts +++ b/server/helpers/express-utils.ts | |||
@@ -1,13 +1,13 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import * as multer from 'multer' | 2 | import * as multer from 'multer' |
3 | import { extname } from 'path' | ||
4 | import { HttpStatusCode } from '../../shared/core-utils/miscs/http-error-codes' | ||
5 | import { CONFIG } from '../initializers/config' | ||
3 | import { REMOTE_SCHEME } from '../initializers/constants' | 6 | import { REMOTE_SCHEME } from '../initializers/constants' |
7 | import { isArray } from './custom-validators/misc' | ||
4 | import { logger } from './logger' | 8 | import { logger } from './logger' |
5 | import { deleteFileAndCatch, generateRandomString } from './utils' | 9 | import { deleteFileAndCatch, generateRandomString } from './utils' |
6 | import { extname } from 'path' | ||
7 | import { isArray } from './custom-validators/misc' | ||
8 | import { CONFIG } from '../initializers/config' | ||
9 | import { getExtFromMimetype } from './video' | 10 | import { getExtFromMimetype } from './video' |
10 | import { HttpStatusCode } from '../../shared/core-utils/miscs/http-error-codes' | ||
11 | 11 | ||
12 | function buildNSFWFilter (res?: express.Response, paramNSFW?: string) { | 12 | function buildNSFWFilter (res?: express.Response, paramNSFW?: string) { |
13 | if (paramNSFW === 'true') return true | 13 | if (paramNSFW === 'true') return true |
@@ -30,21 +30,21 @@ function buildNSFWFilter (res?: express.Response, paramNSFW?: string) { | |||
30 | return null | 30 | return null |
31 | } | 31 | } |
32 | 32 | ||
33 | function cleanUpReqFiles (req: { files: { [fieldname: string]: Express.Multer.File[] } | Express.Multer.File[] }) { | 33 | function cleanUpReqFiles ( |
34 | const files = req.files | 34 | req: { files: { [fieldname: string]: Express.Multer.File[] } | Express.Multer.File[] } |
35 | 35 | ) { | |
36 | if (!files) return | 36 | const filesObject = req.files |
37 | if (!filesObject) return | ||
37 | 38 | ||
38 | if (isArray(files)) { | 39 | if (isArray(filesObject)) { |
39 | (files as Express.Multer.File[]).forEach(f => deleteFileAndCatch(f.path)) | 40 | filesObject.forEach(f => deleteFileAndCatch(f.path)) |
40 | return | 41 | return |
41 | } | 42 | } |
42 | 43 | ||
43 | for (const key of Object.keys(files)) { | 44 | for (const key of Object.keys(filesObject)) { |
44 | const file = files[key] | 45 | const files = filesObject[key] |
45 | 46 | ||
46 | if (isArray(file)) file.forEach(f => deleteFileAndCatch(f.path)) | 47 | files.forEach(f => deleteFileAndCatch(f.path)) |
47 | else deleteFileAndCatch(file.path) | ||
48 | } | 48 | } |
49 | } | 49 | } |
50 | 50 | ||
diff --git a/server/helpers/ffprobe-utils.ts b/server/helpers/ffprobe-utils.ts index 40eaafd57..ef2aa3f89 100644 --- a/server/helpers/ffprobe-utils.ts +++ b/server/helpers/ffprobe-utils.ts | |||
@@ -1,6 +1,5 @@ | |||
1 | import * as ffmpeg from 'fluent-ffmpeg' | 1 | import * as ffmpeg from 'fluent-ffmpeg' |
2 | import { VideoFileMetadata } from '@shared/models/videos/video-file-metadata' | 2 | import { getMaxBitrate, VideoFileMetadata, VideoResolution } from '../../shared/models/videos' |
3 | import { getMaxBitrate, VideoResolution } from '../../shared/models/videos' | ||
4 | import { CONFIG } from '../initializers/config' | 3 | import { CONFIG } from '../initializers/config' |
5 | import { VIDEO_TRANSCODING_FPS } from '../initializers/constants' | 4 | import { VIDEO_TRANSCODING_FPS } from '../initializers/constants' |
6 | import { logger } from './logger' | 5 | import { logger } from './logger' |
diff --git a/server/helpers/markdown.ts b/server/helpers/markdown.ts index 2126bb752..41e57d857 100644 --- a/server/helpers/markdown.ts +++ b/server/helpers/markdown.ts | |||
@@ -1,4 +1,6 @@ | |||
1 | import { SANITIZE_OPTIONS, TEXT_WITH_HTML_RULES } from '@shared/core-utils' | 1 | import { getSanitizeOptions, TEXT_WITH_HTML_RULES } from '@shared/core-utils' |
2 | |||
3 | const sanitizeOptions = getSanitizeOptions() | ||
2 | 4 | ||
3 | const sanitizeHtml = require('sanitize-html') | 5 | const sanitizeHtml = require('sanitize-html') |
4 | const markdownItEmoji = require('markdown-it-emoji/light') | 6 | const markdownItEmoji = require('markdown-it-emoji/light') |
@@ -18,7 +20,7 @@ const toSafeHtml = text => { | |||
18 | const html = markdownIt.render(textWithLineFeed) | 20 | const html = markdownIt.render(textWithLineFeed) |
19 | 21 | ||
20 | // Convert to safe Html | 22 | // Convert to safe Html |
21 | return sanitizeHtml(html, SANITIZE_OPTIONS) | 23 | return sanitizeHtml(html, sanitizeOptions) |
22 | } | 24 | } |
23 | 25 | ||
24 | const mdToPlainText = text => { | 26 | const mdToPlainText = text => { |
@@ -28,7 +30,7 @@ const mdToPlainText = text => { | |||
28 | const html = markdownIt.render(text) | 30 | const html = markdownIt.render(text) |
29 | 31 | ||
30 | // Convert to safe Html | 32 | // Convert to safe Html |
31 | const safeHtml = sanitizeHtml(html, SANITIZE_OPTIONS) | 33 | const safeHtml = sanitizeHtml(html, sanitizeOptions) |
32 | 34 | ||
33 | return safeHtml.replace(/<[^>]+>/g, '') | 35 | return safeHtml.replace(/<[^>]+>/g, '') |
34 | .replace(/\n$/, '') | 36 | .replace(/\n$/, '') |
diff --git a/server/helpers/middlewares/accounts.ts b/server/helpers/middlewares/accounts.ts index 13ae6cdf4..5addd3e1a 100644 --- a/server/helpers/middlewares/accounts.ts +++ b/server/helpers/middlewares/accounts.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import { Response } from 'express' | 1 | import { Response } from 'express' |
2 | import { UserModel } from '@server/models/account/user' | 2 | import { UserModel } from '@server/models/user/user' |
3 | import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' | 3 | import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' |
4 | import { AccountModel } from '../../models/account/account' | 4 | import { AccountModel } from '../../models/account/account' |
5 | import { MAccountDefault } from '../../types/models' | 5 | import { MAccountDefault } from '../../types/models' |
diff --git a/server/helpers/middlewares/video-channels.ts b/server/helpers/middlewares/video-channels.ts index e6eab65a2..602555921 100644 --- a/server/helpers/middlewares/video-channels.ts +++ b/server/helpers/middlewares/video-channels.ts | |||
@@ -32,7 +32,7 @@ export { | |||
32 | function processVideoChannelExist (videoChannel: MChannelBannerAccountDefault, res: express.Response) { | 32 | function processVideoChannelExist (videoChannel: MChannelBannerAccountDefault, res: express.Response) { |
33 | if (!videoChannel) { | 33 | if (!videoChannel) { |
34 | res.status(HttpStatusCode.NOT_FOUND_404) | 34 | res.status(HttpStatusCode.NOT_FOUND_404) |
35 | .json({ error: 'Video channel not found' }) | 35 | .json({ error: 'Video channel not found' }) |
36 | 36 | ||
37 | return false | 37 | return false |
38 | } | 38 | } |
diff --git a/server/helpers/signup.ts b/server/helpers/signup.ts index ed872539b..8fa81e601 100644 --- a/server/helpers/signup.ts +++ b/server/helpers/signup.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | import { UserModel } from '../models/account/user' | 1 | import { UserModel } from '../models/user/user' |
2 | import * as ipaddr from 'ipaddr.js' | 2 | import * as ipaddr from 'ipaddr.js' |
3 | import { CONFIG } from '../initializers/config' | 3 | import { CONFIG } from '../initializers/config' |
4 | 4 | ||
diff --git a/server/helpers/webfinger.ts b/server/helpers/webfinger.ts index da7e88077..33367f651 100644 --- a/server/helpers/webfinger.ts +++ b/server/helpers/webfinger.ts | |||
@@ -1,10 +1,10 @@ | |||
1 | import * as WebFinger from 'webfinger.js' | 1 | import * as WebFinger from 'webfinger.js' |
2 | import { WebFingerData } from '../../shared' | 2 | import { WebFingerData } from '../../shared' |
3 | import { ActorModel } from '../models/activitypub/actor' | ||
4 | import { isTestInstance } from './core-utils' | ||
5 | import { isActivityPubUrlValid } from './custom-validators/activitypub/misc' | ||
6 | import { WEBSERVER } from '../initializers/constants' | 3 | import { WEBSERVER } from '../initializers/constants' |
4 | import { ActorModel } from '../models/actor/actor' | ||
7 | import { MActorFull } from '../types/models' | 5 | import { MActorFull } from '../types/models' |
6 | import { isTestInstance } from './core-utils' | ||
7 | import { isActivityPubUrlValid } from './custom-validators/activitypub/misc' | ||
8 | 8 | ||
9 | const webfinger = new WebFinger({ | 9 | const webfinger = new WebFinger({ |
10 | webfist_fallback: false, | 10 | webfist_fallback: false, |
diff --git a/server/helpers/youtube-dl.ts b/server/helpers/youtube-dl.ts index fac3da6ba..d003ea3cf 100644 --- a/server/helpers/youtube-dl.ts +++ b/server/helpers/youtube-dl.ts | |||
@@ -6,7 +6,6 @@ import { CONFIG } from '@server/initializers/config' | |||
6 | import { HttpStatusCode } from '../../shared/core-utils/miscs/http-error-codes' | 6 | import { HttpStatusCode } from '../../shared/core-utils/miscs/http-error-codes' |
7 | import { VideoResolution } from '../../shared/models/videos' | 7 | import { VideoResolution } from '../../shared/models/videos' |
8 | import { CONSTRAINTS_FIELDS, VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES } from '../initializers/constants' | 8 | import { CONSTRAINTS_FIELDS, VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES } from '../initializers/constants' |
9 | import { getEnabledResolutions } from '../lib/video-transcoding' | ||
10 | import { peertubeTruncate, pipelinePromise, root } from './core-utils' | 9 | import { peertubeTruncate, pipelinePromise, root } from './core-utils' |
11 | import { isVideoFileExtnameValid } from './custom-validators/videos' | 10 | import { isVideoFileExtnameValid } from './custom-validators/videos' |
12 | import { logger } from './logger' | 11 | import { logger } from './logger' |
@@ -35,361 +34,359 @@ const processOptions = { | |||
35 | maxBuffer: 1024 * 1024 * 10 // 10MB | 34 | maxBuffer: 1024 * 1024 * 10 // 10MB |
36 | } | 35 | } |
37 | 36 | ||
38 | function getYoutubeDLInfo (url: string, opts?: string[]): Promise<YoutubeDLInfo> { | 37 | class YoutubeDL { |
39 | return new Promise<YoutubeDLInfo>((res, rej) => { | ||
40 | let args = opts || [ '-j', '--flat-playlist' ] | ||
41 | 38 | ||
42 | if (CONFIG.IMPORT.VIDEOS.HTTP.FORCE_IPV4) { | 39 | constructor (private readonly url: string = '', private readonly enabledResolutions: number[] = []) { |
43 | args.push('--force-ipv4') | ||
44 | } | ||
45 | 40 | ||
46 | args = wrapWithProxyOptions(args) | 41 | } |
47 | args = [ '-f', getYoutubeDLVideoFormat() ].concat(args) | ||
48 | 42 | ||
49 | safeGetYoutubeDL() | 43 | getYoutubeDLInfo (opts?: string[]): Promise<YoutubeDLInfo> { |
50 | .then(youtubeDL => { | 44 | return new Promise<YoutubeDLInfo>((res, rej) => { |
51 | youtubeDL.getInfo(url, args, processOptions, (err, info) => { | 45 | let args = opts || [ '-j', '--flat-playlist' ] |
52 | if (err) return rej(err) | ||
53 | if (info.is_live === true) return rej(new Error('Cannot download a live streaming.')) | ||
54 | 46 | ||
55 | const obj = buildVideoInfo(normalizeObject(info)) | 47 | if (CONFIG.IMPORT.VIDEOS.HTTP.FORCE_IPV4) { |
56 | if (obj.name && obj.name.length < CONSTRAINTS_FIELDS.VIDEOS.NAME.min) obj.name += ' video' | 48 | args.push('--force-ipv4') |
49 | } | ||
57 | 50 | ||
58 | return res(obj) | 51 | args = this.wrapWithProxyOptions(args) |
59 | }) | 52 | args = [ '-f', this.getYoutubeDLVideoFormat() ].concat(args) |
60 | }) | ||
61 | .catch(err => rej(err)) | ||
62 | }) | ||
63 | } | ||
64 | 53 | ||
65 | function getYoutubeDLSubs (url: string, opts?: object): Promise<YoutubeDLSubs> { | 54 | YoutubeDL.safeGetYoutubeDL() |
66 | return new Promise<YoutubeDLSubs>((res, rej) => { | 55 | .then(youtubeDL => { |
67 | const cwd = CONFIG.STORAGE.TMP_DIR | 56 | youtubeDL.getInfo(this.url, args, processOptions, (err, info) => { |
68 | const options = opts || { all: true, format: 'vtt', cwd } | 57 | if (err) return rej(err) |
69 | 58 | if (info.is_live === true) return rej(new Error('Cannot download a live streaming.')) | |
70 | safeGetYoutubeDL() | ||
71 | .then(youtubeDL => { | ||
72 | youtubeDL.getSubs(url, options, (err, files) => { | ||
73 | if (err) return rej(err) | ||
74 | if (!files) return [] | ||
75 | |||
76 | logger.debug('Get subtitles from youtube dl.', { url, files }) | ||
77 | |||
78 | const subtitles = files.reduce((acc, filename) => { | ||
79 | const matched = filename.match(/\.([a-z]{2})(-[a-z]+)?\.(vtt|ttml)/i) | ||
80 | if (!matched || !matched[1]) return acc | ||
81 | |||
82 | return [ | ||
83 | ...acc, | ||
84 | { | ||
85 | language: matched[1], | ||
86 | path: join(cwd, filename), | ||
87 | filename | ||
88 | } | ||
89 | ] | ||
90 | }, []) | ||
91 | 59 | ||
92 | return res(subtitles) | 60 | const obj = this.buildVideoInfo(this.normalizeObject(info)) |
61 | if (obj.name && obj.name.length < CONSTRAINTS_FIELDS.VIDEOS.NAME.min) obj.name += ' video' | ||
62 | |||
63 | return res(obj) | ||
64 | }) | ||
93 | }) | 65 | }) |
94 | }) | 66 | .catch(err => rej(err)) |
95 | .catch(err => rej(err)) | 67 | }) |
96 | }) | 68 | } |
97 | } | ||
98 | 69 | ||
99 | function getYoutubeDLVideoFormat () { | 70 | getYoutubeDLSubs (opts?: object): Promise<YoutubeDLSubs> { |
100 | /** | 71 | return new Promise<YoutubeDLSubs>((res, rej) => { |
101 | * list of format selectors in order or preference | 72 | const cwd = CONFIG.STORAGE.TMP_DIR |
102 | * see https://github.com/ytdl-org/youtube-dl#format-selection | 73 | const options = opts || { all: true, format: 'vtt', cwd } |
103 | * | 74 | |
104 | * case #1 asks for a mp4 using h264 (avc1) and the exact resolution in the hope | 75 | YoutubeDL.safeGetYoutubeDL() |
105 | * of being able to do a "quick-transcode" | 76 | .then(youtubeDL => { |
106 | * case #2 is the first fallback. No "quick-transcode" means we can get anything else (like vp9) | 77 | youtubeDL.getSubs(this.url, options, (err, files) => { |
107 | * case #3 is the resolution-degraded equivalent of #1, and already a pretty safe fallback | 78 | if (err) return rej(err) |
108 | * | 79 | if (!files) return [] |
109 | * in any case we avoid AV1, see https://github.com/Chocobozzz/PeerTube/issues/3499 | 80 | |
110 | **/ | 81 | logger.debug('Get subtitles from youtube dl.', { url: this.url, files }) |
111 | const enabledResolutions = getEnabledResolutions('vod') | 82 | |
112 | const resolution = enabledResolutions.length === 0 | 83 | const subtitles = files.reduce((acc, filename) => { |
113 | ? VideoResolution.H_720P | 84 | const matched = filename.match(/\.([a-z]{2})(-[a-z]+)?\.(vtt|ttml)/i) |
114 | : Math.max(...enabledResolutions) | 85 | if (!matched || !matched[1]) return acc |
115 | 86 | ||
116 | return [ | 87 | return [ |
117 | `bestvideo[vcodec^=avc1][height=${resolution}]+bestaudio[ext=m4a]`, // case #1 | 88 | ...acc, |
118 | `bestvideo[vcodec!*=av01][vcodec!*=vp9.2][height=${resolution}]+bestaudio`, // case #2 | 89 | { |
119 | `bestvideo[vcodec^=avc1][height<=${resolution}]+bestaudio[ext=m4a]`, // case #3 | 90 | language: matched[1], |
120 | `bestvideo[vcodec!*=av01][vcodec!*=vp9.2]+bestaudio`, | 91 | path: join(cwd, filename), |
121 | 'best[vcodec!*=av01][vcodec!*=vp9.2]', // case fallback for known formats | 92 | filename |
122 | 'best' // Ultimate fallback | 93 | } |
123 | ].join('/') | 94 | ] |
124 | } | 95 | }, []) |
96 | |||
97 | return res(subtitles) | ||
98 | }) | ||
99 | }) | ||
100 | .catch(err => rej(err)) | ||
101 | }) | ||
102 | } | ||
125 | 103 | ||
126 | function downloadYoutubeDLVideo (url: string, fileExt: string, timeout: number) { | 104 | getYoutubeDLVideoFormat () { |
127 | // Leave empty the extension, youtube-dl will add it | 105 | /** |
128 | const pathWithoutExtension = generateVideoImportTmpPath(url, '') | 106 | * list of format selectors in order or preference |
107 | * see https://github.com/ytdl-org/youtube-dl#format-selection | ||
108 | * | ||
109 | * case #1 asks for a mp4 using h264 (avc1) and the exact resolution in the hope | ||
110 | * of being able to do a "quick-transcode" | ||
111 | * case #2 is the first fallback. No "quick-transcode" means we can get anything else (like vp9) | ||
112 | * case #3 is the resolution-degraded equivalent of #1, and already a pretty safe fallback | ||
113 | * | ||
114 | * in any case we avoid AV1, see https://github.com/Chocobozzz/PeerTube/issues/3499 | ||
115 | **/ | ||
116 | const resolution = this.enabledResolutions.length === 0 | ||
117 | ? VideoResolution.H_720P | ||
118 | : Math.max(...this.enabledResolutions) | ||
119 | |||
120 | return [ | ||
121 | `bestvideo[vcodec^=avc1][height=${resolution}]+bestaudio[ext=m4a]`, // case #1 | ||
122 | `bestvideo[vcodec!*=av01][vcodec!*=vp9.2][height=${resolution}]+bestaudio`, // case #2 | ||
123 | `bestvideo[vcodec^=avc1][height<=${resolution}]+bestaudio[ext=m4a]`, // case #3 | ||
124 | `bestvideo[vcodec!*=av01][vcodec!*=vp9.2]+bestaudio`, | ||
125 | 'best[vcodec!*=av01][vcodec!*=vp9.2]', // case fallback for known formats | ||
126 | 'best' // Ultimate fallback | ||
127 | ].join('/') | ||
128 | } | ||
129 | 129 | ||
130 | let timer | 130 | downloadYoutubeDLVideo (fileExt: string, timeout: number) { |
131 | // Leave empty the extension, youtube-dl will add it | ||
132 | const pathWithoutExtension = generateVideoImportTmpPath(this.url, '') | ||
131 | 133 | ||
132 | logger.info('Importing youtubeDL video %s to %s', url, pathWithoutExtension) | 134 | let timer |
133 | 135 | ||
134 | let options = [ '-f', getYoutubeDLVideoFormat(), '-o', pathWithoutExtension ] | 136 | logger.info('Importing youtubeDL video %s to %s', this.url, pathWithoutExtension) |
135 | options = wrapWithProxyOptions(options) | ||
136 | 137 | ||
137 | if (process.env.FFMPEG_PATH) { | 138 | let options = [ '-f', this.getYoutubeDLVideoFormat(), '-o', pathWithoutExtension ] |
138 | options = options.concat([ '--ffmpeg-location', process.env.FFMPEG_PATH ]) | 139 | options = this.wrapWithProxyOptions(options) |
139 | } | ||
140 | 140 | ||
141 | logger.debug('YoutubeDL options for %s.', url, { options }) | 141 | if (process.env.FFMPEG_PATH) { |
142 | options = options.concat([ '--ffmpeg-location', process.env.FFMPEG_PATH ]) | ||
143 | } | ||
142 | 144 | ||
143 | return new Promise<string>((res, rej) => { | 145 | logger.debug('YoutubeDL options for %s.', this.url, { options }) |
144 | safeGetYoutubeDL() | ||
145 | .then(youtubeDL => { | ||
146 | youtubeDL.exec(url, options, processOptions, async err => { | ||
147 | clearTimeout(timer) | ||
148 | 146 | ||
149 | try { | 147 | return new Promise<string>((res, rej) => { |
150 | // If youtube-dl did not guess an extension for our file, just use .mp4 as default | 148 | YoutubeDL.safeGetYoutubeDL() |
151 | if (await pathExists(pathWithoutExtension)) { | 149 | .then(youtubeDL => { |
152 | await move(pathWithoutExtension, pathWithoutExtension + '.mp4') | 150 | youtubeDL.exec(this.url, options, processOptions, async err => { |
153 | } | 151 | clearTimeout(timer) |
152 | |||
153 | try { | ||
154 | // If youtube-dl did not guess an extension for our file, just use .mp4 as default | ||
155 | if (await pathExists(pathWithoutExtension)) { | ||
156 | await move(pathWithoutExtension, pathWithoutExtension + '.mp4') | ||
157 | } | ||
154 | 158 | ||
155 | const path = await guessVideoPathWithExtension(pathWithoutExtension, fileExt) | 159 | const path = await this.guessVideoPathWithExtension(pathWithoutExtension, fileExt) |
156 | 160 | ||
157 | if (err) { | 161 | if (err) { |
158 | remove(path) | 162 | remove(path) |
159 | .catch(err => logger.error('Cannot delete path on YoutubeDL error.', { err })) | 163 | .catch(err => logger.error('Cannot delete path on YoutubeDL error.', { err })) |
160 | 164 | ||
165 | return rej(err) | ||
166 | } | ||
167 | |||
168 | return res(path) | ||
169 | } catch (err) { | ||
161 | return rej(err) | 170 | return rej(err) |
162 | } | 171 | } |
163 | 172 | }) | |
164 | return res(path) | 173 | |
165 | } catch (err) { | 174 | timer = setTimeout(() => { |
166 | return rej(err) | 175 | const err = new Error('YoutubeDL download timeout.') |
167 | } | 176 | |
177 | this.guessVideoPathWithExtension(pathWithoutExtension, fileExt) | ||
178 | .then(path => remove(path)) | ||
179 | .finally(() => rej(err)) | ||
180 | .catch(err => { | ||
181 | logger.error('Cannot remove file in youtubeDL timeout.', { err }) | ||
182 | return rej(err) | ||
183 | }) | ||
184 | }, timeout) | ||
168 | }) | 185 | }) |
186 | .catch(err => rej(err)) | ||
187 | }) | ||
188 | } | ||
169 | 189 | ||
170 | timer = setTimeout(() => { | 190 | buildOriginallyPublishedAt (obj: any) { |
171 | const err = new Error('YoutubeDL download timeout.') | 191 | let originallyPublishedAt: Date = null |
172 | 192 | ||
173 | guessVideoPathWithExtension(pathWithoutExtension, fileExt) | 193 | const uploadDateMatcher = /^(\d{4})(\d{2})(\d{2})$/.exec(obj.upload_date) |
174 | .then(path => remove(path)) | 194 | if (uploadDateMatcher) { |
175 | .finally(() => rej(err)) | 195 | originallyPublishedAt = new Date() |
176 | .catch(err => { | 196 | originallyPublishedAt.setHours(0, 0, 0, 0) |
177 | logger.error('Cannot remove file in youtubeDL timeout.', { err }) | ||
178 | return rej(err) | ||
179 | }) | ||
180 | }, timeout) | ||
181 | }) | ||
182 | .catch(err => rej(err)) | ||
183 | }) | ||
184 | } | ||
185 | |||
186 | // Thanks: https://github.com/przemyslawpluta/node-youtube-dl/blob/master/lib/downloader.js | ||
187 | // We rewrote it to avoid sync calls | ||
188 | async function updateYoutubeDLBinary () { | ||
189 | logger.info('Updating youtubeDL binary.') | ||
190 | 197 | ||
191 | const binDirectory = join(root(), 'node_modules', 'youtube-dl', 'bin') | 198 | const year = parseInt(uploadDateMatcher[1], 10) |
192 | const bin = join(binDirectory, 'youtube-dl') | 199 | // Month starts from 0 |
193 | const detailsPath = join(binDirectory, 'details') | 200 | const month = parseInt(uploadDateMatcher[2], 10) - 1 |
194 | const url = process.env.YOUTUBE_DL_DOWNLOAD_HOST || 'https://yt-dl.org/downloads/latest/youtube-dl' | 201 | const day = parseInt(uploadDateMatcher[3], 10) |
195 | 202 | ||
196 | await ensureDir(binDirectory) | 203 | originallyPublishedAt.setFullYear(year, month, day) |
204 | } | ||
197 | 205 | ||
198 | try { | 206 | return originallyPublishedAt |
199 | const result = await got(url, { followRedirect: false }) | 207 | } |
200 | 208 | ||
201 | if (result.statusCode !== HttpStatusCode.FOUND_302) { | 209 | private async guessVideoPathWithExtension (tmpPath: string, sourceExt: string) { |
202 | logger.error('youtube-dl update error: did not get redirect for the latest version link. Status %d', result.statusCode) | 210 | if (!isVideoFileExtnameValid(sourceExt)) { |
203 | return | 211 | throw new Error('Invalid video extension ' + sourceExt) |
204 | } | 212 | } |
205 | 213 | ||
206 | const newUrl = result.headers.location | 214 | const extensions = [ sourceExt, '.mp4', '.mkv', '.webm' ] |
207 | const newVersion = /yt-dl\.org\/downloads\/(\d{4}\.\d\d\.\d\d(\.\d)?)\/youtube-dl/.exec(newUrl)[1] | ||
208 | 215 | ||
209 | const downloadFileStream = got.stream(newUrl) | 216 | for (const extension of extensions) { |
210 | const writeStream = createWriteStream(bin, { mode: 493 }) | 217 | const path = tmpPath + extension |
211 | 218 | ||
212 | await pipelinePromise( | 219 | if (await pathExists(path)) return path |
213 | downloadFileStream, | 220 | } |
214 | writeStream | ||
215 | ) | ||
216 | |||
217 | const details = JSON.stringify({ version: newVersion, path: bin, exec: 'youtube-dl' }) | ||
218 | await writeFile(detailsPath, details, { encoding: 'utf8' }) | ||
219 | 221 | ||
220 | logger.info('youtube-dl updated to version %s.', newVersion) | 222 | throw new Error('Cannot guess path of ' + tmpPath) |
221 | } catch (err) { | ||
222 | logger.error('Cannot update youtube-dl.', { err }) | ||
223 | } | 223 | } |
224 | } | ||
225 | 224 | ||
226 | async function safeGetYoutubeDL () { | 225 | private normalizeObject (obj: any) { |
227 | let youtubeDL | 226 | const newObj: any = {} |
228 | 227 | ||
229 | try { | 228 | for (const key of Object.keys(obj)) { |
230 | youtubeDL = require('youtube-dl') | 229 | // Deprecated key |
231 | } catch (e) { | 230 | if (key === 'resolution') continue |
232 | // Download binary | ||
233 | await updateYoutubeDLBinary() | ||
234 | youtubeDL = require('youtube-dl') | ||
235 | } | ||
236 | 231 | ||
237 | return youtubeDL | 232 | const value = obj[key] |
238 | } | ||
239 | 233 | ||
240 | function buildOriginallyPublishedAt (obj: any) { | 234 | if (typeof value === 'string') { |
241 | let originallyPublishedAt: Date = null | 235 | newObj[key] = value.normalize() |
236 | } else { | ||
237 | newObj[key] = value | ||
238 | } | ||
239 | } | ||
242 | 240 | ||
243 | const uploadDateMatcher = /^(\d{4})(\d{2})(\d{2})$/.exec(obj.upload_date) | 241 | return newObj |
244 | if (uploadDateMatcher) { | 242 | } |
245 | originallyPublishedAt = new Date() | ||
246 | originallyPublishedAt.setHours(0, 0, 0, 0) | ||
247 | 243 | ||
248 | const year = parseInt(uploadDateMatcher[1], 10) | 244 | private buildVideoInfo (obj: any): YoutubeDLInfo { |
249 | // Month starts from 0 | 245 | return { |
250 | const month = parseInt(uploadDateMatcher[2], 10) - 1 | 246 | name: this.titleTruncation(obj.title), |
251 | const day = parseInt(uploadDateMatcher[3], 10) | 247 | description: this.descriptionTruncation(obj.description), |
248 | category: this.getCategory(obj.categories), | ||
249 | licence: this.getLicence(obj.license), | ||
250 | language: this.getLanguage(obj.language), | ||
251 | nsfw: this.isNSFW(obj), | ||
252 | tags: this.getTags(obj.tags), | ||
253 | thumbnailUrl: obj.thumbnail || undefined, | ||
254 | originallyPublishedAt: this.buildOriginallyPublishedAt(obj), | ||
255 | ext: obj.ext | ||
256 | } | ||
257 | } | ||
252 | 258 | ||
253 | originallyPublishedAt.setFullYear(year, month, day) | 259 | private titleTruncation (title: string) { |
260 | return peertubeTruncate(title, { | ||
261 | length: CONSTRAINTS_FIELDS.VIDEOS.NAME.max, | ||
262 | separator: /,? +/, | ||
263 | omission: ' […]' | ||
264 | }) | ||
254 | } | 265 | } |
255 | 266 | ||
256 | return originallyPublishedAt | 267 | private descriptionTruncation (description: string) { |
257 | } | 268 | if (!description || description.length < CONSTRAINTS_FIELDS.VIDEOS.DESCRIPTION.min) return undefined |
258 | 269 | ||
259 | // --------------------------------------------------------------------------- | 270 | return peertubeTruncate(description, { |
271 | length: CONSTRAINTS_FIELDS.VIDEOS.DESCRIPTION.max, | ||
272 | separator: /,? +/, | ||
273 | omission: ' […]' | ||
274 | }) | ||
275 | } | ||
260 | 276 | ||
261 | export { | 277 | private isNSFW (info: any) { |
262 | updateYoutubeDLBinary, | 278 | return info.age_limit && info.age_limit >= 16 |
263 | getYoutubeDLVideoFormat, | 279 | } |
264 | downloadYoutubeDLVideo, | ||
265 | getYoutubeDLSubs, | ||
266 | getYoutubeDLInfo, | ||
267 | safeGetYoutubeDL, | ||
268 | buildOriginallyPublishedAt | ||
269 | } | ||
270 | 280 | ||
271 | // --------------------------------------------------------------------------- | 281 | private getTags (tags: any) { |
282 | if (Array.isArray(tags) === false) return [] | ||
272 | 283 | ||
273 | async function guessVideoPathWithExtension (tmpPath: string, sourceExt: string) { | 284 | return tags |
274 | if (!isVideoFileExtnameValid(sourceExt)) { | 285 | .filter(t => t.length < CONSTRAINTS_FIELDS.VIDEOS.TAG.max && t.length > CONSTRAINTS_FIELDS.VIDEOS.TAG.min) |
275 | throw new Error('Invalid video extension ' + sourceExt) | 286 | .map(t => t.normalize()) |
287 | .slice(0, 5) | ||
276 | } | 288 | } |
277 | 289 | ||
278 | const extensions = [ sourceExt, '.mp4', '.mkv', '.webm' ] | 290 | private getLicence (licence: string) { |
291 | if (!licence) return undefined | ||
279 | 292 | ||
280 | for (const extension of extensions) { | 293 | if (licence.includes('Creative Commons Attribution')) return 1 |
281 | const path = tmpPath + extension | ||
282 | 294 | ||
283 | if (await pathExists(path)) return path | 295 | for (const key of Object.keys(VIDEO_LICENCES)) { |
284 | } | 296 | const peertubeLicence = VIDEO_LICENCES[key] |
297 | if (peertubeLicence.toLowerCase() === licence.toLowerCase()) return parseInt(key, 10) | ||
298 | } | ||
285 | 299 | ||
286 | throw new Error('Cannot guess path of ' + tmpPath) | 300 | return undefined |
287 | } | 301 | } |
288 | 302 | ||
289 | function normalizeObject (obj: any) { | 303 | private getCategory (categories: string[]) { |
290 | const newObj: any = {} | 304 | if (!categories) return undefined |
291 | 305 | ||
292 | for (const key of Object.keys(obj)) { | 306 | const categoryString = categories[0] |
293 | // Deprecated key | 307 | if (!categoryString || typeof categoryString !== 'string') return undefined |
294 | if (key === 'resolution') continue | ||
295 | 308 | ||
296 | const value = obj[key] | 309 | if (categoryString === 'News & Politics') return 11 |
297 | 310 | ||
298 | if (typeof value === 'string') { | 311 | for (const key of Object.keys(VIDEO_CATEGORIES)) { |
299 | newObj[key] = value.normalize() | 312 | const category = VIDEO_CATEGORIES[key] |
300 | } else { | 313 | if (categoryString.toLowerCase() === category.toLowerCase()) return parseInt(key, 10) |
301 | newObj[key] = value | ||
302 | } | 314 | } |
303 | } | ||
304 | 315 | ||
305 | return newObj | 316 | return undefined |
306 | } | ||
307 | |||
308 | function buildVideoInfo (obj: any): YoutubeDLInfo { | ||
309 | return { | ||
310 | name: titleTruncation(obj.title), | ||
311 | description: descriptionTruncation(obj.description), | ||
312 | category: getCategory(obj.categories), | ||
313 | licence: getLicence(obj.license), | ||
314 | language: getLanguage(obj.language), | ||
315 | nsfw: isNSFW(obj), | ||
316 | tags: getTags(obj.tags), | ||
317 | thumbnailUrl: obj.thumbnail || undefined, | ||
318 | originallyPublishedAt: buildOriginallyPublishedAt(obj), | ||
319 | ext: obj.ext | ||
320 | } | 317 | } |
321 | } | ||
322 | 318 | ||
323 | function titleTruncation (title: string) { | 319 | private getLanguage (language: string) { |
324 | return peertubeTruncate(title, { | 320 | return VIDEO_LANGUAGES[language] ? language : undefined |
325 | length: CONSTRAINTS_FIELDS.VIDEOS.NAME.max, | 321 | } |
326 | separator: /,? +/, | ||
327 | omission: ' […]' | ||
328 | }) | ||
329 | } | ||
330 | 322 | ||
331 | function descriptionTruncation (description: string) { | 323 | private wrapWithProxyOptions (options: string[]) { |
332 | if (!description || description.length < CONSTRAINTS_FIELDS.VIDEOS.DESCRIPTION.min) return undefined | 324 | if (CONFIG.IMPORT.VIDEOS.HTTP.PROXY.ENABLED) { |
325 | logger.debug('Using proxy for YoutubeDL') | ||
333 | 326 | ||
334 | return peertubeTruncate(description, { | 327 | return [ '--proxy', CONFIG.IMPORT.VIDEOS.HTTP.PROXY.URL ].concat(options) |
335 | length: CONSTRAINTS_FIELDS.VIDEOS.DESCRIPTION.max, | 328 | } |
336 | separator: /,? +/, | ||
337 | omission: ' […]' | ||
338 | }) | ||
339 | } | ||
340 | 329 | ||
341 | function isNSFW (info: any) { | 330 | return options |
342 | return info.age_limit && info.age_limit >= 16 | 331 | } |
343 | } | ||
344 | 332 | ||
345 | function getTags (tags: any) { | 333 | // Thanks: https://github.com/przemyslawpluta/node-youtube-dl/blob/master/lib/downloader.js |
346 | if (Array.isArray(tags) === false) return [] | 334 | // We rewrote it to avoid sync calls |
335 | static async updateYoutubeDLBinary () { | ||
336 | logger.info('Updating youtubeDL binary.') | ||
347 | 337 | ||
348 | return tags | 338 | const binDirectory = join(root(), 'node_modules', 'youtube-dl', 'bin') |
349 | .filter(t => t.length < CONSTRAINTS_FIELDS.VIDEOS.TAG.max && t.length > CONSTRAINTS_FIELDS.VIDEOS.TAG.min) | 339 | const bin = join(binDirectory, 'youtube-dl') |
350 | .map(t => t.normalize()) | 340 | const detailsPath = join(binDirectory, 'details') |
351 | .slice(0, 5) | 341 | const url = process.env.YOUTUBE_DL_DOWNLOAD_HOST || 'https://yt-dl.org/downloads/latest/youtube-dl' |
352 | } | ||
353 | 342 | ||
354 | function getLicence (licence: string) { | 343 | await ensureDir(binDirectory) |
355 | if (!licence) return undefined | ||
356 | 344 | ||
357 | if (licence.includes('Creative Commons Attribution')) return 1 | 345 | try { |
346 | const result = await got(url, { followRedirect: false }) | ||
358 | 347 | ||
359 | for (const key of Object.keys(VIDEO_LICENCES)) { | 348 | if (result.statusCode !== HttpStatusCode.FOUND_302) { |
360 | const peertubeLicence = VIDEO_LICENCES[key] | 349 | logger.error('youtube-dl update error: did not get redirect for the latest version link. Status %d', result.statusCode) |
361 | if (peertubeLicence.toLowerCase() === licence.toLowerCase()) return parseInt(key, 10) | 350 | return |
362 | } | 351 | } |
363 | 352 | ||
364 | return undefined | 353 | const newUrl = result.headers.location |
365 | } | 354 | const newVersion = /yt-dl\.org\/downloads\/(\d{4}\.\d\d\.\d\d(\.\d)?)\/youtube-dl/.exec(newUrl)[1] |
366 | 355 | ||
367 | function getCategory (categories: string[]) { | 356 | const downloadFileStream = got.stream(newUrl) |
368 | if (!categories) return undefined | 357 | const writeStream = createWriteStream(bin, { mode: 493 }) |
369 | 358 | ||
370 | const categoryString = categories[0] | 359 | await pipelinePromise( |
371 | if (!categoryString || typeof categoryString !== 'string') return undefined | 360 | downloadFileStream, |
361 | writeStream | ||
362 | ) | ||
372 | 363 | ||
373 | if (categoryString === 'News & Politics') return 11 | 364 | const details = JSON.stringify({ version: newVersion, path: bin, exec: 'youtube-dl' }) |
365 | await writeFile(detailsPath, details, { encoding: 'utf8' }) | ||
374 | 366 | ||
375 | for (const key of Object.keys(VIDEO_CATEGORIES)) { | 367 | logger.info('youtube-dl updated to version %s.', newVersion) |
376 | const category = VIDEO_CATEGORIES[key] | 368 | } catch (err) { |
377 | if (categoryString.toLowerCase() === category.toLowerCase()) return parseInt(key, 10) | 369 | logger.error('Cannot update youtube-dl.', { err }) |
370 | } | ||
378 | } | 371 | } |
379 | 372 | ||
380 | return undefined | 373 | static async safeGetYoutubeDL () { |
381 | } | 374 | let youtubeDL |
382 | |||
383 | function getLanguage (language: string) { | ||
384 | return VIDEO_LANGUAGES[language] ? language : undefined | ||
385 | } | ||
386 | 375 | ||
387 | function wrapWithProxyOptions (options: string[]) { | 376 | try { |
388 | if (CONFIG.IMPORT.VIDEOS.HTTP.PROXY.ENABLED) { | 377 | youtubeDL = require('youtube-dl') |
389 | logger.debug('Using proxy for YoutubeDL') | 378 | } catch (e) { |
379 | // Download binary | ||
380 | await this.updateYoutubeDLBinary() | ||
381 | youtubeDL = require('youtube-dl') | ||
382 | } | ||
390 | 383 | ||
391 | return [ '--proxy', CONFIG.IMPORT.VIDEOS.HTTP.PROXY.URL ].concat(options) | 384 | return youtubeDL |
392 | } | 385 | } |
386 | } | ||
387 | |||
388 | // --------------------------------------------------------------------------- | ||
393 | 389 | ||
394 | return options | 390 | export { |
391 | YoutubeDL | ||
395 | } | 392 | } |
diff --git a/server/initializers/checker-after-init.ts b/server/initializers/checker-after-init.ts index a93c8b7fd..911734fa0 100644 --- a/server/initializers/checker-after-init.ts +++ b/server/initializers/checker-after-init.ts | |||
@@ -7,7 +7,7 @@ import { RecentlyAddedStrategy } from '../../shared/models/redundancy' | |||
7 | import { isProdInstance, isTestInstance, parseSemVersion } from '../helpers/core-utils' | 7 | import { isProdInstance, isTestInstance, parseSemVersion } from '../helpers/core-utils' |
8 | import { isArray } from '../helpers/custom-validators/misc' | 8 | import { isArray } from '../helpers/custom-validators/misc' |
9 | import { logger } from '../helpers/logger' | 9 | import { logger } from '../helpers/logger' |
10 | import { UserModel } from '../models/account/user' | 10 | import { UserModel } from '../models/user/user' |
11 | import { ApplicationModel, getServerActor } from '../models/application/application' | 11 | import { ApplicationModel, getServerActor } from '../models/application/application' |
12 | import { OAuthClientModel } from '../models/oauth/oauth-client' | 12 | import { OAuthClientModel } from '../models/oauth/oauth-client' |
13 | import { CONFIG, isEmailEnabled } from './config' | 13 | import { CONFIG, isEmailEnabled } from './config' |
diff --git a/server/initializers/checker-before-init.ts b/server/initializers/checker-before-init.ts index 2864b0287..93c019121 100644 --- a/server/initializers/checker-before-init.ts +++ b/server/initializers/checker-before-init.ts | |||
@@ -19,7 +19,7 @@ function checkMissedConfig () { | |||
19 | 'csp.enabled', 'csp.report_only', 'csp.report_uri', | 19 | 'csp.enabled', 'csp.report_only', 'csp.report_uri', |
20 | 'security.frameguard.enabled', | 20 | 'security.frameguard.enabled', |
21 | 'cache.previews.size', 'cache.captions.size', 'cache.torrents.size', 'admin.email', 'contact_form.enabled', | 21 | 'cache.previews.size', 'cache.captions.size', 'cache.torrents.size', 'admin.email', 'contact_form.enabled', |
22 | 'signup.enabled', 'signup.limit', 'signup.requires_email_verification', | 22 | 'signup.enabled', 'signup.limit', 'signup.requires_email_verification', 'signup.minimum_age', |
23 | 'signup.filters.cidr.whitelist', 'signup.filters.cidr.blacklist', | 23 | 'signup.filters.cidr.whitelist', 'signup.filters.cidr.blacklist', |
24 | 'redundancy.videos.strategies', 'redundancy.videos.check_interval', | 24 | 'redundancy.videos.strategies', 'redundancy.videos.check_interval', |
25 | 'transcoding.enabled', 'transcoding.threads', 'transcoding.allow_additional_extensions', 'transcoding.hls.enabled', | 25 | 'transcoding.enabled', 'transcoding.threads', 'transcoding.allow_additional_extensions', 'transcoding.hls.enabled', |
diff --git a/server/initializers/config.ts b/server/initializers/config.ts index 5281d3a66..30a9823b9 100644 --- a/server/initializers/config.ts +++ b/server/initializers/config.ts | |||
@@ -185,6 +185,7 @@ const CONFIG = { | |||
185 | get ENABLED () { return config.get<boolean>('signup.enabled') }, | 185 | get ENABLED () { return config.get<boolean>('signup.enabled') }, |
186 | get LIMIT () { return config.get<number>('signup.limit') }, | 186 | get LIMIT () { return config.get<number>('signup.limit') }, |
187 | get REQUIRES_EMAIL_VERIFICATION () { return config.get<boolean>('signup.requires_email_verification') }, | 187 | get REQUIRES_EMAIL_VERIFICATION () { return config.get<boolean>('signup.requires_email_verification') }, |
188 | get MINIMUM_AGE () { return config.get<number>('signup.minimum_age') }, | ||
188 | FILTERS: { | 189 | FILTERS: { |
189 | CIDR: { | 190 | CIDR: { |
190 | get WHITELIST () { return config.get<string[]>('signup.filters.cidr.whitelist') }, | 191 | get WHITELIST () { return config.get<string[]>('signup.filters.cidr.whitelist') }, |
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index 6f388420e..919f9ea6e 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts | |||
@@ -24,7 +24,7 @@ import { CONFIG, registerConfigChangedHandler } from './config' | |||
24 | 24 | ||
25 | // --------------------------------------------------------------------------- | 25 | // --------------------------------------------------------------------------- |
26 | 26 | ||
27 | const LAST_MIGRATION_VERSION = 645 | 27 | const LAST_MIGRATION_VERSION = 650 |
28 | 28 | ||
29 | // --------------------------------------------------------------------------- | 29 | // --------------------------------------------------------------------------- |
30 | 30 | ||
@@ -702,7 +702,8 @@ const CUSTOM_HTML_TAG_COMMENTS = { | |||
702 | TITLE: '<!-- title tag -->', | 702 | TITLE: '<!-- title tag -->', |
703 | DESCRIPTION: '<!-- description tag -->', | 703 | DESCRIPTION: '<!-- description tag -->', |
704 | CUSTOM_CSS: '<!-- custom css tag -->', | 704 | CUSTOM_CSS: '<!-- custom css tag -->', |
705 | META_TAGS: '<!-- meta tags -->' | 705 | META_TAGS: '<!-- meta tags -->', |
706 | SERVER_CONFIG: '<!-- server config -->' | ||
706 | } | 707 | } |
707 | 708 | ||
708 | // --------------------------------------------------------------------------- | 709 | // --------------------------------------------------------------------------- |
diff --git a/server/initializers/database.ts b/server/initializers/database.ts index edf12bc41..38e7a76d0 100644 --- a/server/initializers/database.ts +++ b/server/initializers/database.ts | |||
@@ -2,6 +2,9 @@ import { QueryTypes, Transaction } from 'sequelize' | |||
2 | import { Sequelize as SequelizeTypescript } from 'sequelize-typescript' | 2 | import { Sequelize as SequelizeTypescript } from 'sequelize-typescript' |
3 | import { TrackerModel } from '@server/models/server/tracker' | 3 | import { TrackerModel } from '@server/models/server/tracker' |
4 | import { VideoTrackerModel } from '@server/models/server/video-tracker' | 4 | import { VideoTrackerModel } from '@server/models/server/video-tracker' |
5 | import { UserModel } from '@server/models/user/user' | ||
6 | import { UserNotificationModel } from '@server/models/user/user-notification' | ||
7 | import { UserVideoHistoryModel } from '@server/models/user/user-video-history' | ||
5 | import { isTestInstance } from '../helpers/core-utils' | 8 | import { isTestInstance } from '../helpers/core-utils' |
6 | import { logger } from '../helpers/logger' | 9 | import { logger } from '../helpers/logger' |
7 | import { AbuseModel } from '../models/abuse/abuse' | 10 | import { AbuseModel } from '../models/abuse/abuse' |
@@ -11,13 +14,9 @@ import { VideoCommentAbuseModel } from '../models/abuse/video-comment-abuse' | |||
11 | import { AccountModel } from '../models/account/account' | 14 | import { AccountModel } from '../models/account/account' |
12 | import { AccountBlocklistModel } from '../models/account/account-blocklist' | 15 | import { AccountBlocklistModel } from '../models/account/account-blocklist' |
13 | import { AccountVideoRateModel } from '../models/account/account-video-rate' | 16 | import { AccountVideoRateModel } from '../models/account/account-video-rate' |
14 | import { ActorImageModel } from '../models/account/actor-image' | 17 | import { ActorModel } from '../models/actor/actor' |
15 | import { UserModel } from '../models/account/user' | 18 | import { ActorFollowModel } from '../models/actor/actor-follow' |
16 | import { UserNotificationModel } from '../models/account/user-notification' | 19 | import { ActorImageModel } from '../models/actor/actor-image' |
17 | import { UserNotificationSettingModel } from '../models/account/user-notification-setting' | ||
18 | import { UserVideoHistoryModel } from '../models/account/user-video-history' | ||
19 | import { ActorModel } from '../models/activitypub/actor' | ||
20 | import { ActorFollowModel } from '../models/activitypub/actor-follow' | ||
21 | import { ApplicationModel } from '../models/application/application' | 20 | import { ApplicationModel } from '../models/application/application' |
22 | import { OAuthClientModel } from '../models/oauth/oauth-client' | 21 | import { OAuthClientModel } from '../models/oauth/oauth-client' |
23 | import { OAuthTokenModel } from '../models/oauth/oauth-token' | 22 | import { OAuthTokenModel } from '../models/oauth/oauth-token' |
@@ -25,6 +24,7 @@ import { VideoRedundancyModel } from '../models/redundancy/video-redundancy' | |||
25 | import { PluginModel } from '../models/server/plugin' | 24 | import { PluginModel } from '../models/server/plugin' |
26 | import { ServerModel } from '../models/server/server' | 25 | import { ServerModel } from '../models/server/server' |
27 | import { ServerBlocklistModel } from '../models/server/server-blocklist' | 26 | import { ServerBlocklistModel } from '../models/server/server-blocklist' |
27 | import { UserNotificationSettingModel } from '../models/user/user-notification-setting' | ||
28 | import { ScheduleVideoUpdateModel } from '../models/video/schedule-video-update' | 28 | import { ScheduleVideoUpdateModel } from '../models/video/schedule-video-update' |
29 | import { TagModel } from '../models/video/tag' | 29 | import { TagModel } from '../models/video/tag' |
30 | import { ThumbnailModel } from '../models/video/thumbnail' | 30 | import { ThumbnailModel } from '../models/video/thumbnail' |
@@ -44,6 +44,7 @@ import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-pla | |||
44 | import { VideoTagModel } from '../models/video/video-tag' | 44 | import { VideoTagModel } from '../models/video/video-tag' |
45 | import { VideoViewModel } from '../models/video/video-view' | 45 | import { VideoViewModel } from '../models/video/video-view' |
46 | import { CONFIG } from './config' | 46 | import { CONFIG } from './config' |
47 | import { ActorCustomPageModel } from '@server/models/account/actor-custom-page' | ||
47 | 48 | ||
48 | require('pg').defaults.parseInt8 = true // Avoid BIGINT to be converted to string | 49 | require('pg').defaults.parseInt8 = true // Avoid BIGINT to be converted to string |
49 | 50 | ||
@@ -141,7 +142,8 @@ async function initDatabaseModels (silent: boolean) { | |||
141 | ThumbnailModel, | 142 | ThumbnailModel, |
142 | TrackerModel, | 143 | TrackerModel, |
143 | VideoTrackerModel, | 144 | VideoTrackerModel, |
144 | PluginModel | 145 | PluginModel, |
146 | ActorCustomPageModel | ||
145 | ]) | 147 | ]) |
146 | 148 | ||
147 | // Check extensions exist in the database | 149 | // Check extensions exist in the database |
diff --git a/server/initializers/installer.ts b/server/initializers/installer.ts index 8dcff64e2..676f88653 100644 --- a/server/initializers/installer.ts +++ b/server/initializers/installer.ts | |||
@@ -2,7 +2,7 @@ import * as passwordGenerator from 'password-generator' | |||
2 | import { UserRole } from '../../shared' | 2 | import { UserRole } from '../../shared' |
3 | import { logger } from '../helpers/logger' | 3 | import { logger } from '../helpers/logger' |
4 | import { createApplicationActor, createUserAccountAndChannelAndPlaylist } from '../lib/user' | 4 | import { createApplicationActor, createUserAccountAndChannelAndPlaylist } from '../lib/user' |
5 | import { UserModel } from '../models/account/user' | 5 | import { UserModel } from '../models/user/user' |
6 | import { ApplicationModel } from '../models/application/application' | 6 | import { ApplicationModel } from '../models/application/application' |
7 | import { OAuthClientModel } from '../models/oauth/oauth-client' | 7 | import { OAuthClientModel } from '../models/oauth/oauth-client' |
8 | import { applicationExist, clientsExist, usersExist } from './checker-after-init' | 8 | import { applicationExist, clientsExist, usersExist } from './checker-after-init' |
diff --git a/server/initializers/migrations/0650-actor-custom-pages.ts b/server/initializers/migrations/0650-actor-custom-pages.ts new file mode 100644 index 000000000..1338327e8 --- /dev/null +++ b/server/initializers/migrations/0650-actor-custom-pages.ts | |||
@@ -0,0 +1,33 @@ | |||
1 | import * as Sequelize from 'sequelize' | ||
2 | |||
3 | async function up (utils: { | ||
4 | transaction: Sequelize.Transaction | ||
5 | queryInterface: Sequelize.QueryInterface | ||
6 | sequelize: Sequelize.Sequelize | ||
7 | db: any | ||
8 | }): Promise<void> { | ||
9 | { | ||
10 | const query = ` | ||
11 | CREATE TABLE IF NOT EXISTS "actorCustomPage" ( | ||
12 | "id" serial, | ||
13 | "content" TEXT, | ||
14 | "type" varchar(255) NOT NULL, | ||
15 | "actorId" integer NOT NULL REFERENCES "actor" ("id") ON DELETE CASCADE ON UPDATE CASCADE, | ||
16 | "createdAt" timestamp WITH time zone NOT NULL, | ||
17 | "updatedAt" timestamp WITH time zone NOT NULL, | ||
18 | PRIMARY KEY ("id") | ||
19 | ); | ||
20 | ` | ||
21 | |||
22 | await utils.sequelize.query(query) | ||
23 | } | ||
24 | } | ||
25 | |||
26 | function down (options) { | ||
27 | throw new Error('Not implemented.') | ||
28 | } | ||
29 | |||
30 | export { | ||
31 | up, | ||
32 | down | ||
33 | } | ||
diff --git a/server/lib/activitypub/actor.ts b/server/lib/activitypub/actor.ts index 5fe7381c9..1bcee7ef9 100644 --- a/server/lib/activitypub/actor.ts +++ b/server/lib/activitypub/actor.ts | |||
@@ -20,8 +20,8 @@ import { getUrlFromWebfinger } from '../../helpers/webfinger' | |||
20 | import { MIMETYPES, WEBSERVER } from '../../initializers/constants' | 20 | import { MIMETYPES, WEBSERVER } from '../../initializers/constants' |
21 | import { sequelizeTypescript } from '../../initializers/database' | 21 | import { sequelizeTypescript } from '../../initializers/database' |
22 | import { AccountModel } from '../../models/account/account' | 22 | import { AccountModel } from '../../models/account/account' |
23 | import { ActorImageModel } from '../../models/account/actor-image' | 23 | import { ActorModel } from '../../models/actor/actor' |
24 | import { ActorModel } from '../../models/activitypub/actor' | 24 | import { ActorImageModel } from '../../models/actor/actor-image' |
25 | import { ServerModel } from '../../models/server/server' | 25 | import { ServerModel } from '../../models/server/server' |
26 | import { VideoChannelModel } from '../../models/video/video-channel' | 26 | import { VideoChannelModel } from '../../models/video/video-channel' |
27 | import { | 27 | import { |
@@ -132,12 +132,11 @@ async function getOrCreateActorAndServerAndModel ( | |||
132 | return actorRefreshed | 132 | return actorRefreshed |
133 | } | 133 | } |
134 | 134 | ||
135 | function buildActorInstance (type: ActivityPubActorType, url: string, preferredUsername: string, uuid?: string) { | 135 | function buildActorInstance (type: ActivityPubActorType, url: string, preferredUsername: string) { |
136 | return new ActorModel({ | 136 | return new ActorModel({ |
137 | type, | 137 | type, |
138 | url, | 138 | url, |
139 | preferredUsername, | 139 | preferredUsername, |
140 | uuid, | ||
141 | publicKey: null, | 140 | publicKey: null, |
142 | privateKey: null, | 141 | privateKey: null, |
143 | followersCount: 0, | 142 | followersCount: 0, |
diff --git a/server/lib/activitypub/audience.ts b/server/lib/activitypub/audience.ts index 2986714d3..d0558f191 100644 --- a/server/lib/activitypub/audience.ts +++ b/server/lib/activitypub/audience.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import { Transaction } from 'sequelize' | 1 | import { Transaction } from 'sequelize' |
2 | import { ActivityAudience } from '../../../shared/models/activitypub' | 2 | import { ActivityAudience } from '../../../shared/models/activitypub' |
3 | import { ACTIVITY_PUB } from '../../initializers/constants' | 3 | import { ACTIVITY_PUB } from '../../initializers/constants' |
4 | import { ActorModel } from '../../models/activitypub/actor' | 4 | import { ActorModel } from '../../models/actor/actor' |
5 | import { VideoModel } from '../../models/video/video' | 5 | import { VideoModel } from '../../models/video/video' |
6 | import { VideoShareModel } from '../../models/video/video-share' | 6 | import { VideoShareModel } from '../../models/video/video-share' |
7 | import { MActorFollowersUrl, MActorLight, MActorUrl, MCommentOwner, MCommentOwnerVideo, MVideoId } from '../../types/models' | 7 | import { MActorFollowersUrl, MActorLight, MActorUrl, MCommentOwner, MCommentOwnerVideo, MVideoId } from '../../types/models' |
diff --git a/server/lib/activitypub/process/process-accept.ts b/server/lib/activitypub/process/process-accept.ts index 1799829f8..8ad470cf4 100644 --- a/server/lib/activitypub/process/process-accept.ts +++ b/server/lib/activitypub/process/process-accept.ts | |||
@@ -1,8 +1,8 @@ | |||
1 | import { ActivityAccept } from '../../../../shared/models/activitypub' | 1 | import { ActivityAccept } from '../../../../shared/models/activitypub' |
2 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' | 2 | import { ActorFollowModel } from '../../../models/actor/actor-follow' |
3 | import { addFetchOutboxJob } from '../actor' | ||
4 | import { APProcessorOptions } from '../../../types/activitypub-processor.model' | 3 | import { APProcessorOptions } from '../../../types/activitypub-processor.model' |
5 | import { MActorDefault, MActorSignature } from '../../../types/models' | 4 | import { MActorDefault, MActorSignature } from '../../../types/models' |
5 | import { addFetchOutboxJob } from '../actor' | ||
6 | 6 | ||
7 | async function processAcceptActivity (options: APProcessorOptions<ActivityAccept>) { | 7 | async function processAcceptActivity (options: APProcessorOptions<ActivityAccept>) { |
8 | const { byActor: targetActor, inboxActor } = options | 8 | const { byActor: targetActor, inboxActor } = options |
diff --git a/server/lib/activitypub/process/process-delete.ts b/server/lib/activitypub/process/process-delete.ts index 88a968318..20214246c 100644 --- a/server/lib/activitypub/process/process-delete.ts +++ b/server/lib/activitypub/process/process-delete.ts | |||
@@ -2,7 +2,7 @@ import { ActivityDelete } from '../../../../shared/models/activitypub' | |||
2 | import { retryTransactionWrapper } from '../../../helpers/database-utils' | 2 | import { retryTransactionWrapper } from '../../../helpers/database-utils' |
3 | import { logger } from '../../../helpers/logger' | 3 | import { logger } from '../../../helpers/logger' |
4 | import { sequelizeTypescript } from '../../../initializers/database' | 4 | import { sequelizeTypescript } from '../../../initializers/database' |
5 | import { ActorModel } from '../../../models/activitypub/actor' | 5 | import { ActorModel } from '../../../models/actor/actor' |
6 | import { VideoModel } from '../../../models/video/video' | 6 | import { VideoModel } from '../../../models/video/video' |
7 | import { VideoCommentModel } from '../../../models/video/video-comment' | 7 | import { VideoCommentModel } from '../../../models/video/video-comment' |
8 | import { VideoPlaylistModel } from '../../../models/video/video-playlist' | 8 | import { VideoPlaylistModel } from '../../../models/video/video-playlist' |
diff --git a/server/lib/activitypub/process/process-follow.ts b/server/lib/activitypub/process/process-follow.ts index 38d684512..9009c6469 100644 --- a/server/lib/activitypub/process/process-follow.ts +++ b/server/lib/activitypub/process/process-follow.ts | |||
@@ -1,17 +1,17 @@ | |||
1 | import { getServerActor } from '@server/models/application/application' | ||
1 | import { ActivityFollow } from '../../../../shared/models/activitypub' | 2 | import { ActivityFollow } from '../../../../shared/models/activitypub' |
3 | import { getAPId } from '../../../helpers/activitypub' | ||
2 | import { retryTransactionWrapper } from '../../../helpers/database-utils' | 4 | import { retryTransactionWrapper } from '../../../helpers/database-utils' |
3 | import { logger } from '../../../helpers/logger' | 5 | import { logger } from '../../../helpers/logger' |
4 | import { sequelizeTypescript } from '../../../initializers/database' | ||
5 | import { ActorModel } from '../../../models/activitypub/actor' | ||
6 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' | ||
7 | import { sendAccept, sendReject } from '../send' | ||
8 | import { Notifier } from '../../notifier' | ||
9 | import { getAPId } from '../../../helpers/activitypub' | ||
10 | import { CONFIG } from '../../../initializers/config' | 6 | import { CONFIG } from '../../../initializers/config' |
7 | import { sequelizeTypescript } from '../../../initializers/database' | ||
8 | import { ActorModel } from '../../../models/actor/actor' | ||
9 | import { ActorFollowModel } from '../../../models/actor/actor-follow' | ||
11 | import { APProcessorOptions } from '../../../types/activitypub-processor.model' | 10 | import { APProcessorOptions } from '../../../types/activitypub-processor.model' |
12 | import { MActorFollowActors, MActorSignature } from '../../../types/models' | 11 | import { MActorFollowActors, MActorSignature } from '../../../types/models' |
12 | import { Notifier } from '../../notifier' | ||
13 | import { autoFollowBackIfNeeded } from '../follow' | 13 | import { autoFollowBackIfNeeded } from '../follow' |
14 | import { getServerActor } from '@server/models/application/application' | 14 | import { sendAccept, sendReject } from '../send' |
15 | 15 | ||
16 | async function processFollowActivity (options: APProcessorOptions<ActivityFollow>) { | 16 | async function processFollowActivity (options: APProcessorOptions<ActivityFollow>) { |
17 | const { activity, byActor } = options | 17 | const { activity, byActor } = options |
diff --git a/server/lib/activitypub/process/process-reject.ts b/server/lib/activitypub/process/process-reject.ts index 03b669fd9..7f7ab305f 100644 --- a/server/lib/activitypub/process/process-reject.ts +++ b/server/lib/activitypub/process/process-reject.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import { ActivityReject } from '../../../../shared/models/activitypub/activity' | 1 | import { ActivityReject } from '../../../../shared/models/activitypub/activity' |
2 | import { sequelizeTypescript } from '../../../initializers/database' | 2 | import { sequelizeTypescript } from '../../../initializers/database' |
3 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' | 3 | import { ActorFollowModel } from '../../../models/actor/actor-follow' |
4 | import { APProcessorOptions } from '../../../types/activitypub-processor.model' | 4 | import { APProcessorOptions } from '../../../types/activitypub-processor.model' |
5 | import { MActor } from '../../../types/models' | 5 | import { MActor } from '../../../types/models' |
6 | 6 | ||
diff --git a/server/lib/activitypub/process/process-undo.ts b/server/lib/activitypub/process/process-undo.ts index e520c2f0d..9f031b528 100644 --- a/server/lib/activitypub/process/process-undo.ts +++ b/server/lib/activitypub/process/process-undo.ts | |||
@@ -4,8 +4,8 @@ import { retryTransactionWrapper } from '../../../helpers/database-utils' | |||
4 | import { logger } from '../../../helpers/logger' | 4 | import { logger } from '../../../helpers/logger' |
5 | import { sequelizeTypescript } from '../../../initializers/database' | 5 | import { sequelizeTypescript } from '../../../initializers/database' |
6 | import { AccountVideoRateModel } from '../../../models/account/account-video-rate' | 6 | import { AccountVideoRateModel } from '../../../models/account/account-video-rate' |
7 | import { ActorModel } from '../../../models/activitypub/actor' | 7 | import { ActorModel } from '../../../models/actor/actor' |
8 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' | 8 | import { ActorFollowModel } from '../../../models/actor/actor-follow' |
9 | import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy' | 9 | import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy' |
10 | import { VideoShareModel } from '../../../models/video/video-share' | 10 | import { VideoShareModel } from '../../../models/video/video-share' |
11 | import { APProcessorOptions } from '../../../types/activitypub-processor.model' | 11 | import { APProcessorOptions } from '../../../types/activitypub-processor.model' |
diff --git a/server/lib/activitypub/process/process-update.ts b/server/lib/activitypub/process/process-update.ts index 6df9b93b2..6cd9d0fba 100644 --- a/server/lib/activitypub/process/process-update.ts +++ b/server/lib/activitypub/process/process-update.ts | |||
@@ -1,23 +1,23 @@ | |||
1 | import { isRedundancyAccepted } from '@server/lib/redundancy' | ||
2 | import { ActorImageType } from '@shared/models' | ||
1 | import { ActivityUpdate, CacheFileObject, VideoObject } from '../../../../shared/models/activitypub' | 3 | import { ActivityUpdate, CacheFileObject, VideoObject } from '../../../../shared/models/activitypub' |
2 | import { ActivityPubActor } from '../../../../shared/models/activitypub/activitypub-actor' | 4 | import { ActivityPubActor } from '../../../../shared/models/activitypub/activitypub-actor' |
5 | import { PlaylistObject } from '../../../../shared/models/activitypub/objects/playlist-object' | ||
6 | import { isCacheFileObjectValid } from '../../../helpers/custom-validators/activitypub/cache-file' | ||
7 | import { sanitizeAndCheckVideoTorrentObject } from '../../../helpers/custom-validators/activitypub/videos' | ||
3 | import { resetSequelizeInstance, retryTransactionWrapper } from '../../../helpers/database-utils' | 8 | import { resetSequelizeInstance, retryTransactionWrapper } from '../../../helpers/database-utils' |
4 | import { logger } from '../../../helpers/logger' | 9 | import { logger } from '../../../helpers/logger' |
5 | import { sequelizeTypescript } from '../../../initializers/database' | 10 | import { sequelizeTypescript } from '../../../initializers/database' |
6 | import { AccountModel } from '../../../models/account/account' | 11 | import { AccountModel } from '../../../models/account/account' |
7 | import { ActorModel } from '../../../models/activitypub/actor' | 12 | import { ActorModel } from '../../../models/actor/actor' |
8 | import { VideoChannelModel } from '../../../models/video/video-channel' | 13 | import { VideoChannelModel } from '../../../models/video/video-channel' |
14 | import { APProcessorOptions } from '../../../types/activitypub-processor.model' | ||
15 | import { MAccountIdActor, MActorSignature } from '../../../types/models' | ||
9 | import { getImageInfoIfExists, updateActorImageInstance, updateActorInstance } from '../actor' | 16 | import { getImageInfoIfExists, updateActorImageInstance, updateActorInstance } from '../actor' |
10 | import { getOrCreateVideoAndAccountAndChannel, getOrCreateVideoChannelFromVideoObject, updateVideoFromAP } from '../videos' | ||
11 | import { sanitizeAndCheckVideoTorrentObject } from '../../../helpers/custom-validators/activitypub/videos' | ||
12 | import { isCacheFileObjectValid } from '../../../helpers/custom-validators/activitypub/cache-file' | ||
13 | import { createOrUpdateCacheFile } from '../cache-file' | 17 | import { createOrUpdateCacheFile } from '../cache-file' |
14 | import { forwardVideoRelatedActivity } from '../send/utils' | ||
15 | import { PlaylistObject } from '../../../../shared/models/activitypub/objects/playlist-object' | ||
16 | import { createOrUpdateVideoPlaylist } from '../playlist' | 18 | import { createOrUpdateVideoPlaylist } from '../playlist' |
17 | import { APProcessorOptions } from '../../../types/activitypub-processor.model' | 19 | import { forwardVideoRelatedActivity } from '../send/utils' |
18 | import { MActorSignature, MAccountIdActor } from '../../../types/models' | 20 | import { getOrCreateVideoAndAccountAndChannel, getOrCreateVideoChannelFromVideoObject, updateVideoFromAP } from '../videos' |
19 | import { isRedundancyAccepted } from '@server/lib/redundancy' | ||
20 | import { ActorImageType } from '@shared/models' | ||
21 | 21 | ||
22 | async function processUpdateActivity (options: APProcessorOptions<ActivityUpdate>) { | 22 | async function processUpdateActivity (options: APProcessorOptions<ActivityUpdate>) { |
23 | const { activity, byActor } = options | 23 | const { activity, byActor } = options |
diff --git a/server/lib/activitypub/send/send-delete.ts b/server/lib/activitypub/send/send-delete.ts index e0acced18..d31f8c10b 100644 --- a/server/lib/activitypub/send/send-delete.ts +++ b/server/lib/activitypub/send/send-delete.ts | |||
@@ -2,7 +2,7 @@ import { Transaction } from 'sequelize' | |||
2 | import { getServerActor } from '@server/models/application/application' | 2 | import { getServerActor } from '@server/models/application/application' |
3 | import { ActivityAudience, ActivityDelete } from '../../../../shared/models/activitypub' | 3 | import { ActivityAudience, ActivityDelete } from '../../../../shared/models/activitypub' |
4 | import { logger } from '../../../helpers/logger' | 4 | import { logger } from '../../../helpers/logger' |
5 | import { ActorModel } from '../../../models/activitypub/actor' | 5 | import { ActorModel } from '../../../models/actor/actor' |
6 | import { VideoCommentModel } from '../../../models/video/video-comment' | 6 | import { VideoCommentModel } from '../../../models/video/video-comment' |
7 | import { VideoShareModel } from '../../../models/video/video-share' | 7 | import { VideoShareModel } from '../../../models/video/video-share' |
8 | import { MActorUrl } from '../../../types/models' | 8 | import { MActorUrl } from '../../../types/models' |
diff --git a/server/lib/activitypub/send/send-view.ts b/server/lib/activitypub/send/send-view.ts index 9254dc7c5..153e94295 100644 --- a/server/lib/activitypub/send/send-view.ts +++ b/server/lib/activitypub/send/send-view.ts | |||
@@ -2,7 +2,7 @@ import { Transaction } from 'sequelize' | |||
2 | import { MActorAudience, MVideoImmutable, MVideoUrl } from '@server/types/models' | 2 | import { MActorAudience, MVideoImmutable, MVideoUrl } from '@server/types/models' |
3 | import { ActivityAudience, ActivityView } from '../../../../shared/models/activitypub' | 3 | import { ActivityAudience, ActivityView } from '../../../../shared/models/activitypub' |
4 | import { logger } from '../../../helpers/logger' | 4 | import { logger } from '../../../helpers/logger' |
5 | import { ActorModel } from '../../../models/activitypub/actor' | 5 | import { ActorModel } from '../../../models/actor/actor' |
6 | import { audiencify, getAudience } from '../audience' | 6 | import { audiencify, getAudience } from '../audience' |
7 | import { getLocalVideoViewActivityPubUrl } from '../url' | 7 | import { getLocalVideoViewActivityPubUrl } from '../url' |
8 | import { sendVideoRelatedActivity } from './utils' | 8 | import { sendVideoRelatedActivity } from './utils' |
diff --git a/server/lib/activitypub/send/utils.ts b/server/lib/activitypub/send/utils.ts index 85a9f009d..db0e91b71 100644 --- a/server/lib/activitypub/send/utils.ts +++ b/server/lib/activitypub/send/utils.ts | |||
@@ -1,14 +1,14 @@ | |||
1 | import { Transaction } from 'sequelize' | 1 | import { Transaction } from 'sequelize' |
2 | import { getServerActor } from '@server/models/application/application' | ||
3 | import { ContextType } from '@shared/models/activitypub/context' | ||
2 | import { Activity, ActivityAudience } from '../../../../shared/models/activitypub' | 4 | import { Activity, ActivityAudience } from '../../../../shared/models/activitypub' |
5 | import { afterCommitIfTransaction } from '../../../helpers/database-utils' | ||
3 | import { logger } from '../../../helpers/logger' | 6 | import { logger } from '../../../helpers/logger' |
4 | import { ActorModel } from '../../../models/activitypub/actor' | 7 | import { ActorModel } from '../../../models/actor/actor' |
5 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' | 8 | import { ActorFollowModel } from '../../../models/actor/actor-follow' |
9 | import { MActor, MActorId, MActorLight, MActorWithInboxes, MVideoAccountLight, MVideoId, MVideoImmutable } from '../../../types/models' | ||
6 | import { JobQueue } from '../../job-queue' | 10 | import { JobQueue } from '../../job-queue' |
7 | import { getActorsInvolvedInVideo, getAudienceFromFollowersOf, getRemoteVideoAudience } from '../audience' | 11 | import { getActorsInvolvedInVideo, getAudienceFromFollowersOf, getRemoteVideoAudience } from '../audience' |
8 | import { afterCommitIfTransaction } from '../../../helpers/database-utils' | ||
9 | import { MActor, MActorId, MActorLight, MActorWithInboxes, MVideoAccountLight, MVideoId, MVideoImmutable } from '../../../types/models' | ||
10 | import { getServerActor } from '@server/models/application/application' | ||
11 | import { ContextType } from '@shared/models/activitypub/context' | ||
12 | 12 | ||
13 | async function sendVideoRelatedActivity (activityBuilder: (audience: ActivityAudience) => Activity, options: { | 13 | async function sendVideoRelatedActivity (activityBuilder: (audience: ActivityAudience) => Activity, options: { |
14 | byActor: MActorLight | 14 | byActor: MActorLight |
diff --git a/server/lib/auth/oauth-model.ts b/server/lib/auth/oauth-model.ts index b9c69eb2d..ae728d080 100644 --- a/server/lib/auth/oauth-model.ts +++ b/server/lib/auth/oauth-model.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { AccessDeniedError } from 'oauth2-server' | 2 | import { AccessDeniedError } from 'oauth2-server' |
3 | import { PluginManager } from '@server/lib/plugins/plugin-manager' | 3 | import { PluginManager } from '@server/lib/plugins/plugin-manager' |
4 | import { ActorModel } from '@server/models/activitypub/actor' | 4 | import { ActorModel } from '@server/models/actor/actor' |
5 | import { MOAuthClient } from '@server/types/models' | 5 | import { MOAuthClient } from '@server/types/models' |
6 | import { MOAuthTokenUser } from '@server/types/models/oauth/oauth-token' | 6 | import { MOAuthTokenUser } from '@server/types/models/oauth/oauth-token' |
7 | import { MUser } from '@server/types/models/user/user' | 7 | import { MUser } from '@server/types/models/user/user' |
@@ -9,7 +9,7 @@ import { UserAdminFlag } from '@shared/models/users/user-flag.model' | |||
9 | import { UserRole } from '@shared/models/users/user-role' | 9 | import { UserRole } from '@shared/models/users/user-role' |
10 | import { logger } from '../../helpers/logger' | 10 | import { logger } from '../../helpers/logger' |
11 | import { CONFIG } from '../../initializers/config' | 11 | import { CONFIG } from '../../initializers/config' |
12 | import { UserModel } from '../../models/account/user' | 12 | import { UserModel } from '../../models/user/user' |
13 | import { OAuthClientModel } from '../../models/oauth/oauth-client' | 13 | import { OAuthClientModel } from '../../models/oauth/oauth-client' |
14 | import { OAuthTokenModel } from '../../models/oauth/oauth-token' | 14 | import { OAuthTokenModel } from '../../models/oauth/oauth-token' |
15 | import { createUserAccountAndChannelAndPlaylist } from '../user' | 15 | import { createUserAccountAndChannelAndPlaylist } from '../user' |
diff --git a/server/lib/client-html.ts b/server/lib/client-html.ts index 203bd3893..3c09332b5 100644 --- a/server/lib/client-html.ts +++ b/server/lib/client-html.ts | |||
@@ -2,12 +2,14 @@ import * as express from 'express' | |||
2 | import { readFile } from 'fs-extra' | 2 | import { readFile } from 'fs-extra' |
3 | import { join } from 'path' | 3 | import { join } from 'path' |
4 | import validator from 'validator' | 4 | import validator from 'validator' |
5 | import { escapeHTML } from '@shared/core-utils/renderer' | ||
6 | import { HTMLServerConfig } from '@shared/models' | ||
5 | import { buildFileLocale, getDefaultLocale, is18nLocale, POSSIBLE_LOCALES } from '../../shared/core-utils/i18n/i18n' | 7 | import { buildFileLocale, getDefaultLocale, is18nLocale, POSSIBLE_LOCALES } from '../../shared/core-utils/i18n/i18n' |
6 | import { HttpStatusCode } from '../../shared/core-utils/miscs/http-error-codes' | 8 | import { HttpStatusCode } from '../../shared/core-utils/miscs/http-error-codes' |
7 | import { VideoPlaylistPrivacy, VideoPrivacy } from '../../shared/models/videos' | 9 | import { VideoPlaylistPrivacy, VideoPrivacy } from '../../shared/models/videos' |
8 | import { isTestInstance, sha256 } from '../helpers/core-utils' | 10 | import { isTestInstance, sha256 } from '../helpers/core-utils' |
9 | import { escapeHTML } from '@shared/core-utils/renderer' | ||
10 | import { logger } from '../helpers/logger' | 11 | import { logger } from '../helpers/logger' |
12 | import { mdToPlainText } from '../helpers/markdown' | ||
11 | import { CONFIG } from '../initializers/config' | 13 | import { CONFIG } from '../initializers/config' |
12 | import { | 14 | import { |
13 | ACCEPT_HEADERS, | 15 | ACCEPT_HEADERS, |
@@ -24,7 +26,7 @@ import { VideoChannelModel } from '../models/video/video-channel' | |||
24 | import { getActivityStreamDuration } from '../models/video/video-format-utils' | 26 | import { getActivityStreamDuration } from '../models/video/video-format-utils' |
25 | import { VideoPlaylistModel } from '../models/video/video-playlist' | 27 | import { VideoPlaylistModel } from '../models/video/video-playlist' |
26 | import { MAccountActor, MChannelActor } from '../types/models' | 28 | import { MAccountActor, MChannelActor } from '../types/models' |
27 | import { mdToPlainText } from '../helpers/markdown' | 29 | import { ServerConfigManager } from './server-config-manager' |
28 | 30 | ||
29 | type Tags = { | 31 | type Tags = { |
30 | ogType: string | 32 | ogType: string |
@@ -196,11 +198,22 @@ class ClientHtml { | |||
196 | } | 198 | } |
197 | 199 | ||
198 | static async getAccountHTMLPage (nameWithHost: string, req: express.Request, res: express.Response) { | 200 | static async getAccountHTMLPage (nameWithHost: string, req: express.Request, res: express.Response) { |
199 | return this.getAccountOrChannelHTMLPage(() => AccountModel.loadByNameWithHost(nameWithHost), req, res) | 201 | const accountModelPromise = AccountModel.loadByNameWithHost(nameWithHost) |
202 | return this.getAccountOrChannelHTMLPage(() => accountModelPromise, req, res) | ||
200 | } | 203 | } |
201 | 204 | ||
202 | static async getVideoChannelHTMLPage (nameWithHost: string, req: express.Request, res: express.Response) { | 205 | static async getVideoChannelHTMLPage (nameWithHost: string, req: express.Request, res: express.Response) { |
203 | return this.getAccountOrChannelHTMLPage(() => VideoChannelModel.loadByNameWithHostAndPopulateAccount(nameWithHost), req, res) | 206 | const videoChannelModelPromise = VideoChannelModel.loadByNameWithHostAndPopulateAccount(nameWithHost) |
207 | return this.getAccountOrChannelHTMLPage(() => videoChannelModelPromise, req, res) | ||
208 | } | ||
209 | |||
210 | static async getActorHTMLPage (nameWithHost: string, req: express.Request, res: express.Response) { | ||
211 | const [ account, channel ] = await Promise.all([ | ||
212 | AccountModel.loadByNameWithHost(nameWithHost), | ||
213 | VideoChannelModel.loadByNameWithHostAndPopulateAccount(nameWithHost) | ||
214 | ]) | ||
215 | |||
216 | return this.getAccountOrChannelHTMLPage(() => Promise.resolve(account || channel), req, res) | ||
204 | } | 217 | } |
205 | 218 | ||
206 | static async getEmbedHTML () { | 219 | static async getEmbedHTML () { |
@@ -209,11 +222,14 @@ class ClientHtml { | |||
209 | if (!isTestInstance() && ClientHtml.htmlCache[path]) return ClientHtml.htmlCache[path] | 222 | if (!isTestInstance() && ClientHtml.htmlCache[path]) return ClientHtml.htmlCache[path] |
210 | 223 | ||
211 | const buffer = await readFile(path) | 224 | const buffer = await readFile(path) |
225 | const serverConfig = await ServerConfigManager.Instance.getHTMLServerConfig() | ||
212 | 226 | ||
213 | let html = buffer.toString() | 227 | let html = buffer.toString() |
214 | html = await ClientHtml.addAsyncPluginCSS(html) | 228 | html = await ClientHtml.addAsyncPluginCSS(html) |
215 | html = ClientHtml.addCustomCSS(html) | 229 | html = ClientHtml.addCustomCSS(html) |
216 | html = ClientHtml.addTitleTag(html) | 230 | html = ClientHtml.addTitleTag(html) |
231 | html = ClientHtml.addDescriptionTag(html) | ||
232 | html = ClientHtml.addServerConfig(html, serverConfig) | ||
217 | 233 | ||
218 | ClientHtml.htmlCache[path] = html | 234 | ClientHtml.htmlCache[path] = html |
219 | 235 | ||
@@ -275,6 +291,7 @@ class ClientHtml { | |||
275 | if (!isTestInstance() && ClientHtml.htmlCache[path]) return ClientHtml.htmlCache[path] | 291 | if (!isTestInstance() && ClientHtml.htmlCache[path]) return ClientHtml.htmlCache[path] |
276 | 292 | ||
277 | const buffer = await readFile(path) | 293 | const buffer = await readFile(path) |
294 | const serverConfig = await ServerConfigManager.Instance.getHTMLServerConfig() | ||
278 | 295 | ||
279 | let html = buffer.toString() | 296 | let html = buffer.toString() |
280 | 297 | ||
@@ -283,6 +300,7 @@ class ClientHtml { | |||
283 | html = ClientHtml.addFaviconContentHash(html) | 300 | html = ClientHtml.addFaviconContentHash(html) |
284 | html = ClientHtml.addLogoContentHash(html) | 301 | html = ClientHtml.addLogoContentHash(html) |
285 | html = ClientHtml.addCustomCSS(html) | 302 | html = ClientHtml.addCustomCSS(html) |
303 | html = ClientHtml.addServerConfig(html, serverConfig) | ||
286 | html = await ClientHtml.addAsyncPluginCSS(html) | 304 | html = await ClientHtml.addAsyncPluginCSS(html) |
287 | 305 | ||
288 | ClientHtml.htmlCache[path] = html | 306 | ClientHtml.htmlCache[path] = html |
@@ -355,6 +373,13 @@ class ClientHtml { | |||
355 | return htmlStringPage.replace(CUSTOM_HTML_TAG_COMMENTS.CUSTOM_CSS, styleTag) | 373 | return htmlStringPage.replace(CUSTOM_HTML_TAG_COMMENTS.CUSTOM_CSS, styleTag) |
356 | } | 374 | } |
357 | 375 | ||
376 | private static addServerConfig (htmlStringPage: string, serverConfig: HTMLServerConfig) { | ||
377 | const serverConfigString = JSON.stringify(serverConfig) | ||
378 | const configScriptTag = `<script type="application/javascript">window.PeerTubeServerConfig = '${serverConfigString}'</script>` | ||
379 | |||
380 | return htmlStringPage.replace(CUSTOM_HTML_TAG_COMMENTS.SERVER_CONFIG, configScriptTag) | ||
381 | } | ||
382 | |||
358 | private static async addAsyncPluginCSS (htmlStringPage: string) { | 383 | private static async addAsyncPluginCSS (htmlStringPage: string) { |
359 | const globalCSSContent = await readFile(PLUGIN_GLOBAL_CSS_PATH) | 384 | const globalCSSContent = await readFile(PLUGIN_GLOBAL_CSS_PATH) |
360 | if (globalCSSContent.byteLength === 0) return htmlStringPage | 385 | if (globalCSSContent.byteLength === 0) return htmlStringPage |
diff --git a/server/lib/config.ts b/server/lib/config.ts deleted file mode 100644 index b4c4c9299..000000000 --- a/server/lib/config.ts +++ /dev/null | |||
@@ -1,255 +0,0 @@ | |||
1 | import { isSignupAllowed, isSignupAllowedForCurrentIP } from '@server/helpers/signup' | ||
2 | import { getServerCommit } from '@server/helpers/utils' | ||
3 | import { CONFIG, isEmailEnabled } from '@server/initializers/config' | ||
4 | import { CONSTRAINTS_FIELDS, DEFAULT_THEME_NAME, PEERTUBE_VERSION } from '@server/initializers/constants' | ||
5 | import { RegisteredExternalAuthConfig, RegisteredIdAndPassAuthConfig, ServerConfig } from '@shared/models' | ||
6 | import { Hooks } from './plugins/hooks' | ||
7 | import { PluginManager } from './plugins/plugin-manager' | ||
8 | import { getThemeOrDefault } from './plugins/theme-utils' | ||
9 | import { getEnabledResolutions } from './video-transcoding' | ||
10 | import { VideoTranscodingProfilesManager } from './video-transcoding-profiles' | ||
11 | |||
12 | let serverCommit: string | ||
13 | |||
14 | async function getServerConfig (ip?: string): Promise<ServerConfig> { | ||
15 | if (serverCommit === undefined) serverCommit = await getServerCommit() | ||
16 | |||
17 | const { allowed } = await Hooks.wrapPromiseFun( | ||
18 | isSignupAllowed, | ||
19 | { | ||
20 | ip | ||
21 | }, | ||
22 | 'filter:api.user.signup.allowed.result' | ||
23 | ) | ||
24 | |||
25 | const allowedForCurrentIP = isSignupAllowedForCurrentIP(ip) | ||
26 | const defaultTheme = getThemeOrDefault(CONFIG.THEME.DEFAULT, DEFAULT_THEME_NAME) | ||
27 | |||
28 | return { | ||
29 | instance: { | ||
30 | name: CONFIG.INSTANCE.NAME, | ||
31 | shortDescription: CONFIG.INSTANCE.SHORT_DESCRIPTION, | ||
32 | isNSFW: CONFIG.INSTANCE.IS_NSFW, | ||
33 | defaultNSFWPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY, | ||
34 | defaultClientRoute: CONFIG.INSTANCE.DEFAULT_CLIENT_ROUTE, | ||
35 | customizations: { | ||
36 | javascript: CONFIG.INSTANCE.CUSTOMIZATIONS.JAVASCRIPT, | ||
37 | css: CONFIG.INSTANCE.CUSTOMIZATIONS.CSS | ||
38 | } | ||
39 | }, | ||
40 | search: { | ||
41 | remoteUri: { | ||
42 | users: CONFIG.SEARCH.REMOTE_URI.USERS, | ||
43 | anonymous: CONFIG.SEARCH.REMOTE_URI.ANONYMOUS | ||
44 | }, | ||
45 | searchIndex: { | ||
46 | enabled: CONFIG.SEARCH.SEARCH_INDEX.ENABLED, | ||
47 | url: CONFIG.SEARCH.SEARCH_INDEX.URL, | ||
48 | disableLocalSearch: CONFIG.SEARCH.SEARCH_INDEX.DISABLE_LOCAL_SEARCH, | ||
49 | isDefaultSearch: CONFIG.SEARCH.SEARCH_INDEX.IS_DEFAULT_SEARCH | ||
50 | } | ||
51 | }, | ||
52 | plugin: { | ||
53 | registered: getRegisteredPlugins(), | ||
54 | registeredExternalAuths: getExternalAuthsPlugins(), | ||
55 | registeredIdAndPassAuths: getIdAndPassAuthPlugins() | ||
56 | }, | ||
57 | theme: { | ||
58 | registered: getRegisteredThemes(), | ||
59 | default: defaultTheme | ||
60 | }, | ||
61 | email: { | ||
62 | enabled: isEmailEnabled() | ||
63 | }, | ||
64 | contactForm: { | ||
65 | enabled: CONFIG.CONTACT_FORM.ENABLED | ||
66 | }, | ||
67 | serverVersion: PEERTUBE_VERSION, | ||
68 | serverCommit, | ||
69 | signup: { | ||
70 | allowed, | ||
71 | allowedForCurrentIP, | ||
72 | requiresEmailVerification: CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION | ||
73 | }, | ||
74 | transcoding: { | ||
75 | hls: { | ||
76 | enabled: CONFIG.TRANSCODING.HLS.ENABLED | ||
77 | }, | ||
78 | webtorrent: { | ||
79 | enabled: CONFIG.TRANSCODING.WEBTORRENT.ENABLED | ||
80 | }, | ||
81 | enabledResolutions: getEnabledResolutions('vod'), | ||
82 | profile: CONFIG.TRANSCODING.PROFILE, | ||
83 | availableProfiles: VideoTranscodingProfilesManager.Instance.getAvailableProfiles('vod') | ||
84 | }, | ||
85 | live: { | ||
86 | enabled: CONFIG.LIVE.ENABLED, | ||
87 | |||
88 | allowReplay: CONFIG.LIVE.ALLOW_REPLAY, | ||
89 | maxDuration: CONFIG.LIVE.MAX_DURATION, | ||
90 | maxInstanceLives: CONFIG.LIVE.MAX_INSTANCE_LIVES, | ||
91 | maxUserLives: CONFIG.LIVE.MAX_USER_LIVES, | ||
92 | |||
93 | transcoding: { | ||
94 | enabled: CONFIG.LIVE.TRANSCODING.ENABLED, | ||
95 | enabledResolutions: getEnabledResolutions('live'), | ||
96 | profile: CONFIG.LIVE.TRANSCODING.PROFILE, | ||
97 | availableProfiles: VideoTranscodingProfilesManager.Instance.getAvailableProfiles('live') | ||
98 | }, | ||
99 | |||
100 | rtmp: { | ||
101 | port: CONFIG.LIVE.RTMP.PORT | ||
102 | } | ||
103 | }, | ||
104 | import: { | ||
105 | videos: { | ||
106 | http: { | ||
107 | enabled: CONFIG.IMPORT.VIDEOS.HTTP.ENABLED | ||
108 | }, | ||
109 | torrent: { | ||
110 | enabled: CONFIG.IMPORT.VIDEOS.TORRENT.ENABLED | ||
111 | } | ||
112 | } | ||
113 | }, | ||
114 | autoBlacklist: { | ||
115 | videos: { | ||
116 | ofUsers: { | ||
117 | enabled: CONFIG.AUTO_BLACKLIST.VIDEOS.OF_USERS.ENABLED | ||
118 | } | ||
119 | } | ||
120 | }, | ||
121 | avatar: { | ||
122 | file: { | ||
123 | size: { | ||
124 | max: CONSTRAINTS_FIELDS.ACTORS.IMAGE.FILE_SIZE.max | ||
125 | }, | ||
126 | extensions: CONSTRAINTS_FIELDS.ACTORS.IMAGE.EXTNAME | ||
127 | } | ||
128 | }, | ||
129 | banner: { | ||
130 | file: { | ||
131 | size: { | ||
132 | max: CONSTRAINTS_FIELDS.ACTORS.IMAGE.FILE_SIZE.max | ||
133 | }, | ||
134 | extensions: CONSTRAINTS_FIELDS.ACTORS.IMAGE.EXTNAME | ||
135 | } | ||
136 | }, | ||
137 | video: { | ||
138 | image: { | ||
139 | extensions: CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME, | ||
140 | size: { | ||
141 | max: CONSTRAINTS_FIELDS.VIDEOS.IMAGE.FILE_SIZE.max | ||
142 | } | ||
143 | }, | ||
144 | file: { | ||
145 | extensions: CONSTRAINTS_FIELDS.VIDEOS.EXTNAME | ||
146 | } | ||
147 | }, | ||
148 | videoCaption: { | ||
149 | file: { | ||
150 | size: { | ||
151 | max: CONSTRAINTS_FIELDS.VIDEO_CAPTIONS.CAPTION_FILE.FILE_SIZE.max | ||
152 | }, | ||
153 | extensions: CONSTRAINTS_FIELDS.VIDEO_CAPTIONS.CAPTION_FILE.EXTNAME | ||
154 | } | ||
155 | }, | ||
156 | user: { | ||
157 | videoQuota: CONFIG.USER.VIDEO_QUOTA, | ||
158 | videoQuotaDaily: CONFIG.USER.VIDEO_QUOTA_DAILY | ||
159 | }, | ||
160 | trending: { | ||
161 | videos: { | ||
162 | intervalDays: CONFIG.TRENDING.VIDEOS.INTERVAL_DAYS, | ||
163 | algorithms: { | ||
164 | enabled: CONFIG.TRENDING.VIDEOS.ALGORITHMS.ENABLED, | ||
165 | default: CONFIG.TRENDING.VIDEOS.ALGORITHMS.DEFAULT | ||
166 | } | ||
167 | } | ||
168 | }, | ||
169 | tracker: { | ||
170 | enabled: CONFIG.TRACKER.ENABLED | ||
171 | }, | ||
172 | |||
173 | followings: { | ||
174 | instance: { | ||
175 | autoFollowIndex: { | ||
176 | indexUrl: CONFIG.FOLLOWINGS.INSTANCE.AUTO_FOLLOW_INDEX.INDEX_URL | ||
177 | } | ||
178 | } | ||
179 | }, | ||
180 | |||
181 | broadcastMessage: { | ||
182 | enabled: CONFIG.BROADCAST_MESSAGE.ENABLED, | ||
183 | message: CONFIG.BROADCAST_MESSAGE.MESSAGE, | ||
184 | level: CONFIG.BROADCAST_MESSAGE.LEVEL, | ||
185 | dismissable: CONFIG.BROADCAST_MESSAGE.DISMISSABLE | ||
186 | } | ||
187 | } | ||
188 | } | ||
189 | |||
190 | function getRegisteredThemes () { | ||
191 | return PluginManager.Instance.getRegisteredThemes() | ||
192 | .map(t => ({ | ||
193 | name: t.name, | ||
194 | version: t.version, | ||
195 | description: t.description, | ||
196 | css: t.css, | ||
197 | clientScripts: t.clientScripts | ||
198 | })) | ||
199 | } | ||
200 | |||
201 | function getRegisteredPlugins () { | ||
202 | return PluginManager.Instance.getRegisteredPlugins() | ||
203 | .map(p => ({ | ||
204 | name: p.name, | ||
205 | version: p.version, | ||
206 | description: p.description, | ||
207 | clientScripts: p.clientScripts | ||
208 | })) | ||
209 | } | ||
210 | |||
211 | // --------------------------------------------------------------------------- | ||
212 | |||
213 | export { | ||
214 | getServerConfig, | ||
215 | getRegisteredThemes, | ||
216 | getRegisteredPlugins | ||
217 | } | ||
218 | |||
219 | // --------------------------------------------------------------------------- | ||
220 | |||
221 | function getIdAndPassAuthPlugins () { | ||
222 | const result: RegisteredIdAndPassAuthConfig[] = [] | ||
223 | |||
224 | for (const p of PluginManager.Instance.getIdAndPassAuths()) { | ||
225 | for (const auth of p.idAndPassAuths) { | ||
226 | result.push({ | ||
227 | npmName: p.npmName, | ||
228 | name: p.name, | ||
229 | version: p.version, | ||
230 | authName: auth.authName, | ||
231 | weight: auth.getWeight() | ||
232 | }) | ||
233 | } | ||
234 | } | ||
235 | |||
236 | return result | ||
237 | } | ||
238 | |||
239 | function getExternalAuthsPlugins () { | ||
240 | const result: RegisteredExternalAuthConfig[] = [] | ||
241 | |||
242 | for (const p of PluginManager.Instance.getExternalAuths()) { | ||
243 | for (const auth of p.externalAuths) { | ||
244 | result.push({ | ||
245 | npmName: p.npmName, | ||
246 | name: p.name, | ||
247 | version: p.version, | ||
248 | authName: auth.authName, | ||
249 | authDisplayName: auth.authDisplayName() | ||
250 | }) | ||
251 | } | ||
252 | } | ||
253 | |||
254 | return result | ||
255 | } | ||
diff --git a/server/lib/job-queue/handlers/activitypub-follow.ts b/server/lib/job-queue/handlers/activitypub-follow.ts index 82c95be80..ec8df8969 100644 --- a/server/lib/job-queue/handlers/activitypub-follow.ts +++ b/server/lib/job-queue/handlers/activitypub-follow.ts | |||
@@ -1,18 +1,18 @@ | |||
1 | import * as Bull from 'bull' | 1 | import * as Bull from 'bull' |
2 | import { logger } from '../../../helpers/logger' | 2 | import { getLocalActorFollowActivityPubUrl } from '@server/lib/activitypub/url' |
3 | import { REMOTE_SCHEME, WEBSERVER } from '../../../initializers/constants' | 3 | import { ActivitypubFollowPayload } from '@shared/models' |
4 | import { sendFollow } from '../../activitypub/send' | ||
5 | import { sanitizeHost } from '../../../helpers/core-utils' | 4 | import { sanitizeHost } from '../../../helpers/core-utils' |
6 | import { loadActorUrlOrGetFromWebfinger } from '../../../helpers/webfinger' | ||
7 | import { getOrCreateActorAndServerAndModel } from '../../activitypub/actor' | ||
8 | import { retryTransactionWrapper } from '../../../helpers/database-utils' | 5 | import { retryTransactionWrapper } from '../../../helpers/database-utils' |
9 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' | 6 | import { logger } from '../../../helpers/logger' |
10 | import { ActorModel } from '../../../models/activitypub/actor' | 7 | import { loadActorUrlOrGetFromWebfinger } from '../../../helpers/webfinger' |
11 | import { Notifier } from '../../notifier' | 8 | import { REMOTE_SCHEME, WEBSERVER } from '../../../initializers/constants' |
12 | import { sequelizeTypescript } from '../../../initializers/database' | 9 | import { sequelizeTypescript } from '../../../initializers/database' |
10 | import { ActorModel } from '../../../models/actor/actor' | ||
11 | import { ActorFollowModel } from '../../../models/actor/actor-follow' | ||
13 | import { MActor, MActorFollowActors, MActorFull } from '../../../types/models' | 12 | import { MActor, MActorFollowActors, MActorFull } from '../../../types/models' |
14 | import { ActivitypubFollowPayload } from '@shared/models' | 13 | import { getOrCreateActorAndServerAndModel } from '../../activitypub/actor' |
15 | import { getLocalActorFollowActivityPubUrl } from '@server/lib/activitypub/url' | 14 | import { sendFollow } from '../../activitypub/send' |
15 | import { Notifier } from '../../notifier' | ||
16 | 16 | ||
17 | async function processActivityPubFollow (job: Bull.Job) { | 17 | async function processActivityPubFollow (job: Bull.Job) { |
18 | const payload = job.data as ActivitypubFollowPayload | 18 | const payload = job.data as ActivitypubFollowPayload |
diff --git a/server/lib/job-queue/handlers/activitypub-refresher.ts b/server/lib/job-queue/handlers/activitypub-refresher.ts index 666e56868..c09b1bcc8 100644 --- a/server/lib/job-queue/handlers/activitypub-refresher.ts +++ b/server/lib/job-queue/handlers/activitypub-refresher.ts | |||
@@ -1,12 +1,12 @@ | |||
1 | import * as Bull from 'bull' | 1 | import * as Bull from 'bull' |
2 | import { refreshVideoPlaylistIfNeeded } from '@server/lib/activitypub/playlist' | ||
3 | import { RefreshPayload } from '@shared/models' | ||
2 | import { logger } from '../../../helpers/logger' | 4 | import { logger } from '../../../helpers/logger' |
3 | import { fetchVideoByUrl } from '../../../helpers/video' | 5 | import { fetchVideoByUrl } from '../../../helpers/video' |
6 | import { ActorModel } from '../../../models/actor/actor' | ||
7 | import { VideoPlaylistModel } from '../../../models/video/video-playlist' | ||
4 | import { refreshActorIfNeeded } from '../../activitypub/actor' | 8 | import { refreshActorIfNeeded } from '../../activitypub/actor' |
5 | import { refreshVideoIfNeeded } from '../../activitypub/videos' | 9 | import { refreshVideoIfNeeded } from '../../activitypub/videos' |
6 | import { ActorModel } from '../../../models/activitypub/actor' | ||
7 | import { VideoPlaylistModel } from '../../../models/video/video-playlist' | ||
8 | import { RefreshPayload } from '@shared/models' | ||
9 | import { refreshVideoPlaylistIfNeeded } from '@server/lib/activitypub/playlist' | ||
10 | 10 | ||
11 | async function refreshAPObject (job: Bull.Job) { | 11 | async function refreshAPObject (job: Bull.Job) { |
12 | const payload = job.data as RefreshPayload | 12 | const payload = job.data as RefreshPayload |
diff --git a/server/lib/job-queue/handlers/actor-keys.ts b/server/lib/job-queue/handlers/actor-keys.ts index 125307843..3eef565d0 100644 --- a/server/lib/job-queue/handlers/actor-keys.ts +++ b/server/lib/job-queue/handlers/actor-keys.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import * as Bull from 'bull' | 1 | import * as Bull from 'bull' |
2 | import { generateAndSaveActorKeys } from '@server/lib/activitypub/actor' | 2 | import { generateAndSaveActorKeys } from '@server/lib/activitypub/actor' |
3 | import { ActorModel } from '@server/models/activitypub/actor' | 3 | import { ActorModel } from '@server/models/actor/actor' |
4 | import { ActorKeysPayload } from '@shared/models' | 4 | import { ActorKeysPayload } from '@shared/models' |
5 | import { logger } from '../../../helpers/logger' | 5 | import { logger } from '../../../helpers/logger' |
6 | 6 | ||
diff --git a/server/lib/job-queue/handlers/utils/activitypub-http-utils.ts b/server/lib/job-queue/handlers/utils/activitypub-http-utils.ts index e8a91450d..37e7c1fad 100644 --- a/server/lib/job-queue/handlers/utils/activitypub-http-utils.ts +++ b/server/lib/job-queue/handlers/utils/activitypub-http-utils.ts | |||
@@ -1,10 +1,10 @@ | |||
1 | import { buildDigest } from '@server/helpers/peertube-crypto' | ||
2 | import { getServerActor } from '@server/models/application/application' | ||
3 | import { ContextType } from '@shared/models/activitypub/context' | ||
1 | import { buildSignedActivity } from '../../../../helpers/activitypub' | 4 | import { buildSignedActivity } from '../../../../helpers/activitypub' |
2 | import { ActorModel } from '../../../../models/activitypub/actor' | ||
3 | import { ACTIVITY_PUB, HTTP_SIGNATURE } from '../../../../initializers/constants' | 5 | import { ACTIVITY_PUB, HTTP_SIGNATURE } from '../../../../initializers/constants' |
6 | import { ActorModel } from '../../../../models/actor/actor' | ||
4 | import { MActor } from '../../../../types/models' | 7 | import { MActor } from '../../../../types/models' |
5 | import { getServerActor } from '@server/models/application/application' | ||
6 | import { buildDigest } from '@server/helpers/peertube-crypto' | ||
7 | import { ContextType } from '@shared/models/activitypub/context' | ||
8 | 8 | ||
9 | type Payload <T> = { body: T, contextType?: ContextType, signatureActorId?: number } | 9 | type Payload <T> = { body: T, contextType?: ContextType, signatureActorId?: number } |
10 | 10 | ||
diff --git a/server/lib/job-queue/handlers/video-file-import.ts b/server/lib/job-queue/handlers/video-file-import.ts index 71f2cafcd..8297a1571 100644 --- a/server/lib/job-queue/handlers/video-file-import.ts +++ b/server/lib/job-queue/handlers/video-file-import.ts | |||
@@ -3,7 +3,7 @@ import { copy, stat } from 'fs-extra' | |||
3 | import { extname } from 'path' | 3 | import { extname } from 'path' |
4 | import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent' | 4 | import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent' |
5 | import { generateVideoFilename, getVideoFilePath } from '@server/lib/video-paths' | 5 | import { generateVideoFilename, getVideoFilePath } from '@server/lib/video-paths' |
6 | import { UserModel } from '@server/models/account/user' | 6 | import { UserModel } from '@server/models/user/user' |
7 | import { MVideoFullLight } from '@server/types/models' | 7 | import { MVideoFullLight } from '@server/types/models' |
8 | import { VideoFileImportPayload } from '@shared/models' | 8 | import { VideoFileImportPayload } from '@shared/models' |
9 | import { getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffprobe-utils' | 9 | import { getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffprobe-utils' |
diff --git a/server/lib/job-queue/handlers/video-import.ts b/server/lib/job-queue/handlers/video-import.ts index ed2c5eac0..d71053e87 100644 --- a/server/lib/job-queue/handlers/video-import.ts +++ b/server/lib/job-queue/handlers/video-import.ts | |||
@@ -2,8 +2,10 @@ import * as Bull from 'bull' | |||
2 | import { move, remove, stat } from 'fs-extra' | 2 | import { move, remove, stat } from 'fs-extra' |
3 | import { extname } from 'path' | 3 | import { extname } from 'path' |
4 | import { retryTransactionWrapper } from '@server/helpers/database-utils' | 4 | import { retryTransactionWrapper } from '@server/helpers/database-utils' |
5 | import { YoutubeDL } from '@server/helpers/youtube-dl' | ||
5 | import { isPostImportVideoAccepted } from '@server/lib/moderation' | 6 | import { isPostImportVideoAccepted } from '@server/lib/moderation' |
6 | import { Hooks } from '@server/lib/plugins/hooks' | 7 | import { Hooks } from '@server/lib/plugins/hooks' |
8 | import { ServerConfigManager } from '@server/lib/server-config-manager' | ||
7 | import { isAbleToUploadVideo } from '@server/lib/user' | 9 | import { isAbleToUploadVideo } from '@server/lib/user' |
8 | import { addOptimizeOrMergeAudioJob } from '@server/lib/video' | 10 | import { addOptimizeOrMergeAudioJob } from '@server/lib/video' |
9 | import { generateVideoFilename, getVideoFilePath } from '@server/lib/video-paths' | 11 | import { generateVideoFilename, getVideoFilePath } from '@server/lib/video-paths' |
@@ -23,7 +25,6 @@ import { getDurationFromVideoFile, getVideoFileFPS, getVideoFileResolution } fro | |||
23 | import { logger } from '../../../helpers/logger' | 25 | import { logger } from '../../../helpers/logger' |
24 | import { getSecureTorrentName } from '../../../helpers/utils' | 26 | import { getSecureTorrentName } from '../../../helpers/utils' |
25 | import { createTorrentAndSetInfoHash, downloadWebTorrentVideo } from '../../../helpers/webtorrent' | 27 | import { createTorrentAndSetInfoHash, downloadWebTorrentVideo } from '../../../helpers/webtorrent' |
26 | import { downloadYoutubeDLVideo } from '../../../helpers/youtube-dl' | ||
27 | import { CONFIG } from '../../../initializers/config' | 28 | import { CONFIG } from '../../../initializers/config' |
28 | import { VIDEO_IMPORT_TIMEOUT } from '../../../initializers/constants' | 29 | import { VIDEO_IMPORT_TIMEOUT } from '../../../initializers/constants' |
29 | import { sequelizeTypescript } from '../../../initializers/database' | 30 | import { sequelizeTypescript } from '../../../initializers/database' |
@@ -75,8 +76,10 @@ async function processYoutubeDLImport (job: Bull.Job, payload: VideoImportYoutub | |||
75 | videoImportId: videoImport.id | 76 | videoImportId: videoImport.id |
76 | } | 77 | } |
77 | 78 | ||
79 | const youtubeDL = new YoutubeDL(videoImport.targetUrl, ServerConfigManager.Instance.getEnabledResolutions('vod')) | ||
80 | |||
78 | return processFile( | 81 | return processFile( |
79 | () => downloadYoutubeDLVideo(videoImport.targetUrl, payload.fileExt, VIDEO_IMPORT_TIMEOUT), | 82 | () => youtubeDL.downloadYoutubeDLVideo(payload.fileExt, VIDEO_IMPORT_TIMEOUT), |
80 | videoImport, | 83 | videoImport, |
81 | options | 84 | options |
82 | ) | 85 | ) |
diff --git a/server/lib/job-queue/handlers/video-live-ending.ts b/server/lib/job-queue/handlers/video-live-ending.ts index d57202ca5..517b90abc 100644 --- a/server/lib/job-queue/handlers/video-live-ending.ts +++ b/server/lib/job-queue/handlers/video-live-ending.ts | |||
@@ -5,9 +5,9 @@ import { ffprobePromise, getAudioStream, getDurationFromVideoFile, getVideoFileR | |||
5 | import { VIDEO_LIVE } from '@server/initializers/constants' | 5 | import { VIDEO_LIVE } from '@server/initializers/constants' |
6 | import { LiveManager } from '@server/lib/live-manager' | 6 | import { LiveManager } from '@server/lib/live-manager' |
7 | import { generateVideoMiniature } from '@server/lib/thumbnail' | 7 | import { generateVideoMiniature } from '@server/lib/thumbnail' |
8 | import { generateHlsPlaylistResolutionFromTS } from '@server/lib/transcoding/video-transcoding' | ||
8 | import { publishAndFederateIfNeeded } from '@server/lib/video' | 9 | import { publishAndFederateIfNeeded } from '@server/lib/video' |
9 | import { getHLSDirectory } from '@server/lib/video-paths' | 10 | import { getHLSDirectory } from '@server/lib/video-paths' |
10 | import { generateHlsPlaylistResolutionFromTS } from '@server/lib/video-transcoding' | ||
11 | import { VideoModel } from '@server/models/video/video' | 11 | import { VideoModel } from '@server/models/video/video' |
12 | import { VideoFileModel } from '@server/models/video/video-file' | 12 | import { VideoFileModel } from '@server/models/video/video-file' |
13 | import { VideoLiveModel } from '@server/models/video/video-live' | 13 | import { VideoLiveModel } from '@server/models/video/video-live' |
diff --git a/server/lib/job-queue/handlers/video-transcoding.ts b/server/lib/job-queue/handlers/video-transcoding.ts index 010b95b05..8d659daa6 100644 --- a/server/lib/job-queue/handlers/video-transcoding.ts +++ b/server/lib/job-queue/handlers/video-transcoding.ts | |||
@@ -2,7 +2,7 @@ import * as Bull from 'bull' | |||
2 | import { TranscodeOptionsType } from '@server/helpers/ffmpeg-utils' | 2 | import { TranscodeOptionsType } from '@server/helpers/ffmpeg-utils' |
3 | import { getTranscodingJobPriority, publishAndFederateIfNeeded } from '@server/lib/video' | 3 | import { getTranscodingJobPriority, publishAndFederateIfNeeded } from '@server/lib/video' |
4 | import { getVideoFilePath } from '@server/lib/video-paths' | 4 | import { getVideoFilePath } from '@server/lib/video-paths' |
5 | import { UserModel } from '@server/models/account/user' | 5 | import { UserModel } from '@server/models/user/user' |
6 | import { MUser, MUserId, MVideoFullLight, MVideoUUID, MVideoWithFile } from '@server/types/models' | 6 | import { MUser, MUserId, MVideoFullLight, MVideoUUID, MVideoWithFile } from '@server/types/models' |
7 | import { | 7 | import { |
8 | HLSTranscodingPayload, | 8 | HLSTranscodingPayload, |
@@ -24,7 +24,7 @@ import { | |||
24 | mergeAudioVideofile, | 24 | mergeAudioVideofile, |
25 | optimizeOriginalVideofile, | 25 | optimizeOriginalVideofile, |
26 | transcodeNewWebTorrentResolution | 26 | transcodeNewWebTorrentResolution |
27 | } from '../../video-transcoding' | 27 | } from '../../transcoding/video-transcoding' |
28 | import { JobQueue } from '../job-queue' | 28 | import { JobQueue } from '../job-queue' |
29 | 29 | ||
30 | type HandlerFunction = (job: Bull.Job, payload: VideoTranscodingPayload, video: MVideoFullLight, user: MUser) => Promise<any> | 30 | type HandlerFunction = (job: Bull.Job, payload: VideoTranscodingPayload, video: MVideoFullLight, user: MUser) => Promise<any> |
diff --git a/server/lib/job-queue/handlers/video-views.ts b/server/lib/job-queue/handlers/video-views.ts index 897235ec0..86d0a271f 100644 --- a/server/lib/job-queue/handlers/video-views.ts +++ b/server/lib/job-queue/handlers/video-views.ts | |||
@@ -36,8 +36,8 @@ async function processVideosViews () { | |||
36 | } | 36 | } |
37 | 37 | ||
38 | await VideoViewModel.create({ | 38 | await VideoViewModel.create({ |
39 | startDate, | 39 | startDate: new Date(startDate), |
40 | endDate, | 40 | endDate: new Date(endDate), |
41 | views, | 41 | views, |
42 | videoId | 42 | videoId |
43 | }) | 43 | }) |
diff --git a/server/lib/live-manager.ts b/server/lib/live-manager.ts index 66b5d119b..563ba2578 100644 --- a/server/lib/live-manager.ts +++ b/server/lib/live-manager.ts | |||
@@ -8,10 +8,10 @@ import { basename, join } from 'path' | |||
8 | import { isTestInstance } from '@server/helpers/core-utils' | 8 | import { isTestInstance } from '@server/helpers/core-utils' |
9 | import { getLiveMuxingCommand, getLiveTranscodingCommand } from '@server/helpers/ffmpeg-utils' | 9 | import { getLiveMuxingCommand, getLiveTranscodingCommand } from '@server/helpers/ffmpeg-utils' |
10 | import { computeResolutionsToTranscode, getVideoFileFPS, getVideoFileResolution } from '@server/helpers/ffprobe-utils' | 10 | import { computeResolutionsToTranscode, getVideoFileFPS, getVideoFileResolution } from '@server/helpers/ffprobe-utils' |
11 | import { logger } from '@server/helpers/logger' | 11 | import { logger, loggerTagsFactory } from '@server/helpers/logger' |
12 | import { CONFIG, registerConfigChangedHandler } from '@server/initializers/config' | 12 | import { CONFIG, registerConfigChangedHandler } from '@server/initializers/config' |
13 | import { MEMOIZE_TTL, P2P_MEDIA_LOADER_PEER_VERSION, VIDEO_LIVE, VIEW_LIFETIME, WEBSERVER } from '@server/initializers/constants' | 13 | import { MEMOIZE_TTL, P2P_MEDIA_LOADER_PEER_VERSION, VIDEO_LIVE, VIEW_LIFETIME, WEBSERVER } from '@server/initializers/constants' |
14 | import { UserModel } from '@server/models/account/user' | 14 | import { UserModel } from '@server/models/user/user' |
15 | import { VideoModel } from '@server/models/video/video' | 15 | import { VideoModel } from '@server/models/video/video' |
16 | import { VideoFileModel } from '@server/models/video/video-file' | 16 | import { VideoFileModel } from '@server/models/video/video-file' |
17 | import { VideoLiveModel } from '@server/models/video/video-live' | 17 | import { VideoLiveModel } from '@server/models/video/video-live' |
@@ -23,9 +23,9 @@ import { buildSha256Segment } from './hls' | |||
23 | import { JobQueue } from './job-queue' | 23 | import { JobQueue } from './job-queue' |
24 | import { cleanupLive } from './job-queue/handlers/video-live-ending' | 24 | import { cleanupLive } from './job-queue/handlers/video-live-ending' |
25 | import { PeerTubeSocket } from './peertube-socket' | 25 | import { PeerTubeSocket } from './peertube-socket' |
26 | import { VideoTranscodingProfilesManager } from './transcoding/video-transcoding-profiles' | ||
26 | import { isAbleToUploadVideo } from './user' | 27 | import { isAbleToUploadVideo } from './user' |
27 | import { getHLSDirectory } from './video-paths' | 28 | import { getHLSDirectory } from './video-paths' |
28 | import { VideoTranscodingProfilesManager } from './video-transcoding-profiles' | ||
29 | 29 | ||
30 | import memoizee = require('memoizee') | 30 | import memoizee = require('memoizee') |
31 | const NodeRtmpSession = require('node-media-server/node_rtmp_session') | 31 | const NodeRtmpSession = require('node-media-server/node_rtmp_session') |
@@ -48,6 +48,8 @@ const config = { | |||
48 | } | 48 | } |
49 | } | 49 | } |
50 | 50 | ||
51 | const lTags = loggerTagsFactory('live') | ||
52 | |||
51 | class LiveManager { | 53 | class LiveManager { |
52 | 54 | ||
53 | private static instance: LiveManager | 55 | private static instance: LiveManager |
@@ -75,20 +77,20 @@ class LiveManager { | |||
75 | init () { | 77 | init () { |
76 | const events = this.getContext().nodeEvent | 78 | const events = this.getContext().nodeEvent |
77 | events.on('postPublish', (sessionId: string, streamPath: string) => { | 79 | events.on('postPublish', (sessionId: string, streamPath: string) => { |
78 | logger.debug('RTMP received stream', { id: sessionId, streamPath }) | 80 | logger.debug('RTMP received stream', { id: sessionId, streamPath, ...lTags(sessionId) }) |
79 | 81 | ||
80 | const splittedPath = streamPath.split('/') | 82 | const splittedPath = streamPath.split('/') |
81 | if (splittedPath.length !== 3 || splittedPath[1] !== VIDEO_LIVE.RTMP.BASE_PATH) { | 83 | if (splittedPath.length !== 3 || splittedPath[1] !== VIDEO_LIVE.RTMP.BASE_PATH) { |
82 | logger.warn('Live path is incorrect.', { streamPath }) | 84 | logger.warn('Live path is incorrect.', { streamPath, ...lTags(sessionId) }) |
83 | return this.abortSession(sessionId) | 85 | return this.abortSession(sessionId) |
84 | } | 86 | } |
85 | 87 | ||
86 | this.handleSession(sessionId, streamPath, splittedPath[2]) | 88 | this.handleSession(sessionId, streamPath, splittedPath[2]) |
87 | .catch(err => logger.error('Cannot handle sessions.', { err })) | 89 | .catch(err => logger.error('Cannot handle sessions.', { err, ...lTags(sessionId) })) |
88 | }) | 90 | }) |
89 | 91 | ||
90 | events.on('donePublish', sessionId => { | 92 | events.on('donePublish', sessionId => { |
91 | logger.info('Live session ended.', { sessionId }) | 93 | logger.info('Live session ended.', { sessionId, ...lTags(sessionId) }) |
92 | }) | 94 | }) |
93 | 95 | ||
94 | registerConfigChangedHandler(() => { | 96 | registerConfigChangedHandler(() => { |
@@ -104,13 +106,13 @@ class LiveManager { | |||
104 | 106 | ||
105 | // Cleanup broken lives, that were terminated by a server restart for example | 107 | // Cleanup broken lives, that were terminated by a server restart for example |
106 | this.handleBrokenLives() | 108 | this.handleBrokenLives() |
107 | .catch(err => logger.error('Cannot handle broken lives.', { err })) | 109 | .catch(err => logger.error('Cannot handle broken lives.', { err, ...lTags() })) |
108 | 110 | ||
109 | setInterval(() => this.updateLiveViews(), VIEW_LIFETIME.LIVE) | 111 | setInterval(() => this.updateLiveViews(), VIEW_LIFETIME.LIVE) |
110 | } | 112 | } |
111 | 113 | ||
112 | run () { | 114 | run () { |
113 | logger.info('Running RTMP server on port %d', config.rtmp.port) | 115 | logger.info('Running RTMP server on port %d', config.rtmp.port, lTags()) |
114 | 116 | ||
115 | this.rtmpServer = createServer(socket => { | 117 | this.rtmpServer = createServer(socket => { |
116 | const session = new NodeRtmpSession(config, socket) | 118 | const session = new NodeRtmpSession(config, socket) |
@@ -119,14 +121,14 @@ class LiveManager { | |||
119 | }) | 121 | }) |
120 | 122 | ||
121 | this.rtmpServer.on('error', err => { | 123 | this.rtmpServer.on('error', err => { |
122 | logger.error('Cannot run RTMP server.', { err }) | 124 | logger.error('Cannot run RTMP server.', { err, ...lTags() }) |
123 | }) | 125 | }) |
124 | 126 | ||
125 | this.rtmpServer.listen(CONFIG.LIVE.RTMP.PORT) | 127 | this.rtmpServer.listen(CONFIG.LIVE.RTMP.PORT) |
126 | } | 128 | } |
127 | 129 | ||
128 | stop () { | 130 | stop () { |
129 | logger.info('Stopping RTMP server.') | 131 | logger.info('Stopping RTMP server.', lTags()) |
130 | 132 | ||
131 | this.rtmpServer.close() | 133 | this.rtmpServer.close() |
132 | this.rtmpServer = undefined | 134 | this.rtmpServer = undefined |
@@ -185,7 +187,7 @@ class LiveManager { | |||
185 | 187 | ||
186 | return readFile(segmentPath) | 188 | return readFile(segmentPath) |
187 | .then(data => appendFile(dest, data)) | 189 | .then(data => appendFile(dest, data)) |
188 | .catch(err => logger.error('Cannot copy segment %s to repay directory.', segmentPath, { err })) | 190 | .catch(err => logger.error('Cannot copy segment %s to replay directory.', segmentPath, { err, ...lTags() })) |
189 | } | 191 | } |
190 | 192 | ||
191 | buildConcatenatedName (segmentOrPlaylistPath: string) { | 193 | buildConcatenatedName (segmentOrPlaylistPath: string) { |
@@ -202,7 +204,7 @@ class LiveManager { | |||
202 | if (videoLive.saveReplay) { | 204 | if (videoLive.saveReplay) { |
203 | await this.addSegmentToReplay(hlsVideoPath, previousSegment) | 205 | await this.addSegmentToReplay(hlsVideoPath, previousSegment) |
204 | } | 206 | } |
205 | }).catch(err => logger.error('Cannot process segments in %s', hlsVideoPath, { err })) | 207 | }).catch(err => logger.error('Cannot process segments in %s', hlsVideoPath, { err, ...lTags(videoUUID) })) |
206 | } | 208 | } |
207 | 209 | ||
208 | private getContext () { | 210 | private getContext () { |
@@ -226,13 +228,13 @@ class LiveManager { | |||
226 | private async handleSession (sessionId: string, streamPath: string, streamKey: string) { | 228 | private async handleSession (sessionId: string, streamPath: string, streamKey: string) { |
227 | const videoLive = await VideoLiveModel.loadByStreamKey(streamKey) | 229 | const videoLive = await VideoLiveModel.loadByStreamKey(streamKey) |
228 | if (!videoLive) { | 230 | if (!videoLive) { |
229 | logger.warn('Unknown live video with stream key %s.', streamKey) | 231 | logger.warn('Unknown live video with stream key %s.', streamKey, lTags(sessionId)) |
230 | return this.abortSession(sessionId) | 232 | return this.abortSession(sessionId) |
231 | } | 233 | } |
232 | 234 | ||
233 | const video = videoLive.Video | 235 | const video = videoLive.Video |
234 | if (video.isBlacklisted()) { | 236 | if (video.isBlacklisted()) { |
235 | logger.warn('Video is blacklisted. Refusing stream %s.', streamKey) | 237 | logger.warn('Video is blacklisted. Refusing stream %s.', streamKey, lTags(sessionId, video.uuid)) |
236 | return this.abortSession(sessionId) | 238 | return this.abortSession(sessionId) |
237 | } | 239 | } |
238 | 240 | ||
@@ -262,7 +264,10 @@ class LiveManager { | |||
262 | 264 | ||
263 | const allResolutions = resolutionsEnabled.concat([ session.videoHeight ]) | 265 | const allResolutions = resolutionsEnabled.concat([ session.videoHeight ]) |
264 | 266 | ||
265 | logger.info('Will mux/transcode live video of original resolution %d.', session.videoHeight, { allResolutions }) | 267 | logger.info( |
268 | 'Will mux/transcode live video of original resolution %d.', session.videoHeight, | ||
269 | { allResolutions, ...lTags(sessionId, video.uuid) } | ||
270 | ) | ||
266 | 271 | ||
267 | const [ videoStreamingPlaylist ] = await VideoStreamingPlaylistModel.upsert({ | 272 | const [ videoStreamingPlaylist ] = await VideoStreamingPlaylistModel.upsert({ |
268 | videoId: video.id, | 273 | videoId: video.id, |
@@ -317,7 +322,7 @@ class LiveManager { | |||
317 | }) | 322 | }) |
318 | 323 | ||
319 | VideoFileModel.customUpsert(file, 'streaming-playlist', null) | 324 | VideoFileModel.customUpsert(file, 'streaming-playlist', null) |
320 | .catch(err => logger.error('Cannot create file for live streaming.', { err })) | 325 | .catch(err => logger.error('Cannot create file for live streaming.', { err, ...lTags(sessionId, videoLive.Video.uuid) })) |
321 | } | 326 | } |
322 | 327 | ||
323 | const outPath = getHLSDirectory(videoLive.Video) | 328 | const outPath = getHLSDirectory(videoLive.Video) |
@@ -342,7 +347,7 @@ class LiveManager { | |||
342 | }) | 347 | }) |
343 | : getLiveMuxingCommand(rtmpUrl, outPath) | 348 | : getLiveMuxingCommand(rtmpUrl, outPath) |
344 | 349 | ||
345 | logger.info('Running live muxing/transcoding for %s.', videoUUID) | 350 | logger.info('Running live muxing/transcoding for %s.', videoUUID, lTags(sessionId, videoUUID)) |
346 | this.transSessions.set(sessionId, ffmpegExec) | 351 | this.transSessions.set(sessionId, ffmpegExec) |
347 | 352 | ||
348 | const tsWatcher = chokidar.watch(outPath + '/*.ts') | 353 | const tsWatcher = chokidar.watch(outPath + '/*.ts') |
@@ -351,7 +356,7 @@ class LiveManager { | |||
351 | const playlistIdMatcher = /^([\d+])-/ | 356 | const playlistIdMatcher = /^([\d+])-/ |
352 | 357 | ||
353 | const addHandler = segmentPath => { | 358 | const addHandler = segmentPath => { |
354 | logger.debug('Live add handler of %s.', segmentPath) | 359 | logger.debug('Live add handler of %s.', segmentPath, lTags(sessionId, videoUUID)) |
355 | 360 | ||
356 | const playlistId = basename(segmentPath).match(playlistIdMatcher)[0] | 361 | const playlistId = basename(segmentPath).match(playlistIdMatcher)[0] |
357 | 362 | ||
@@ -363,7 +368,9 @@ class LiveManager { | |||
363 | if (this.hasClientSocketsInBadHealthWithCache(sessionId)) { | 368 | if (this.hasClientSocketsInBadHealthWithCache(sessionId)) { |
364 | logger.error( | 369 | logger.error( |
365 | 'Too much data in client socket stream (ffmpeg is too slow to transcode the video).' + | 370 | 'Too much data in client socket stream (ffmpeg is too slow to transcode the video).' + |
366 | ' Stopping session of video %s.', videoUUID) | 371 | ' Stopping session of video %s.', videoUUID, |
372 | lTags(sessionId, videoUUID) | ||
373 | ) | ||
367 | 374 | ||
368 | this.stopSessionOf(videoLive.videoId) | 375 | this.stopSessionOf(videoLive.videoId) |
369 | return | 376 | return |
@@ -371,7 +378,7 @@ class LiveManager { | |||
371 | 378 | ||
372 | // Duration constraint check | 379 | // Duration constraint check |
373 | if (this.isDurationConstraintValid(startStreamDateTime) !== true) { | 380 | if (this.isDurationConstraintValid(startStreamDateTime) !== true) { |
374 | logger.info('Stopping session of %s: max duration exceeded.', videoUUID) | 381 | logger.info('Stopping session of %s: max duration exceeded.', videoUUID, lTags(sessionId, videoUUID)) |
375 | 382 | ||
376 | this.stopSessionOf(videoLive.videoId) | 383 | this.stopSessionOf(videoLive.videoId) |
377 | return | 384 | return |
@@ -386,12 +393,12 @@ class LiveManager { | |||
386 | .then(() => this.isQuotaConstraintValid(user, videoLive)) | 393 | .then(() => this.isQuotaConstraintValid(user, videoLive)) |
387 | .then(quotaValid => { | 394 | .then(quotaValid => { |
388 | if (quotaValid !== true) { | 395 | if (quotaValid !== true) { |
389 | logger.info('Stopping session of %s: user quota exceeded.', videoUUID) | 396 | logger.info('Stopping session of %s: user quota exceeded.', videoUUID, lTags(sessionId, videoUUID)) |
390 | 397 | ||
391 | this.stopSessionOf(videoLive.videoId) | 398 | this.stopSessionOf(videoLive.videoId) |
392 | } | 399 | } |
393 | }) | 400 | }) |
394 | .catch(err => logger.error('Cannot stat %s or check quota of %d.', segmentPath, user.id, { err })) | 401 | .catch(err => logger.error('Cannot stat %s or check quota of %d.', segmentPath, user.id, { err, ...lTags(sessionId, videoUUID) })) |
395 | } | 402 | } |
396 | } | 403 | } |
397 | 404 | ||
@@ -411,21 +418,21 @@ class LiveManager { | |||
411 | 418 | ||
412 | setTimeout(() => { | 419 | setTimeout(() => { |
413 | federateVideoIfNeeded(video, false) | 420 | federateVideoIfNeeded(video, false) |
414 | .catch(err => logger.error('Cannot federate live video %s.', video.url, { err })) | 421 | .catch(err => logger.error('Cannot federate live video %s.', video.url, { err, ...lTags(sessionId, videoUUID) })) |
415 | 422 | ||
416 | PeerTubeSocket.Instance.sendVideoLiveNewState(video) | 423 | PeerTubeSocket.Instance.sendVideoLiveNewState(video) |
417 | }, VIDEO_LIVE.SEGMENT_TIME_SECONDS * 1000 * VIDEO_LIVE.EDGE_LIVE_DELAY_SEGMENTS_NOTIFICATION) | 424 | }, VIDEO_LIVE.SEGMENT_TIME_SECONDS * 1000 * VIDEO_LIVE.EDGE_LIVE_DELAY_SEGMENTS_NOTIFICATION) |
418 | 425 | ||
419 | } catch (err) { | 426 | } catch (err) { |
420 | logger.error('Cannot save/federate live video %d.', videoLive.videoId, { err }) | 427 | logger.error('Cannot save/federate live video %d.', videoLive.videoId, { err, ...lTags(sessionId, videoUUID) }) |
421 | } finally { | 428 | } finally { |
422 | masterWatcher.close() | 429 | masterWatcher.close() |
423 | .catch(err => logger.error('Cannot close master watcher of %s.', outPath, { err })) | 430 | .catch(err => logger.error('Cannot close master watcher of %s.', outPath, { err, ...lTags(sessionId, videoUUID) })) |
424 | } | 431 | } |
425 | }) | 432 | }) |
426 | 433 | ||
427 | const onFFmpegEnded = () => { | 434 | const onFFmpegEnded = () => { |
428 | logger.info('RTMP transmuxing for video %s ended. Scheduling cleanup', rtmpUrl) | 435 | logger.info('RTMP transmuxing for video %s ended. Scheduling cleanup', rtmpUrl, lTags(sessionId, videoUUID)) |
429 | 436 | ||
430 | this.transSessions.delete(sessionId) | 437 | this.transSessions.delete(sessionId) |
431 | 438 | ||
@@ -446,10 +453,15 @@ class LiveManager { | |||
446 | this.processSegments(outPath, videoUUID, videoLive, segmentsToProcessPerPlaylist[key]) | 453 | this.processSegments(outPath, videoUUID, videoLive, segmentsToProcessPerPlaylist[key]) |
447 | } | 454 | } |
448 | }) | 455 | }) |
449 | .catch(err => logger.error('Cannot close watchers of %s or process remaining hash segments.', outPath, { err })) | 456 | .catch(err => { |
457 | logger.error( | ||
458 | 'Cannot close watchers of %s or process remaining hash segments.', outPath, | ||
459 | { err, ...lTags(sessionId, videoUUID) } | ||
460 | ) | ||
461 | }) | ||
450 | 462 | ||
451 | this.onEndTransmuxing(videoLive.Video.id) | 463 | this.onEndTransmuxing(videoLive.Video.id) |
452 | .catch(err => logger.error('Error in closed transmuxing.', { err })) | 464 | .catch(err => logger.error('Error in closed transmuxing.', { err, ...lTags(sessionId, videoUUID) })) |
453 | }, 1000) | 465 | }, 1000) |
454 | } | 466 | } |
455 | 467 | ||
@@ -459,7 +471,7 @@ class LiveManager { | |||
459 | // Don't care that we killed the ffmpeg process | 471 | // Don't care that we killed the ffmpeg process |
460 | if (err?.message?.includes('Exiting normally')) return | 472 | if (err?.message?.includes('Exiting normally')) return |
461 | 473 | ||
462 | logger.error('Live transcoding error.', { err, stdout, stderr }) | 474 | logger.error('Live transcoding error.', { err, stdout, stderr, ...lTags(sessionId, videoUUID) }) |
463 | 475 | ||
464 | this.abortSession(sessionId) | 476 | this.abortSession(sessionId) |
465 | }) | 477 | }) |
@@ -469,12 +481,12 @@ class LiveManager { | |||
469 | ffmpegExec.run() | 481 | ffmpegExec.run() |
470 | } | 482 | } |
471 | 483 | ||
472 | private async onEndTransmuxing (videoId: number, cleanupNow = false) { | 484 | private async onEndTransmuxing (videoUUID: string, cleanupNow = false) { |
473 | try { | 485 | try { |
474 | const fullVideo = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoId) | 486 | const fullVideo = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoUUID) |
475 | if (!fullVideo) return | 487 | if (!fullVideo) return |
476 | 488 | ||
477 | const live = await VideoLiveModel.loadByVideoId(videoId) | 489 | const live = await VideoLiveModel.loadByVideoId(fullVideo.id) |
478 | 490 | ||
479 | if (!live.permanentLive) { | 491 | if (!live.permanentLive) { |
480 | JobQueue.Instance.createJob({ | 492 | JobQueue.Instance.createJob({ |
@@ -495,13 +507,13 @@ class LiveManager { | |||
495 | 507 | ||
496 | await federateVideoIfNeeded(fullVideo, false) | 508 | await federateVideoIfNeeded(fullVideo, false) |
497 | } catch (err) { | 509 | } catch (err) { |
498 | logger.error('Cannot save/federate new video state of live streaming of video id %d.', videoId, { err }) | 510 | logger.error('Cannot save/federate new video state of live streaming of video %d.', videoUUID, { err, ...lTags(videoUUID) }) |
499 | } | 511 | } |
500 | } | 512 | } |
501 | 513 | ||
502 | private async addSegmentSha (videoUUID: string, segmentPath: string) { | 514 | private async addSegmentSha (videoUUID: string, segmentPath: string) { |
503 | const segmentName = basename(segmentPath) | 515 | const segmentName = basename(segmentPath) |
504 | logger.debug('Adding live sha segment %s.', segmentPath) | 516 | logger.debug('Adding live sha segment %s.', segmentPath, lTags(videoUUID)) |
505 | 517 | ||
506 | const shaResult = await buildSha256Segment(segmentPath) | 518 | const shaResult = await buildSha256Segment(segmentPath) |
507 | 519 | ||
@@ -516,16 +528,16 @@ class LiveManager { | |||
516 | private removeSegmentSha (videoUUID: string, segmentPath: string) { | 528 | private removeSegmentSha (videoUUID: string, segmentPath: string) { |
517 | const segmentName = basename(segmentPath) | 529 | const segmentName = basename(segmentPath) |
518 | 530 | ||
519 | logger.debug('Removing live sha segment %s.', segmentPath) | 531 | logger.debug('Removing live sha segment %s.', segmentPath, lTags(videoUUID)) |
520 | 532 | ||
521 | const filesMap = this.segmentsSha256.get(videoUUID) | 533 | const filesMap = this.segmentsSha256.get(videoUUID) |
522 | if (!filesMap) { | 534 | if (!filesMap) { |
523 | logger.warn('Unknown files map to remove sha for %s.', videoUUID) | 535 | logger.warn('Unknown files map to remove sha for %s.', videoUUID, lTags(videoUUID)) |
524 | return | 536 | return |
525 | } | 537 | } |
526 | 538 | ||
527 | if (!filesMap.has(segmentName)) { | 539 | if (!filesMap.has(segmentName)) { |
528 | logger.warn('Unknown segment in files map for video %s and segment %s.', videoUUID, segmentPath) | 540 | logger.warn('Unknown segment in files map for video %s and segment %s.', videoUUID, segmentPath, lTags(videoUUID)) |
529 | return | 541 | return |
530 | } | 542 | } |
531 | 543 | ||
@@ -547,7 +559,7 @@ class LiveManager { | |||
547 | const rtmpSession = this.getContext().sessions.get(sessionId) | 559 | const rtmpSession = this.getContext().sessions.get(sessionId) |
548 | 560 | ||
549 | if (!rtmpSession) { | 561 | if (!rtmpSession) { |
550 | logger.warn('Cannot get session %s to check players socket health.', sessionId) | 562 | logger.warn('Cannot get session %s to check players socket health.', sessionId, lTags(sessionId)) |
551 | return | 563 | return |
552 | } | 564 | } |
553 | 565 | ||
@@ -555,7 +567,7 @@ class LiveManager { | |||
555 | const playerSession = this.getContext().sessions.get(playerSessionId) | 567 | const playerSession = this.getContext().sessions.get(playerSessionId) |
556 | 568 | ||
557 | if (!playerSession) { | 569 | if (!playerSession) { |
558 | logger.error('Cannot get player session %s to check socket health.', playerSession) | 570 | logger.error('Cannot get player session %s to check socket health.', playerSession, lTags(sessionId)) |
559 | continue | 571 | continue |
560 | } | 572 | } |
561 | 573 | ||
@@ -576,7 +588,7 @@ class LiveManager { | |||
576 | private async updateLiveViews () { | 588 | private async updateLiveViews () { |
577 | if (!this.isRunning()) return | 589 | if (!this.isRunning()) return |
578 | 590 | ||
579 | if (!isTestInstance()) logger.info('Updating live video views.') | 591 | if (!isTestInstance()) logger.info('Updating live video views.', lTags()) |
580 | 592 | ||
581 | for (const videoId of this.watchersPerVideo.keys()) { | 593 | for (const videoId of this.watchersPerVideo.keys()) { |
582 | const notBefore = new Date().getTime() - VIEW_LIFETIME.LIVE | 594 | const notBefore = new Date().getTime() - VIEW_LIFETIME.LIVE |
@@ -597,15 +609,15 @@ class LiveManager { | |||
597 | const newWatchers = watchers.filter(w => w > notBefore) | 609 | const newWatchers = watchers.filter(w => w > notBefore) |
598 | this.watchersPerVideo.set(videoId, newWatchers) | 610 | this.watchersPerVideo.set(videoId, newWatchers) |
599 | 611 | ||
600 | logger.debug('New live video views for %s is %d.', video.url, numWatchers) | 612 | logger.debug('New live video views for %s is %d.', video.url, numWatchers, lTags()) |
601 | } | 613 | } |
602 | } | 614 | } |
603 | 615 | ||
604 | private async handleBrokenLives () { | 616 | private async handleBrokenLives () { |
605 | const videoIds = await VideoModel.listPublishedLiveIds() | 617 | const videoUUIDs = await VideoModel.listPublishedLiveUUIDs() |
606 | 618 | ||
607 | for (const id of videoIds) { | 619 | for (const uuid of videoUUIDs) { |
608 | await this.onEndTransmuxing(id, true) | 620 | await this.onEndTransmuxing(uuid, true) |
609 | } | 621 | } |
610 | } | 622 | } |
611 | 623 | ||
diff --git a/server/lib/moderation.ts b/server/lib/moderation.ts index 925d64902..0cefe1648 100644 --- a/server/lib/moderation.ts +++ b/server/lib/moderation.ts | |||
@@ -23,9 +23,9 @@ import { ActivityCreate } from '../../shared/models/activitypub' | |||
23 | import { VideoObject } from '../../shared/models/activitypub/objects' | 23 | import { VideoObject } from '../../shared/models/activitypub/objects' |
24 | import { VideoCommentObject } from '../../shared/models/activitypub/objects/video-comment-object' | 24 | import { VideoCommentObject } from '../../shared/models/activitypub/objects/video-comment-object' |
25 | import { LiveVideoCreate, VideoCreate, VideoImportCreate } from '../../shared/models/videos' | 25 | import { LiveVideoCreate, VideoCreate, VideoImportCreate } from '../../shared/models/videos' |
26 | import { VideoCommentCreate } from '../../shared/models/videos/video-comment.model' | 26 | import { VideoCommentCreate } from '../../shared/models/videos/comment/video-comment.model' |
27 | import { UserModel } from '../models/account/user' | 27 | import { ActorModel } from '../models/actor/actor' |
28 | import { ActorModel } from '../models/activitypub/actor' | 28 | import { UserModel } from '../models/user/user' |
29 | import { VideoModel } from '../models/video/video' | 29 | import { VideoModel } from '../models/video/video' |
30 | import { VideoCommentModel } from '../models/video/video-comment' | 30 | import { VideoCommentModel } from '../models/video/video-comment' |
31 | import { sendAbuse } from './activitypub/send/send-flag' | 31 | import { sendAbuse } from './activitypub/send/send-flag' |
diff --git a/server/lib/notifier.ts b/server/lib/notifier.ts index da7f7cc05..1f9ff16df 100644 --- a/server/lib/notifier.ts +++ b/server/lib/notifier.ts | |||
@@ -17,8 +17,8 @@ import { VideoPrivacy, VideoState } from '../../shared/models/videos' | |||
17 | import { logger } from '../helpers/logger' | 17 | import { logger } from '../helpers/logger' |
18 | import { CONFIG } from '../initializers/config' | 18 | import { CONFIG } from '../initializers/config' |
19 | import { AccountBlocklistModel } from '../models/account/account-blocklist' | 19 | import { AccountBlocklistModel } from '../models/account/account-blocklist' |
20 | import { UserModel } from '../models/account/user' | 20 | import { UserModel } from '../models/user/user' |
21 | import { UserNotificationModel } from '../models/account/user-notification' | 21 | import { UserNotificationModel } from '../models/user/user-notification' |
22 | import { MAbuseFull, MAbuseMessage, MAccountServer, MActorFollowFull, MApplication, MPlugin } from '../types/models' | 22 | import { MAbuseFull, MAbuseMessage, MAccountServer, MActorFollowFull, MApplication, MPlugin } from '../types/models' |
23 | import { MCommentOwnerVideo, MVideoAccountLight, MVideoFullLight } from '../types/models/video' | 23 | import { MCommentOwnerVideo, MVideoAccountLight, MVideoFullLight } from '../types/models/video' |
24 | import { isBlockedByServerOrAccount } from './blocklist' | 24 | import { isBlockedByServerOrAccount } from './blocklist' |
diff --git a/server/lib/plugins/hooks.ts b/server/lib/plugins/hooks.ts index aa92f03cc..5e97b52a0 100644 --- a/server/lib/plugins/hooks.ts +++ b/server/lib/plugins/hooks.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import { ServerActionHookName, ServerFilterHookName } from '../../../shared/models/plugins/server-hook.model' | ||
2 | import { PluginManager } from './plugin-manager' | ||
3 | import { logger } from '../../helpers/logger' | ||
4 | import * as Bluebird from 'bluebird' | 1 | import * as Bluebird from 'bluebird' |
2 | import { ServerActionHookName, ServerFilterHookName } from '../../../shared/models' | ||
3 | import { logger } from '../../helpers/logger' | ||
4 | import { PluginManager } from './plugin-manager' | ||
5 | 5 | ||
6 | type PromiseFunction <U, T> = (params: U) => Promise<T> | Bluebird<T> | 6 | type PromiseFunction <U, T> = (params: U) => Promise<T> | Bluebird<T> |
7 | type RawFunction <U, T> = (params: U) => T | 7 | type RawFunction <U, T> = (params: U) => T |
diff --git a/server/lib/plugins/plugin-helpers-builder.ts b/server/lib/plugins/plugin-helpers-builder.ts index f1bc24d8b..8487672ba 100644 --- a/server/lib/plugins/plugin-helpers-builder.ts +++ b/server/lib/plugins/plugin-helpers-builder.ts | |||
@@ -15,9 +15,9 @@ import { MPlugin } from '@server/types/models' | |||
15 | import { PeerTubeHelpers } from '@server/types/plugins' | 15 | import { PeerTubeHelpers } from '@server/types/plugins' |
16 | import { VideoBlacklistCreate } from '@shared/models' | 16 | import { VideoBlacklistCreate } from '@shared/models' |
17 | import { addAccountInBlocklist, addServerInBlocklist, removeAccountFromBlocklist, removeServerFromBlocklist } from '../blocklist' | 17 | import { addAccountInBlocklist, addServerInBlocklist, removeAccountFromBlocklist, removeServerFromBlocklist } from '../blocklist' |
18 | import { getServerConfig } from '../config' | 18 | import { ServerConfigManager } from '../server-config-manager' |
19 | import { blacklistVideo, unblacklistVideo } from '../video-blacklist' | 19 | import { blacklistVideo, unblacklistVideo } from '../video-blacklist' |
20 | import { UserModel } from '@server/models/account/user' | 20 | import { UserModel } from '@server/models/user/user' |
21 | 21 | ||
22 | function buildPluginHelpers (pluginModel: MPlugin, npmName: string): PeerTubeHelpers { | 22 | function buildPluginHelpers (pluginModel: MPlugin, npmName: string): PeerTubeHelpers { |
23 | const logger = buildPluginLogger(npmName) | 23 | const logger = buildPluginLogger(npmName) |
@@ -147,7 +147,7 @@ function buildConfigHelpers () { | |||
147 | }, | 147 | }, |
148 | 148 | ||
149 | getServerConfig () { | 149 | getServerConfig () { |
150 | return getServerConfig() | 150 | return ServerConfigManager.Instance.getServerConfig() |
151 | } | 151 | } |
152 | } | 152 | } |
153 | } | 153 | } |
diff --git a/server/lib/plugins/plugin-index.ts b/server/lib/plugins/plugin-index.ts index 165bc91b3..119cee8e0 100644 --- a/server/lib/plugins/plugin-index.ts +++ b/server/lib/plugins/plugin-index.ts | |||
@@ -1,16 +1,16 @@ | |||
1 | import { sanitizeUrl } from '@server/helpers/core-utils' | 1 | import { sanitizeUrl } from '@server/helpers/core-utils' |
2 | import { ResultList } from '../../../shared/models' | 2 | import { logger } from '@server/helpers/logger' |
3 | import { PeertubePluginIndexList } from '../../../shared/models/plugins/peertube-plugin-index-list.model' | 3 | import { doJSONRequest } from '@server/helpers/requests' |
4 | import { PeerTubePluginIndex } from '../../../shared/models/plugins/peertube-plugin-index.model' | 4 | import { CONFIG } from '@server/initializers/config' |
5 | import { PEERTUBE_VERSION } from '@server/initializers/constants' | ||
6 | import { PluginModel } from '@server/models/server/plugin' | ||
5 | import { | 7 | import { |
8 | PeerTubePluginIndex, | ||
9 | PeertubePluginIndexList, | ||
6 | PeertubePluginLatestVersionRequest, | 10 | PeertubePluginLatestVersionRequest, |
7 | PeertubePluginLatestVersionResponse | 11 | PeertubePluginLatestVersionResponse, |
8 | } from '../../../shared/models/plugins/peertube-plugin-latest-version.model' | 12 | ResultList |
9 | import { logger } from '../../helpers/logger' | 13 | } from '@shared/models' |
10 | import { doJSONRequest } from '../../helpers/requests' | ||
11 | import { CONFIG } from '../../initializers/config' | ||
12 | import { PEERTUBE_VERSION } from '../../initializers/constants' | ||
13 | import { PluginModel } from '../../models/server/plugin' | ||
14 | import { PluginManager } from './plugin-manager' | 14 | import { PluginManager } from './plugin-manager' |
15 | 15 | ||
16 | async function listAvailablePluginsFromIndex (options: PeertubePluginIndexList) { | 16 | async function listAvailablePluginsFromIndex (options: PeertubePluginIndexList) { |
diff --git a/server/lib/plugins/plugin-manager.ts b/server/lib/plugins/plugin-manager.ts index ba9814383..6b9a255a4 100644 --- a/server/lib/plugins/plugin-manager.ts +++ b/server/lib/plugins/plugin-manager.ts | |||
@@ -4,16 +4,11 @@ import { createReadStream, createWriteStream } from 'fs' | |||
4 | import { ensureDir, outputFile, readJSON } from 'fs-extra' | 4 | import { ensureDir, outputFile, readJSON } from 'fs-extra' |
5 | import { basename, join } from 'path' | 5 | import { basename, join } from 'path' |
6 | import { MOAuthTokenUser, MUser } from '@server/types/models' | 6 | import { MOAuthTokenUser, MUser } from '@server/types/models' |
7 | import { RegisterServerHookOptions } from '@shared/models/plugins/register-server-hook.model' | 7 | import { getCompleteLocale } from '@shared/core-utils' |
8 | import { ClientScript, PluginPackageJson, PluginTranslation, PluginTranslationPaths, RegisterServerHookOptions } from '@shared/models' | ||
8 | import { getHookType, internalRunHook } from '../../../shared/core-utils/plugins/hooks' | 9 | import { getHookType, internalRunHook } from '../../../shared/core-utils/plugins/hooks' |
9 | import { | ||
10 | ClientScript, | ||
11 | PluginPackageJson, | ||
12 | PluginTranslationPaths as PackagePluginTranslations | ||
13 | } from '../../../shared/models/plugins/plugin-package-json.model' | ||
14 | import { PluginTranslation } from '../../../shared/models/plugins/plugin-translation.model' | ||
15 | import { PluginType } from '../../../shared/models/plugins/plugin.type' | 10 | import { PluginType } from '../../../shared/models/plugins/plugin.type' |
16 | import { ServerHook, ServerHookName } from '../../../shared/models/plugins/server-hook.model' | 11 | import { ServerHook, ServerHookName } from '../../../shared/models/plugins/server/server-hook.model' |
17 | import { isLibraryCodeValid, isPackageJSONValid } from '../../helpers/custom-validators/plugins' | 12 | import { isLibraryCodeValid, isPackageJSONValid } from '../../helpers/custom-validators/plugins' |
18 | import { logger } from '../../helpers/logger' | 13 | import { logger } from '../../helpers/logger' |
19 | import { CONFIG } from '../../initializers/config' | 14 | import { CONFIG } from '../../initializers/config' |
@@ -23,7 +18,6 @@ import { PluginLibrary, RegisterServerAuthExternalOptions, RegisterServerAuthPas | |||
23 | import { ClientHtml } from '../client-html' | 18 | import { ClientHtml } from '../client-html' |
24 | import { RegisterHelpers } from './register-helpers' | 19 | import { RegisterHelpers } from './register-helpers' |
25 | import { installNpmPlugin, installNpmPluginFromDisk, removeNpmPlugin } from './yarn' | 20 | import { installNpmPlugin, installNpmPluginFromDisk, removeNpmPlugin } from './yarn' |
26 | import { getCompleteLocale } from '@shared/core-utils' | ||
27 | 21 | ||
28 | export interface RegisteredPlugin { | 22 | export interface RegisteredPlugin { |
29 | npmName: string | 23 | npmName: string |
@@ -443,7 +437,7 @@ export class PluginManager implements ServerHook { | |||
443 | 437 | ||
444 | // ###################### Translations ###################### | 438 | // ###################### Translations ###################### |
445 | 439 | ||
446 | private async addTranslations (plugin: PluginModel, npmName: string, translationPaths: PackagePluginTranslations) { | 440 | private async addTranslations (plugin: PluginModel, npmName: string, translationPaths: PluginTranslationPaths) { |
447 | for (const locale of Object.keys(translationPaths)) { | 441 | for (const locale of Object.keys(translationPaths)) { |
448 | const path = translationPaths[locale] | 442 | const path = translationPaths[locale] |
449 | const json = await readJSON(join(this.getPluginPath(plugin.name, plugin.type), path)) | 443 | const json = await readJSON(join(this.getPluginPath(plugin.name, plugin.type), path)) |
diff --git a/server/lib/plugins/register-helpers.ts b/server/lib/plugins/register-helpers.ts index aa69ca2a2..f5b573370 100644 --- a/server/lib/plugins/register-helpers.ts +++ b/server/lib/plugins/register-helpers.ts | |||
@@ -26,10 +26,10 @@ import { | |||
26 | PluginVideoLicenceManager, | 26 | PluginVideoLicenceManager, |
27 | PluginVideoPrivacyManager, | 27 | PluginVideoPrivacyManager, |
28 | RegisterServerHookOptions, | 28 | RegisterServerHookOptions, |
29 | RegisterServerSettingOptions | 29 | RegisterServerSettingOptions, |
30 | serverHookObject | ||
30 | } from '@shared/models' | 31 | } from '@shared/models' |
31 | import { serverHookObject } from '@shared/models/plugins/server-hook.model' | 32 | import { VideoTranscodingProfilesManager } from '../transcoding/video-transcoding-profiles' |
32 | import { VideoTranscodingProfilesManager } from '../video-transcoding-profiles' | ||
33 | import { buildPluginHelpers } from './plugin-helpers-builder' | 33 | import { buildPluginHelpers } from './plugin-helpers-builder' |
34 | 34 | ||
35 | type AlterableVideoConstant = 'language' | 'licence' | 'category' | 'privacy' | 'playlistPrivacy' | 35 | type AlterableVideoConstant = 'language' | 'licence' | 'category' | 'privacy' | 'playlistPrivacy' |
diff --git a/server/lib/redundancy.ts b/server/lib/redundancy.ts index da620b607..2a9241249 100644 --- a/server/lib/redundancy.ts +++ b/server/lib/redundancy.ts | |||
@@ -1,12 +1,12 @@ | |||
1 | import { VideoRedundancyModel } from '../models/redundancy/video-redundancy' | ||
2 | import { sendUndoCacheFile } from './activitypub/send' | ||
3 | import { Transaction } from 'sequelize' | 1 | import { Transaction } from 'sequelize' |
4 | import { MActorSignature, MVideoRedundancyVideo } from '@server/types/models' | ||
5 | import { CONFIG } from '@server/initializers/config' | ||
6 | import { logger } from '@server/helpers/logger' | 2 | import { logger } from '@server/helpers/logger' |
7 | import { ActorFollowModel } from '@server/models/activitypub/actor-follow' | 3 | import { CONFIG } from '@server/initializers/config' |
8 | import { Activity } from '@shared/models' | 4 | import { ActorFollowModel } from '@server/models/actor/actor-follow' |
9 | import { getServerActor } from '@server/models/application/application' | 5 | import { getServerActor } from '@server/models/application/application' |
6 | import { MActorSignature, MVideoRedundancyVideo } from '@server/types/models' | ||
7 | import { Activity } from '@shared/models' | ||
8 | import { VideoRedundancyModel } from '../models/redundancy/video-redundancy' | ||
9 | import { sendUndoCacheFile } from './activitypub/send' | ||
10 | 10 | ||
11 | async function removeVideoRedundancy (videoRedundancy: MVideoRedundancyVideo, t?: Transaction) { | 11 | async function removeVideoRedundancy (videoRedundancy: MVideoRedundancyVideo, t?: Transaction) { |
12 | const serverActor = await getServerActor() | 12 | const serverActor = await getServerActor() |
diff --git a/server/lib/schedulers/actor-follow-scheduler.ts b/server/lib/schedulers/actor-follow-scheduler.ts index 598c0211f..1b80316e9 100644 --- a/server/lib/schedulers/actor-follow-scheduler.ts +++ b/server/lib/schedulers/actor-follow-scheduler.ts | |||
@@ -1,9 +1,9 @@ | |||
1 | import { isTestInstance } from '../../helpers/core-utils' | 1 | import { isTestInstance } from '../../helpers/core-utils' |
2 | import { logger } from '../../helpers/logger' | 2 | import { logger } from '../../helpers/logger' |
3 | import { ActorFollowModel } from '../../models/activitypub/actor-follow' | ||
4 | import { AbstractScheduler } from './abstract-scheduler' | ||
5 | import { ACTOR_FOLLOW_SCORE, SCHEDULER_INTERVALS_MS } from '../../initializers/constants' | 3 | import { ACTOR_FOLLOW_SCORE, SCHEDULER_INTERVALS_MS } from '../../initializers/constants' |
4 | import { ActorFollowModel } from '../../models/actor/actor-follow' | ||
6 | import { ActorFollowScoreCache } from '../files-cache' | 5 | import { ActorFollowScoreCache } from '../files-cache' |
6 | import { AbstractScheduler } from './abstract-scheduler' | ||
7 | 7 | ||
8 | export class ActorFollowScheduler extends AbstractScheduler { | 8 | export class ActorFollowScheduler extends AbstractScheduler { |
9 | 9 | ||
diff --git a/server/lib/schedulers/auto-follow-index-instances.ts b/server/lib/schedulers/auto-follow-index-instances.ts index 0b8cd1389..aaa5feed5 100644 --- a/server/lib/schedulers/auto-follow-index-instances.ts +++ b/server/lib/schedulers/auto-follow-index-instances.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import { chunk } from 'lodash' | 1 | import { chunk } from 'lodash' |
2 | import { doJSONRequest } from '@server/helpers/requests' | 2 | import { doJSONRequest } from '@server/helpers/requests' |
3 | import { JobQueue } from '@server/lib/job-queue' | 3 | import { JobQueue } from '@server/lib/job-queue' |
4 | import { ActorFollowModel } from '@server/models/activitypub/actor-follow' | 4 | import { ActorFollowModel } from '@server/models/actor/actor-follow' |
5 | import { getServerActor } from '@server/models/application/application' | 5 | import { getServerActor } from '@server/models/application/application' |
6 | import { logger } from '../../helpers/logger' | 6 | import { logger } from '../../helpers/logger' |
7 | import { CONFIG } from '../../initializers/config' | 7 | import { CONFIG } from '../../initializers/config' |
diff --git a/server/lib/schedulers/remove-old-history-scheduler.ts b/server/lib/schedulers/remove-old-history-scheduler.ts index 17a42b2c4..225669ea2 100644 --- a/server/lib/schedulers/remove-old-history-scheduler.ts +++ b/server/lib/schedulers/remove-old-history-scheduler.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import { logger } from '../../helpers/logger' | 1 | import { logger } from '../../helpers/logger' |
2 | import { AbstractScheduler } from './abstract-scheduler' | 2 | import { AbstractScheduler } from './abstract-scheduler' |
3 | import { SCHEDULER_INTERVALS_MS } from '../../initializers/constants' | 3 | import { SCHEDULER_INTERVALS_MS } from '../../initializers/constants' |
4 | import { UserVideoHistoryModel } from '../../models/account/user-video-history' | 4 | import { UserVideoHistoryModel } from '../../models/user/user-video-history' |
5 | import { CONFIG } from '../../initializers/config' | 5 | import { CONFIG } from '../../initializers/config' |
6 | 6 | ||
7 | export class RemoveOldHistoryScheduler extends AbstractScheduler { | 7 | export class RemoveOldHistoryScheduler extends AbstractScheduler { |
diff --git a/server/lib/schedulers/youtube-dl-update-scheduler.ts b/server/lib/schedulers/youtube-dl-update-scheduler.ts index aefe6aba4..898691c13 100644 --- a/server/lib/schedulers/youtube-dl-update-scheduler.ts +++ b/server/lib/schedulers/youtube-dl-update-scheduler.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import { AbstractScheduler } from './abstract-scheduler' | 1 | import { YoutubeDL } from '@server/helpers/youtube-dl' |
2 | import { SCHEDULER_INTERVALS_MS } from '../../initializers/constants' | 2 | import { SCHEDULER_INTERVALS_MS } from '../../initializers/constants' |
3 | import { updateYoutubeDLBinary } from '../../helpers/youtube-dl' | 3 | import { AbstractScheduler } from './abstract-scheduler' |
4 | 4 | ||
5 | export class YoutubeDlUpdateScheduler extends AbstractScheduler { | 5 | export class YoutubeDlUpdateScheduler extends AbstractScheduler { |
6 | 6 | ||
@@ -13,7 +13,7 @@ export class YoutubeDlUpdateScheduler extends AbstractScheduler { | |||
13 | } | 13 | } |
14 | 14 | ||
15 | protected internalExecute () { | 15 | protected internalExecute () { |
16 | return updateYoutubeDLBinary() | 16 | return YoutubeDL.updateYoutubeDLBinary() |
17 | } | 17 | } |
18 | 18 | ||
19 | static get Instance () { | 19 | static get Instance () { |
diff --git a/server/lib/server-config-manager.ts b/server/lib/server-config-manager.ts new file mode 100644 index 000000000..25a770c6b --- /dev/null +++ b/server/lib/server-config-manager.ts | |||
@@ -0,0 +1,304 @@ | |||
1 | import { isSignupAllowed, isSignupAllowedForCurrentIP } from '@server/helpers/signup' | ||
2 | import { getServerCommit } from '@server/helpers/utils' | ||
3 | import { CONFIG, isEmailEnabled } from '@server/initializers/config' | ||
4 | import { CONSTRAINTS_FIELDS, DEFAULT_THEME_NAME, PEERTUBE_VERSION } from '@server/initializers/constants' | ||
5 | import { ActorCustomPageModel } from '@server/models/account/actor-custom-page' | ||
6 | import { HTMLServerConfig, RegisteredExternalAuthConfig, RegisteredIdAndPassAuthConfig, ServerConfig } from '@shared/models' | ||
7 | import { Hooks } from './plugins/hooks' | ||
8 | import { PluginManager } from './plugins/plugin-manager' | ||
9 | import { getThemeOrDefault } from './plugins/theme-utils' | ||
10 | import { VideoTranscodingProfilesManager } from './transcoding/video-transcoding-profiles' | ||
11 | |||
12 | /** | ||
13 | * | ||
14 | * Used to send the server config to clients (using REST/API or plugins API) | ||
15 | * We need a singleton class to manage config state depending on external events (to build menu entries etc) | ||
16 | * | ||
17 | */ | ||
18 | |||
19 | class ServerConfigManager { | ||
20 | |||
21 | private static instance: ServerConfigManager | ||
22 | |||
23 | private serverCommit: string | ||
24 | |||
25 | private homepageEnabled = false | ||
26 | |||
27 | private constructor () {} | ||
28 | |||
29 | async init () { | ||
30 | const instanceHomepage = await ActorCustomPageModel.loadInstanceHomepage() | ||
31 | |||
32 | this.updateHomepageState(instanceHomepage?.content) | ||
33 | } | ||
34 | |||
35 | updateHomepageState (content: string) { | ||
36 | this.homepageEnabled = !!content | ||
37 | } | ||
38 | |||
39 | async getHTMLServerConfig (): Promise<HTMLServerConfig> { | ||
40 | if (this.serverCommit === undefined) this.serverCommit = await getServerCommit() | ||
41 | |||
42 | const defaultTheme = getThemeOrDefault(CONFIG.THEME.DEFAULT, DEFAULT_THEME_NAME) | ||
43 | |||
44 | return { | ||
45 | instance: { | ||
46 | name: CONFIG.INSTANCE.NAME, | ||
47 | shortDescription: CONFIG.INSTANCE.SHORT_DESCRIPTION, | ||
48 | isNSFW: CONFIG.INSTANCE.IS_NSFW, | ||
49 | defaultNSFWPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY, | ||
50 | defaultClientRoute: CONFIG.INSTANCE.DEFAULT_CLIENT_ROUTE, | ||
51 | customizations: { | ||
52 | javascript: CONFIG.INSTANCE.CUSTOMIZATIONS.JAVASCRIPT, | ||
53 | css: CONFIG.INSTANCE.CUSTOMIZATIONS.CSS | ||
54 | } | ||
55 | }, | ||
56 | search: { | ||
57 | remoteUri: { | ||
58 | users: CONFIG.SEARCH.REMOTE_URI.USERS, | ||
59 | anonymous: CONFIG.SEARCH.REMOTE_URI.ANONYMOUS | ||
60 | }, | ||
61 | searchIndex: { | ||
62 | enabled: CONFIG.SEARCH.SEARCH_INDEX.ENABLED, | ||
63 | url: CONFIG.SEARCH.SEARCH_INDEX.URL, | ||
64 | disableLocalSearch: CONFIG.SEARCH.SEARCH_INDEX.DISABLE_LOCAL_SEARCH, | ||
65 | isDefaultSearch: CONFIG.SEARCH.SEARCH_INDEX.IS_DEFAULT_SEARCH | ||
66 | } | ||
67 | }, | ||
68 | plugin: { | ||
69 | registered: this.getRegisteredPlugins(), | ||
70 | registeredExternalAuths: this.getExternalAuthsPlugins(), | ||
71 | registeredIdAndPassAuths: this.getIdAndPassAuthPlugins() | ||
72 | }, | ||
73 | theme: { | ||
74 | registered: this.getRegisteredThemes(), | ||
75 | default: defaultTheme | ||
76 | }, | ||
77 | email: { | ||
78 | enabled: isEmailEnabled() | ||
79 | }, | ||
80 | contactForm: { | ||
81 | enabled: CONFIG.CONTACT_FORM.ENABLED | ||
82 | }, | ||
83 | serverVersion: PEERTUBE_VERSION, | ||
84 | serverCommit: this.serverCommit, | ||
85 | transcoding: { | ||
86 | hls: { | ||
87 | enabled: CONFIG.TRANSCODING.HLS.ENABLED | ||
88 | }, | ||
89 | webtorrent: { | ||
90 | enabled: CONFIG.TRANSCODING.WEBTORRENT.ENABLED | ||
91 | }, | ||
92 | enabledResolutions: this.getEnabledResolutions('vod'), | ||
93 | profile: CONFIG.TRANSCODING.PROFILE, | ||
94 | availableProfiles: VideoTranscodingProfilesManager.Instance.getAvailableProfiles('vod') | ||
95 | }, | ||
96 | live: { | ||
97 | enabled: CONFIG.LIVE.ENABLED, | ||
98 | |||
99 | allowReplay: CONFIG.LIVE.ALLOW_REPLAY, | ||
100 | maxDuration: CONFIG.LIVE.MAX_DURATION, | ||
101 | maxInstanceLives: CONFIG.LIVE.MAX_INSTANCE_LIVES, | ||
102 | maxUserLives: CONFIG.LIVE.MAX_USER_LIVES, | ||
103 | |||
104 | transcoding: { | ||
105 | enabled: CONFIG.LIVE.TRANSCODING.ENABLED, | ||
106 | enabledResolutions: this.getEnabledResolutions('live'), | ||
107 | profile: CONFIG.LIVE.TRANSCODING.PROFILE, | ||
108 | availableProfiles: VideoTranscodingProfilesManager.Instance.getAvailableProfiles('live') | ||
109 | }, | ||
110 | |||
111 | rtmp: { | ||
112 | port: CONFIG.LIVE.RTMP.PORT | ||
113 | } | ||
114 | }, | ||
115 | import: { | ||
116 | videos: { | ||
117 | http: { | ||
118 | enabled: CONFIG.IMPORT.VIDEOS.HTTP.ENABLED | ||
119 | }, | ||
120 | torrent: { | ||
121 | enabled: CONFIG.IMPORT.VIDEOS.TORRENT.ENABLED | ||
122 | } | ||
123 | } | ||
124 | }, | ||
125 | autoBlacklist: { | ||
126 | videos: { | ||
127 | ofUsers: { | ||
128 | enabled: CONFIG.AUTO_BLACKLIST.VIDEOS.OF_USERS.ENABLED | ||
129 | } | ||
130 | } | ||
131 | }, | ||
132 | avatar: { | ||
133 | file: { | ||
134 | size: { | ||
135 | max: CONSTRAINTS_FIELDS.ACTORS.IMAGE.FILE_SIZE.max | ||
136 | }, | ||
137 | extensions: CONSTRAINTS_FIELDS.ACTORS.IMAGE.EXTNAME | ||
138 | } | ||
139 | }, | ||
140 | banner: { | ||
141 | file: { | ||
142 | size: { | ||
143 | max: CONSTRAINTS_FIELDS.ACTORS.IMAGE.FILE_SIZE.max | ||
144 | }, | ||
145 | extensions: CONSTRAINTS_FIELDS.ACTORS.IMAGE.EXTNAME | ||
146 | } | ||
147 | }, | ||
148 | video: { | ||
149 | image: { | ||
150 | extensions: CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME, | ||
151 | size: { | ||
152 | max: CONSTRAINTS_FIELDS.VIDEOS.IMAGE.FILE_SIZE.max | ||
153 | } | ||
154 | }, | ||
155 | file: { | ||
156 | extensions: CONSTRAINTS_FIELDS.VIDEOS.EXTNAME | ||
157 | } | ||
158 | }, | ||
159 | videoCaption: { | ||
160 | file: { | ||
161 | size: { | ||
162 | max: CONSTRAINTS_FIELDS.VIDEO_CAPTIONS.CAPTION_FILE.FILE_SIZE.max | ||
163 | }, | ||
164 | extensions: CONSTRAINTS_FIELDS.VIDEO_CAPTIONS.CAPTION_FILE.EXTNAME | ||
165 | } | ||
166 | }, | ||
167 | user: { | ||
168 | videoQuota: CONFIG.USER.VIDEO_QUOTA, | ||
169 | videoQuotaDaily: CONFIG.USER.VIDEO_QUOTA_DAILY | ||
170 | }, | ||
171 | trending: { | ||
172 | videos: { | ||
173 | intervalDays: CONFIG.TRENDING.VIDEOS.INTERVAL_DAYS, | ||
174 | algorithms: { | ||
175 | enabled: CONFIG.TRENDING.VIDEOS.ALGORITHMS.ENABLED, | ||
176 | default: CONFIG.TRENDING.VIDEOS.ALGORITHMS.DEFAULT | ||
177 | } | ||
178 | } | ||
179 | }, | ||
180 | tracker: { | ||
181 | enabled: CONFIG.TRACKER.ENABLED | ||
182 | }, | ||
183 | |||
184 | followings: { | ||
185 | instance: { | ||
186 | autoFollowIndex: { | ||
187 | indexUrl: CONFIG.FOLLOWINGS.INSTANCE.AUTO_FOLLOW_INDEX.INDEX_URL | ||
188 | } | ||
189 | } | ||
190 | }, | ||
191 | |||
192 | broadcastMessage: { | ||
193 | enabled: CONFIG.BROADCAST_MESSAGE.ENABLED, | ||
194 | message: CONFIG.BROADCAST_MESSAGE.MESSAGE, | ||
195 | level: CONFIG.BROADCAST_MESSAGE.LEVEL, | ||
196 | dismissable: CONFIG.BROADCAST_MESSAGE.DISMISSABLE | ||
197 | }, | ||
198 | |||
199 | homepage: { | ||
200 | enabled: this.homepageEnabled | ||
201 | } | ||
202 | } | ||
203 | } | ||
204 | |||
205 | async getServerConfig (ip?: string): Promise<ServerConfig> { | ||
206 | const { allowed } = await Hooks.wrapPromiseFun( | ||
207 | isSignupAllowed, | ||
208 | { | ||
209 | ip | ||
210 | }, | ||
211 | 'filter:api.user.signup.allowed.result' | ||
212 | ) | ||
213 | |||
214 | const allowedForCurrentIP = isSignupAllowedForCurrentIP(ip) | ||
215 | |||
216 | const signup = { | ||
217 | allowed, | ||
218 | allowedForCurrentIP, | ||
219 | minimumAge: CONFIG.SIGNUP.MINIMUM_AGE, | ||
220 | requiresEmailVerification: CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION | ||
221 | } | ||
222 | |||
223 | const htmlConfig = await this.getHTMLServerConfig() | ||
224 | |||
225 | return { ...htmlConfig, signup } | ||
226 | } | ||
227 | |||
228 | getRegisteredThemes () { | ||
229 | return PluginManager.Instance.getRegisteredThemes() | ||
230 | .map(t => ({ | ||
231 | name: t.name, | ||
232 | version: t.version, | ||
233 | description: t.description, | ||
234 | css: t.css, | ||
235 | clientScripts: t.clientScripts | ||
236 | })) | ||
237 | } | ||
238 | |||
239 | getRegisteredPlugins () { | ||
240 | return PluginManager.Instance.getRegisteredPlugins() | ||
241 | .map(p => ({ | ||
242 | name: p.name, | ||
243 | version: p.version, | ||
244 | description: p.description, | ||
245 | clientScripts: p.clientScripts | ||
246 | })) | ||
247 | } | ||
248 | |||
249 | getEnabledResolutions (type: 'vod' | 'live') { | ||
250 | const transcoding = type === 'vod' | ||
251 | ? CONFIG.TRANSCODING | ||
252 | : CONFIG.LIVE.TRANSCODING | ||
253 | |||
254 | return Object.keys(transcoding.RESOLUTIONS) | ||
255 | .filter(key => transcoding.ENABLED && transcoding.RESOLUTIONS[key] === true) | ||
256 | .map(r => parseInt(r, 10)) | ||
257 | } | ||
258 | |||
259 | private getIdAndPassAuthPlugins () { | ||
260 | const result: RegisteredIdAndPassAuthConfig[] = [] | ||
261 | |||
262 | for (const p of PluginManager.Instance.getIdAndPassAuths()) { | ||
263 | for (const auth of p.idAndPassAuths) { | ||
264 | result.push({ | ||
265 | npmName: p.npmName, | ||
266 | name: p.name, | ||
267 | version: p.version, | ||
268 | authName: auth.authName, | ||
269 | weight: auth.getWeight() | ||
270 | }) | ||
271 | } | ||
272 | } | ||
273 | |||
274 | return result | ||
275 | } | ||
276 | |||
277 | private getExternalAuthsPlugins () { | ||
278 | const result: RegisteredExternalAuthConfig[] = [] | ||
279 | |||
280 | for (const p of PluginManager.Instance.getExternalAuths()) { | ||
281 | for (const auth of p.externalAuths) { | ||
282 | result.push({ | ||
283 | npmName: p.npmName, | ||
284 | name: p.name, | ||
285 | version: p.version, | ||
286 | authName: auth.authName, | ||
287 | authDisplayName: auth.authDisplayName() | ||
288 | }) | ||
289 | } | ||
290 | } | ||
291 | |||
292 | return result | ||
293 | } | ||
294 | |||
295 | static get Instance () { | ||
296 | return this.instance || (this.instance = new this()) | ||
297 | } | ||
298 | } | ||
299 | |||
300 | // --------------------------------------------------------------------------- | ||
301 | |||
302 | export { | ||
303 | ServerConfigManager | ||
304 | } | ||
diff --git a/server/lib/stat-manager.ts b/server/lib/stat-manager.ts index 09ba208bd..25ed21927 100644 --- a/server/lib/stat-manager.ts +++ b/server/lib/stat-manager.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import { CONFIG } from '@server/initializers/config' | 1 | import { CONFIG } from '@server/initializers/config' |
2 | import { UserModel } from '@server/models/account/user' | 2 | import { UserModel } from '@server/models/user/user' |
3 | import { ActorFollowModel } from '@server/models/activitypub/actor-follow' | 3 | import { ActorFollowModel } from '@server/models/actor/actor-follow' |
4 | import { VideoRedundancyModel } from '@server/models/redundancy/video-redundancy' | 4 | import { VideoRedundancyModel } from '@server/models/redundancy/video-redundancy' |
5 | import { VideoModel } from '@server/models/video/video' | 5 | import { VideoModel } from '@server/models/video/video' |
6 | import { VideoChannelModel } from '@server/models/video/video-channel' | 6 | import { VideoChannelModel } from '@server/models/video/video-channel' |
diff --git a/server/lib/video-transcoding-profiles.ts b/server/lib/transcoding/video-transcoding-profiles.ts index 81f5e1962..c5ea72a5f 100644 --- a/server/lib/video-transcoding-profiles.ts +++ b/server/lib/transcoding/video-transcoding-profiles.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import { logger } from '@server/helpers/logger' | 1 | import { logger } from '@server/helpers/logger' |
2 | import { AvailableEncoders, EncoderOptionsBuilder, getTargetBitrate, VideoResolution } from '../../shared/models/videos' | 2 | import { AvailableEncoders, EncoderOptionsBuilder, getTargetBitrate, VideoResolution } from '../../../shared/models/videos' |
3 | import { buildStreamSuffix, resetSupportedEncoders } from '../helpers/ffmpeg-utils' | 3 | import { buildStreamSuffix, resetSupportedEncoders } from '../../helpers/ffmpeg-utils' |
4 | import { | 4 | import { |
5 | canDoQuickAudioTranscode, | 5 | canDoQuickAudioTranscode, |
6 | ffprobePromise, | 6 | ffprobePromise, |
@@ -8,8 +8,8 @@ import { | |||
8 | getMaxAudioBitrate, | 8 | getMaxAudioBitrate, |
9 | getVideoFileBitrate, | 9 | getVideoFileBitrate, |
10 | getVideoStreamFromFile | 10 | getVideoStreamFromFile |
11 | } from '../helpers/ffprobe-utils' | 11 | } from '../../helpers/ffprobe-utils' |
12 | import { VIDEO_TRANSCODING_FPS } from '../initializers/constants' | 12 | import { VIDEO_TRANSCODING_FPS } from '../../initializers/constants' |
13 | 13 | ||
14 | /** | 14 | /** |
15 | * | 15 | * |
diff --git a/server/lib/video-transcoding.ts b/server/lib/transcoding/video-transcoding.ts index c949dca2e..5df192575 100644 --- a/server/lib/video-transcoding.ts +++ b/server/lib/transcoding/video-transcoding.ts | |||
@@ -3,17 +3,17 @@ import { copyFile, ensureDir, move, remove, stat } from 'fs-extra' | |||
3 | import { basename, extname as extnameUtil, join } from 'path' | 3 | import { basename, extname as extnameUtil, join } from 'path' |
4 | import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent' | 4 | import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent' |
5 | import { MStreamingPlaylistFilesVideo, MVideoFile, MVideoFullLight } from '@server/types/models' | 5 | import { MStreamingPlaylistFilesVideo, MVideoFile, MVideoFullLight } from '@server/types/models' |
6 | import { VideoResolution } from '../../shared/models/videos' | 6 | import { VideoResolution } from '../../../shared/models/videos' |
7 | import { VideoStreamingPlaylistType } from '../../shared/models/videos/video-streaming-playlist.type' | 7 | import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type' |
8 | import { transcode, TranscodeOptions, TranscodeOptionsType } from '../helpers/ffmpeg-utils' | 8 | import { transcode, TranscodeOptions, TranscodeOptionsType } from '../../helpers/ffmpeg-utils' |
9 | import { canDoQuickTranscode, getDurationFromVideoFile, getMetadataFromFile, getVideoFileFPS } from '../helpers/ffprobe-utils' | 9 | import { canDoQuickTranscode, getDurationFromVideoFile, getMetadataFromFile, getVideoFileFPS } from '../../helpers/ffprobe-utils' |
10 | import { logger } from '../helpers/logger' | 10 | import { logger } from '../../helpers/logger' |
11 | import { CONFIG } from '../initializers/config' | 11 | import { CONFIG } from '../../initializers/config' |
12 | import { HLS_STREAMING_PLAYLIST_DIRECTORY, P2P_MEDIA_LOADER_PEER_VERSION, WEBSERVER } from '../initializers/constants' | 12 | import { HLS_STREAMING_PLAYLIST_DIRECTORY, P2P_MEDIA_LOADER_PEER_VERSION, WEBSERVER } from '../../initializers/constants' |
13 | import { VideoFileModel } from '../models/video/video-file' | 13 | import { VideoFileModel } from '../../models/video/video-file' |
14 | import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist' | 14 | import { VideoStreamingPlaylistModel } from '../../models/video/video-streaming-playlist' |
15 | import { updateMasterHLSPlaylist, updateSha256VODSegments } from './hls' | 15 | import { updateMasterHLSPlaylist, updateSha256VODSegments } from '../hls' |
16 | import { generateVideoFilename, generateVideoStreamingPlaylistName, getVideoFilePath } from './video-paths' | 16 | import { generateVideoFilename, generateVideoStreamingPlaylistName, getVideoFilePath } from '../video-paths' |
17 | import { VideoTranscodingProfilesManager } from './video-transcoding-profiles' | 17 | import { VideoTranscodingProfilesManager } from './video-transcoding-profiles' |
18 | 18 | ||
19 | /** | 19 | /** |
@@ -215,16 +215,6 @@ function generateHlsPlaylistResolution (options: { | |||
215 | }) | 215 | }) |
216 | } | 216 | } |
217 | 217 | ||
218 | function getEnabledResolutions (type: 'vod' | 'live') { | ||
219 | const transcoding = type === 'vod' | ||
220 | ? CONFIG.TRANSCODING | ||
221 | : CONFIG.LIVE.TRANSCODING | ||
222 | |||
223 | return Object.keys(transcoding.RESOLUTIONS) | ||
224 | .filter(key => transcoding.ENABLED && transcoding.RESOLUTIONS[key] === true) | ||
225 | .map(r => parseInt(r, 10)) | ||
226 | } | ||
227 | |||
228 | // --------------------------------------------------------------------------- | 218 | // --------------------------------------------------------------------------- |
229 | 219 | ||
230 | export { | 220 | export { |
@@ -232,8 +222,7 @@ export { | |||
232 | generateHlsPlaylistResolutionFromTS, | 222 | generateHlsPlaylistResolutionFromTS, |
233 | optimizeOriginalVideofile, | 223 | optimizeOriginalVideofile, |
234 | transcodeNewWebTorrentResolution, | 224 | transcodeNewWebTorrentResolution, |
235 | mergeAudioVideofile, | 225 | mergeAudioVideofile |
236 | getEnabledResolutions | ||
237 | } | 226 | } |
238 | 227 | ||
239 | // --------------------------------------------------------------------------- | 228 | // --------------------------------------------------------------------------- |
diff --git a/server/lib/user.ts b/server/lib/user.ts index 9b0a0a2f1..8a6fcebc7 100644 --- a/server/lib/user.ts +++ b/server/lib/user.ts | |||
@@ -1,14 +1,15 @@ | |||
1 | import { Transaction } from 'sequelize/types' | 1 | import { Transaction } from 'sequelize/types' |
2 | import { v4 as uuidv4 } from 'uuid' | 2 | import { v4 as uuidv4 } from 'uuid' |
3 | import { UserModel } from '@server/models/account/user' | 3 | import { UserModel } from '@server/models/user/user' |
4 | import { MActorDefault } from '@server/types/models/actor' | ||
4 | import { ActivityPubActorType } from '../../shared/models/activitypub' | 5 | import { ActivityPubActorType } from '../../shared/models/activitypub' |
5 | import { UserNotificationSetting, UserNotificationSettingValue } from '../../shared/models/users' | 6 | import { UserNotificationSetting, UserNotificationSettingValue } from '../../shared/models/users' |
6 | import { SERVER_ACTOR_NAME, WEBSERVER } from '../initializers/constants' | 7 | import { SERVER_ACTOR_NAME, WEBSERVER } from '../initializers/constants' |
7 | import { sequelizeTypescript } from '../initializers/database' | 8 | import { sequelizeTypescript } from '../initializers/database' |
8 | import { AccountModel } from '../models/account/account' | 9 | import { AccountModel } from '../models/account/account' |
9 | import { UserNotificationSettingModel } from '../models/account/user-notification-setting' | 10 | import { ActorModel } from '../models/actor/actor' |
10 | import { ActorModel } from '../models/activitypub/actor' | 11 | import { UserNotificationSettingModel } from '../models/user/user-notification-setting' |
11 | import { MAccountDefault, MActorDefault, MChannelActor } from '../types/models' | 12 | import { MAccountDefault, MChannelActor } from '../types/models' |
12 | import { MUser, MUserDefault, MUserId } from '../types/models/user' | 13 | import { MUser, MUserDefault, MUserId } from '../types/models/user' |
13 | import { buildActorInstance, generateAndSaveActorKeys } from './activitypub/actor' | 14 | import { buildActorInstance, generateAndSaveActorKeys } from './activitypub/actor' |
14 | import { getLocalAccountActivityPubUrl } from './activitypub/url' | 15 | import { getLocalAccountActivityPubUrl } from './activitypub/url' |
diff --git a/server/lib/video-channel.ts b/server/lib/video-channel.ts index 0476cb2d5..d57e832fe 100644 --- a/server/lib/video-channel.ts +++ b/server/lib/video-channel.ts | |||
@@ -1,5 +1,4 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | import { v4 as uuidv4 } from 'uuid' | ||
3 | import { VideoChannelCreate } from '../../shared/models' | 2 | import { VideoChannelCreate } from '../../shared/models' |
4 | import { VideoModel } from '../models/video/video' | 3 | import { VideoModel } from '../models/video/video' |
5 | import { VideoChannelModel } from '../models/video/video-channel' | 4 | import { VideoChannelModel } from '../models/video/video-channel' |
@@ -9,9 +8,8 @@ import { getLocalVideoChannelActivityPubUrl } from './activitypub/url' | |||
9 | import { federateVideoIfNeeded } from './activitypub/videos' | 8 | import { federateVideoIfNeeded } from './activitypub/videos' |
10 | 9 | ||
11 | async function createLocalVideoChannel (videoChannelInfo: VideoChannelCreate, account: MAccountId, t: Sequelize.Transaction) { | 10 | async function createLocalVideoChannel (videoChannelInfo: VideoChannelCreate, account: MAccountId, t: Sequelize.Transaction) { |
12 | const uuid = uuidv4() | ||
13 | const url = getLocalVideoChannelActivityPubUrl(videoChannelInfo.name) | 11 | const url = getLocalVideoChannelActivityPubUrl(videoChannelInfo.name) |
14 | const actorInstance = buildActorInstance('Group', url, videoChannelInfo.name, uuid) | 12 | const actorInstance = buildActorInstance('Group', url, videoChannelInfo.name) |
15 | 13 | ||
16 | const actorInstanceCreated = await actorInstance.save({ transaction: t }) | 14 | const actorInstanceCreated = await actorInstance.save({ transaction: t }) |
17 | 15 | ||
diff --git a/server/lib/video-comment.ts b/server/lib/video-comment.ts index 736ebb2f8..51a9c747e 100644 --- a/server/lib/video-comment.ts +++ b/server/lib/video-comment.ts | |||
@@ -3,7 +3,7 @@ import * as Sequelize from 'sequelize' | |||
3 | import { logger } from '@server/helpers/logger' | 3 | import { logger } from '@server/helpers/logger' |
4 | import { sequelizeTypescript } from '@server/initializers/database' | 4 | import { sequelizeTypescript } from '@server/initializers/database' |
5 | import { ResultList } from '../../shared/models' | 5 | import { ResultList } from '../../shared/models' |
6 | import { VideoCommentThreadTree } from '../../shared/models/videos/video-comment.model' | 6 | import { VideoCommentThreadTree } from '../../shared/models/videos/comment/video-comment.model' |
7 | import { VideoCommentModel } from '../models/video/video-comment' | 7 | import { VideoCommentModel } from '../models/video/video-comment' |
8 | import { MAccountDefault, MComment, MCommentOwnerVideo, MCommentOwnerVideoReply, MVideoFullLight } from '../types/models' | 8 | import { MAccountDefault, MComment, MCommentOwnerVideo, MCommentOwnerVideoReply, MVideoFullLight } from '../types/models' |
9 | import { sendCreateVideoComment, sendDeleteVideoComment } from './activitypub/send' | 9 | import { sendCreateVideoComment, sendDeleteVideoComment } from './activitypub/send' |
diff --git a/server/lib/video.ts b/server/lib/video.ts index 21e4b7ff2..d26cf85cd 100644 --- a/server/lib/video.ts +++ b/server/lib/video.ts | |||
@@ -28,6 +28,8 @@ function buildLocalVideoFromReq (videoInfo: VideoCreate, channelId: number): Fil | |||
28 | privacy: videoInfo.privacy || VideoPrivacy.PRIVATE, | 28 | privacy: videoInfo.privacy || VideoPrivacy.PRIVATE, |
29 | channelId: channelId, | 29 | channelId: channelId, |
30 | originallyPublishedAt: videoInfo.originallyPublishedAt | 30 | originallyPublishedAt: videoInfo.originallyPublishedAt |
31 | ? new Date(videoInfo.originallyPublishedAt) | ||
32 | : null | ||
31 | } | 33 | } |
32 | } | 34 | } |
33 | 35 | ||
diff --git a/server/middlewares/validators/config.ts b/server/middlewares/validators/config.ts index a85883b19..e3e0c2058 100644 --- a/server/middlewares/validators/config.ts +++ b/server/middlewares/validators/config.ts | |||
@@ -30,6 +30,7 @@ const customConfigUpdateValidator = [ | |||
30 | body('signup.enabled').isBoolean().withMessage('Should have a valid signup enabled boolean'), | 30 | body('signup.enabled').isBoolean().withMessage('Should have a valid signup enabled boolean'), |
31 | body('signup.limit').isInt().withMessage('Should have a valid signup limit'), | 31 | body('signup.limit').isInt().withMessage('Should have a valid signup limit'), |
32 | body('signup.requiresEmailVerification').isBoolean().withMessage('Should have a valid requiresEmailVerification boolean'), | 32 | body('signup.requiresEmailVerification').isBoolean().withMessage('Should have a valid requiresEmailVerification boolean'), |
33 | body('signup.minimumAge').isInt().withMessage("Should have a valid minimum age required"), | ||
33 | 34 | ||
34 | body('admin.email').isEmail().withMessage('Should have a valid administrator email'), | 35 | body('admin.email').isEmail().withMessage('Should have a valid administrator email'), |
35 | body('contactForm.enabled').isBoolean().withMessage('Should have a valid contact form enabled boolean'), | 36 | body('contactForm.enabled').isBoolean().withMessage('Should have a valid contact form enabled boolean'), |
diff --git a/server/middlewares/validators/follows.ts b/server/middlewares/validators/follows.ts index bb849dc72..1d18de8cd 100644 --- a/server/middlewares/validators/follows.ts +++ b/server/middlewares/validators/follows.ts | |||
@@ -1,18 +1,18 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { body, param, query } from 'express-validator' | 2 | import { body, param, query } from 'express-validator' |
3 | import { isFollowStateValid } from '@server/helpers/custom-validators/follows' | ||
4 | import { getServerActor } from '@server/models/application/application' | ||
5 | import { MActorFollowActorsDefault } from '@server/types/models' | ||
6 | import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' | ||
3 | import { isTestInstance } from '../../helpers/core-utils' | 7 | import { isTestInstance } from '../../helpers/core-utils' |
8 | import { isActorTypeValid, isValidActorHandle } from '../../helpers/custom-validators/activitypub/actor' | ||
4 | import { isEachUniqueHostValid, isHostValid } from '../../helpers/custom-validators/servers' | 9 | import { isEachUniqueHostValid, isHostValid } from '../../helpers/custom-validators/servers' |
5 | import { logger } from '../../helpers/logger' | 10 | import { logger } from '../../helpers/logger' |
11 | import { loadActorUrlOrGetFromWebfinger } from '../../helpers/webfinger' | ||
6 | import { SERVER_ACTOR_NAME, WEBSERVER } from '../../initializers/constants' | 12 | import { SERVER_ACTOR_NAME, WEBSERVER } from '../../initializers/constants' |
7 | import { ActorFollowModel } from '../../models/activitypub/actor-follow' | 13 | import { ActorModel } from '../../models/actor/actor' |
14 | import { ActorFollowModel } from '../../models/actor/actor-follow' | ||
8 | import { areValidationErrors } from './utils' | 15 | import { areValidationErrors } from './utils' |
9 | import { ActorModel } from '../../models/activitypub/actor' | ||
10 | import { loadActorUrlOrGetFromWebfinger } from '../../helpers/webfinger' | ||
11 | import { isActorTypeValid, isValidActorHandle } from '../../helpers/custom-validators/activitypub/actor' | ||
12 | import { MActorFollowActorsDefault } from '@server/types/models' | ||
13 | import { isFollowStateValid } from '@server/helpers/custom-validators/follows' | ||
14 | import { getServerActor } from '@server/models/application/application' | ||
15 | import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' | ||
16 | 16 | ||
17 | const listFollowsValidator = [ | 17 | const listFollowsValidator = [ |
18 | query('state') | 18 | query('state') |
diff --git a/server/middlewares/validators/oembed.ts b/server/middlewares/validators/oembed.ts index 2a7dc257b..165eda6d5 100644 --- a/server/middlewares/validators/oembed.ts +++ b/server/middlewares/validators/oembed.ts | |||
@@ -4,15 +4,29 @@ import { join } from 'path' | |||
4 | import { fetchVideo } from '@server/helpers/video' | 4 | import { fetchVideo } from '@server/helpers/video' |
5 | import { VideoPlaylistModel } from '@server/models/video/video-playlist' | 5 | import { VideoPlaylistModel } from '@server/models/video/video-playlist' |
6 | import { VideoPlaylistPrivacy, VideoPrivacy } from '@shared/models' | 6 | import { VideoPlaylistPrivacy, VideoPrivacy } from '@shared/models' |
7 | import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' | ||
7 | import { isTestInstance } from '../../helpers/core-utils' | 8 | import { isTestInstance } from '../../helpers/core-utils' |
8 | import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc' | 9 | import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc' |
9 | import { logger } from '../../helpers/logger' | 10 | import { logger } from '../../helpers/logger' |
10 | import { WEBSERVER } from '../../initializers/constants' | 11 | import { WEBSERVER } from '../../initializers/constants' |
11 | import { areValidationErrors } from './utils' | 12 | import { areValidationErrors } from './utils' |
12 | import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' | ||
13 | 13 | ||
14 | const startVideoPlaylistsURL = WEBSERVER.SCHEME + '://' + join(WEBSERVER.HOST, 'videos', 'watch', 'playlist') + '/' | 14 | const playlistPaths = [ |
15 | const startVideosURL = WEBSERVER.SCHEME + '://' + join(WEBSERVER.HOST, 'videos', 'watch') + '/' | 15 | join('videos', 'watch', 'playlist'), |
16 | join('w', 'p') | ||
17 | ] | ||
18 | |||
19 | const videoPaths = [ | ||
20 | join('videos', 'watch'), | ||
21 | 'w' | ||
22 | ] | ||
23 | |||
24 | function buildUrls (paths: string[]) { | ||
25 | return paths.map(p => WEBSERVER.SCHEME + '://' + join(WEBSERVER.HOST, p) + '/') | ||
26 | } | ||
27 | |||
28 | const startPlaylistURLs = buildUrls(playlistPaths) | ||
29 | const startVideoURLs = buildUrls(videoPaths) | ||
16 | 30 | ||
17 | const watchRegex = /([^/]+)$/ | 31 | const watchRegex = /([^/]+)$/ |
18 | const isURLOptions = { | 32 | const isURLOptions = { |
@@ -43,8 +57,8 @@ const oembedValidator = [ | |||
43 | 57 | ||
44 | const url = req.query.url as string | 58 | const url = req.query.url as string |
45 | 59 | ||
46 | const isPlaylist = url.startsWith(startVideoPlaylistsURL) | 60 | const isPlaylist = startPlaylistURLs.some(u => url.startsWith(u)) |
47 | const isVideo = isPlaylist ? false : url.startsWith(startVideosURL) | 61 | const isVideo = isPlaylist ? false : startVideoURLs.some(u => url.startsWith(u)) |
48 | 62 | ||
49 | const startIsOk = isVideo || isPlaylist | 63 | const startIsOk = isVideo || isPlaylist |
50 | 64 | ||
diff --git a/server/middlewares/validators/plugins.ts b/server/middlewares/validators/plugins.ts index ab87fe720..2c47ec5bb 100644 --- a/server/middlewares/validators/plugins.ts +++ b/server/middlewares/validators/plugins.ts | |||
@@ -1,15 +1,15 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { body, param, query, ValidationChain } from 'express-validator' | 2 | import { body, param, query, ValidationChain } from 'express-validator' |
3 | import { logger } from '../../helpers/logger' | 3 | import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' |
4 | import { areValidationErrors } from './utils' | 4 | import { PluginType } from '../../../shared/models/plugins/plugin.type' |
5 | import { InstallOrUpdatePlugin } from '../../../shared/models/plugins/server/api/install-plugin.model' | ||
6 | import { exists, isBooleanValid, isSafePath, toBooleanOrNull, toIntOrNull } from '../../helpers/custom-validators/misc' | ||
5 | import { isNpmPluginNameValid, isPluginNameValid, isPluginTypeValid, isPluginVersionValid } from '../../helpers/custom-validators/plugins' | 7 | import { isNpmPluginNameValid, isPluginNameValid, isPluginTypeValid, isPluginVersionValid } from '../../helpers/custom-validators/plugins' |
8 | import { logger } from '../../helpers/logger' | ||
9 | import { CONFIG } from '../../initializers/config' | ||
6 | import { PluginManager } from '../../lib/plugins/plugin-manager' | 10 | import { PluginManager } from '../../lib/plugins/plugin-manager' |
7 | import { isBooleanValid, isSafePath, toBooleanOrNull, exists, toIntOrNull } from '../../helpers/custom-validators/misc' | ||
8 | import { PluginModel } from '../../models/server/plugin' | 11 | import { PluginModel } from '../../models/server/plugin' |
9 | import { InstallOrUpdatePlugin } from '../../../shared/models/plugins/install-plugin.model' | 12 | import { areValidationErrors } from './utils' |
10 | import { PluginType } from '../../../shared/models/plugins/plugin.type' | ||
11 | import { CONFIG } from '../../initializers/config' | ||
12 | import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' | ||
13 | 13 | ||
14 | const getPluginValidator = (pluginType: PluginType, withVersion = true) => { | 14 | const getPluginValidator = (pluginType: PluginType, withVersion = true) => { |
15 | const validators: (ValidationChain | express.Handler)[] = [ | 15 | const validators: (ValidationChain | express.Handler)[] = [ |
diff --git a/server/middlewares/validators/user-subscriptions.ts b/server/middlewares/validators/user-subscriptions.ts index 0d0c8ccbf..1823892b6 100644 --- a/server/middlewares/validators/user-subscriptions.ts +++ b/server/middlewares/validators/user-subscriptions.ts | |||
@@ -1,12 +1,12 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { body, param, query } from 'express-validator' | 2 | import { body, param, query } from 'express-validator' |
3 | import { logger } from '../../helpers/logger' | 3 | import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' |
4 | import { areValidationErrors } from './utils' | ||
5 | import { ActorFollowModel } from '../../models/activitypub/actor-follow' | ||
6 | import { areValidActorHandles, isValidActorHandle } from '../../helpers/custom-validators/activitypub/actor' | 4 | import { areValidActorHandles, isValidActorHandle } from '../../helpers/custom-validators/activitypub/actor' |
7 | import { toArray } from '../../helpers/custom-validators/misc' | 5 | import { toArray } from '../../helpers/custom-validators/misc' |
6 | import { logger } from '../../helpers/logger' | ||
8 | import { WEBSERVER } from '../../initializers/constants' | 7 | import { WEBSERVER } from '../../initializers/constants' |
9 | import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' | 8 | import { ActorFollowModel } from '../../models/actor/actor-follow' |
9 | import { areValidationErrors } from './utils' | ||
10 | 10 | ||
11 | const userSubscriptionListValidator = [ | 11 | const userSubscriptionListValidator = [ |
12 | query('search').optional().not().isEmpty().withMessage('Should have a valid search'), | 12 | query('search').optional().not().isEmpty().withMessage('Should have a valid search'), |
diff --git a/server/middlewares/validators/users.ts b/server/middlewares/validators/users.ts index 37119e279..548d5df4d 100644 --- a/server/middlewares/validators/users.ts +++ b/server/middlewares/validators/users.ts | |||
@@ -34,8 +34,8 @@ import { doesVideoExist } from '../../helpers/middlewares' | |||
34 | import { isSignupAllowed, isSignupAllowedForCurrentIP } from '../../helpers/signup' | 34 | import { isSignupAllowed, isSignupAllowedForCurrentIP } from '../../helpers/signup' |
35 | import { isThemeRegistered } from '../../lib/plugins/theme-utils' | 35 | import { isThemeRegistered } from '../../lib/plugins/theme-utils' |
36 | import { Redis } from '../../lib/redis' | 36 | import { Redis } from '../../lib/redis' |
37 | import { UserModel } from '../../models/account/user' | 37 | import { UserModel } from '../../models/user/user' |
38 | import { ActorModel } from '../../models/activitypub/actor' | 38 | import { ActorModel } from '../../models/actor/actor' |
39 | import { areValidationErrors } from './utils' | 39 | import { areValidationErrors } from './utils' |
40 | 40 | ||
41 | const usersListValidator = [ | 41 | const usersListValidator = [ |
diff --git a/server/middlewares/validators/videos/video-channels.ts b/server/middlewares/validators/videos/video-channels.ts index 2463d281c..e881f0d3e 100644 --- a/server/middlewares/validators/videos/video-channels.ts +++ b/server/middlewares/validators/videos/video-channels.ts | |||
@@ -3,6 +3,7 @@ import { body, param, query } from 'express-validator' | |||
3 | import { VIDEO_CHANNELS } from '@server/initializers/constants' | 3 | import { VIDEO_CHANNELS } from '@server/initializers/constants' |
4 | import { MChannelAccountDefault, MUser } from '@server/types/models' | 4 | import { MChannelAccountDefault, MUser } from '@server/types/models' |
5 | import { UserRight } from '../../../../shared' | 5 | import { UserRight } from '../../../../shared' |
6 | import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' | ||
6 | import { isActorPreferredUsernameValid } from '../../../helpers/custom-validators/activitypub/actor' | 7 | import { isActorPreferredUsernameValid } from '../../../helpers/custom-validators/activitypub/actor' |
7 | import { isBooleanValid, toBooleanOrNull } from '../../../helpers/custom-validators/misc' | 8 | import { isBooleanValid, toBooleanOrNull } from '../../../helpers/custom-validators/misc' |
8 | import { | 9 | import { |
@@ -12,10 +13,9 @@ import { | |||
12 | } from '../../../helpers/custom-validators/video-channels' | 13 | } from '../../../helpers/custom-validators/video-channels' |
13 | import { logger } from '../../../helpers/logger' | 14 | import { logger } from '../../../helpers/logger' |
14 | import { doesLocalVideoChannelNameExist, doesVideoChannelNameWithHostExist } from '../../../helpers/middlewares' | 15 | import { doesLocalVideoChannelNameExist, doesVideoChannelNameWithHostExist } from '../../../helpers/middlewares' |
15 | import { ActorModel } from '../../../models/activitypub/actor' | 16 | import { ActorModel } from '../../../models/actor/actor' |
16 | import { VideoChannelModel } from '../../../models/video/video-channel' | 17 | import { VideoChannelModel } from '../../../models/video/video-channel' |
17 | import { areValidationErrors } from '../utils' | 18 | import { areValidationErrors } from '../utils' |
18 | import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' | ||
19 | 19 | ||
20 | const videoChannelsAddValidator = [ | 20 | const videoChannelsAddValidator = [ |
21 | body('name').custom(isActorPreferredUsernameValid).withMessage('Should have a valid channel name'), | 21 | body('name').custom(isActorPreferredUsernameValid).withMessage('Should have a valid channel name'), |
diff --git a/server/middlewares/validators/videos/video-imports.ts b/server/middlewares/validators/videos/video-imports.ts index c53af3861..d0643ff26 100644 --- a/server/middlewares/validators/videos/video-imports.ts +++ b/server/middlewares/validators/videos/video-imports.ts | |||
@@ -47,14 +47,12 @@ const videoImportAddValidator = getCommonVideoEditAttributes().concat([ | |||
47 | cleanUpReqFiles(req) | 47 | cleanUpReqFiles(req) |
48 | return res.status(HttpStatusCode.CONFLICT_409) | 48 | return res.status(HttpStatusCode.CONFLICT_409) |
49 | .json({ error: 'HTTP import is not enabled on this instance.' }) | 49 | .json({ error: 'HTTP import is not enabled on this instance.' }) |
50 | .end() | ||
51 | } | 50 | } |
52 | 51 | ||
53 | if (CONFIG.IMPORT.VIDEOS.TORRENT.ENABLED !== true && (req.body.magnetUri || torrentFile)) { | 52 | if (CONFIG.IMPORT.VIDEOS.TORRENT.ENABLED !== true && (req.body.magnetUri || torrentFile)) { |
54 | cleanUpReqFiles(req) | 53 | cleanUpReqFiles(req) |
55 | return res.status(HttpStatusCode.CONFLICT_409) | 54 | return res.status(HttpStatusCode.CONFLICT_409) |
56 | .json({ error: 'Torrent/magnet URI import is not enabled on this instance.' }) | 55 | .json({ error: 'Torrent/magnet URI import is not enabled on this instance.' }) |
57 | .end() | ||
58 | } | 56 | } |
59 | 57 | ||
60 | if (!await doesVideoChannelOfAccountExist(req.body.channelId, user, res)) return cleanUpReqFiles(req) | 58 | if (!await doesVideoChannelOfAccountExist(req.body.channelId, user, res)) return cleanUpReqFiles(req) |
@@ -65,7 +63,6 @@ const videoImportAddValidator = getCommonVideoEditAttributes().concat([ | |||
65 | 63 | ||
66 | return res.status(HttpStatusCode.BAD_REQUEST_400) | 64 | return res.status(HttpStatusCode.BAD_REQUEST_400) |
67 | .json({ error: 'Should have a magnetUri or a targetUrl or a torrent file.' }) | 65 | .json({ error: 'Should have a magnetUri or a targetUrl or a torrent file.' }) |
68 | .end() | ||
69 | } | 66 | } |
70 | 67 | ||
71 | if (!await isImportAccepted(req, res)) return cleanUpReqFiles(req) | 68 | if (!await isImportAccepted(req, res)) return cleanUpReqFiles(req) |
diff --git a/server/middlewares/validators/videos/videos.ts b/server/middlewares/validators/videos/videos.ts index d26bcd4a6..3219e10d4 100644 --- a/server/middlewares/validators/videos/videos.ts +++ b/server/middlewares/validators/videos/videos.ts | |||
@@ -7,7 +7,7 @@ import { ExpressPromiseHandler } from '@server/types/express' | |||
7 | import { MUserAccountId, MVideoWithRights } from '@server/types/models' | 7 | import { MUserAccountId, MVideoWithRights } from '@server/types/models' |
8 | import { ServerErrorCode, UserRight, VideoChangeOwnershipStatus, VideoPrivacy } from '../../../../shared' | 8 | import { ServerErrorCode, UserRight, VideoChangeOwnershipStatus, VideoPrivacy } from '../../../../shared' |
9 | import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' | 9 | import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' |
10 | import { VideoChangeOwnershipAccept } from '../../../../shared/models/videos/video-change-ownership-accept.model' | 10 | import { VideoChangeOwnershipAccept } from '../../../../shared/models/videos/change-ownership/video-change-ownership-accept.model' |
11 | import { | 11 | import { |
12 | exists, | 12 | exists, |
13 | isBooleanValid, | 13 | isBooleanValid, |
diff --git a/server/middlewares/validators/webfinger.ts b/server/middlewares/validators/webfinger.ts index a71422ed8..c2dfccc96 100644 --- a/server/middlewares/validators/webfinger.ts +++ b/server/middlewares/validators/webfinger.ts | |||
@@ -1,11 +1,11 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { query } from 'express-validator' | 2 | import { query } from 'express-validator' |
3 | import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' | ||
3 | import { isWebfingerLocalResourceValid } from '../../helpers/custom-validators/webfinger' | 4 | import { isWebfingerLocalResourceValid } from '../../helpers/custom-validators/webfinger' |
5 | import { getHostWithPort } from '../../helpers/express-utils' | ||
4 | import { logger } from '../../helpers/logger' | 6 | import { logger } from '../../helpers/logger' |
5 | import { ActorModel } from '../../models/activitypub/actor' | 7 | import { ActorModel } from '../../models/actor/actor' |
6 | import { areValidationErrors } from './utils' | 8 | import { areValidationErrors } from './utils' |
7 | import { getHostWithPort } from '../../helpers/express-utils' | ||
8 | import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' | ||
9 | 9 | ||
10 | const webfingerValidator = [ | 10 | const webfingerValidator = [ |
11 | query('resource').custom(isWebfingerLocalResourceValid).withMessage('Should have a valid webfinger resource'), | 11 | query('resource').custom(isWebfingerLocalResourceValid).withMessage('Should have a valid webfinger resource'), |
diff --git a/server/models/abuse/abuse-message.ts b/server/models/abuse/abuse-message.ts index 7e51b3e07..2c5987e96 100644 --- a/server/models/abuse/abuse-message.ts +++ b/server/models/abuse/abuse-message.ts | |||
@@ -1,6 +1,7 @@ | |||
1 | import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' | 1 | import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' |
2 | import { isAbuseMessageValid } from '@server/helpers/custom-validators/abuses' | 2 | import { isAbuseMessageValid } from '@server/helpers/custom-validators/abuses' |
3 | import { MAbuseMessage, MAbuseMessageFormattable } from '@server/types/models' | 3 | import { MAbuseMessage, MAbuseMessageFormattable } from '@server/types/models' |
4 | import { AttributesOnly } from '@shared/core-utils' | ||
4 | import { AbuseMessage } from '@shared/models' | 5 | import { AbuseMessage } from '@shared/models' |
5 | import { AccountModel, ScopeNames as AccountScopeNames } from '../account/account' | 6 | import { AccountModel, ScopeNames as AccountScopeNames } from '../account/account' |
6 | import { getSort, throwIfNotValid } from '../utils' | 7 | import { getSort, throwIfNotValid } from '../utils' |
@@ -17,7 +18,7 @@ import { AbuseModel } from './abuse' | |||
17 | } | 18 | } |
18 | ] | 19 | ] |
19 | }) | 20 | }) |
20 | export class AbuseMessageModel extends Model { | 21 | export class AbuseMessageModel extends Model<Partial<AttributesOnly<AbuseMessageModel>>> { |
21 | 22 | ||
22 | @AllowNull(false) | 23 | @AllowNull(false) |
23 | @Is('AbuseMessage', value => throwIfNotValid(value, isAbuseMessageValid, 'message')) | 24 | @Is('AbuseMessage', value => throwIfNotValid(value, isAbuseMessageValid, 'message')) |
diff --git a/server/models/abuse/abuse.ts b/server/models/abuse/abuse.ts index 262f364f1..3518f5c02 100644 --- a/server/models/abuse/abuse.ts +++ b/server/models/abuse/abuse.ts | |||
@@ -16,7 +16,7 @@ import { | |||
16 | UpdatedAt | 16 | UpdatedAt |
17 | } from 'sequelize-typescript' | 17 | } from 'sequelize-typescript' |
18 | import { isAbuseModerationCommentValid, isAbuseReasonValid, isAbuseStateValid } from '@server/helpers/custom-validators/abuses' | 18 | import { isAbuseModerationCommentValid, isAbuseReasonValid, isAbuseStateValid } from '@server/helpers/custom-validators/abuses' |
19 | import { abusePredefinedReasonsMap } from '@shared/core-utils/abuse' | 19 | import { abusePredefinedReasonsMap, AttributesOnly } from '@shared/core-utils' |
20 | import { | 20 | import { |
21 | AbuseFilter, | 21 | AbuseFilter, |
22 | AbuseObject, | 22 | AbuseObject, |
@@ -187,7 +187,7 @@ export enum ScopeNames { | |||
187 | } | 187 | } |
188 | ] | 188 | ] |
189 | }) | 189 | }) |
190 | export class AbuseModel extends Model { | 190 | export class AbuseModel extends Model<Partial<AttributesOnly<AbuseModel>>> { |
191 | 191 | ||
192 | @AllowNull(false) | 192 | @AllowNull(false) |
193 | @Default(null) | 193 | @Default(null) |
diff --git a/server/models/abuse/video-abuse.ts b/server/models/abuse/video-abuse.ts index 90aa0695e..95bff50d0 100644 --- a/server/models/abuse/video-abuse.ts +++ b/server/models/abuse/video-abuse.ts | |||
@@ -1,4 +1,5 @@ | |||
1 | import { AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript' | 1 | import { AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript' |
2 | import { AttributesOnly } from '@shared/core-utils' | ||
2 | import { VideoDetails } from '@shared/models' | 3 | import { VideoDetails } from '@shared/models' |
3 | import { VideoModel } from '../video/video' | 4 | import { VideoModel } from '../video/video' |
4 | import { AbuseModel } from './abuse' | 5 | import { AbuseModel } from './abuse' |
@@ -14,7 +15,7 @@ import { AbuseModel } from './abuse' | |||
14 | } | 15 | } |
15 | ] | 16 | ] |
16 | }) | 17 | }) |
17 | export class VideoAbuseModel extends Model { | 18 | export class VideoAbuseModel extends Model<Partial<AttributesOnly<VideoAbuseModel>>> { |
18 | 19 | ||
19 | @CreatedAt | 20 | @CreatedAt |
20 | createdAt: Date | 21 | createdAt: Date |
diff --git a/server/models/abuse/video-comment-abuse.ts b/server/models/abuse/video-comment-abuse.ts index d3fce76a5..32cb2ca64 100644 --- a/server/models/abuse/video-comment-abuse.ts +++ b/server/models/abuse/video-comment-abuse.ts | |||
@@ -1,4 +1,5 @@ | |||
1 | import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript' | 1 | import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript' |
2 | import { AttributesOnly } from '@shared/core-utils' | ||
2 | import { VideoCommentModel } from '../video/video-comment' | 3 | import { VideoCommentModel } from '../video/video-comment' |
3 | import { AbuseModel } from './abuse' | 4 | import { AbuseModel } from './abuse' |
4 | 5 | ||
@@ -13,7 +14,7 @@ import { AbuseModel } from './abuse' | |||
13 | } | 14 | } |
14 | ] | 15 | ] |
15 | }) | 16 | }) |
16 | export class VideoCommentAbuseModel extends Model { | 17 | export class VideoCommentAbuseModel extends Model<Partial<AttributesOnly<VideoCommentAbuseModel>>> { |
17 | 18 | ||
18 | @CreatedAt | 19 | @CreatedAt |
19 | createdAt: Date | 20 | createdAt: Date |
diff --git a/server/models/account/account-blocklist.ts b/server/models/account/account-blocklist.ts index fe9168ab8..b2375b006 100644 --- a/server/models/account/account-blocklist.ts +++ b/server/models/account/account-blocklist.ts | |||
@@ -1,8 +1,9 @@ | |||
1 | import { Op } from 'sequelize' | 1 | import { Op } from 'sequelize' |
2 | import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' | 2 | import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' |
3 | import { MAccountBlocklist, MAccountBlocklistAccounts, MAccountBlocklistFormattable } from '@server/types/models' | 3 | import { MAccountBlocklist, MAccountBlocklistAccounts, MAccountBlocklistFormattable } from '@server/types/models' |
4 | import { AttributesOnly } from '@shared/core-utils' | ||
4 | import { AccountBlock } from '../../../shared/models' | 5 | import { AccountBlock } from '../../../shared/models' |
5 | import { ActorModel } from '../activitypub/actor' | 6 | import { ActorModel } from '../actor/actor' |
6 | import { ServerModel } from '../server/server' | 7 | import { ServerModel } from '../server/server' |
7 | import { getSort, searchAttribute } from '../utils' | 8 | import { getSort, searchAttribute } from '../utils' |
8 | import { AccountModel } from './account' | 9 | import { AccountModel } from './account' |
@@ -40,7 +41,7 @@ enum ScopeNames { | |||
40 | } | 41 | } |
41 | ] | 42 | ] |
42 | }) | 43 | }) |
43 | export class AccountBlocklistModel extends Model { | 44 | export class AccountBlocklistModel extends Model<Partial<AttributesOnly<AccountBlocklistModel>>> { |
44 | 45 | ||
45 | @CreatedAt | 46 | @CreatedAt |
46 | createdAt: Date | 47 | createdAt: Date |
diff --git a/server/models/account/account-video-rate.ts b/server/models/account/account-video-rate.ts index 801f76bba..ee6dbc6da 100644 --- a/server/models/account/account-video-rate.ts +++ b/server/models/account/account-video-rate.ts | |||
@@ -7,11 +7,12 @@ import { | |||
7 | MAccountVideoRateAccountVideo, | 7 | MAccountVideoRateAccountVideo, |
8 | MAccountVideoRateFormattable | 8 | MAccountVideoRateFormattable |
9 | } from '@server/types/models/video/video-rate' | 9 | } from '@server/types/models/video/video-rate' |
10 | import { AttributesOnly } from '@shared/core-utils' | ||
10 | import { AccountVideoRate } from '../../../shared' | 11 | import { AccountVideoRate } from '../../../shared' |
11 | import { VideoRateType } from '../../../shared/models/videos' | 12 | import { VideoRateType } from '../../../shared/models/videos' |
12 | import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' | 13 | import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' |
13 | import { CONSTRAINTS_FIELDS, VIDEO_RATE_TYPES } from '../../initializers/constants' | 14 | import { CONSTRAINTS_FIELDS, VIDEO_RATE_TYPES } from '../../initializers/constants' |
14 | import { ActorModel } from '../activitypub/actor' | 15 | import { ActorModel } from '../actor/actor' |
15 | import { buildLocalAccountIdsIn, getSort, throwIfNotValid } from '../utils' | 16 | import { buildLocalAccountIdsIn, getSort, throwIfNotValid } from '../utils' |
16 | import { VideoModel } from '../video/video' | 17 | import { VideoModel } from '../video/video' |
17 | import { ScopeNames as VideoChannelScopeNames, SummaryOptions, VideoChannelModel } from '../video/video-channel' | 18 | import { ScopeNames as VideoChannelScopeNames, SummaryOptions, VideoChannelModel } from '../video/video-channel' |
@@ -42,7 +43,7 @@ import { AccountModel } from './account' | |||
42 | } | 43 | } |
43 | ] | 44 | ] |
44 | }) | 45 | }) |
45 | export class AccountVideoRateModel extends Model { | 46 | export class AccountVideoRateModel extends Model<Partial<AttributesOnly<AccountVideoRateModel>>> { |
46 | 47 | ||
47 | @AllowNull(false) | 48 | @AllowNull(false) |
48 | @Column(DataType.ENUM(...values(VIDEO_RATE_TYPES))) | 49 | @Column(DataType.ENUM(...values(VIDEO_RATE_TYPES))) |
diff --git a/server/models/account/account.ts b/server/models/account/account.ts index d33353af7..665ecd595 100644 --- a/server/models/account/account.ts +++ b/server/models/account/account.ts | |||
@@ -17,10 +17,11 @@ import { | |||
17 | UpdatedAt | 17 | UpdatedAt |
18 | } from 'sequelize-typescript' | 18 | } from 'sequelize-typescript' |
19 | import { ModelCache } from '@server/models/model-cache' | 19 | import { ModelCache } from '@server/models/model-cache' |
20 | import { AttributesOnly } from '@shared/core-utils' | ||
20 | import { Account, AccountSummary } from '../../../shared/models/actors' | 21 | import { Account, AccountSummary } from '../../../shared/models/actors' |
21 | import { isAccountDescriptionValid } from '../../helpers/custom-validators/accounts' | 22 | import { isAccountDescriptionValid } from '../../helpers/custom-validators/accounts' |
22 | import { CONSTRAINTS_FIELDS, SERVER_ACTOR_NAME, WEBSERVER } from '../../initializers/constants' | 23 | import { CONSTRAINTS_FIELDS, SERVER_ACTOR_NAME, WEBSERVER } from '../../initializers/constants' |
23 | import { sendDeleteActor } from '../../lib/activitypub/send' | 24 | import { sendDeleteActor } from '../../lib/activitypub/send/send-delete' |
24 | import { | 25 | import { |
25 | MAccount, | 26 | MAccount, |
26 | MAccountActor, | 27 | MAccountActor, |
@@ -30,19 +31,19 @@ import { | |||
30 | MAccountSummaryFormattable, | 31 | MAccountSummaryFormattable, |
31 | MChannelActor | 32 | MChannelActor |
32 | } from '../../types/models' | 33 | } from '../../types/models' |
33 | import { ActorModel } from '../activitypub/actor' | 34 | import { ActorModel } from '../actor/actor' |
34 | import { ActorFollowModel } from '../activitypub/actor-follow' | 35 | import { ActorFollowModel } from '../actor/actor-follow' |
36 | import { ActorImageModel } from '../actor/actor-image' | ||
35 | import { ApplicationModel } from '../application/application' | 37 | import { ApplicationModel } from '../application/application' |
36 | import { ActorImageModel } from './actor-image' | ||
37 | import { ServerModel } from '../server/server' | 38 | import { ServerModel } from '../server/server' |
38 | import { ServerBlocklistModel } from '../server/server-blocklist' | 39 | import { ServerBlocklistModel } from '../server/server-blocklist' |
40 | import { UserModel } from '../user/user' | ||
39 | import { getSort, throwIfNotValid } from '../utils' | 41 | import { getSort, throwIfNotValid } from '../utils' |
40 | import { VideoModel } from '../video/video' | 42 | import { VideoModel } from '../video/video' |
41 | import { VideoChannelModel } from '../video/video-channel' | 43 | import { VideoChannelModel } from '../video/video-channel' |
42 | import { VideoCommentModel } from '../video/video-comment' | 44 | import { VideoCommentModel } from '../video/video-comment' |
43 | import { VideoPlaylistModel } from '../video/video-playlist' | 45 | import { VideoPlaylistModel } from '../video/video-playlist' |
44 | import { AccountBlocklistModel } from './account-blocklist' | 46 | import { AccountBlocklistModel } from './account-blocklist' |
45 | import { UserModel } from './user' | ||
46 | 47 | ||
47 | export enum ScopeNames { | 48 | export enum ScopeNames { |
48 | SUMMARY = 'SUMMARY' | 49 | SUMMARY = 'SUMMARY' |
@@ -141,7 +142,7 @@ export type SummaryOptions = { | |||
141 | } | 142 | } |
142 | ] | 143 | ] |
143 | }) | 144 | }) |
144 | export class AccountModel extends Model { | 145 | export class AccountModel extends Model<Partial<AttributesOnly<AccountModel>>> { |
145 | 146 | ||
146 | @AllowNull(false) | 147 | @AllowNull(false) |
147 | @Column | 148 | @Column |
diff --git a/server/models/account/actor-custom-page.ts b/server/models/account/actor-custom-page.ts new file mode 100644 index 000000000..893023181 --- /dev/null +++ b/server/models/account/actor-custom-page.ts | |||
@@ -0,0 +1,69 @@ | |||
1 | import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript' | ||
2 | import { CustomPage } from '@shared/models' | ||
3 | import { ActorModel } from '../actor/actor' | ||
4 | import { getServerActor } from '../application/application' | ||
5 | |||
6 | @Table({ | ||
7 | tableName: 'actorCustomPage', | ||
8 | indexes: [ | ||
9 | { | ||
10 | fields: [ 'actorId', 'type' ], | ||
11 | unique: true | ||
12 | } | ||
13 | ] | ||
14 | }) | ||
15 | export class ActorCustomPageModel extends Model { | ||
16 | |||
17 | @AllowNull(true) | ||
18 | @Column(DataType.TEXT) | ||
19 | content: string | ||
20 | |||
21 | @AllowNull(false) | ||
22 | @Column | ||
23 | type: 'homepage' | ||
24 | |||
25 | @CreatedAt | ||
26 | createdAt: Date | ||
27 | |||
28 | @UpdatedAt | ||
29 | updatedAt: Date | ||
30 | |||
31 | @ForeignKey(() => ActorModel) | ||
32 | @Column | ||
33 | actorId: number | ||
34 | |||
35 | @BelongsTo(() => ActorModel, { | ||
36 | foreignKey: { | ||
37 | name: 'actorId', | ||
38 | allowNull: false | ||
39 | }, | ||
40 | onDelete: 'cascade' | ||
41 | }) | ||
42 | Actor: ActorModel | ||
43 | |||
44 | static async updateInstanceHomepage (content: string) { | ||
45 | const serverActor = await getServerActor() | ||
46 | |||
47 | return ActorCustomPageModel.upsert({ | ||
48 | content, | ||
49 | actorId: serverActor.id, | ||
50 | type: 'homepage' | ||
51 | }) | ||
52 | } | ||
53 | |||
54 | static async loadInstanceHomepage () { | ||
55 | const serverActor = await getServerActor() | ||
56 | |||
57 | return ActorCustomPageModel.findOne({ | ||
58 | where: { | ||
59 | actorId: serverActor.id | ||
60 | } | ||
61 | }) | ||
62 | } | ||
63 | |||
64 | toFormattedJSON (): CustomPage { | ||
65 | return { | ||
66 | content: this.content | ||
67 | } | ||
68 | } | ||
69 | } | ||
diff --git a/server/models/activitypub/actor-follow.ts b/server/models/actor/actor-follow.ts index 4c5f37620..3a09e51d6 100644 --- a/server/models/activitypub/actor-follow.ts +++ b/server/models/actor/actor-follow.ts | |||
@@ -28,6 +28,7 @@ import { | |||
28 | MActorFollowFormattable, | 28 | MActorFollowFormattable, |
29 | MActorFollowSubscriptions | 29 | MActorFollowSubscriptions |
30 | } from '@server/types/models' | 30 | } from '@server/types/models' |
31 | import { AttributesOnly } from '@shared/core-utils' | ||
31 | import { ActivityPubActorType } from '@shared/models' | 32 | import { ActivityPubActorType } from '@shared/models' |
32 | import { FollowState } from '../../../shared/models/actors' | 33 | import { FollowState } from '../../../shared/models/actors' |
33 | import { ActorFollow } from '../../../shared/models/actors/follow.model' | 34 | import { ActorFollow } from '../../../shared/models/actors/follow.model' |
@@ -61,7 +62,7 @@ import { ActorModel, unusedActorAttributesForAPI } from './actor' | |||
61 | } | 62 | } |
62 | ] | 63 | ] |
63 | }) | 64 | }) |
64 | export class ActorFollowModel extends Model { | 65 | export class ActorFollowModel extends Model<Partial<AttributesOnly<ActorFollowModel>>> { |
65 | 66 | ||
66 | @AllowNull(false) | 67 | @AllowNull(false) |
67 | @Column(DataType.ENUM(...values(FOLLOW_STATES))) | 68 | @Column(DataType.ENUM(...values(FOLLOW_STATES))) |
@@ -619,7 +620,7 @@ export class ActorFollowModel extends Model { | |||
619 | if (serverIds.length === 0) return | 620 | if (serverIds.length === 0) return |
620 | 621 | ||
621 | const me = await getServerActor() | 622 | const me = await getServerActor() |
622 | const serverIdsString = createSafeIn(ActorFollowModel, serverIds) | 623 | const serverIdsString = createSafeIn(ActorFollowModel.sequelize, serverIds) |
623 | 624 | ||
624 | const query = `UPDATE "actorFollow" SET "score" = LEAST("score" + ${value}, ${ACTOR_FOLLOW_SCORE.MAX}) ` + | 625 | const query = `UPDATE "actorFollow" SET "score" = LEAST("score" + ${value}, ${ACTOR_FOLLOW_SCORE.MAX}) ` + |
625 | 'WHERE id IN (' + | 626 | 'WHERE id IN (' + |
diff --git a/server/models/account/actor-image.ts b/server/models/actor/actor-image.ts index ae05b4969..a35f9edb0 100644 --- a/server/models/account/actor-image.ts +++ b/server/models/actor/actor-image.ts | |||
@@ -2,6 +2,7 @@ import { remove } from 'fs-extra' | |||
2 | import { join } from 'path' | 2 | import { join } from 'path' |
3 | import { AfterDestroy, AllowNull, Column, CreatedAt, Default, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' | 3 | import { AfterDestroy, AllowNull, Column, CreatedAt, Default, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' |
4 | import { MActorImageFormattable } from '@server/types/models' | 4 | import { MActorImageFormattable } from '@server/types/models' |
5 | import { AttributesOnly } from '@shared/core-utils' | ||
5 | import { ActorImageType } from '@shared/models' | 6 | import { ActorImageType } from '@shared/models' |
6 | import { ActorImage } from '../../../shared/models/actors/actor-image.model' | 7 | import { ActorImage } from '../../../shared/models/actors/actor-image.model' |
7 | import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' | 8 | import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' |
@@ -19,7 +20,7 @@ import { throwIfNotValid } from '../utils' | |||
19 | } | 20 | } |
20 | ] | 21 | ] |
21 | }) | 22 | }) |
22 | export class ActorImageModel extends Model { | 23 | export class ActorImageModel extends Model<Partial<AttributesOnly<ActorImageModel>>> { |
23 | 24 | ||
24 | @AllowNull(false) | 25 | @AllowNull(false) |
25 | @Column | 26 | @Column |
diff --git a/server/models/activitypub/actor.ts b/server/models/actor/actor.ts index 1af9efac2..65c53f8f8 100644 --- a/server/models/activitypub/actor.ts +++ b/server/models/actor/actor.ts | |||
@@ -18,6 +18,7 @@ import { | |||
18 | UpdatedAt | 18 | UpdatedAt |
19 | } from 'sequelize-typescript' | 19 | } from 'sequelize-typescript' |
20 | import { ModelCache } from '@server/models/model-cache' | 20 | import { ModelCache } from '@server/models/model-cache' |
21 | import { AttributesOnly } from '@shared/core-utils' | ||
21 | import { ActivityIconObject, ActivityPubActorType } from '../../../shared/models/activitypub' | 22 | import { ActivityIconObject, ActivityPubActorType } from '../../../shared/models/activitypub' |
22 | import { ActorImage } from '../../../shared/models/actors/actor-image.model' | 23 | import { ActorImage } from '../../../shared/models/actors/actor-image.model' |
23 | import { activityPubContextify } from '../../helpers/activitypub' | 24 | import { activityPubContextify } from '../../helpers/activitypub' |
@@ -51,12 +52,12 @@ import { | |||
51 | MActorWithInboxes | 52 | MActorWithInboxes |
52 | } from '../../types/models' | 53 | } from '../../types/models' |
53 | import { AccountModel } from '../account/account' | 54 | import { AccountModel } from '../account/account' |
54 | import { ActorImageModel } from '../account/actor-image' | ||
55 | import { ServerModel } from '../server/server' | 55 | import { ServerModel } from '../server/server' |
56 | import { isOutdated, throwIfNotValid } from '../utils' | 56 | import { isOutdated, throwIfNotValid } from '../utils' |
57 | import { VideoModel } from '../video/video' | 57 | import { VideoModel } from '../video/video' |
58 | import { VideoChannelModel } from '../video/video-channel' | 58 | import { VideoChannelModel } from '../video/video-channel' |
59 | import { ActorFollowModel } from './actor-follow' | 59 | import { ActorFollowModel } from './actor-follow' |
60 | import { ActorImageModel } from './actor-image' | ||
60 | 61 | ||
61 | enum ScopeNames { | 62 | enum ScopeNames { |
62 | FULL = 'FULL' | 63 | FULL = 'FULL' |
@@ -159,7 +160,7 @@ export const unusedActorAttributesForAPI = [ | |||
159 | } | 160 | } |
160 | ] | 161 | ] |
161 | }) | 162 | }) |
162 | export class ActorModel extends Model { | 163 | export class ActorModel extends Model<Partial<AttributesOnly<ActorModel>>> { |
163 | 164 | ||
164 | @AllowNull(false) | 165 | @AllowNull(false) |
165 | @Column(DataType.ENUM(...values(ACTIVITY_PUB_ACTOR_TYPES))) | 166 | @Column(DataType.ENUM(...values(ACTIVITY_PUB_ACTOR_TYPES))) |
diff --git a/server/models/application/application.ts b/server/models/application/application.ts index 21f8b1cbc..5531d134a 100644 --- a/server/models/application/application.ts +++ b/server/models/application/application.ts | |||
@@ -1,6 +1,7 @@ | |||
1 | import * as memoizee from 'memoizee' | ||
1 | import { AllowNull, Column, Default, DefaultScope, HasOne, IsInt, Model, Table } from 'sequelize-typescript' | 2 | import { AllowNull, Column, Default, DefaultScope, HasOne, IsInt, Model, Table } from 'sequelize-typescript' |
3 | import { AttributesOnly } from '@shared/core-utils' | ||
2 | import { AccountModel } from '../account/account' | 4 | import { AccountModel } from '../account/account' |
3 | import * as memoizee from 'memoizee' | ||
4 | 5 | ||
5 | export const getServerActor = memoizee(async function () { | 6 | export const getServerActor = memoizee(async function () { |
6 | const application = await ApplicationModel.load() | 7 | const application = await ApplicationModel.load() |
@@ -24,7 +25,7 @@ export const getServerActor = memoizee(async function () { | |||
24 | tableName: 'application', | 25 | tableName: 'application', |
25 | timestamps: false | 26 | timestamps: false |
26 | }) | 27 | }) |
27 | export class ApplicationModel extends Model { | 28 | export class ApplicationModel extends Model<Partial<AttributesOnly<ApplicationModel>>> { |
28 | 29 | ||
29 | @AllowNull(false) | 30 | @AllowNull(false) |
30 | @Default(0) | 31 | @Default(0) |
diff --git a/server/models/oauth/oauth-client.ts b/server/models/oauth/oauth-client.ts index 8dbc1c2f5..890954bdb 100644 --- a/server/models/oauth/oauth-client.ts +++ b/server/models/oauth/oauth-client.ts | |||
@@ -1,4 +1,5 @@ | |||
1 | import { AllowNull, Column, CreatedAt, DataType, HasMany, Model, Table, UpdatedAt } from 'sequelize-typescript' | 1 | import { AllowNull, Column, CreatedAt, DataType, HasMany, Model, Table, UpdatedAt } from 'sequelize-typescript' |
2 | import { AttributesOnly } from '@shared/core-utils' | ||
2 | import { OAuthTokenModel } from './oauth-token' | 3 | import { OAuthTokenModel } from './oauth-token' |
3 | 4 | ||
4 | @Table({ | 5 | @Table({ |
@@ -14,7 +15,7 @@ import { OAuthTokenModel } from './oauth-token' | |||
14 | } | 15 | } |
15 | ] | 16 | ] |
16 | }) | 17 | }) |
17 | export class OAuthClientModel extends Model { | 18 | export class OAuthClientModel extends Model<Partial<AttributesOnly<OAuthClientModel>>> { |
18 | 19 | ||
19 | @AllowNull(false) | 20 | @AllowNull(false) |
20 | @Column | 21 | @Column |
diff --git a/server/models/oauth/oauth-token.ts b/server/models/oauth/oauth-token.ts index 27e643aa7..af4b0ec42 100644 --- a/server/models/oauth/oauth-token.ts +++ b/server/models/oauth/oauth-token.ts | |||
@@ -15,10 +15,11 @@ import { | |||
15 | import { TokensCache } from '@server/lib/auth/tokens-cache' | 15 | import { TokensCache } from '@server/lib/auth/tokens-cache' |
16 | import { MUserAccountId } from '@server/types/models' | 16 | import { MUserAccountId } from '@server/types/models' |
17 | import { MOAuthTokenUser } from '@server/types/models/oauth/oauth-token' | 17 | import { MOAuthTokenUser } from '@server/types/models/oauth/oauth-token' |
18 | import { AttributesOnly } from '@shared/core-utils' | ||
18 | import { logger } from '../../helpers/logger' | 19 | import { logger } from '../../helpers/logger' |
19 | import { AccountModel } from '../account/account' | 20 | import { AccountModel } from '../account/account' |
20 | import { UserModel } from '../account/user' | 21 | import { ActorModel } from '../actor/actor' |
21 | import { ActorModel } from '../activitypub/actor' | 22 | import { UserModel } from '../user/user' |
22 | import { OAuthClientModel } from './oauth-client' | 23 | import { OAuthClientModel } from './oauth-client' |
23 | 24 | ||
24 | export type OAuthTokenInfo = { | 25 | export type OAuthTokenInfo = { |
@@ -78,7 +79,7 @@ enum ScopeNames { | |||
78 | } | 79 | } |
79 | ] | 80 | ] |
80 | }) | 81 | }) |
81 | export class OAuthTokenModel extends Model { | 82 | export class OAuthTokenModel extends Model<Partial<AttributesOnly<OAuthTokenModel>>> { |
82 | 83 | ||
83 | @AllowNull(false) | 84 | @AllowNull(false) |
84 | @Column | 85 | @Column |
diff --git a/server/models/redundancy/video-redundancy.ts b/server/models/redundancy/video-redundancy.ts index 349dba513..ca56a57dc 100644 --- a/server/models/redundancy/video-redundancy.ts +++ b/server/models/redundancy/video-redundancy.ts | |||
@@ -16,6 +16,7 @@ import { | |||
16 | } from 'sequelize-typescript' | 16 | } from 'sequelize-typescript' |
17 | import { getServerActor } from '@server/models/application/application' | 17 | import { getServerActor } from '@server/models/application/application' |
18 | import { MActor, MVideoForRedundancyAPI, MVideoRedundancy, MVideoRedundancyAP, MVideoRedundancyVideo } from '@server/types/models' | 18 | import { MActor, MVideoForRedundancyAPI, MVideoRedundancy, MVideoRedundancyAP, MVideoRedundancyVideo } from '@server/types/models' |
19 | import { AttributesOnly } from '@shared/core-utils' | ||
19 | import { VideoRedundanciesTarget } from '@shared/models/redundancy/video-redundancies-filters.model' | 20 | import { VideoRedundanciesTarget } from '@shared/models/redundancy/video-redundancies-filters.model' |
20 | import { | 21 | import { |
21 | FileRedundancyInformation, | 22 | FileRedundancyInformation, |
@@ -29,7 +30,7 @@ import { isActivityPubUrlValid, isUrlValid } from '../../helpers/custom-validato | |||
29 | import { logger } from '../../helpers/logger' | 30 | import { logger } from '../../helpers/logger' |
30 | import { CONFIG } from '../../initializers/config' | 31 | import { CONFIG } from '../../initializers/config' |
31 | import { CONSTRAINTS_FIELDS, MIMETYPES } from '../../initializers/constants' | 32 | import { CONSTRAINTS_FIELDS, MIMETYPES } from '../../initializers/constants' |
32 | import { ActorModel } from '../activitypub/actor' | 33 | import { ActorModel } from '../actor/actor' |
33 | import { ServerModel } from '../server/server' | 34 | import { ServerModel } from '../server/server' |
34 | import { getSort, getVideoSort, parseAggregateResult, throwIfNotValid } from '../utils' | 35 | import { getSort, getVideoSort, parseAggregateResult, throwIfNotValid } from '../utils' |
35 | import { ScheduleVideoUpdateModel } from '../video/schedule-video-update' | 36 | import { ScheduleVideoUpdateModel } from '../video/schedule-video-update' |
@@ -84,7 +85,7 @@ export enum ScopeNames { | |||
84 | } | 85 | } |
85 | ] | 86 | ] |
86 | }) | 87 | }) |
87 | export class VideoRedundancyModel extends Model { | 88 | export class VideoRedundancyModel extends Model<Partial<AttributesOnly<VideoRedundancyModel>>> { |
88 | 89 | ||
89 | @CreatedAt | 90 | @CreatedAt |
90 | createdAt: Date | 91 | createdAt: Date |
diff --git a/server/models/server/plugin.ts b/server/models/server/plugin.ts index 80c8a6be5..a8de64dd4 100644 --- a/server/models/server/plugin.ts +++ b/server/models/server/plugin.ts | |||
@@ -1,9 +1,8 @@ | |||
1 | import { FindAndCountOptions, json, QueryTypes } from 'sequelize' | 1 | import { FindAndCountOptions, json, QueryTypes } from 'sequelize' |
2 | import { AllowNull, Column, CreatedAt, DataType, DefaultScope, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' | 2 | import { AllowNull, Column, CreatedAt, DataType, DefaultScope, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' |
3 | import { MPlugin, MPluginFormattable } from '@server/types/models' | 3 | import { MPlugin, MPluginFormattable } from '@server/types/models' |
4 | import { PeerTubePlugin } from '../../../shared/models/plugins/peertube-plugin.model' | 4 | import { AttributesOnly } from '@shared/core-utils' |
5 | import { PluginType } from '../../../shared/models/plugins/plugin.type' | 5 | import { PeerTubePlugin, PluginType, RegisterServerSettingOptions } from '../../../shared/models' |
6 | import { RegisterServerSettingOptions } from '../../../shared/models/plugins/register-server-setting.model' | ||
7 | import { | 6 | import { |
8 | isPluginDescriptionValid, | 7 | isPluginDescriptionValid, |
9 | isPluginHomepage, | 8 | isPluginHomepage, |
@@ -28,7 +27,7 @@ import { getSort, throwIfNotValid } from '../utils' | |||
28 | } | 27 | } |
29 | ] | 28 | ] |
30 | }) | 29 | }) |
31 | export class PluginModel extends Model { | 30 | export class PluginModel extends Model<Partial<AttributesOnly<PluginModel>>> { |
32 | 31 | ||
33 | @AllowNull(false) | 32 | @AllowNull(false) |
34 | @Is('PluginName', value => throwIfNotValid(value, isPluginNameValid, 'name')) | 33 | @Is('PluginName', value => throwIfNotValid(value, isPluginNameValid, 'name')) |
diff --git a/server/models/server/server-blocklist.ts b/server/models/server/server-blocklist.ts index 4dc236537..b3579d589 100644 --- a/server/models/server/server-blocklist.ts +++ b/server/models/server/server-blocklist.ts | |||
@@ -1,6 +1,7 @@ | |||
1 | import { Op } from 'sequelize' | 1 | import { Op } from 'sequelize' |
2 | import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' | 2 | import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' |
3 | import { MServerBlocklist, MServerBlocklistAccountServer, MServerBlocklistFormattable } from '@server/types/models' | 3 | import { MServerBlocklist, MServerBlocklistAccountServer, MServerBlocklistFormattable } from '@server/types/models' |
4 | import { AttributesOnly } from '@shared/core-utils' | ||
4 | import { ServerBlock } from '@shared/models' | 5 | import { ServerBlock } from '@shared/models' |
5 | import { AccountModel } from '../account/account' | 6 | import { AccountModel } from '../account/account' |
6 | import { getSort, searchAttribute } from '../utils' | 7 | import { getSort, searchAttribute } from '../utils' |
@@ -42,7 +43,7 @@ enum ScopeNames { | |||
42 | } | 43 | } |
43 | ] | 44 | ] |
44 | }) | 45 | }) |
45 | export class ServerBlocklistModel extends Model { | 46 | export class ServerBlocklistModel extends Model<Partial<AttributesOnly<ServerBlocklistModel>>> { |
46 | 47 | ||
47 | @CreatedAt | 48 | @CreatedAt |
48 | createdAt: Date | 49 | createdAt: Date |
diff --git a/server/models/server/server.ts b/server/models/server/server.ts index 0e58beeaf..25d9924fb 100644 --- a/server/models/server/server.ts +++ b/server/models/server/server.ts | |||
@@ -1,7 +1,8 @@ | |||
1 | import { AllowNull, Column, CreatedAt, Default, HasMany, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' | 1 | import { AllowNull, Column, CreatedAt, Default, HasMany, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' |
2 | import { MServer, MServerFormattable } from '@server/types/models/server' | 2 | import { MServer, MServerFormattable } from '@server/types/models/server' |
3 | import { AttributesOnly } from '@shared/core-utils' | ||
3 | import { isHostValid } from '../../helpers/custom-validators/servers' | 4 | import { isHostValid } from '../../helpers/custom-validators/servers' |
4 | import { ActorModel } from '../activitypub/actor' | 5 | import { ActorModel } from '../actor/actor' |
5 | import { throwIfNotValid } from '../utils' | 6 | import { throwIfNotValid } from '../utils' |
6 | import { ServerBlocklistModel } from './server-blocklist' | 7 | import { ServerBlocklistModel } from './server-blocklist' |
7 | 8 | ||
@@ -14,7 +15,7 @@ import { ServerBlocklistModel } from './server-blocklist' | |||
14 | } | 15 | } |
15 | ] | 16 | ] |
16 | }) | 17 | }) |
17 | export class ServerModel extends Model { | 18 | export class ServerModel extends Model<Partial<AttributesOnly<ServerModel>>> { |
18 | 19 | ||
19 | @AllowNull(false) | 20 | @AllowNull(false) |
20 | @Is('Host', value => throwIfNotValid(value, isHostValid, 'valid host')) | 21 | @Is('Host', value => throwIfNotValid(value, isHostValid, 'valid host')) |
diff --git a/server/models/server/tracker.ts b/server/models/server/tracker.ts index 97520f92d..c09fdd64b 100644 --- a/server/models/server/tracker.ts +++ b/server/models/server/tracker.ts | |||
@@ -1,6 +1,7 @@ | |||
1 | import { AllowNull, BelongsToMany, Column, CreatedAt, Model, Table, UpdatedAt } from 'sequelize-typescript' | 1 | import { AllowNull, BelongsToMany, Column, CreatedAt, Model, Table, UpdatedAt } from 'sequelize-typescript' |
2 | import { Transaction } from 'sequelize/types' | 2 | import { Transaction } from 'sequelize/types' |
3 | import { MTracker } from '@server/types/models/server/tracker' | 3 | import { MTracker } from '@server/types/models/server/tracker' |
4 | import { AttributesOnly } from '@shared/core-utils' | ||
4 | import { VideoModel } from '../video/video' | 5 | import { VideoModel } from '../video/video' |
5 | import { VideoTrackerModel } from './video-tracker' | 6 | import { VideoTrackerModel } from './video-tracker' |
6 | 7 | ||
@@ -13,7 +14,7 @@ import { VideoTrackerModel } from './video-tracker' | |||
13 | } | 14 | } |
14 | ] | 15 | ] |
15 | }) | 16 | }) |
16 | export class TrackerModel extends Model { | 17 | export class TrackerModel extends Model<Partial<AttributesOnly<TrackerModel>>> { |
17 | 18 | ||
18 | @AllowNull(false) | 19 | @AllowNull(false) |
19 | @Column | 20 | @Column |
diff --git a/server/models/server/video-tracker.ts b/server/models/server/video-tracker.ts index 367bf0117..c49fbd1c6 100644 --- a/server/models/server/video-tracker.ts +++ b/server/models/server/video-tracker.ts | |||
@@ -1,4 +1,5 @@ | |||
1 | import { Column, CreatedAt, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript' | 1 | import { Column, CreatedAt, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript' |
2 | import { AttributesOnly } from '@shared/core-utils' | ||
2 | import { VideoModel } from '../video/video' | 3 | import { VideoModel } from '../video/video' |
3 | import { TrackerModel } from './tracker' | 4 | import { TrackerModel } from './tracker' |
4 | 5 | ||
@@ -13,7 +14,7 @@ import { TrackerModel } from './tracker' | |||
13 | } | 14 | } |
14 | ] | 15 | ] |
15 | }) | 16 | }) |
16 | export class VideoTrackerModel extends Model { | 17 | export class VideoTrackerModel extends Model<Partial<AttributesOnly<VideoTrackerModel>>> { |
17 | @CreatedAt | 18 | @CreatedAt |
18 | createdAt: Date | 19 | createdAt: Date |
19 | 20 | ||
diff --git a/server/models/account/user-notification-setting.ts b/server/models/user/user-notification-setting.ts index 138051528..bee7d7851 100644 --- a/server/models/account/user-notification-setting.ts +++ b/server/models/user/user-notification-setting.ts | |||
@@ -14,6 +14,7 @@ import { | |||
14 | } from 'sequelize-typescript' | 14 | } from 'sequelize-typescript' |
15 | import { TokensCache } from '@server/lib/auth/tokens-cache' | 15 | import { TokensCache } from '@server/lib/auth/tokens-cache' |
16 | import { MNotificationSettingFormattable } from '@server/types/models' | 16 | import { MNotificationSettingFormattable } from '@server/types/models' |
17 | import { AttributesOnly } from '@shared/core-utils' | ||
17 | import { UserNotificationSetting, UserNotificationSettingValue } from '../../../shared/models/users/user-notification-setting.model' | 18 | import { UserNotificationSetting, UserNotificationSettingValue } from '../../../shared/models/users/user-notification-setting.model' |
18 | import { isUserNotificationSettingValid } from '../../helpers/custom-validators/user-notifications' | 19 | import { isUserNotificationSettingValid } from '../../helpers/custom-validators/user-notifications' |
19 | import { throwIfNotValid } from '../utils' | 20 | import { throwIfNotValid } from '../utils' |
@@ -28,7 +29,7 @@ import { UserModel } from './user' | |||
28 | } | 29 | } |
29 | ] | 30 | ] |
30 | }) | 31 | }) |
31 | export class UserNotificationSettingModel extends Model { | 32 | export class UserNotificationSettingModel extends Model<Partial<AttributesOnly<UserNotificationSettingModel>>> { |
32 | 33 | ||
33 | @AllowNull(false) | 34 | @AllowNull(false) |
34 | @Default(null) | 35 | @Default(null) |
diff --git a/server/models/account/user-notification.ts b/server/models/user/user-notification.ts index 805095002..a7f84e9ca 100644 --- a/server/models/account/user-notification.ts +++ b/server/models/user/user-notification.ts | |||
@@ -1,14 +1,17 @@ | |||
1 | import { FindOptions, ModelIndexesOptions, Op, WhereOptions } from 'sequelize' | 1 | import { FindOptions, ModelIndexesOptions, Op, WhereOptions } from 'sequelize' |
2 | import { AllowNull, BelongsTo, Column, CreatedAt, Default, ForeignKey, Is, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' | 2 | import { AllowNull, BelongsTo, Column, CreatedAt, Default, ForeignKey, Is, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' |
3 | import { UserNotificationIncludes, UserNotificationModelForApi } from '@server/types/models/user' | 3 | import { UserNotificationIncludes, UserNotificationModelForApi } from '@server/types/models/user' |
4 | import { AttributesOnly } from '@shared/core-utils' | ||
4 | import { UserNotification, UserNotificationType } from '../../../shared' | 5 | import { UserNotification, UserNotificationType } from '../../../shared' |
5 | import { isBooleanValid } from '../../helpers/custom-validators/misc' | 6 | import { isBooleanValid } from '../../helpers/custom-validators/misc' |
6 | import { isUserNotificationTypeValid } from '../../helpers/custom-validators/user-notifications' | 7 | import { isUserNotificationTypeValid } from '../../helpers/custom-validators/user-notifications' |
7 | import { AbuseModel } from '../abuse/abuse' | 8 | import { AbuseModel } from '../abuse/abuse' |
8 | import { VideoAbuseModel } from '../abuse/video-abuse' | 9 | import { VideoAbuseModel } from '../abuse/video-abuse' |
9 | import { VideoCommentAbuseModel } from '../abuse/video-comment-abuse' | 10 | import { VideoCommentAbuseModel } from '../abuse/video-comment-abuse' |
10 | import { ActorModel } from '../activitypub/actor' | 11 | import { AccountModel } from '../account/account' |
11 | import { ActorFollowModel } from '../activitypub/actor-follow' | 12 | import { ActorModel } from '../actor/actor' |
13 | import { ActorFollowModel } from '../actor/actor-follow' | ||
14 | import { ActorImageModel } from '../actor/actor-image' | ||
12 | import { ApplicationModel } from '../application/application' | 15 | import { ApplicationModel } from '../application/application' |
13 | import { PluginModel } from '../server/plugin' | 16 | import { PluginModel } from '../server/plugin' |
14 | import { ServerModel } from '../server/server' | 17 | import { ServerModel } from '../server/server' |
@@ -18,8 +21,6 @@ import { VideoBlacklistModel } from '../video/video-blacklist' | |||
18 | import { VideoChannelModel } from '../video/video-channel' | 21 | import { VideoChannelModel } from '../video/video-channel' |
19 | import { VideoCommentModel } from '../video/video-comment' | 22 | import { VideoCommentModel } from '../video/video-comment' |
20 | import { VideoImportModel } from '../video/video-import' | 23 | import { VideoImportModel } from '../video/video-import' |
21 | import { AccountModel } from './account' | ||
22 | import { ActorImageModel } from './actor-image' | ||
23 | import { UserModel } from './user' | 24 | import { UserModel } from './user' |
24 | 25 | ||
25 | enum ScopeNames { | 26 | enum ScopeNames { |
@@ -286,7 +287,7 @@ function buildAccountInclude (required: boolean, withActor = false) { | |||
286 | } | 287 | } |
287 | ] as (ModelIndexesOptions & { where?: WhereOptions })[] | 288 | ] as (ModelIndexesOptions & { where?: WhereOptions })[] |
288 | }) | 289 | }) |
289 | export class UserNotificationModel extends Model { | 290 | export class UserNotificationModel extends Model<Partial<AttributesOnly<UserNotificationModel>>> { |
290 | 291 | ||
291 | @AllowNull(false) | 292 | @AllowNull(false) |
292 | @Default(null) | 293 | @Default(null) |
diff --git a/server/models/account/user-video-history.ts b/server/models/user/user-video-history.ts index 6be1d65ea..e3dc4a062 100644 --- a/server/models/account/user-video-history.ts +++ b/server/models/user/user-video-history.ts | |||
@@ -1,8 +1,9 @@ | |||
1 | import { DestroyOptions, Op, Transaction } from 'sequelize' | ||
1 | import { AllowNull, BelongsTo, Column, CreatedAt, ForeignKey, IsInt, Model, Table, UpdatedAt } from 'sequelize-typescript' | 2 | import { AllowNull, BelongsTo, Column, CreatedAt, ForeignKey, IsInt, Model, Table, UpdatedAt } from 'sequelize-typescript' |
3 | import { MUserAccountId, MUserId } from '@server/types/models' | ||
4 | import { AttributesOnly } from '@shared/core-utils' | ||
2 | import { VideoModel } from '../video/video' | 5 | import { VideoModel } from '../video/video' |
3 | import { UserModel } from './user' | 6 | import { UserModel } from './user' |
4 | import { DestroyOptions, Op, Transaction } from 'sequelize' | ||
5 | import { MUserAccountId, MUserId } from '@server/types/models' | ||
6 | 7 | ||
7 | @Table({ | 8 | @Table({ |
8 | tableName: 'userVideoHistory', | 9 | tableName: 'userVideoHistory', |
@@ -19,7 +20,7 @@ import { MUserAccountId, MUserId } from '@server/types/models' | |||
19 | } | 20 | } |
20 | ] | 21 | ] |
21 | }) | 22 | }) |
22 | export class UserVideoHistoryModel extends Model { | 23 | export class UserVideoHistoryModel extends Model<Partial<AttributesOnly<UserVideoHistoryModel>>> { |
23 | @CreatedAt | 24 | @CreatedAt |
24 | createdAt: Date | 25 | createdAt: Date |
25 | 26 | ||
diff --git a/server/models/account/user.ts b/server/models/user/user.ts index 513455773..20696b1f4 100644 --- a/server/models/account/user.ts +++ b/server/models/user/user.ts | |||
@@ -31,6 +31,7 @@ import { | |||
31 | MUserWithNotificationSetting, | 31 | MUserWithNotificationSetting, |
32 | MVideoWithRights | 32 | MVideoWithRights |
33 | } from '@server/types/models' | 33 | } from '@server/types/models' |
34 | import { AttributesOnly } from '@shared/core-utils' | ||
34 | import { hasUserRight, USER_ROLE_LABELS } from '../../../shared/core-utils/users' | 35 | import { hasUserRight, USER_ROLE_LABELS } from '../../../shared/core-utils/users' |
35 | import { AbuseState, MyUser, UserRight, VideoPlaylistType, VideoPrivacy } from '../../../shared/models' | 36 | import { AbuseState, MyUser, UserRight, VideoPlaylistType, VideoPrivacy } from '../../../shared/models' |
36 | import { User, UserRole } from '../../../shared/models/users' | 37 | import { User, UserRole } from '../../../shared/models/users' |
@@ -60,8 +61,10 @@ import { | |||
60 | import { comparePassword, cryptPassword } from '../../helpers/peertube-crypto' | 61 | import { comparePassword, cryptPassword } from '../../helpers/peertube-crypto' |
61 | import { DEFAULT_USER_THEME_NAME, NSFW_POLICY_TYPES } from '../../initializers/constants' | 62 | import { DEFAULT_USER_THEME_NAME, NSFW_POLICY_TYPES } from '../../initializers/constants' |
62 | import { getThemeOrDefault } from '../../lib/plugins/theme-utils' | 63 | import { getThemeOrDefault } from '../../lib/plugins/theme-utils' |
63 | import { ActorModel } from '../activitypub/actor' | 64 | import { AccountModel } from '../account/account' |
64 | import { ActorFollowModel } from '../activitypub/actor-follow' | 65 | import { ActorModel } from '../actor/actor' |
66 | import { ActorFollowModel } from '../actor/actor-follow' | ||
67 | import { ActorImageModel } from '../actor/actor-image' | ||
65 | import { OAuthTokenModel } from '../oauth/oauth-token' | 68 | import { OAuthTokenModel } from '../oauth/oauth-token' |
66 | import { getSort, throwIfNotValid } from '../utils' | 69 | import { getSort, throwIfNotValid } from '../utils' |
67 | import { VideoModel } from '../video/video' | 70 | import { VideoModel } from '../video/video' |
@@ -69,9 +72,7 @@ import { VideoChannelModel } from '../video/video-channel' | |||
69 | import { VideoImportModel } from '../video/video-import' | 72 | import { VideoImportModel } from '../video/video-import' |
70 | import { VideoLiveModel } from '../video/video-live' | 73 | import { VideoLiveModel } from '../video/video-live' |
71 | import { VideoPlaylistModel } from '../video/video-playlist' | 74 | import { VideoPlaylistModel } from '../video/video-playlist' |
72 | import { AccountModel } from './account' | ||
73 | import { UserNotificationSettingModel } from './user-notification-setting' | 75 | import { UserNotificationSettingModel } from './user-notification-setting' |
74 | import { ActorImageModel } from './actor-image' | ||
75 | 76 | ||
76 | enum ScopeNames { | 77 | enum ScopeNames { |
77 | FOR_ME_API = 'FOR_ME_API', | 78 | FOR_ME_API = 'FOR_ME_API', |
@@ -233,7 +234,7 @@ enum ScopeNames { | |||
233 | } | 234 | } |
234 | ] | 235 | ] |
235 | }) | 236 | }) |
236 | export class UserModel extends Model { | 237 | export class UserModel extends Model<Partial<AttributesOnly<UserModel>>> { |
237 | 238 | ||
238 | @AllowNull(true) | 239 | @AllowNull(true) |
239 | @Is('UserPassword', value => throwIfNotValid(value, isUserPasswordValid, 'user password', true)) | 240 | @Is('UserPassword', value => throwIfNotValid(value, isUserPasswordValid, 'user password', true)) |
diff --git a/server/models/utils.ts b/server/models/utils.ts index ec51c66bf..e27625bc8 100644 --- a/server/models/utils.ts +++ b/server/models/utils.ts | |||
@@ -1,5 +1,4 @@ | |||
1 | import { literal, Op, OrderItem } from 'sequelize' | 1 | import { literal, Op, OrderItem, Sequelize } from 'sequelize' |
2 | import { Model, Sequelize } from 'sequelize-typescript' | ||
3 | import { Col } from 'sequelize/types/lib/utils' | 2 | import { Col } from 'sequelize/types/lib/utils' |
4 | import validator from 'validator' | 3 | import validator from 'validator' |
5 | 4 | ||
@@ -195,11 +194,11 @@ function parseAggregateResult (result: any) { | |||
195 | return total | 194 | return total |
196 | } | 195 | } |
197 | 196 | ||
198 | const createSafeIn = (model: typeof Model, stringArr: (string | number)[]) => { | 197 | function createSafeIn (sequelize: Sequelize, stringArr: (string | number)[]) { |
199 | return stringArr.map(t => { | 198 | return stringArr.map(t => { |
200 | return t === null | 199 | return t === null |
201 | ? null | 200 | ? null |
202 | : model.sequelize.escape('' + t) | 201 | : sequelize.escape('' + t) |
203 | }).join(', ') | 202 | }).join(', ') |
204 | } | 203 | } |
205 | 204 | ||
diff --git a/server/models/video/schedule-video-update.ts b/server/models/video/schedule-video-update.ts index 22b08e91a..b0952c431 100644 --- a/server/models/video/schedule-video-update.ts +++ b/server/models/video/schedule-video-update.ts | |||
@@ -1,8 +1,9 @@ | |||
1 | import { AllowNull, BelongsTo, Column, CreatedAt, Default, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript' | ||
2 | import { ScopeNames as VideoScopeNames, VideoModel } from './video' | ||
3 | import { VideoPrivacy } from '../../../shared/models/videos' | ||
4 | import { Op, Transaction } from 'sequelize' | 1 | import { Op, Transaction } from 'sequelize' |
2 | import { AllowNull, BelongsTo, Column, CreatedAt, Default, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript' | ||
5 | import { MScheduleVideoUpdateFormattable, MScheduleVideoUpdateVideoAll } from '@server/types/models' | 3 | import { MScheduleVideoUpdateFormattable, MScheduleVideoUpdateVideoAll } from '@server/types/models' |
4 | import { AttributesOnly } from '@shared/core-utils' | ||
5 | import { VideoPrivacy } from '../../../shared/models/videos' | ||
6 | import { ScopeNames as VideoScopeNames, VideoModel } from './video' | ||
6 | 7 | ||
7 | @Table({ | 8 | @Table({ |
8 | tableName: 'scheduleVideoUpdate', | 9 | tableName: 'scheduleVideoUpdate', |
@@ -16,7 +17,7 @@ import { MScheduleVideoUpdateFormattable, MScheduleVideoUpdateVideoAll } from '@ | |||
16 | } | 17 | } |
17 | ] | 18 | ] |
18 | }) | 19 | }) |
19 | export class ScheduleVideoUpdateModel extends Model { | 20 | export class ScheduleVideoUpdateModel extends Model<Partial<AttributesOnly<ScheduleVideoUpdateModel>>> { |
20 | 21 | ||
21 | @AllowNull(false) | 22 | @AllowNull(false) |
22 | @Default(null) | 23 | @Default(null) |
diff --git a/server/models/video/tag.ts b/server/models/video/tag.ts index d04205703..c1eebe27f 100644 --- a/server/models/video/tag.ts +++ b/server/models/video/tag.ts | |||
@@ -1,6 +1,7 @@ | |||
1 | import { col, fn, QueryTypes, Transaction } from 'sequelize' | 1 | import { col, fn, QueryTypes, Transaction } from 'sequelize' |
2 | import { AllowNull, BelongsToMany, Column, CreatedAt, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' | 2 | import { AllowNull, BelongsToMany, Column, CreatedAt, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' |
3 | import { MTag } from '@server/types/models' | 3 | import { MTag } from '@server/types/models' |
4 | import { AttributesOnly } from '@shared/core-utils' | ||
4 | import { VideoPrivacy, VideoState } from '../../../shared/models/videos' | 5 | import { VideoPrivacy, VideoState } from '../../../shared/models/videos' |
5 | import { isVideoTagValid } from '../../helpers/custom-validators/videos' | 6 | import { isVideoTagValid } from '../../helpers/custom-validators/videos' |
6 | import { throwIfNotValid } from '../utils' | 7 | import { throwIfNotValid } from '../utils' |
@@ -21,7 +22,7 @@ import { VideoTagModel } from './video-tag' | |||
21 | } | 22 | } |
22 | ] | 23 | ] |
23 | }) | 24 | }) |
24 | export class TagModel extends Model { | 25 | export class TagModel extends Model<Partial<AttributesOnly<TagModel>>> { |
25 | 26 | ||
26 | @AllowNull(false) | 27 | @AllowNull(false) |
27 | @Is('VideoTag', value => throwIfNotValid(value, isVideoTagValid, 'tag')) | 28 | @Is('VideoTag', value => throwIfNotValid(value, isVideoTagValid, 'tag')) |
diff --git a/server/models/video/thumbnail.ts b/server/models/video/thumbnail.ts index f1187c8d6..3388478d9 100644 --- a/server/models/video/thumbnail.ts +++ b/server/models/video/thumbnail.ts | |||
@@ -17,6 +17,7 @@ import { | |||
17 | } from 'sequelize-typescript' | 17 | } from 'sequelize-typescript' |
18 | import { afterCommitIfTransaction } from '@server/helpers/database-utils' | 18 | import { afterCommitIfTransaction } from '@server/helpers/database-utils' |
19 | import { MThumbnail, MThumbnailVideo, MVideo } from '@server/types/models' | 19 | import { MThumbnail, MThumbnailVideo, MVideo } from '@server/types/models' |
20 | import { AttributesOnly } from '@shared/core-utils' | ||
20 | import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type' | 21 | import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type' |
21 | import { logger } from '../../helpers/logger' | 22 | import { logger } from '../../helpers/logger' |
22 | import { CONFIG } from '../../initializers/config' | 23 | import { CONFIG } from '../../initializers/config' |
@@ -40,7 +41,7 @@ import { VideoPlaylistModel } from './video-playlist' | |||
40 | } | 41 | } |
41 | ] | 42 | ] |
42 | }) | 43 | }) |
43 | export class ThumbnailModel extends Model { | 44 | export class ThumbnailModel extends Model<Partial<AttributesOnly<ThumbnailModel>>> { |
44 | 45 | ||
45 | @AllowNull(false) | 46 | @AllowNull(false) |
46 | @Column | 47 | @Column |
diff --git a/server/models/video/video-blacklist.ts b/server/models/video/video-blacklist.ts index aa18896da..98f4ec9c5 100644 --- a/server/models/video/video-blacklist.ts +++ b/server/models/video/video-blacklist.ts | |||
@@ -1,6 +1,7 @@ | |||
1 | import { FindOptions } from 'sequelize' | 1 | import { FindOptions } from 'sequelize' |
2 | import { AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' | 2 | import { AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' |
3 | import { MVideoBlacklist, MVideoBlacklistFormattable } from '@server/types/models' | 3 | import { MVideoBlacklist, MVideoBlacklistFormattable } from '@server/types/models' |
4 | import { AttributesOnly } from '@shared/core-utils' | ||
4 | import { VideoBlacklist, VideoBlacklistType } from '../../../shared/models/videos' | 5 | import { VideoBlacklist, VideoBlacklistType } from '../../../shared/models/videos' |
5 | import { isVideoBlacklistReasonValid, isVideoBlacklistTypeValid } from '../../helpers/custom-validators/video-blacklist' | 6 | import { isVideoBlacklistReasonValid, isVideoBlacklistTypeValid } from '../../helpers/custom-validators/video-blacklist' |
6 | import { CONSTRAINTS_FIELDS } from '../../initializers/constants' | 7 | import { CONSTRAINTS_FIELDS } from '../../initializers/constants' |
@@ -18,7 +19,7 @@ import { ScopeNames as VideoChannelScopeNames, SummaryOptions, VideoChannelModel | |||
18 | } | 19 | } |
19 | ] | 20 | ] |
20 | }) | 21 | }) |
21 | export class VideoBlacklistModel extends Model { | 22 | export class VideoBlacklistModel extends Model<Partial<AttributesOnly<VideoBlacklistModel>>> { |
22 | 23 | ||
23 | @AllowNull(true) | 24 | @AllowNull(true) |
24 | @Is('VideoBlacklistReason', value => throwIfNotValid(value, isVideoBlacklistReasonValid, 'reason', true)) | 25 | @Is('VideoBlacklistReason', value => throwIfNotValid(value, isVideoBlacklistReasonValid, 'reason', true)) |
diff --git a/server/models/video/video-caption.ts b/server/models/video/video-caption.ts index bfdec73e9..d2c742b66 100644 --- a/server/models/video/video-caption.ts +++ b/server/models/video/video-caption.ts | |||
@@ -17,6 +17,7 @@ import { | |||
17 | } from 'sequelize-typescript' | 17 | } from 'sequelize-typescript' |
18 | import { v4 as uuidv4 } from 'uuid' | 18 | import { v4 as uuidv4 } from 'uuid' |
19 | import { MVideo, MVideoCaption, MVideoCaptionFormattable, MVideoCaptionVideo } from '@server/types/models' | 19 | import { MVideo, MVideoCaption, MVideoCaptionFormattable, MVideoCaptionVideo } from '@server/types/models' |
20 | import { AttributesOnly } from '@shared/core-utils' | ||
20 | import { VideoCaption } from '../../../shared/models/videos/caption/video-caption.model' | 21 | import { VideoCaption } from '../../../shared/models/videos/caption/video-caption.model' |
21 | import { isVideoCaptionLanguageValid } from '../../helpers/custom-validators/video-captions' | 22 | import { isVideoCaptionLanguageValid } from '../../helpers/custom-validators/video-captions' |
22 | import { logger } from '../../helpers/logger' | 23 | import { logger } from '../../helpers/logger' |
@@ -57,7 +58,7 @@ export enum ScopeNames { | |||
57 | } | 58 | } |
58 | ] | 59 | ] |
59 | }) | 60 | }) |
60 | export class VideoCaptionModel extends Model { | 61 | export class VideoCaptionModel extends Model<Partial<AttributesOnly<VideoCaptionModel>>> { |
61 | @CreatedAt | 62 | @CreatedAt |
62 | createdAt: Date | 63 | createdAt: Date |
63 | 64 | ||
diff --git a/server/models/video/video-change-ownership.ts b/server/models/video/video-change-ownership.ts index 298e8bfe2..7d20a954d 100644 --- a/server/models/video/video-change-ownership.ts +++ b/server/models/video/video-change-ownership.ts | |||
@@ -1,5 +1,6 @@ | |||
1 | import { AllowNull, BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' | 1 | import { AllowNull, BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' |
2 | import { MVideoChangeOwnershipFormattable, MVideoChangeOwnershipFull } from '@server/types/models/video/video-change-ownership' | 2 | import { MVideoChangeOwnershipFormattable, MVideoChangeOwnershipFull } from '@server/types/models/video/video-change-ownership' |
3 | import { AttributesOnly } from '@shared/core-utils' | ||
3 | import { VideoChangeOwnership, VideoChangeOwnershipStatus } from '../../../shared/models/videos' | 4 | import { VideoChangeOwnership, VideoChangeOwnershipStatus } from '../../../shared/models/videos' |
4 | import { AccountModel } from '../account/account' | 5 | import { AccountModel } from '../account/account' |
5 | import { getSort } from '../utils' | 6 | import { getSort } from '../utils' |
@@ -53,7 +54,7 @@ enum ScopeNames { | |||
53 | ] | 54 | ] |
54 | } | 55 | } |
55 | })) | 56 | })) |
56 | export class VideoChangeOwnershipModel extends Model { | 57 | export class VideoChangeOwnershipModel extends Model<Partial<AttributesOnly<VideoChangeOwnershipModel>>> { |
57 | @CreatedAt | 58 | @CreatedAt |
58 | createdAt: Date | 59 | createdAt: Date |
59 | 60 | ||
diff --git a/server/models/video/video-channel.ts b/server/models/video/video-channel.ts index 081b21f2d..8c4357009 100644 --- a/server/models/video/video-channel.ts +++ b/server/models/video/video-channel.ts | |||
@@ -19,6 +19,7 @@ import { | |||
19 | } from 'sequelize-typescript' | 19 | } from 'sequelize-typescript' |
20 | import { setAsUpdated } from '@server/helpers/database-utils' | 20 | import { setAsUpdated } from '@server/helpers/database-utils' |
21 | import { MAccountActor } from '@server/types/models' | 21 | import { MAccountActor } from '@server/types/models' |
22 | import { AttributesOnly } from '@shared/core-utils' | ||
22 | import { ActivityPubActor } from '../../../shared/models/activitypub' | 23 | import { ActivityPubActor } from '../../../shared/models/activitypub' |
23 | import { VideoChannel, VideoChannelSummary } from '../../../shared/models/videos' | 24 | import { VideoChannel, VideoChannelSummary } from '../../../shared/models/videos' |
24 | import { | 25 | import { |
@@ -36,9 +37,9 @@ import { | |||
36 | MChannelSummaryFormattable | 37 | MChannelSummaryFormattable |
37 | } from '../../types/models/video' | 38 | } from '../../types/models/video' |
38 | import { AccountModel, ScopeNames as AccountModelScopeNames, SummaryOptions as AccountSummaryOptions } from '../account/account' | 39 | import { AccountModel, ScopeNames as AccountModelScopeNames, SummaryOptions as AccountSummaryOptions } from '../account/account' |
39 | import { ActorImageModel } from '../account/actor-image' | 40 | import { ActorModel, unusedActorAttributesForAPI } from '../actor/actor' |
40 | import { ActorModel, unusedActorAttributesForAPI } from '../activitypub/actor' | 41 | import { ActorFollowModel } from '../actor/actor-follow' |
41 | import { ActorFollowModel } from '../activitypub/actor-follow' | 42 | import { ActorImageModel } from '../actor/actor-image' |
42 | import { ServerModel } from '../server/server' | 43 | import { ServerModel } from '../server/server' |
43 | import { buildServerIdsFollowedBy, buildTrigramSearchIndex, createSimilarityAttribute, getSort, throwIfNotValid } from '../utils' | 44 | import { buildServerIdsFollowedBy, buildTrigramSearchIndex, createSimilarityAttribute, getSort, throwIfNotValid } from '../utils' |
44 | import { VideoModel } from './video' | 45 | import { VideoModel } from './video' |
@@ -246,7 +247,7 @@ export type SummaryOptions = { | |||
246 | } | 247 | } |
247 | ] | 248 | ] |
248 | }) | 249 | }) |
249 | export class VideoChannelModel extends Model { | 250 | export class VideoChannelModel extends Model<Partial<AttributesOnly<VideoChannelModel>>> { |
250 | 251 | ||
251 | @AllowNull(false) | 252 | @AllowNull(false) |
252 | @Is('VideoChannelName', value => throwIfNotValid(value, isVideoChannelNameValid, 'name')) | 253 | @Is('VideoChannelName', value => throwIfNotValid(value, isVideoChannelNameValid, 'name')) |
diff --git a/server/models/video/video-comment.ts b/server/models/video/video-comment.ts index 151c2bc81..bdf5d86bc 100644 --- a/server/models/video/video-comment.ts +++ b/server/models/video/video-comment.ts | |||
@@ -16,10 +16,11 @@ import { | |||
16 | } from 'sequelize-typescript' | 16 | } from 'sequelize-typescript' |
17 | import { getServerActor } from '@server/models/application/application' | 17 | import { getServerActor } from '@server/models/application/application' |
18 | import { MAccount, MAccountId, MUserAccountId } from '@server/types/models' | 18 | import { MAccount, MAccountId, MUserAccountId } from '@server/types/models' |
19 | import { AttributesOnly } from '@shared/core-utils' | ||
19 | import { VideoPrivacy } from '@shared/models' | 20 | import { VideoPrivacy } from '@shared/models' |
20 | import { ActivityTagObject, ActivityTombstoneObject } from '../../../shared/models/activitypub/objects/common-objects' | 21 | import { ActivityTagObject, ActivityTombstoneObject } from '../../../shared/models/activitypub/objects/common-objects' |
21 | import { VideoCommentObject } from '../../../shared/models/activitypub/objects/video-comment-object' | 22 | import { VideoCommentObject } from '../../../shared/models/activitypub/objects/video-comment-object' |
22 | import { VideoComment, VideoCommentAdmin } from '../../../shared/models/videos/video-comment.model' | 23 | import { VideoComment, VideoCommentAdmin } from '../../../shared/models/videos/comment/video-comment.model' |
23 | import { actorNameAlphabet } from '../../helpers/custom-validators/activitypub/actor' | 24 | import { actorNameAlphabet } from '../../helpers/custom-validators/activitypub/actor' |
24 | import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' | 25 | import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' |
25 | import { regexpCapture } from '../../helpers/regexp' | 26 | import { regexpCapture } from '../../helpers/regexp' |
@@ -39,7 +40,7 @@ import { | |||
39 | } from '../../types/models/video' | 40 | } from '../../types/models/video' |
40 | import { VideoCommentAbuseModel } from '../abuse/video-comment-abuse' | 41 | import { VideoCommentAbuseModel } from '../abuse/video-comment-abuse' |
41 | import { AccountModel } from '../account/account' | 42 | import { AccountModel } from '../account/account' |
42 | import { ActorModel, unusedActorAttributesForAPI } from '../activitypub/actor' | 43 | import { ActorModel, unusedActorAttributesForAPI } from '../actor/actor' |
43 | import { | 44 | import { |
44 | buildBlockedAccountSQL, | 45 | buildBlockedAccountSQL, |
45 | buildBlockedAccountSQLOptimized, | 46 | buildBlockedAccountSQLOptimized, |
@@ -173,7 +174,7 @@ export enum ScopeNames { | |||
173 | } | 174 | } |
174 | ] | 175 | ] |
175 | }) | 176 | }) |
176 | export class VideoCommentModel extends Model { | 177 | export class VideoCommentModel extends Model<Partial<AttributesOnly<VideoCommentModel>>> { |
177 | @CreatedAt | 178 | @CreatedAt |
178 | createdAt: Date | 179 | createdAt: Date |
179 | 180 | ||
diff --git a/server/models/video/video-file.ts b/server/models/video/video-file.ts index 0b5946149..22cf63804 100644 --- a/server/models/video/video-file.ts +++ b/server/models/video/video-file.ts | |||
@@ -25,6 +25,7 @@ import { logger } from '@server/helpers/logger' | |||
25 | import { extractVideo } from '@server/helpers/video' | 25 | import { extractVideo } from '@server/helpers/video' |
26 | import { getTorrentFilePath } from '@server/lib/video-paths' | 26 | import { getTorrentFilePath } from '@server/lib/video-paths' |
27 | import { MStreamingPlaylistVideo, MVideo, MVideoWithHost } from '@server/types/models' | 27 | import { MStreamingPlaylistVideo, MVideo, MVideoWithHost } from '@server/types/models' |
28 | import { AttributesOnly } from '@shared/core-utils' | ||
28 | import { | 29 | import { |
29 | isVideoFileExtnameValid, | 30 | isVideoFileExtnameValid, |
30 | isVideoFileInfoHashValid, | 31 | isVideoFileInfoHashValid, |
@@ -149,7 +150,7 @@ export enum ScopeNames { | |||
149 | } | 150 | } |
150 | ] | 151 | ] |
151 | }) | 152 | }) |
152 | export class VideoFileModel extends Model { | 153 | export class VideoFileModel extends Model<Partial<AttributesOnly<VideoFileModel>>> { |
153 | @CreatedAt | 154 | @CreatedAt |
154 | createdAt: Date | 155 | createdAt: Date |
155 | 156 | ||
diff --git a/server/models/video/video-import.ts b/server/models/video/video-import.ts index 8324166cc..5c73fb07c 100644 --- a/server/models/video/video-import.ts +++ b/server/models/video/video-import.ts | |||
@@ -13,15 +13,16 @@ import { | |||
13 | Table, | 13 | Table, |
14 | UpdatedAt | 14 | UpdatedAt |
15 | } from 'sequelize-typescript' | 15 | } from 'sequelize-typescript' |
16 | import { afterCommitIfTransaction } from '@server/helpers/database-utils' | ||
16 | import { MVideoImportDefault, MVideoImportFormattable } from '@server/types/models/video/video-import' | 17 | import { MVideoImportDefault, MVideoImportFormattable } from '@server/types/models/video/video-import' |
18 | import { AttributesOnly } from '@shared/core-utils' | ||
17 | import { VideoImport, VideoImportState } from '../../../shared' | 19 | import { VideoImport, VideoImportState } from '../../../shared' |
18 | import { isVideoImportStateValid, isVideoImportTargetUrlValid } from '../../helpers/custom-validators/video-imports' | 20 | import { isVideoImportStateValid, isVideoImportTargetUrlValid } from '../../helpers/custom-validators/video-imports' |
19 | import { isVideoMagnetUriValid } from '../../helpers/custom-validators/videos' | 21 | import { isVideoMagnetUriValid } from '../../helpers/custom-validators/videos' |
20 | import { CONSTRAINTS_FIELDS, VIDEO_IMPORT_STATES } from '../../initializers/constants' | 22 | import { CONSTRAINTS_FIELDS, VIDEO_IMPORT_STATES } from '../../initializers/constants' |
21 | import { UserModel } from '../account/user' | 23 | import { UserModel } from '../user/user' |
22 | import { getSort, throwIfNotValid } from '../utils' | 24 | import { getSort, throwIfNotValid } from '../utils' |
23 | import { ScopeNames as VideoModelScopeNames, VideoModel } from './video' | 25 | import { ScopeNames as VideoModelScopeNames, VideoModel } from './video' |
24 | import { afterCommitIfTransaction } from '@server/helpers/database-utils' | ||
25 | 26 | ||
26 | @DefaultScope(() => ({ | 27 | @DefaultScope(() => ({ |
27 | include: [ | 28 | include: [ |
@@ -52,7 +53,7 @@ import { afterCommitIfTransaction } from '@server/helpers/database-utils' | |||
52 | } | 53 | } |
53 | ] | 54 | ] |
54 | }) | 55 | }) |
55 | export class VideoImportModel extends Model { | 56 | export class VideoImportModel extends Model<Partial<AttributesOnly<VideoImportModel>>> { |
56 | @CreatedAt | 57 | @CreatedAt |
57 | createdAt: Date | 58 | createdAt: Date |
58 | 59 | ||
diff --git a/server/models/video/video-live.ts b/server/models/video/video-live.ts index cb4a9b896..014491d50 100644 --- a/server/models/video/video-live.ts +++ b/server/models/video/video-live.ts | |||
@@ -1,6 +1,7 @@ | |||
1 | import { AllowNull, BelongsTo, Column, CreatedAt, DataType, DefaultScope, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript' | 1 | import { AllowNull, BelongsTo, Column, CreatedAt, DataType, DefaultScope, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript' |
2 | import { WEBSERVER } from '@server/initializers/constants' | 2 | import { WEBSERVER } from '@server/initializers/constants' |
3 | import { MVideoLive, MVideoLiveVideo } from '@server/types/models' | 3 | import { MVideoLive, MVideoLiveVideo } from '@server/types/models' |
4 | import { AttributesOnly } from '@shared/core-utils' | ||
4 | import { LiveVideo, VideoState } from '@shared/models' | 5 | import { LiveVideo, VideoState } from '@shared/models' |
5 | import { VideoModel } from './video' | 6 | import { VideoModel } from './video' |
6 | import { VideoBlacklistModel } from './video-blacklist' | 7 | import { VideoBlacklistModel } from './video-blacklist' |
@@ -28,7 +29,7 @@ import { VideoBlacklistModel } from './video-blacklist' | |||
28 | } | 29 | } |
29 | ] | 30 | ] |
30 | }) | 31 | }) |
31 | export class VideoLiveModel extends Model { | 32 | export class VideoLiveModel extends Model<Partial<AttributesOnly<VideoLiveModel>>> { |
32 | 33 | ||
33 | @AllowNull(true) | 34 | @AllowNull(true) |
34 | @Column(DataType.STRING) | 35 | @Column(DataType.STRING) |
diff --git a/server/models/video/video-playlist-element.ts b/server/models/video/video-playlist-element.ts index d2d7e2740..e6906cb19 100644 --- a/server/models/video/video-playlist-element.ts +++ b/server/models/video/video-playlist-element.ts | |||
@@ -32,6 +32,7 @@ import { AccountModel } from '../account/account' | |||
32 | import { getSort, throwIfNotValid } from '../utils' | 32 | import { getSort, throwIfNotValid } from '../utils' |
33 | import { ForAPIOptions, ScopeNames as VideoScopeNames, VideoModel } from './video' | 33 | import { ForAPIOptions, ScopeNames as VideoScopeNames, VideoModel } from './video' |
34 | import { VideoPlaylistModel } from './video-playlist' | 34 | import { VideoPlaylistModel } from './video-playlist' |
35 | import { AttributesOnly } from '@shared/core-utils' | ||
35 | 36 | ||
36 | @Table({ | 37 | @Table({ |
37 | tableName: 'videoPlaylistElement', | 38 | tableName: 'videoPlaylistElement', |
@@ -48,7 +49,7 @@ import { VideoPlaylistModel } from './video-playlist' | |||
48 | } | 49 | } |
49 | ] | 50 | ] |
50 | }) | 51 | }) |
51 | export class VideoPlaylistElementModel extends Model { | 52 | export class VideoPlaylistElementModel extends Model<Partial<AttributesOnly<VideoPlaylistElementModel>>> { |
52 | @CreatedAt | 53 | @CreatedAt |
53 | createdAt: Date | 54 | createdAt: Date |
54 | 55 | ||
@@ -274,7 +275,8 @@ export class VideoPlaylistElementModel extends Model { | |||
274 | validate: false // We use a literal to update the position | 275 | validate: false // We use a literal to update the position |
275 | } | 276 | } |
276 | 277 | ||
277 | return VideoPlaylistElementModel.update({ position: Sequelize.literal(`${newPosition} + "position" - ${firstPosition}`) }, query) | 278 | const positionQuery = Sequelize.literal(`${newPosition} + "position" - ${firstPosition}`) |
279 | return VideoPlaylistElementModel.update({ position: positionQuery as any }, query) | ||
278 | } | 280 | } |
279 | 281 | ||
280 | static increasePositionOf ( | 282 | static increasePositionOf ( |
diff --git a/server/models/video/video-playlist.ts b/server/models/video/video-playlist.ts index efe5be36d..98cea1b64 100644 --- a/server/models/video/video-playlist.ts +++ b/server/models/video/video-playlist.ts | |||
@@ -19,6 +19,7 @@ import { | |||
19 | } from 'sequelize-typescript' | 19 | } from 'sequelize-typescript' |
20 | import { v4 as uuidv4 } from 'uuid' | 20 | import { v4 as uuidv4 } from 'uuid' |
21 | import { MAccountId, MChannelId } from '@server/types/models' | 21 | import { MAccountId, MChannelId } from '@server/types/models' |
22 | import { AttributesOnly } from '@shared/core-utils' | ||
22 | import { ActivityIconObject } from '../../../shared/models/activitypub/objects' | 23 | import { ActivityIconObject } from '../../../shared/models/activitypub/objects' |
23 | import { PlaylistObject } from '../../../shared/models/activitypub/objects/playlist-object' | 24 | import { PlaylistObject } from '../../../shared/models/activitypub/objects/playlist-object' |
24 | import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/video-playlist-privacy.model' | 25 | import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/video-playlist-privacy.model' |
@@ -50,11 +51,11 @@ import { | |||
50 | MVideoPlaylistIdWithElements | 51 | MVideoPlaylistIdWithElements |
51 | } from '../../types/models/video/video-playlist' | 52 | } from '../../types/models/video/video-playlist' |
52 | import { AccountModel, ScopeNames as AccountScopeNames, SummaryOptions } from '../account/account' | 53 | import { AccountModel, ScopeNames as AccountScopeNames, SummaryOptions } from '../account/account' |
54 | import { ActorModel } from '../actor/actor' | ||
53 | import { buildServerIdsFollowedBy, buildWhereIdOrUUID, getPlaylistSort, isOutdated, throwIfNotValid } from '../utils' | 55 | import { buildServerIdsFollowedBy, buildWhereIdOrUUID, getPlaylistSort, isOutdated, throwIfNotValid } from '../utils' |
54 | import { ThumbnailModel } from './thumbnail' | 56 | import { ThumbnailModel } from './thumbnail' |
55 | import { ScopeNames as VideoChannelScopeNames, VideoChannelModel } from './video-channel' | 57 | import { ScopeNames as VideoChannelScopeNames, VideoChannelModel } from './video-channel' |
56 | import { VideoPlaylistElementModel } from './video-playlist-element' | 58 | import { VideoPlaylistElementModel } from './video-playlist-element' |
57 | import { ActorModel } from '../activitypub/actor' | ||
58 | 59 | ||
59 | enum ScopeNames { | 60 | enum ScopeNames { |
60 | AVAILABLE_FOR_LIST = 'AVAILABLE_FOR_LIST', | 61 | AVAILABLE_FOR_LIST = 'AVAILABLE_FOR_LIST', |
@@ -221,7 +222,7 @@ type AvailableForListOptions = { | |||
221 | } | 222 | } |
222 | ] | 223 | ] |
223 | }) | 224 | }) |
224 | export class VideoPlaylistModel extends Model { | 225 | export class VideoPlaylistModel extends Model<Partial<AttributesOnly<VideoPlaylistModel>>> { |
225 | @CreatedAt | 226 | @CreatedAt |
226 | createdAt: Date | 227 | createdAt: Date |
227 | 228 | ||
@@ -495,7 +496,7 @@ export class VideoPlaylistModel extends Model { | |||
495 | } | 496 | } |
496 | 497 | ||
497 | getWatchUrl () { | 498 | getWatchUrl () { |
498 | return WEBSERVER.URL + '/videos/watch/playlist/' + this.uuid | 499 | return WEBSERVER.URL + '/w/p/' + this.uuid |
499 | } | 500 | } |
500 | 501 | ||
501 | getEmbedStaticPath () { | 502 | getEmbedStaticPath () { |
diff --git a/server/models/video/video-query-builder.ts b/server/models/video/video-query-builder.ts index 155afe64b..2aa5e65c8 100644 --- a/server/models/video/video-query-builder.ts +++ b/server/models/video/video-query-builder.ts | |||
@@ -1,9 +1,9 @@ | |||
1 | import { VideoFilter, VideoPrivacy, VideoState } from '@shared/models' | 1 | import { Sequelize } from 'sequelize/types' |
2 | import { buildDirectionAndField, createSafeIn } from '@server/models/utils' | ||
3 | import { Model } from 'sequelize-typescript' | ||
4 | import { MUserAccountId, MUserId } from '@server/types/models' | ||
5 | import validator from 'validator' | 2 | import validator from 'validator' |
6 | import { exists } from '@server/helpers/custom-validators/misc' | 3 | import { exists } from '@server/helpers/custom-validators/misc' |
4 | import { buildDirectionAndField, createSafeIn } from '@server/models/utils' | ||
5 | import { MUserAccountId, MUserId } from '@server/types/models' | ||
6 | import { VideoFilter, VideoPrivacy, VideoState } from '@shared/models' | ||
7 | 7 | ||
8 | export type BuildVideosQueryOptions = { | 8 | export type BuildVideosQueryOptions = { |
9 | attributes?: string[] | 9 | attributes?: string[] |
@@ -55,7 +55,7 @@ export type BuildVideosQueryOptions = { | |||
55 | having?: string | 55 | having?: string |
56 | } | 56 | } |
57 | 57 | ||
58 | function buildListQuery (model: typeof Model, options: BuildVideosQueryOptions) { | 58 | function buildListQuery (sequelize: Sequelize, options: BuildVideosQueryOptions) { |
59 | const and: string[] = [] | 59 | const and: string[] = [] |
60 | const joins: string[] = [] | 60 | const joins: string[] = [] |
61 | const replacements: any = {} | 61 | const replacements: any = {} |
@@ -77,7 +77,7 @@ function buildListQuery (model: typeof Model, options: BuildVideosQueryOptions) | |||
77 | const blockerIds = [ options.serverAccountId ] | 77 | const blockerIds = [ options.serverAccountId ] |
78 | if (options.user) blockerIds.push(options.user.Account.id) | 78 | if (options.user) blockerIds.push(options.user.Account.id) |
79 | 79 | ||
80 | const inClause = createSafeIn(model, blockerIds) | 80 | const inClause = createSafeIn(sequelize, blockerIds) |
81 | 81 | ||
82 | and.push( | 82 | and.push( |
83 | 'NOT EXISTS (' + | 83 | 'NOT EXISTS (' + |
@@ -179,7 +179,7 @@ function buildListQuery (model: typeof Model, options: BuildVideosQueryOptions) | |||
179 | 'EXISTS (' + | 179 | 'EXISTS (' + |
180 | ' SELECT 1 FROM "videoTag" ' + | 180 | ' SELECT 1 FROM "videoTag" ' + |
181 | ' INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' + | 181 | ' INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' + |
182 | ' WHERE lower("tag"."name") IN (' + createSafeIn(model, tagsOneOfLower) + ') ' + | 182 | ' WHERE lower("tag"."name") IN (' + createSafeIn(sequelize, tagsOneOfLower) + ') ' + |
183 | ' AND "video"."id" = "videoTag"."videoId"' + | 183 | ' AND "video"."id" = "videoTag"."videoId"' + |
184 | ')' | 184 | ')' |
185 | ) | 185 | ) |
@@ -192,7 +192,7 @@ function buildListQuery (model: typeof Model, options: BuildVideosQueryOptions) | |||
192 | 'EXISTS (' + | 192 | 'EXISTS (' + |
193 | ' SELECT 1 FROM "videoTag" ' + | 193 | ' SELECT 1 FROM "videoTag" ' + |
194 | ' INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' + | 194 | ' INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' + |
195 | ' WHERE lower("tag"."name") IN (' + createSafeIn(model, tagsAllOfLower) + ') ' + | 195 | ' WHERE lower("tag"."name") IN (' + createSafeIn(sequelize, tagsAllOfLower) + ') ' + |
196 | ' AND "video"."id" = "videoTag"."videoId" ' + | 196 | ' AND "video"."id" = "videoTag"."videoId" ' + |
197 | ' GROUP BY "videoTag"."videoId" HAVING COUNT(*) = ' + tagsAllOfLower.length + | 197 | ' GROUP BY "videoTag"."videoId" HAVING COUNT(*) = ' + tagsAllOfLower.length + |
198 | ')' | 198 | ')' |
@@ -232,7 +232,7 @@ function buildListQuery (model: typeof Model, options: BuildVideosQueryOptions) | |||
232 | languagesQueryParts.push( | 232 | languagesQueryParts.push( |
233 | 'EXISTS (' + | 233 | 'EXISTS (' + |
234 | ' SELECT 1 FROM "videoCaption" WHERE "videoCaption"."language" ' + | 234 | ' SELECT 1 FROM "videoCaption" WHERE "videoCaption"."language" ' + |
235 | ' IN (' + createSafeIn(model, languages) + ') AND ' + | 235 | ' IN (' + createSafeIn(sequelize, languages) + ') AND ' + |
236 | ' "videoCaption"."videoId" = "video"."id"' + | 236 | ' "videoCaption"."videoId" = "video"."id"' + |
237 | ')' | 237 | ')' |
238 | ) | 238 | ) |
@@ -345,8 +345,8 @@ function buildListQuery (model: typeof Model, options: BuildVideosQueryOptions) | |||
345 | } | 345 | } |
346 | 346 | ||
347 | if (options.search) { | 347 | if (options.search) { |
348 | const escapedSearch = model.sequelize.escape(options.search) | 348 | const escapedSearch = sequelize.escape(options.search) |
349 | const escapedLikeSearch = model.sequelize.escape('%' + options.search + '%') | 349 | const escapedLikeSearch = sequelize.escape('%' + options.search + '%') |
350 | 350 | ||
351 | cte.push( | 351 | cte.push( |
352 | '"trigramSearch" AS (' + | 352 | '"trigramSearch" AS (' + |
diff --git a/server/models/video/video-share.ts b/server/models/video/video-share.ts index 5059c1fa6..505c305e2 100644 --- a/server/models/video/video-share.ts +++ b/server/models/video/video-share.ts | |||
@@ -1,10 +1,11 @@ | |||
1 | import { literal, Op, QueryTypes, Transaction } from 'sequelize' | 1 | import { literal, Op, QueryTypes, Transaction } from 'sequelize' |
2 | import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Is, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' | 2 | import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Is, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' |
3 | import { AttributesOnly } from '@shared/core-utils' | ||
3 | import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' | 4 | import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' |
4 | import { CONSTRAINTS_FIELDS } from '../../initializers/constants' | 5 | import { CONSTRAINTS_FIELDS } from '../../initializers/constants' |
5 | import { MActorDefault } from '../../types/models' | 6 | import { MActorDefault } from '../../types/models' |
6 | import { MVideoShareActor, MVideoShareFull } from '../../types/models/video' | 7 | import { MVideoShareActor, MVideoShareFull } from '../../types/models/video' |
7 | import { ActorModel } from '../activitypub/actor' | 8 | import { ActorModel } from '../actor/actor' |
8 | import { buildLocalActorIdsIn, throwIfNotValid } from '../utils' | 9 | import { buildLocalActorIdsIn, throwIfNotValid } from '../utils' |
9 | import { VideoModel } from './video' | 10 | import { VideoModel } from './video' |
10 | 11 | ||
@@ -50,7 +51,7 @@ enum ScopeNames { | |||
50 | } | 51 | } |
51 | ] | 52 | ] |
52 | }) | 53 | }) |
53 | export class VideoShareModel extends Model { | 54 | export class VideoShareModel extends Model<Partial<AttributesOnly<VideoShareModel>>> { |
54 | 55 | ||
55 | @AllowNull(false) | 56 | @AllowNull(false) |
56 | @Is('VideoShareUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'url')) | 57 | @Is('VideoShareUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'url')) |
diff --git a/server/models/video/video-streaming-playlist.ts b/server/models/video/video-streaming-playlist.ts index c9375b433..d627e8c9d 100644 --- a/server/models/video/video-streaming-playlist.ts +++ b/server/models/video/video-streaming-playlist.ts | |||
@@ -13,6 +13,7 @@ import { CONSTRAINTS_FIELDS, MEMOIZE_LENGTH, MEMOIZE_TTL, P2P_MEDIA_LOADER_PEER_ | |||
13 | import { VideoRedundancyModel } from '../redundancy/video-redundancy' | 13 | import { VideoRedundancyModel } from '../redundancy/video-redundancy' |
14 | import { throwIfNotValid } from '../utils' | 14 | import { throwIfNotValid } from '../utils' |
15 | import { VideoModel } from './video' | 15 | import { VideoModel } from './video' |
16 | import { AttributesOnly } from '@shared/core-utils' | ||
16 | 17 | ||
17 | @Table({ | 18 | @Table({ |
18 | tableName: 'videoStreamingPlaylist', | 19 | tableName: 'videoStreamingPlaylist', |
@@ -30,7 +31,7 @@ import { VideoModel } from './video' | |||
30 | } | 31 | } |
31 | ] | 32 | ] |
32 | }) | 33 | }) |
33 | export class VideoStreamingPlaylistModel extends Model { | 34 | export class VideoStreamingPlaylistModel extends Model<Partial<AttributesOnly<VideoStreamingPlaylistModel>>> { |
34 | @CreatedAt | 35 | @CreatedAt |
35 | createdAt: Date | 36 | createdAt: Date |
36 | 37 | ||
diff --git a/server/models/video/video-tag.ts b/server/models/video/video-tag.ts index 5052b8c4d..1285d375b 100644 --- a/server/models/video/video-tag.ts +++ b/server/models/video/video-tag.ts | |||
@@ -1,4 +1,5 @@ | |||
1 | import { Column, CreatedAt, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript' | 1 | import { Column, CreatedAt, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript' |
2 | import { AttributesOnly } from '@shared/core-utils' | ||
2 | import { TagModel } from './tag' | 3 | import { TagModel } from './tag' |
3 | import { VideoModel } from './video' | 4 | import { VideoModel } from './video' |
4 | 5 | ||
@@ -13,7 +14,7 @@ import { VideoModel } from './video' | |||
13 | } | 14 | } |
14 | ] | 15 | ] |
15 | }) | 16 | }) |
16 | export class VideoTagModel extends Model { | 17 | export class VideoTagModel extends Model<Partial<AttributesOnly<VideoTagModel>>> { |
17 | @CreatedAt | 18 | @CreatedAt |
18 | createdAt: Date | 19 | createdAt: Date |
19 | 20 | ||
diff --git a/server/models/video/video-view.ts b/server/models/video/video-view.ts index 992cf258a..dfc6296ce 100644 --- a/server/models/video/video-view.ts +++ b/server/models/video/video-view.ts | |||
@@ -1,6 +1,7 @@ | |||
1 | import * as Sequelize from 'sequelize' | ||
1 | import { AllowNull, BelongsTo, Column, CreatedAt, ForeignKey, Model, Table } from 'sequelize-typescript' | 2 | import { AllowNull, BelongsTo, Column, CreatedAt, ForeignKey, Model, Table } from 'sequelize-typescript' |
3 | import { AttributesOnly } from '@shared/core-utils' | ||
2 | import { VideoModel } from './video' | 4 | import { VideoModel } from './video' |
3 | import * as Sequelize from 'sequelize' | ||
4 | 5 | ||
5 | @Table({ | 6 | @Table({ |
6 | tableName: 'videoView', | 7 | tableName: 'videoView', |
@@ -14,7 +15,7 @@ import * as Sequelize from 'sequelize' | |||
14 | } | 15 | } |
15 | ] | 16 | ] |
16 | }) | 17 | }) |
17 | export class VideoViewModel extends Model { | 18 | export class VideoViewModel extends Model<Partial<AttributesOnly<VideoViewModel>>> { |
18 | @CreatedAt | 19 | @CreatedAt |
19 | createdAt: Date | 20 | createdAt: Date |
20 | 21 | ||
diff --git a/server/models/video/video.ts b/server/models/video/video.ts index 18afba1ba..8f561116b 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts | |||
@@ -31,6 +31,7 @@ import { LiveManager } from '@server/lib/live-manager' | |||
31 | import { getHLSDirectory, getVideoFilePath } from '@server/lib/video-paths' | 31 | import { getHLSDirectory, getVideoFilePath } from '@server/lib/video-paths' |
32 | import { getServerActor } from '@server/models/application/application' | 32 | import { getServerActor } from '@server/models/application/application' |
33 | import { ModelCache } from '@server/models/model-cache' | 33 | import { ModelCache } from '@server/models/model-cache' |
34 | import { AttributesOnly } from '@shared/core-utils' | ||
34 | import { VideoFile } from '@shared/models/videos/video-file.model' | 35 | import { VideoFile } from '@shared/models/videos/video-file.model' |
35 | import { ResultList, UserRight, VideoPrivacy, VideoState } from '../../../shared' | 36 | import { ResultList, UserRight, VideoPrivacy, VideoState } from '../../../shared' |
36 | import { VideoObject } from '../../../shared/models/activitypub/objects' | 37 | import { VideoObject } from '../../../shared/models/activitypub/objects' |
@@ -100,14 +101,14 @@ import { MVideoFile, MVideoFileStreamingPlaylistVideo } from '../../types/models | |||
100 | import { VideoAbuseModel } from '../abuse/video-abuse' | 101 | import { VideoAbuseModel } from '../abuse/video-abuse' |
101 | import { AccountModel } from '../account/account' | 102 | import { AccountModel } from '../account/account' |
102 | import { AccountVideoRateModel } from '../account/account-video-rate' | 103 | import { AccountVideoRateModel } from '../account/account-video-rate' |
103 | import { ActorImageModel } from '../account/actor-image' | 104 | import { ActorModel } from '../actor/actor' |
104 | import { UserModel } from '../account/user' | 105 | import { ActorImageModel } from '../actor/actor-image' |
105 | import { UserVideoHistoryModel } from '../account/user-video-history' | ||
106 | import { ActorModel } from '../activitypub/actor' | ||
107 | import { VideoRedundancyModel } from '../redundancy/video-redundancy' | 106 | import { VideoRedundancyModel } from '../redundancy/video-redundancy' |
108 | import { ServerModel } from '../server/server' | 107 | import { ServerModel } from '../server/server' |
109 | import { TrackerModel } from '../server/tracker' | 108 | import { TrackerModel } from '../server/tracker' |
110 | import { VideoTrackerModel } from '../server/video-tracker' | 109 | import { VideoTrackerModel } from '../server/video-tracker' |
110 | import { UserModel } from '../user/user' | ||
111 | import { UserVideoHistoryModel } from '../user/user-video-history' | ||
111 | import { buildTrigramSearchIndex, buildWhereIdOrUUID, getVideoSort, isOutdated, throwIfNotValid } from '../utils' | 112 | import { buildTrigramSearchIndex, buildWhereIdOrUUID, getVideoSort, isOutdated, throwIfNotValid } from '../utils' |
112 | import { ScheduleVideoUpdateModel } from './schedule-video-update' | 113 | import { ScheduleVideoUpdateModel } from './schedule-video-update' |
113 | import { TagModel } from './tag' | 114 | import { TagModel } from './tag' |
@@ -489,7 +490,7 @@ export type AvailableForListIDsOptions = { | |||
489 | } | 490 | } |
490 | ] | 491 | ] |
491 | }) | 492 | }) |
492 | export class VideoModel extends Model { | 493 | export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> { |
493 | 494 | ||
494 | @AllowNull(false) | 495 | @AllowNull(false) |
495 | @Default(DataType.UUIDV4) | 496 | @Default(DataType.UUIDV4) |
@@ -1003,9 +1004,9 @@ export class VideoModel extends Model { | |||
1003 | }) | 1004 | }) |
1004 | } | 1005 | } |
1005 | 1006 | ||
1006 | static async listPublishedLiveIds () { | 1007 | static async listPublishedLiveUUIDs () { |
1007 | const options = { | 1008 | const options = { |
1008 | attributes: [ 'id' ], | 1009 | attributes: [ 'uuid' ], |
1009 | where: { | 1010 | where: { |
1010 | isLive: true, | 1011 | isLive: true, |
1011 | remote: false, | 1012 | remote: false, |
@@ -1015,7 +1016,7 @@ export class VideoModel extends Model { | |||
1015 | 1016 | ||
1016 | const result = await VideoModel.findAll(options) | 1017 | const result = await VideoModel.findAll(options) |
1017 | 1018 | ||
1018 | return result.map(v => v.id) | 1019 | return result.map(v => v.uuid) |
1019 | } | 1020 | } |
1020 | 1021 | ||
1021 | static listUserVideosForApi (options: { | 1022 | static listUserVideosForApi (options: { |
@@ -1618,7 +1619,7 @@ export class VideoModel extends Model { | |||
1618 | includeLocalVideos: true | 1619 | includeLocalVideos: true |
1619 | } | 1620 | } |
1620 | 1621 | ||
1621 | const { query, replacements } = buildListQuery(VideoModel, queryOptions) | 1622 | const { query, replacements } = buildListQuery(VideoModel.sequelize, queryOptions) |
1622 | 1623 | ||
1623 | return this.sequelize.query<any>(query, { replacements, type: QueryTypes.SELECT }) | 1624 | return this.sequelize.query<any>(query, { replacements, type: QueryTypes.SELECT }) |
1624 | .then(rows => rows.map(r => r[field])) | 1625 | .then(rows => rows.map(r => r[field])) |
@@ -1646,7 +1647,7 @@ export class VideoModel extends Model { | |||
1646 | if (countVideos !== true) return Promise.resolve(undefined) | 1647 | if (countVideos !== true) return Promise.resolve(undefined) |
1647 | 1648 | ||
1648 | const countOptions = Object.assign({}, options, { isCount: true }) | 1649 | const countOptions = Object.assign({}, options, { isCount: true }) |
1649 | const { query: queryCount, replacements: replacementsCount } = buildListQuery(VideoModel, countOptions) | 1650 | const { query: queryCount, replacements: replacementsCount } = buildListQuery(VideoModel.sequelize, countOptions) |
1650 | 1651 | ||
1651 | return VideoModel.sequelize.query<any>(queryCount, { replacements: replacementsCount, type: QueryTypes.SELECT }) | 1652 | return VideoModel.sequelize.query<any>(queryCount, { replacements: replacementsCount, type: QueryTypes.SELECT }) |
1652 | .then(rows => rows.length !== 0 ? rows[0].total : 0) | 1653 | .then(rows => rows.length !== 0 ? rows[0].total : 0) |
@@ -1655,7 +1656,7 @@ export class VideoModel extends Model { | |||
1655 | function getModels () { | 1656 | function getModels () { |
1656 | if (options.count === 0) return Promise.resolve([]) | 1657 | if (options.count === 0) return Promise.resolve([]) |
1657 | 1658 | ||
1658 | const { query, replacements, order } = buildListQuery(VideoModel, options) | 1659 | const { query, replacements, order } = buildListQuery(VideoModel.sequelize, options) |
1659 | const queryModels = wrapForAPIResults(query, replacements, options, order) | 1660 | const queryModels = wrapForAPIResults(query, replacements, options, order) |
1660 | 1661 | ||
1661 | return VideoModel.sequelize.query<any>(queryModels, { replacements, type: QueryTypes.SELECT, nest: true }) | 1662 | return VideoModel.sequelize.query<any>(queryModels, { replacements, type: QueryTypes.SELECT, nest: true }) |
@@ -1919,7 +1920,7 @@ export class VideoModel extends Model { | |||
1919 | } | 1920 | } |
1920 | 1921 | ||
1921 | getWatchStaticPath () { | 1922 | getWatchStaticPath () { |
1922 | return '/videos/watch/' + this.uuid | 1923 | return '/w/' + this.uuid |
1923 | } | 1924 | } |
1924 | 1925 | ||
1925 | getEmbedStaticPath () { | 1926 | getEmbedStaticPath () { |
diff --git a/server/tests/api/check-params/config.ts b/server/tests/api/check-params/config.ts index 004aa65b3..9549070ef 100644 --- a/server/tests/api/check-params/config.ts +++ b/server/tests/api/check-params/config.ts | |||
@@ -73,7 +73,8 @@ describe('Test config API validators', function () { | |||
73 | signup: { | 73 | signup: { |
74 | enabled: false, | 74 | enabled: false, |
75 | limit: 5, | 75 | limit: 5, |
76 | requiresEmailVerification: false | 76 | requiresEmailVerification: false, |
77 | minimumAge: 16 | ||
77 | }, | 78 | }, |
78 | admin: { | 79 | admin: { |
79 | email: 'superadmin1@example.com' | 80 | email: 'superadmin1@example.com' |
diff --git a/server/tests/api/check-params/custom-pages.ts b/server/tests/api/check-params/custom-pages.ts new file mode 100644 index 000000000..74ca3384c --- /dev/null +++ b/server/tests/api/check-params/custom-pages.ts | |||
@@ -0,0 +1,81 @@ | |||
1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ | ||
2 | |||
3 | import 'mocha' | ||
4 | import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' | ||
5 | import { | ||
6 | cleanupTests, | ||
7 | createUser, | ||
8 | flushAndRunServer, | ||
9 | ServerInfo, | ||
10 | setAccessTokensToServers, | ||
11 | userLogin | ||
12 | } from '../../../../shared/extra-utils' | ||
13 | import { makeGetRequest, makePutBodyRequest } from '../../../../shared/extra-utils/requests/requests' | ||
14 | |||
15 | describe('Test custom pages validators', function () { | ||
16 | const path = '/api/v1/custom-pages/homepage/instance' | ||
17 | |||
18 | let server: ServerInfo | ||
19 | let userAccessToken: string | ||
20 | |||
21 | // --------------------------------------------------------------- | ||
22 | |||
23 | before(async function () { | ||
24 | this.timeout(120000) | ||
25 | |||
26 | server = await flushAndRunServer(1) | ||
27 | await setAccessTokensToServers([ server ]) | ||
28 | |||
29 | const user = { username: 'user1', password: 'password' } | ||
30 | await createUser({ url: server.url, accessToken: server.accessToken, username: user.username, password: user.password }) | ||
31 | |||
32 | userAccessToken = await userLogin(server, user) | ||
33 | }) | ||
34 | |||
35 | describe('When updating instance homepage', function () { | ||
36 | |||
37 | it('Should fail with an unauthenticated user', async function () { | ||
38 | await makePutBodyRequest({ | ||
39 | url: server.url, | ||
40 | path, | ||
41 | fields: { content: 'super content' }, | ||
42 | statusCodeExpected: HttpStatusCode.UNAUTHORIZED_401 | ||
43 | }) | ||
44 | }) | ||
45 | |||
46 | it('Should fail with a non admin user', async function () { | ||
47 | await makePutBodyRequest({ | ||
48 | url: server.url, | ||
49 | path, | ||
50 | token: userAccessToken, | ||
51 | fields: { content: 'super content' }, | ||
52 | statusCodeExpected: HttpStatusCode.FORBIDDEN_403 | ||
53 | }) | ||
54 | }) | ||
55 | |||
56 | it('Should succeed with the correct params', async function () { | ||
57 | await makePutBodyRequest({ | ||
58 | url: server.url, | ||
59 | path, | ||
60 | token: server.accessToken, | ||
61 | fields: { content: 'super content' }, | ||
62 | statusCodeExpected: HttpStatusCode.NO_CONTENT_204 | ||
63 | }) | ||
64 | }) | ||
65 | }) | ||
66 | |||
67 | describe('When getting instance homapage', function () { | ||
68 | |||
69 | it('Should succeed with the correct params', async function () { | ||
70 | await makeGetRequest({ | ||
71 | url: server.url, | ||
72 | path, | ||
73 | statusCodeExpected: HttpStatusCode.OK_200 | ||
74 | }) | ||
75 | }) | ||
76 | }) | ||
77 | |||
78 | after(async function () { | ||
79 | await cleanupTests([ server ]) | ||
80 | }) | ||
81 | }) | ||
diff --git a/server/tests/api/check-params/index.ts b/server/tests/api/check-params/index.ts index 143515838..ce2335e42 100644 --- a/server/tests/api/check-params/index.ts +++ b/server/tests/api/check-params/index.ts | |||
@@ -3,6 +3,7 @@ import './accounts' | |||
3 | import './blocklist' | 3 | import './blocklist' |
4 | import './bulk' | 4 | import './bulk' |
5 | import './config' | 5 | import './config' |
6 | import './custom-pages' | ||
6 | import './contact-form' | 7 | import './contact-form' |
7 | import './debug' | 8 | import './debug' |
8 | import './follows' | 9 | import './follows' |
diff --git a/server/tests/api/check-params/plugins.ts b/server/tests/api/check-params/plugins.ts index 6e540bcbb..a833fe6ff 100644 --- a/server/tests/api/check-params/plugins.ts +++ b/server/tests/api/check-params/plugins.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import 'mocha' | 3 | import 'mocha' |
4 | 4 | import { HttpStatusCode } from '@shared/core-utils' | |
5 | import { | 5 | import { |
6 | checkBadCountPagination, | 6 | checkBadCountPagination, |
7 | checkBadSortPagination, | 7 | checkBadSortPagination, |
@@ -11,14 +11,14 @@ import { | |||
11 | flushAndRunServer, | 11 | flushAndRunServer, |
12 | immutableAssign, | 12 | immutableAssign, |
13 | installPlugin, | 13 | installPlugin, |
14 | makeGetRequest, makePostBodyRequest, makePutBodyRequest, | 14 | makeGetRequest, |
15 | makePostBodyRequest, | ||
16 | makePutBodyRequest, | ||
15 | ServerInfo, | 17 | ServerInfo, |
16 | setAccessTokensToServers, | 18 | setAccessTokensToServers, |
17 | userLogin | 19 | userLogin |
18 | } from '../../../../shared/extra-utils' | 20 | } from '@shared/extra-utils' |
19 | import { PluginType } from '../../../../shared/models/plugins/plugin.type' | 21 | import { PeerTubePlugin, PluginType } from '@shared/models' |
20 | import { PeerTubePlugin } from '../../../../shared/models/plugins/peertube-plugin.model' | ||
21 | import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' | ||
22 | 22 | ||
23 | describe('Test server plugins API validators', function () { | 23 | describe('Test server plugins API validators', function () { |
24 | let server: ServerInfo | 24 | let server: ServerInfo |
diff --git a/server/tests/api/moderation/blocklist.ts b/server/tests/api/moderation/blocklist.ts index e8202aff1..b767d38c7 100644 --- a/server/tests/api/moderation/blocklist.ts +++ b/server/tests/api/moderation/blocklist.ts | |||
@@ -1,46 +1,50 @@ | |||
1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import * as chai from 'chai' | ||
4 | import 'mocha' | 3 | import 'mocha' |
5 | import { AccountBlock, ServerBlock, Video, UserNotification, UserNotificationType } from '../../../../shared/index' | 4 | import * as chai from 'chai' |
6 | import { | 5 | import { |
6 | addAccountToAccountBlocklist, | ||
7 | addAccountToServerBlocklist, | ||
8 | addServerToAccountBlocklist, | ||
9 | addServerToServerBlocklist, | ||
10 | addVideoCommentReply, | ||
11 | addVideoCommentThread, | ||
7 | cleanupTests, | 12 | cleanupTests, |
8 | createUser, | 13 | createUser, |
9 | deleteVideoComment, | 14 | deleteVideoComment, |
10 | doubleFollow, | 15 | doubleFollow, |
16 | findCommentId, | ||
11 | flushAndRunMultipleServers, | 17 | flushAndRunMultipleServers, |
12 | ServerInfo, | ||
13 | uploadVideo, | ||
14 | userLogin, | ||
15 | follow, | 18 | follow, |
16 | unfollow | ||
17 | } from '../../../../shared/extra-utils/index' | ||
18 | import { setAccessTokensToServers } from '../../../../shared/extra-utils/users/login' | ||
19 | import { getVideosList, getVideosListWithToken } from '../../../../shared/extra-utils/videos/videos' | ||
20 | import { | ||
21 | addVideoCommentReply, | ||
22 | addVideoCommentThread, | ||
23 | getVideoCommentThreads, | ||
24 | getVideoThreadComments, | ||
25 | findCommentId | ||
26 | } from '../../../../shared/extra-utils/videos/video-comments' | ||
27 | import { waitJobs } from '../../../../shared/extra-utils/server/jobs' | ||
28 | import { VideoComment, VideoCommentThreadTree } from '../../../../shared/models/videos/video-comment.model' | ||
29 | import { | ||
30 | addAccountToAccountBlocklist, | ||
31 | addAccountToServerBlocklist, | ||
32 | addServerToAccountBlocklist, | ||
33 | addServerToServerBlocklist, | ||
34 | getAccountBlocklistByAccount, | 19 | getAccountBlocklistByAccount, |
35 | getAccountBlocklistByServer, | 20 | getAccountBlocklistByServer, |
36 | getServerBlocklistByAccount, | 21 | getServerBlocklistByAccount, |
37 | getServerBlocklistByServer, | 22 | getServerBlocklistByServer, |
23 | getUserNotifications, | ||
24 | getVideoCommentThreads, | ||
25 | getVideosList, | ||
26 | getVideosListWithToken, | ||
27 | getVideoThreadComments, | ||
38 | removeAccountFromAccountBlocklist, | 28 | removeAccountFromAccountBlocklist, |
39 | removeAccountFromServerBlocklist, | 29 | removeAccountFromServerBlocklist, |
40 | removeServerFromAccountBlocklist, | 30 | removeServerFromAccountBlocklist, |
41 | removeServerFromServerBlocklist | 31 | removeServerFromServerBlocklist, |
42 | } from '../../../../shared/extra-utils/users/blocklist' | 32 | ServerInfo, |
43 | import { getUserNotifications } from '../../../../shared/extra-utils/users/user-notifications' | 33 | setAccessTokensToServers, |
34 | unfollow, | ||
35 | uploadVideo, | ||
36 | userLogin, | ||
37 | waitJobs | ||
38 | } from '@shared/extra-utils' | ||
39 | import { | ||
40 | AccountBlock, | ||
41 | ServerBlock, | ||
42 | UserNotification, | ||
43 | UserNotificationType, | ||
44 | Video, | ||
45 | VideoComment, | ||
46 | VideoCommentThreadTree | ||
47 | } from '@shared/models' | ||
44 | 48 | ||
45 | const expect = chai.expect | 49 | const expect = chai.expect |
46 | 50 | ||
diff --git a/server/tests/api/notifications/comments-notifications.ts b/server/tests/api/notifications/comments-notifications.ts index 5e4ab0d6c..d2badf237 100644 --- a/server/tests/api/notifications/comments-notifications.ts +++ b/server/tests/api/notifications/comments-notifications.ts | |||
@@ -2,20 +2,25 @@ | |||
2 | 2 | ||
3 | import 'mocha' | 3 | import 'mocha' |
4 | import * as chai from 'chai' | 4 | import * as chai from 'chai' |
5 | import { cleanupTests, getVideoCommentThreads, getVideoThreadComments, updateMyUser } from '../../../../shared/extra-utils' | ||
6 | import { ServerInfo, uploadVideo } from '../../../../shared/extra-utils/index' | ||
7 | import { MockSmtpServer } from '../../../../shared/extra-utils/miscs/email' | ||
8 | import { waitJobs } from '../../../../shared/extra-utils/server/jobs' | ||
9 | import { addAccountToAccountBlocklist, removeAccountFromAccountBlocklist } from '../../../../shared/extra-utils/users/blocklist' | ||
10 | import { | 5 | import { |
6 | addAccountToAccountBlocklist, | ||
7 | addVideoCommentReply, | ||
8 | addVideoCommentThread, | ||
11 | checkCommentMention, | 9 | checkCommentMention, |
12 | CheckerBaseParams, | 10 | CheckerBaseParams, |
13 | checkNewCommentOnMyVideo, | 11 | checkNewCommentOnMyVideo, |
14 | prepareNotificationsTest | 12 | cleanupTests, |
15 | } from '../../../../shared/extra-utils/users/user-notifications' | 13 | getVideoCommentThreads, |
16 | import { addVideoCommentReply, addVideoCommentThread } from '../../../../shared/extra-utils/videos/video-comments' | 14 | getVideoThreadComments, |
17 | import { UserNotification } from '../../../../shared/models/users' | 15 | MockSmtpServer, |
18 | import { VideoCommentThreadTree } from '../../../../shared/models/videos/video-comment.model' | 16 | prepareNotificationsTest, |
17 | removeAccountFromAccountBlocklist, | ||
18 | ServerInfo, | ||
19 | updateMyUser, | ||
20 | uploadVideo, | ||
21 | waitJobs | ||
22 | } from '@shared/extra-utils' | ||
23 | import { UserNotification, VideoCommentThreadTree } from '@shared/models' | ||
19 | 24 | ||
20 | const expect = chai.expect | 25 | const expect = chai.expect |
21 | 26 | ||
diff --git a/server/tests/api/server/bulk.ts b/server/tests/api/server/bulk.ts index 51ba0e7af..80fa7fce6 100644 --- a/server/tests/api/server/bulk.ts +++ b/server/tests/api/server/bulk.ts | |||
@@ -2,12 +2,14 @@ | |||
2 | 2 | ||
3 | import 'mocha' | 3 | import 'mocha' |
4 | import * as chai from 'chai' | 4 | import * as chai from 'chai' |
5 | import { VideoComment } from '@shared/models/videos/video-comment.model' | 5 | import { Video, VideoComment } from '@shared/models' |
6 | import { | 6 | import { |
7 | addVideoCommentReply, | ||
7 | addVideoCommentThread, | 8 | addVideoCommentThread, |
8 | bulkRemoveCommentsOf, | 9 | bulkRemoveCommentsOf, |
9 | cleanupTests, | 10 | cleanupTests, |
10 | createUser, | 11 | createUser, |
12 | doubleFollow, | ||
11 | flushAndRunMultipleServers, | 13 | flushAndRunMultipleServers, |
12 | getVideoCommentThreads, | 14 | getVideoCommentThreads, |
13 | getVideosList, | 15 | getVideosList, |
@@ -15,11 +17,8 @@ import { | |||
15 | setAccessTokensToServers, | 17 | setAccessTokensToServers, |
16 | uploadVideo, | 18 | uploadVideo, |
17 | userLogin, | 19 | userLogin, |
18 | waitJobs, | 20 | waitJobs |
19 | addVideoCommentReply | ||
20 | } from '../../../../shared/extra-utils/index' | 21 | } from '../../../../shared/extra-utils/index' |
21 | import { doubleFollow } from '../../../../shared/extra-utils/server/follows' | ||
22 | import { Video } from '@shared/models' | ||
23 | 22 | ||
24 | const expect = chai.expect | 23 | const expect = chai.expect |
25 | 24 | ||
diff --git a/server/tests/api/server/config.ts b/server/tests/api/server/config.ts index 1d9ea31df..19bf9582c 100644 --- a/server/tests/api/server/config.ts +++ b/server/tests/api/server/config.ts | |||
@@ -60,6 +60,7 @@ function checkInitialConfig (server: ServerInfo, data: CustomConfig) { | |||
60 | 60 | ||
61 | expect(data.signup.enabled).to.be.true | 61 | expect(data.signup.enabled).to.be.true |
62 | expect(data.signup.limit).to.equal(4) | 62 | expect(data.signup.limit).to.equal(4) |
63 | expect(data.signup.minimumAge).to.equal(16) | ||
63 | expect(data.signup.requiresEmailVerification).to.be.false | 64 | expect(data.signup.requiresEmailVerification).to.be.false |
64 | 65 | ||
65 | expect(data.admin.email).to.equal('admin' + server.internalServerNumber + '@example.com') | 66 | expect(data.admin.email).to.equal('admin' + server.internalServerNumber + '@example.com') |
@@ -151,6 +152,7 @@ function checkUpdatedConfig (data: CustomConfig) { | |||
151 | expect(data.signup.enabled).to.be.false | 152 | expect(data.signup.enabled).to.be.false |
152 | expect(data.signup.limit).to.equal(5) | 153 | expect(data.signup.limit).to.equal(5) |
153 | expect(data.signup.requiresEmailVerification).to.be.false | 154 | expect(data.signup.requiresEmailVerification).to.be.false |
155 | expect(data.signup.minimumAge).to.equal(10) | ||
154 | 156 | ||
155 | // We override admin email in parallel tests, so skip this exception | 157 | // We override admin email in parallel tests, so skip this exception |
156 | if (parallelTests() === false) { | 158 | if (parallelTests() === false) { |
@@ -316,7 +318,8 @@ describe('Test config', function () { | |||
316 | signup: { | 318 | signup: { |
317 | enabled: false, | 319 | enabled: false, |
318 | limit: 5, | 320 | limit: 5, |
319 | requiresEmailVerification: false | 321 | requiresEmailVerification: false, |
322 | minimumAge: 10 | ||
320 | }, | 323 | }, |
321 | admin: { | 324 | admin: { |
322 | email: 'superadmin1@example.com' | 325 | email: 'superadmin1@example.com' |
diff --git a/server/tests/api/server/follows.ts b/server/tests/api/server/follows.ts index eb9ab10eb..e1c062020 100644 --- a/server/tests/api/server/follows.ts +++ b/server/tests/api/server/follows.ts | |||
@@ -1,37 +1,35 @@ | |||
1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import * as chai from 'chai' | ||
4 | import 'mocha' | 3 | import 'mocha' |
5 | import { Video, VideoPrivacy } from '../../../../shared/models/videos' | 4 | import * as chai from 'chai' |
6 | import { VideoComment, VideoCommentThreadTree } from '../../../../shared/models/videos/video-comment.model' | ||
7 | import { cleanupTests, completeVideoCheck, deleteVideoComment } from '../../../../shared/extra-utils' | ||
8 | import { | 5 | import { |
6 | addVideoCommentReply, | ||
7 | addVideoCommentThread, | ||
8 | cleanupTests, | ||
9 | completeVideoCheck, | ||
10 | createUser, | ||
11 | createVideoCaption, | ||
12 | dateIsValid, | ||
13 | deleteVideoComment, | ||
14 | expectAccountFollows, | ||
9 | flushAndRunMultipleServers, | 15 | flushAndRunMultipleServers, |
10 | getVideosList, | ||
11 | ServerInfo, | ||
12 | setAccessTokensToServers, | ||
13 | uploadVideo | ||
14 | } from '../../../../shared/extra-utils/index' | ||
15 | import { dateIsValid } from '../../../../shared/extra-utils/miscs/miscs' | ||
16 | import { | ||
17 | follow, | 16 | follow, |
18 | getFollowersListPaginationAndSort, | 17 | getFollowersListPaginationAndSort, |
19 | getFollowingListPaginationAndSort, | 18 | getFollowingListPaginationAndSort, |
20 | unfollow | ||
21 | } from '../../../../shared/extra-utils/server/follows' | ||
22 | import { expectAccountFollows } from '../../../../shared/extra-utils/users/accounts' | ||
23 | import { userLogin } from '../../../../shared/extra-utils/users/login' | ||
24 | import { createUser } from '../../../../shared/extra-utils/users/users' | ||
25 | import { | ||
26 | addVideoCommentReply, | ||
27 | addVideoCommentThread, | ||
28 | getVideoCommentThreads, | 19 | getVideoCommentThreads, |
29 | getVideoThreadComments | 20 | getVideosList, |
30 | } from '../../../../shared/extra-utils/videos/video-comments' | 21 | getVideoThreadComments, |
31 | import { rateVideo } from '../../../../shared/extra-utils/videos/videos' | 22 | listVideoCaptions, |
32 | import { waitJobs } from '../../../../shared/extra-utils/server/jobs' | 23 | rateVideo, |
33 | import { createVideoCaption, listVideoCaptions, testCaptionFile } from '../../../../shared/extra-utils/videos/video-captions' | 24 | ServerInfo, |
34 | import { VideoCaption } from '../../../../shared/models/videos/caption/video-caption.model' | 25 | setAccessTokensToServers, |
26 | testCaptionFile, | ||
27 | unfollow, | ||
28 | uploadVideo, | ||
29 | userLogin, | ||
30 | waitJobs | ||
31 | } from '@shared/extra-utils' | ||
32 | import { Video, VideoCaption, VideoComment, VideoCommentThreadTree, VideoPrivacy } from '@shared/models' | ||
35 | 33 | ||
36 | const expect = chai.expect | 34 | const expect = chai.expect |
37 | 35 | ||
diff --git a/server/tests/api/server/handle-down.ts b/server/tests/api/server/handle-down.ts index 817c79f6e..fe4a0e100 100644 --- a/server/tests/api/server/handle-down.ts +++ b/server/tests/api/server/handle-down.ts | |||
@@ -4,7 +4,7 @@ import * as chai from 'chai' | |||
4 | import 'mocha' | 4 | import 'mocha' |
5 | import { JobState, Video } from '../../../../shared/models' | 5 | import { JobState, Video } from '../../../../shared/models' |
6 | import { VideoPrivacy } from '../../../../shared/models/videos' | 6 | import { VideoPrivacy } from '../../../../shared/models/videos' |
7 | import { VideoCommentThreadTree } from '../../../../shared/models/videos/video-comment.model' | 7 | import { VideoCommentThreadTree } from '../../../../shared/models/videos/comment/video-comment.model' |
8 | 8 | ||
9 | import { | 9 | import { |
10 | cleanupTests, | 10 | cleanupTests, |
@@ -346,10 +346,12 @@ describe('Test handle downs', function () { | |||
346 | // Wait video expiration | 346 | // Wait video expiration |
347 | await wait(11000) | 347 | await wait(11000) |
348 | 348 | ||
349 | for (let i = 0; i < 3; i++) { | 349 | for (let i = 0; i < 5; i++) { |
350 | await getVideo(servers[1].url, videoIdsServer1[i]) | 350 | try { |
351 | await waitJobs([ servers[1] ]) | 351 | await getVideo(servers[1].url, videoIdsServer1[i]) |
352 | await wait(1500) | 352 | await waitJobs([ servers[1] ]) |
353 | await wait(1500) | ||
354 | } catch {} | ||
353 | } | 355 | } |
354 | 356 | ||
355 | for (const id of videoIdsServer1) { | 357 | for (const id of videoIdsServer1) { |
diff --git a/server/tests/api/server/homepage.ts b/server/tests/api/server/homepage.ts new file mode 100644 index 000000000..e8ba89ca6 --- /dev/null +++ b/server/tests/api/server/homepage.ts | |||
@@ -0,0 +1,85 @@ | |||
1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ | ||
2 | |||
3 | import 'mocha' | ||
4 | import * as chai from 'chai' | ||
5 | import { HttpStatusCode } from '@shared/core-utils' | ||
6 | import { CustomPage, ServerConfig } from '@shared/models' | ||
7 | import { | ||
8 | cleanupTests, | ||
9 | flushAndRunServer, | ||
10 | getConfig, | ||
11 | getInstanceHomepage, | ||
12 | killallServers, | ||
13 | reRunServer, | ||
14 | ServerInfo, | ||
15 | setAccessTokensToServers, | ||
16 | updateInstanceHomepage | ||
17 | } from '../../../../shared/extra-utils/index' | ||
18 | |||
19 | const expect = chai.expect | ||
20 | |||
21 | async function getHomepageState (server: ServerInfo) { | ||
22 | const res = await getConfig(server.url) | ||
23 | |||
24 | const config = res.body as ServerConfig | ||
25 | return config.homepage.enabled | ||
26 | } | ||
27 | |||
28 | describe('Test instance homepage actions', function () { | ||
29 | let server: ServerInfo | ||
30 | |||
31 | before(async function () { | ||
32 | this.timeout(30000) | ||
33 | |||
34 | server = await flushAndRunServer(1) | ||
35 | await setAccessTokensToServers([ server ]) | ||
36 | }) | ||
37 | |||
38 | it('Should not have a homepage', async function () { | ||
39 | const state = await getHomepageState(server) | ||
40 | expect(state).to.be.false | ||
41 | |||
42 | await getInstanceHomepage(server.url, HttpStatusCode.NOT_FOUND_404) | ||
43 | }) | ||
44 | |||
45 | it('Should set a homepage', async function () { | ||
46 | await updateInstanceHomepage(server.url, server.accessToken, '<picsou-magazine></picsou-magazine>') | ||
47 | |||
48 | const res = await getInstanceHomepage(server.url) | ||
49 | const page: CustomPage = res.body | ||
50 | expect(page.content).to.equal('<picsou-magazine></picsou-magazine>') | ||
51 | |||
52 | const state = await getHomepageState(server) | ||
53 | expect(state).to.be.true | ||
54 | }) | ||
55 | |||
56 | it('Should have the same homepage after a restart', async function () { | ||
57 | this.timeout(30000) | ||
58 | |||
59 | killallServers([ server ]) | ||
60 | |||
61 | await reRunServer(server) | ||
62 | |||
63 | const res = await getInstanceHomepage(server.url) | ||
64 | const page: CustomPage = res.body | ||
65 | expect(page.content).to.equal('<picsou-magazine></picsou-magazine>') | ||
66 | |||
67 | const state = await getHomepageState(server) | ||
68 | expect(state).to.be.true | ||
69 | }) | ||
70 | |||
71 | it('Should empty the homepage', async function () { | ||
72 | await updateInstanceHomepage(server.url, server.accessToken, '') | ||
73 | |||
74 | const res = await getInstanceHomepage(server.url) | ||
75 | const page: CustomPage = res.body | ||
76 | expect(page.content).to.be.empty | ||
77 | |||
78 | const state = await getHomepageState(server) | ||
79 | expect(state).to.be.false | ||
80 | }) | ||
81 | |||
82 | after(async function () { | ||
83 | await cleanupTests([ server ]) | ||
84 | }) | ||
85 | }) | ||
diff --git a/server/tests/api/server/index.ts b/server/tests/api/server/index.ts index be743973a..56e6eb5da 100644 --- a/server/tests/api/server/index.ts +++ b/server/tests/api/server/index.ts | |||
@@ -5,6 +5,7 @@ import './email' | |||
5 | import './follow-constraints' | 5 | import './follow-constraints' |
6 | import './follows' | 6 | import './follows' |
7 | import './follows-moderation' | 7 | import './follows-moderation' |
8 | import './homepage' | ||
8 | import './handle-down' | 9 | import './handle-down' |
9 | import './jobs' | 10 | import './jobs' |
10 | import './logs' | 11 | import './logs' |
diff --git a/server/tests/api/server/plugins.ts b/server/tests/api/server/plugins.ts index f4190c352..6046ab97e 100644 --- a/server/tests/api/server/plugins.ts +++ b/server/tests/api/server/plugins.ts | |||
@@ -28,14 +28,8 @@ import { | |||
28 | updatePluginSettings, | 28 | updatePluginSettings, |
29 | wait, | 29 | wait, |
30 | waitUntilLog | 30 | waitUntilLog |
31 | } from '../../../../shared/extra-utils' | 31 | } from '@shared/extra-utils' |
32 | import { PeerTubePluginIndex } from '../../../../shared/models/plugins/peertube-plugin-index.model' | 32 | import { PeerTubePlugin, PeerTubePluginIndex, PluginPackageJson, PluginType, PublicServerSetting, ServerConfig, User } from '@shared/models' |
33 | import { PeerTubePlugin } from '../../../../shared/models/plugins/peertube-plugin.model' | ||
34 | import { PluginPackageJson } from '../../../../shared/models/plugins/plugin-package-json.model' | ||
35 | import { PluginType } from '../../../../shared/models/plugins/plugin.type' | ||
36 | import { PublicServerSetting } from '../../../../shared/models/plugins/public-server.setting' | ||
37 | import { ServerConfig } from '../../../../shared/models/server' | ||
38 | import { User } from '../../../../shared/models/users' | ||
39 | 33 | ||
40 | const expect = chai.expect | 34 | const expect = chai.expect |
41 | 35 | ||
diff --git a/server/tests/api/server/services.ts b/server/tests/api/server/services.ts index f0fa91674..ea64e4040 100644 --- a/server/tests/api/server/services.ts +++ b/server/tests/api/server/services.ts | |||
@@ -67,61 +67,67 @@ describe('Test services', function () { | |||
67 | }) | 67 | }) |
68 | 68 | ||
69 | it('Should have a valid oEmbed video response', async function () { | 69 | it('Should have a valid oEmbed video response', async function () { |
70 | const oembedUrl = 'http://localhost:' + server.port + '/videos/watch/' + video.uuid | 70 | for (const basePath of [ '/videos/watch/', '/w/' ]) { |
71 | 71 | const oembedUrl = 'http://localhost:' + server.port + basePath + video.uuid | |
72 | const res = await getOEmbed(server.url, oembedUrl) | 72 | |
73 | const expectedHtml = '<iframe width="560" height="315" sandbox="allow-same-origin allow-scripts" ' + | 73 | const res = await getOEmbed(server.url, oembedUrl) |
74 | `title="${video.name}" src="http://localhost:${server.port}/videos/embed/${video.uuid}" ` + | 74 | const expectedHtml = '<iframe width="560" height="315" sandbox="allow-same-origin allow-scripts" ' + |
75 | 'frameborder="0" allowfullscreen></iframe>' | 75 | `title="${video.name}" src="http://localhost:${server.port}/videos/embed/${video.uuid}" ` + |
76 | const expectedThumbnailUrl = 'http://localhost:' + server.port + video.previewPath | 76 | 'frameborder="0" allowfullscreen></iframe>' |
77 | 77 | const expectedThumbnailUrl = 'http://localhost:' + server.port + video.previewPath | |
78 | expect(res.body.html).to.equal(expectedHtml) | 78 | |
79 | expect(res.body.title).to.equal(video.name) | 79 | expect(res.body.html).to.equal(expectedHtml) |
80 | expect(res.body.author_name).to.equal(server.videoChannel.displayName) | 80 | expect(res.body.title).to.equal(video.name) |
81 | expect(res.body.width).to.equal(560) | 81 | expect(res.body.author_name).to.equal(server.videoChannel.displayName) |
82 | expect(res.body.height).to.equal(315) | 82 | expect(res.body.width).to.equal(560) |
83 | expect(res.body.thumbnail_url).to.equal(expectedThumbnailUrl) | 83 | expect(res.body.height).to.equal(315) |
84 | expect(res.body.thumbnail_width).to.equal(850) | 84 | expect(res.body.thumbnail_url).to.equal(expectedThumbnailUrl) |
85 | expect(res.body.thumbnail_height).to.equal(480) | 85 | expect(res.body.thumbnail_width).to.equal(850) |
86 | expect(res.body.thumbnail_height).to.equal(480) | ||
87 | } | ||
86 | }) | 88 | }) |
87 | 89 | ||
88 | it('Should have a valid playlist oEmbed response', async function () { | 90 | it('Should have a valid playlist oEmbed response', async function () { |
89 | const oembedUrl = 'http://localhost:' + server.port + '/videos/watch/playlist/' + playlistUUID | 91 | for (const basePath of [ '/videos/watch/playlist/', '/w/p/' ]) { |
90 | 92 | const oembedUrl = 'http://localhost:' + server.port + basePath + playlistUUID | |
91 | const res = await getOEmbed(server.url, oembedUrl) | 93 | |
92 | const expectedHtml = '<iframe width="560" height="315" sandbox="allow-same-origin allow-scripts" ' + | 94 | const res = await getOEmbed(server.url, oembedUrl) |
93 | `title="${playlistDisplayName}" src="http://localhost:${server.port}/video-playlists/embed/${playlistUUID}" ` + | 95 | const expectedHtml = '<iframe width="560" height="315" sandbox="allow-same-origin allow-scripts" ' + |
94 | 'frameborder="0" allowfullscreen></iframe>' | 96 | `title="${playlistDisplayName}" src="http://localhost:${server.port}/video-playlists/embed/${playlistUUID}" ` + |
95 | 97 | 'frameborder="0" allowfullscreen></iframe>' | |
96 | expect(res.body.html).to.equal(expectedHtml) | 98 | |
97 | expect(res.body.title).to.equal('The Life and Times of Scrooge McDuck') | 99 | expect(res.body.html).to.equal(expectedHtml) |
98 | expect(res.body.author_name).to.equal(server.videoChannel.displayName) | 100 | expect(res.body.title).to.equal('The Life and Times of Scrooge McDuck') |
99 | expect(res.body.width).to.equal(560) | 101 | expect(res.body.author_name).to.equal(server.videoChannel.displayName) |
100 | expect(res.body.height).to.equal(315) | 102 | expect(res.body.width).to.equal(560) |
101 | expect(res.body.thumbnail_url).exist | 103 | expect(res.body.height).to.equal(315) |
102 | expect(res.body.thumbnail_width).to.equal(280) | 104 | expect(res.body.thumbnail_url).exist |
103 | expect(res.body.thumbnail_height).to.equal(157) | 105 | expect(res.body.thumbnail_width).to.equal(280) |
106 | expect(res.body.thumbnail_height).to.equal(157) | ||
107 | } | ||
104 | }) | 108 | }) |
105 | 109 | ||
106 | it('Should have a valid oEmbed response with small max height query', async function () { | 110 | it('Should have a valid oEmbed response with small max height query', async function () { |
107 | const oembedUrl = 'http://localhost:' + server.port + '/videos/watch/' + video.uuid | 111 | for (const basePath of [ '/videos/watch/', '/w/' ]) { |
108 | const format = 'json' | 112 | const oembedUrl = 'http://localhost:' + server.port + basePath + video.uuid |
109 | const maxHeight = 50 | 113 | const format = 'json' |
110 | const maxWidth = 50 | 114 | const maxHeight = 50 |
111 | 115 | const maxWidth = 50 | |
112 | const res = await getOEmbed(server.url, oembedUrl, format, maxHeight, maxWidth) | 116 | |
113 | const expectedHtml = '<iframe width="50" height="50" sandbox="allow-same-origin allow-scripts" ' + | 117 | const res = await getOEmbed(server.url, oembedUrl, format, maxHeight, maxWidth) |
114 | `title="${video.name}" src="http://localhost:${server.port}/videos/embed/${video.uuid}" ` + | 118 | const expectedHtml = '<iframe width="50" height="50" sandbox="allow-same-origin allow-scripts" ' + |
115 | 'frameborder="0" allowfullscreen></iframe>' | 119 | `title="${video.name}" src="http://localhost:${server.port}/videos/embed/${video.uuid}" ` + |
116 | 120 | 'frameborder="0" allowfullscreen></iframe>' | |
117 | expect(res.body.html).to.equal(expectedHtml) | 121 | |
118 | expect(res.body.title).to.equal(video.name) | 122 | expect(res.body.html).to.equal(expectedHtml) |
119 | expect(res.body.author_name).to.equal(server.videoChannel.displayName) | 123 | expect(res.body.title).to.equal(video.name) |
120 | expect(res.body.height).to.equal(50) | 124 | expect(res.body.author_name).to.equal(server.videoChannel.displayName) |
121 | expect(res.body.width).to.equal(50) | 125 | expect(res.body.height).to.equal(50) |
122 | expect(res.body).to.not.have.property('thumbnail_url') | 126 | expect(res.body.width).to.equal(50) |
123 | expect(res.body).to.not.have.property('thumbnail_width') | 127 | expect(res.body).to.not.have.property('thumbnail_url') |
124 | expect(res.body).to.not.have.property('thumbnail_height') | 128 | expect(res.body).to.not.have.property('thumbnail_width') |
129 | expect(res.body).to.not.have.property('thumbnail_height') | ||
130 | } | ||
125 | }) | 131 | }) |
126 | 132 | ||
127 | after(async function () { | 133 | after(async function () { |
diff --git a/server/tests/api/videos/multiple-servers.ts b/server/tests/api/videos/multiple-servers.ts index 41cd814e0..6aa996038 100644 --- a/server/tests/api/videos/multiple-servers.ts +++ b/server/tests/api/videos/multiple-servers.ts | |||
@@ -1,11 +1,10 @@ | |||
1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import * as chai from 'chai' | ||
4 | import 'mocha' | 3 | import 'mocha' |
4 | import * as chai from 'chai' | ||
5 | import { join } from 'path' | 5 | import { join } from 'path' |
6 | import * as request from 'supertest' | 6 | import * as request from 'supertest' |
7 | import { VideoPrivacy } from '../../../../shared/models/videos' | 7 | import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' |
8 | import { VideoComment, VideoCommentThreadTree } from '../../../../shared/models/videos/video-comment.model' | ||
9 | import { | 8 | import { |
10 | addVideoChannel, | 9 | addVideoChannel, |
11 | checkTmpIsEmpty, | 10 | checkTmpIsEmpty, |
@@ -32,16 +31,16 @@ import { | |||
32 | wait, | 31 | wait, |
33 | webtorrentAdd | 32 | webtorrentAdd |
34 | } from '../../../../shared/extra-utils' | 33 | } from '../../../../shared/extra-utils' |
34 | import { waitJobs } from '../../../../shared/extra-utils/server/jobs' | ||
35 | import { | 35 | import { |
36 | addVideoCommentReply, | 36 | addVideoCommentReply, |
37 | addVideoCommentThread, | 37 | addVideoCommentThread, |
38 | deleteVideoComment, | 38 | deleteVideoComment, |
39 | findCommentId, | ||
39 | getVideoCommentThreads, | 40 | getVideoCommentThreads, |
40 | getVideoThreadComments, | 41 | getVideoThreadComments |
41 | findCommentId | ||
42 | } from '../../../../shared/extra-utils/videos/video-comments' | 42 | } from '../../../../shared/extra-utils/videos/video-comments' |
43 | import { waitJobs } from '../../../../shared/extra-utils/server/jobs' | 43 | import { VideoComment, VideoCommentThreadTree, VideoPrivacy } from '../../../../shared/models/videos' |
44 | import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' | ||
45 | 44 | ||
46 | const expect = chai.expect | 45 | const expect = chai.expect |
47 | 46 | ||
diff --git a/server/tests/api/videos/video-comments.ts b/server/tests/api/videos/video-comments.ts index 615e0ea45..a5ff3a39d 100644 --- a/server/tests/api/videos/video-comments.ts +++ b/server/tests/api/videos/video-comments.ts | |||
@@ -2,7 +2,7 @@ | |||
2 | 2 | ||
3 | import 'mocha' | 3 | import 'mocha' |
4 | import * as chai from 'chai' | 4 | import * as chai from 'chai' |
5 | 5 | import { VideoComment, VideoCommentAdmin, VideoCommentThreadTree } from '@shared/models' | |
6 | import { cleanupTests, testImage } from '../../../../shared/extra-utils' | 6 | import { cleanupTests, testImage } from '../../../../shared/extra-utils' |
7 | import { | 7 | import { |
8 | createUser, | 8 | createUser, |
@@ -22,7 +22,6 @@ import { | |||
22 | getVideoCommentThreads, | 22 | getVideoCommentThreads, |
23 | getVideoThreadComments | 23 | getVideoThreadComments |
24 | } from '../../../../shared/extra-utils/videos/video-comments' | 24 | } from '../../../../shared/extra-utils/videos/video-comments' |
25 | import { VideoComment, VideoCommentAdmin, VideoCommentThreadTree } from '../../../../shared/models/videos/video-comment.model' | ||
26 | 25 | ||
27 | const expect = chai.expect | 26 | const expect = chai.expect |
28 | 27 | ||
diff --git a/server/tests/client.ts b/server/tests/client.ts index 3c99bcd1f..253a95624 100644 --- a/server/tests/client.ts +++ b/server/tests/client.ts | |||
@@ -2,8 +2,10 @@ | |||
2 | 2 | ||
3 | import 'mocha' | 3 | import 'mocha' |
4 | import * as chai from 'chai' | 4 | import * as chai from 'chai' |
5 | import { omit } from 'lodash' | ||
5 | import * as request from 'supertest' | 6 | import * as request from 'supertest' |
6 | import { Account, VideoPlaylistPrivacy } from '@shared/models' | 7 | import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes' |
8 | import { Account, CustomConfig, HTMLServerConfig, ServerConfig, VideoPlaylistPrivacy } from '@shared/models' | ||
7 | import { | 9 | import { |
8 | addVideoInPlaylist, | 10 | addVideoInPlaylist, |
9 | cleanupTests, | 11 | cleanupTests, |
@@ -11,8 +13,10 @@ import { | |||
11 | doubleFollow, | 13 | doubleFollow, |
12 | flushAndRunMultipleServers, | 14 | flushAndRunMultipleServers, |
13 | getAccount, | 15 | getAccount, |
16 | getConfig, | ||
14 | getCustomConfig, | 17 | getCustomConfig, |
15 | getVideosList, | 18 | getVideosList, |
19 | makeGetRequest, | ||
16 | makeHTMLRequest, | 20 | makeHTMLRequest, |
17 | ServerInfo, | 21 | ServerInfo, |
18 | setAccessTokensToServers, | 22 | setAccessTokensToServers, |
@@ -24,14 +28,16 @@ import { | |||
24 | uploadVideo, | 28 | uploadVideo, |
25 | waitJobs | 29 | waitJobs |
26 | } from '../../shared/extra-utils' | 30 | } from '../../shared/extra-utils' |
27 | import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes' | ||
28 | 31 | ||
29 | const expect = chai.expect | 32 | const expect = chai.expect |
30 | 33 | ||
31 | function checkIndexTags (html: string, title: string, description: string, css: string) { | 34 | function checkIndexTags (html: string, title: string, description: string, css: string, config: ServerConfig) { |
32 | expect(html).to.contain('<title>' + title + '</title>') | 35 | expect(html).to.contain('<title>' + title + '</title>') |
33 | expect(html).to.contain('<meta name="description" content="' + description + '" />') | 36 | expect(html).to.contain('<meta name="description" content="' + description + '" />') |
34 | expect(html).to.contain('<style class="custom-css-style">' + css + '</style>') | 37 | expect(html).to.contain('<style class="custom-css-style">' + css + '</style>') |
38 | |||
39 | const htmlConfig: HTMLServerConfig = omit(config, 'signup') | ||
40 | expect(html).to.contain(`<script type="application/javascript">window.PeerTubeServerConfig = '${JSON.stringify(htmlConfig)}'</script>`) | ||
35 | } | 41 | } |
36 | 42 | ||
37 | describe('Test a client controllers', function () { | 43 | describe('Test a client controllers', function () { |
@@ -48,6 +54,9 @@ describe('Test a client controllers', function () { | |||
48 | 54 | ||
49 | const channelDescription = 'my super channel description' | 55 | const channelDescription = 'my super channel description' |
50 | 56 | ||
57 | const watchVideoBasePaths = [ '/videos/watch/', '/w/' ] | ||
58 | const watchPlaylistBasePaths = [ '/videos/watch/playlist/', '/w/p/' ] | ||
59 | |||
51 | before(async function () { | 60 | before(async function () { |
52 | this.timeout(120000) | 61 | this.timeout(120000) |
53 | 62 | ||
@@ -105,201 +114,258 @@ describe('Test a client controllers', function () { | |||
105 | }) | 114 | }) |
106 | 115 | ||
107 | describe('oEmbed', function () { | 116 | describe('oEmbed', function () { |
117 | |||
108 | it('Should have valid oEmbed discovery tags for videos', async function () { | 118 | it('Should have valid oEmbed discovery tags for videos', async function () { |
109 | const path = '/videos/watch/' + servers[0].video.uuid | 119 | for (const basePath of watchVideoBasePaths) { |
110 | const res = await request(servers[0].url) | 120 | const path = basePath + servers[0].video.uuid |
111 | .get(path) | 121 | const res = await request(servers[0].url) |
112 | .set('Accept', 'text/html') | 122 | .get(path) |
113 | .expect(HttpStatusCode.OK_200) | 123 | .set('Accept', 'text/html') |
124 | .expect(HttpStatusCode.OK_200) | ||
114 | 125 | ||
115 | const port = servers[0].port | 126 | const port = servers[0].port |
116 | 127 | ||
117 | const expectedLink = '<link rel="alternate" type="application/json+oembed" href="http://localhost:' + port + '/services/oembed?' + | 128 | const expectedLink = '<link rel="alternate" type="application/json+oembed" href="http://localhost:' + port + '/services/oembed?' + |
118 | `url=http%3A%2F%2Flocalhost%3A${port}%2Fvideos%2Fwatch%2F${servers[0].video.uuid}" ` + | 129 | `url=http%3A%2F%2Flocalhost%3A${port}%2Fw%2F${servers[0].video.uuid}" ` + |
119 | `title="${servers[0].video.name}" />` | 130 | `title="${servers[0].video.name}" />` |
120 | 131 | ||
121 | expect(res.text).to.contain(expectedLink) | 132 | expect(res.text).to.contain(expectedLink) |
133 | } | ||
122 | }) | 134 | }) |
123 | 135 | ||
124 | it('Should have valid oEmbed discovery tags for a playlist', async function () { | 136 | it('Should have valid oEmbed discovery tags for a playlist', async function () { |
125 | const res = await request(servers[0].url) | 137 | for (const basePath of watchPlaylistBasePaths) { |
126 | .get('/videos/watch/playlist/' + playlistUUID) | 138 | const res = await request(servers[0].url) |
127 | .set('Accept', 'text/html') | 139 | .get(basePath + playlistUUID) |
128 | .expect(HttpStatusCode.OK_200) | 140 | .set('Accept', 'text/html') |
141 | .expect(HttpStatusCode.OK_200) | ||
129 | 142 | ||
130 | const port = servers[0].port | 143 | const port = servers[0].port |
131 | 144 | ||
132 | const expectedLink = '<link rel="alternate" type="application/json+oembed" href="http://localhost:' + port + '/services/oembed?' + | 145 | const expectedLink = '<link rel="alternate" type="application/json+oembed" href="http://localhost:' + port + '/services/oembed?' + |
133 | `url=http%3A%2F%2Flocalhost%3A${port}%2Fvideos%2Fwatch%2Fplaylist%2F${playlistUUID}" ` + | 146 | `url=http%3A%2F%2Flocalhost%3A${port}%2Fw%2Fp%2F${playlistUUID}" ` + |
134 | `title="${playlistName}" />` | 147 | `title="${playlistName}" />` |
135 | 148 | ||
136 | expect(res.text).to.contain(expectedLink) | 149 | expect(res.text).to.contain(expectedLink) |
150 | } | ||
137 | }) | 151 | }) |
138 | }) | 152 | }) |
139 | 153 | ||
140 | describe('Open Graph', function () { | 154 | describe('Open Graph', function () { |
141 | 155 | ||
142 | it('Should have valid Open Graph tags on the account page', async function () { | 156 | async function accountPageTest (path: string) { |
143 | const res = await request(servers[0].url) | 157 | const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 }) |
144 | .get('/accounts/' + servers[0].user.username) | 158 | const text = res.text |
145 | .set('Accept', 'text/html') | ||
146 | .expect(HttpStatusCode.OK_200) | ||
147 | |||
148 | expect(res.text).to.contain(`<meta property="og:title" content="${account.displayName}" />`) | ||
149 | expect(res.text).to.contain(`<meta property="og:description" content="${account.description}" />`) | ||
150 | expect(res.text).to.contain('<meta property="og:type" content="website" />') | ||
151 | expect(res.text).to.contain(`<meta property="og:url" content="${servers[0].url}/accounts/${servers[0].user.username}" />`) | ||
152 | }) | ||
153 | 159 | ||
154 | it('Should have valid Open Graph tags on the channel page', async function () { | 160 | expect(text).to.contain(`<meta property="og:title" content="${account.displayName}" />`) |
155 | const res = await request(servers[0].url) | 161 | expect(text).to.contain(`<meta property="og:description" content="${account.description}" />`) |
156 | .get('/video-channels/' + servers[0].videoChannel.name) | 162 | expect(text).to.contain('<meta property="og:type" content="website" />') |
157 | .set('Accept', 'text/html') | 163 | expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/accounts/${servers[0].user.username}" />`) |
158 | .expect(HttpStatusCode.OK_200) | 164 | } |
159 | 165 | ||
160 | expect(res.text).to.contain(`<meta property="og:title" content="${servers[0].videoChannel.displayName}" />`) | 166 | async function channelPageTest (path: string) { |
161 | expect(res.text).to.contain(`<meta property="og:description" content="${channelDescription}" />`) | 167 | const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 }) |
162 | expect(res.text).to.contain('<meta property="og:type" content="website" />') | 168 | const text = res.text |
163 | expect(res.text).to.contain(`<meta property="og:url" content="${servers[0].url}/video-channels/${servers[0].videoChannel.name}" />`) | ||
164 | }) | ||
165 | 169 | ||
166 | it('Should have valid Open Graph tags on the watch page with video id', async function () { | 170 | expect(text).to.contain(`<meta property="og:title" content="${servers[0].videoChannel.displayName}" />`) |
167 | const res = await request(servers[0].url) | 171 | expect(text).to.contain(`<meta property="og:description" content="${channelDescription}" />`) |
168 | .get('/videos/watch/' + servers[0].video.id) | 172 | expect(text).to.contain('<meta property="og:type" content="website" />') |
169 | .set('Accept', 'text/html') | 173 | expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/video-channels/${servers[0].videoChannel.name}" />`) |
170 | .expect(HttpStatusCode.OK_200) | 174 | } |
175 | |||
176 | async function watchVideoPageTest (path: string) { | ||
177 | const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 }) | ||
178 | const text = res.text | ||
179 | |||
180 | expect(text).to.contain(`<meta property="og:title" content="${videoName}" />`) | ||
181 | expect(text).to.contain(`<meta property="og:description" content="${videoDescriptionPlainText}" />`) | ||
182 | expect(text).to.contain('<meta property="og:type" content="video" />') | ||
183 | expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/w/${servers[0].video.uuid}" />`) | ||
184 | } | ||
171 | 185 | ||
172 | expect(res.text).to.contain(`<meta property="og:title" content="${videoName}" />`) | 186 | async function watchPlaylistPageTest (path: string) { |
173 | expect(res.text).to.contain(`<meta property="og:description" content="${videoDescriptionPlainText}" />`) | 187 | const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 }) |
174 | expect(res.text).to.contain('<meta property="og:type" content="video" />') | 188 | const text = res.text |
175 | expect(res.text).to.contain(`<meta property="og:url" content="${servers[0].url}/videos/watch/${servers[0].video.uuid}" />`) | 189 | |
190 | expect(text).to.contain(`<meta property="og:title" content="${playlistName}" />`) | ||
191 | expect(text).to.contain(`<meta property="og:description" content="${playlistDescription}" />`) | ||
192 | expect(text).to.contain('<meta property="og:type" content="video" />') | ||
193 | expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/w/p/${playlistUUID}" />`) | ||
194 | } | ||
195 | |||
196 | it('Should have valid Open Graph tags on the account page', async function () { | ||
197 | await accountPageTest('/accounts/' + servers[0].user.username) | ||
198 | await accountPageTest('/a/' + servers[0].user.username) | ||
199 | await accountPageTest('/@' + servers[0].user.username) | ||
176 | }) | 200 | }) |
177 | 201 | ||
178 | it('Should have valid Open Graph tags on the watch page with video uuid', async function () { | 202 | it('Should have valid Open Graph tags on the channel page', async function () { |
179 | const res = await request(servers[0].url) | 203 | await channelPageTest('/video-channels/' + servers[0].videoChannel.name) |
180 | .get('/videos/watch/' + servers[0].video.uuid) | 204 | await channelPageTest('/c/' + servers[0].videoChannel.name) |
181 | .set('Accept', 'text/html') | 205 | await channelPageTest('/@' + servers[0].videoChannel.name) |
182 | .expect(HttpStatusCode.OK_200) | 206 | }) |
183 | 207 | ||
184 | expect(res.text).to.contain(`<meta property="og:title" content="${videoName}" />`) | 208 | it('Should have valid Open Graph tags on the watch page', async function () { |
185 | expect(res.text).to.contain(`<meta property="og:description" content="${videoDescriptionPlainText}" />`) | 209 | await watchVideoPageTest('/videos/watch/' + servers[0].video.id) |
186 | expect(res.text).to.contain('<meta property="og:type" content="video" />') | 210 | await watchVideoPageTest('/videos/watch/' + servers[0].video.uuid) |
187 | expect(res.text).to.contain(`<meta property="og:url" content="${servers[0].url}/videos/watch/${servers[0].video.uuid}" />`) | 211 | await watchVideoPageTest('/w/' + servers[0].video.uuid) |
212 | await watchVideoPageTest('/w/' + servers[0].video.id) | ||
188 | }) | 213 | }) |
189 | 214 | ||
190 | it('Should have valid Open Graph tags on the watch playlist page', async function () { | 215 | it('Should have valid Open Graph tags on the watch playlist page', async function () { |
191 | const res = await request(servers[0].url) | 216 | await watchPlaylistPageTest('/videos/watch/playlist/' + playlistUUID) |
192 | .get('/videos/watch/playlist/' + playlistUUID) | 217 | await watchPlaylistPageTest('/w/p/' + playlistUUID) |
193 | .set('Accept', 'text/html') | ||
194 | .expect(HttpStatusCode.OK_200) | ||
195 | |||
196 | expect(res.text).to.contain(`<meta property="og:title" content="${playlistName}" />`) | ||
197 | expect(res.text).to.contain(`<meta property="og:description" content="${playlistDescription}" />`) | ||
198 | expect(res.text).to.contain('<meta property="og:type" content="video" />') | ||
199 | expect(res.text).to.contain(`<meta property="og:url" content="${servers[0].url}/videos/watch/playlist/${playlistUUID}" />`) | ||
200 | }) | 218 | }) |
201 | }) | 219 | }) |
202 | 220 | ||
203 | describe('Twitter card', async function () { | 221 | describe('Twitter card', async function () { |
204 | 222 | ||
205 | it('Should have valid twitter card on the watch video page', async function () { | 223 | describe('Not whitelisted', function () { |
206 | const res = await request(servers[0].url) | ||
207 | .get('/videos/watch/' + servers[0].video.uuid) | ||
208 | .set('Accept', 'text/html') | ||
209 | .expect(HttpStatusCode.OK_200) | ||
210 | 224 | ||
211 | expect(res.text).to.contain('<meta property="twitter:card" content="summary_large_image" />') | 225 | async function accountPageTest (path: string) { |
212 | expect(res.text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />') | 226 | const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 }) |
213 | expect(res.text).to.contain(`<meta property="twitter:title" content="${videoName}" />`) | 227 | const text = res.text |
214 | expect(res.text).to.contain(`<meta property="twitter:description" content="${videoDescriptionPlainText}" />`) | 228 | |
215 | }) | 229 | expect(text).to.contain('<meta property="twitter:card" content="summary" />') |
230 | expect(text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />') | ||
231 | expect(text).to.contain(`<meta property="twitter:title" content="${account.name}" />`) | ||
232 | expect(text).to.contain(`<meta property="twitter:description" content="${account.description}" />`) | ||
233 | } | ||
216 | 234 | ||
217 | it('Should have valid twitter card on the watch playlist page', async function () { | 235 | async function channelPageTest (path: string) { |
218 | const res = await request(servers[0].url) | 236 | const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 }) |
219 | .get('/videos/watch/playlist/' + playlistUUID) | 237 | const text = res.text |
220 | .set('Accept', 'text/html') | ||
221 | .expect(HttpStatusCode.OK_200) | ||
222 | 238 | ||
223 | expect(res.text).to.contain('<meta property="twitter:card" content="summary" />') | 239 | expect(text).to.contain('<meta property="twitter:card" content="summary" />') |
224 | expect(res.text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />') | 240 | expect(text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />') |
225 | expect(res.text).to.contain(`<meta property="twitter:title" content="${playlistName}" />`) | 241 | expect(text).to.contain(`<meta property="twitter:title" content="${servers[0].videoChannel.displayName}" />`) |
226 | expect(res.text).to.contain(`<meta property="twitter:description" content="${playlistDescription}" />`) | 242 | expect(text).to.contain(`<meta property="twitter:description" content="${channelDescription}" />`) |
227 | }) | 243 | } |
228 | 244 | ||
229 | it('Should have valid twitter card on the account page', async function () { | 245 | async function watchVideoPageTest (path: string) { |
230 | const res = await request(servers[0].url) | 246 | const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 }) |
231 | .get('/accounts/' + account.name) | 247 | const text = res.text |
232 | .set('Accept', 'text/html') | ||
233 | .expect(HttpStatusCode.OK_200) | ||
234 | 248 | ||
235 | expect(res.text).to.contain('<meta property="twitter:card" content="summary" />') | 249 | expect(text).to.contain('<meta property="twitter:card" content="summary_large_image" />') |
236 | expect(res.text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />') | 250 | expect(text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />') |
237 | expect(res.text).to.contain(`<meta property="twitter:title" content="${account.name}" />`) | 251 | expect(text).to.contain(`<meta property="twitter:title" content="${videoName}" />`) |
238 | expect(res.text).to.contain(`<meta property="twitter:description" content="${account.description}" />`) | 252 | expect(text).to.contain(`<meta property="twitter:description" content="${videoDescriptionPlainText}" />`) |
239 | }) | 253 | } |
254 | |||
255 | async function watchPlaylistPageTest (path: string) { | ||
256 | const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 }) | ||
257 | const text = res.text | ||
240 | 258 | ||
241 | it('Should have valid twitter card on the channel page', async function () { | 259 | expect(text).to.contain('<meta property="twitter:card" content="summary" />') |
242 | const res = await request(servers[0].url) | 260 | expect(text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />') |
243 | .get('/video-channels/' + servers[0].videoChannel.name) | 261 | expect(text).to.contain(`<meta property="twitter:title" content="${playlistName}" />`) |
244 | .set('Accept', 'text/html') | 262 | expect(text).to.contain(`<meta property="twitter:description" content="${playlistDescription}" />`) |
245 | .expect(HttpStatusCode.OK_200) | 263 | } |
246 | 264 | ||
247 | expect(res.text).to.contain('<meta property="twitter:card" content="summary" />') | 265 | it('Should have valid twitter card on the watch video page', async function () { |
248 | expect(res.text).to.contain('<meta property="twitter:site" content="@Chocobozzz" />') | 266 | await watchVideoPageTest('/videos/watch/' + servers[0].video.id) |
249 | expect(res.text).to.contain(`<meta property="twitter:title" content="${servers[0].videoChannel.displayName}" />`) | 267 | await watchVideoPageTest('/videos/watch/' + servers[0].video.uuid) |
250 | expect(res.text).to.contain(`<meta property="twitter:description" content="${channelDescription}" />`) | 268 | await watchVideoPageTest('/w/' + servers[0].video.uuid) |
269 | await watchVideoPageTest('/w/' + servers[0].video.id) | ||
270 | }) | ||
271 | |||
272 | it('Should have valid twitter card on the watch playlist page', async function () { | ||
273 | await watchPlaylistPageTest('/videos/watch/playlist/' + playlistUUID) | ||
274 | await watchPlaylistPageTest('/w/p/' + playlistUUID) | ||
275 | }) | ||
276 | |||
277 | it('Should have valid twitter card on the account page', async function () { | ||
278 | await accountPageTest('/accounts/' + account.name) | ||
279 | await accountPageTest('/a/' + account.name) | ||
280 | await accountPageTest('/@' + account.name) | ||
281 | }) | ||
282 | |||
283 | it('Should have valid twitter card on the channel page', async function () { | ||
284 | await channelPageTest('/video-channels/' + servers[0].videoChannel.name) | ||
285 | await channelPageTest('/c/' + servers[0].videoChannel.name) | ||
286 | await channelPageTest('/@' + servers[0].videoChannel.name) | ||
287 | }) | ||
251 | }) | 288 | }) |
252 | 289 | ||
253 | it('Should have valid twitter card if Twitter is whitelisted', async function () { | 290 | describe('Whitelisted', function () { |
254 | const res1 = await getCustomConfig(servers[0].url, servers[0].accessToken) | 291 | |
255 | const config = res1.body | 292 | before(async function () { |
256 | config.services.twitter = { | 293 | const res = await getCustomConfig(servers[0].url, servers[0].accessToken) |
257 | username: '@Kuja', | 294 | const config = res.body as CustomConfig |
258 | whitelisted: true | 295 | config.services.twitter = { |
296 | username: '@Kuja', | ||
297 | whitelisted: true | ||
298 | } | ||
299 | |||
300 | await updateCustomConfig(servers[0].url, servers[0].accessToken, config) | ||
301 | }) | ||
302 | |||
303 | async function accountPageTest (path: string) { | ||
304 | const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 }) | ||
305 | const text = res.text | ||
306 | |||
307 | expect(text).to.contain('<meta property="twitter:card" content="summary" />') | ||
308 | expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />') | ||
259 | } | 309 | } |
260 | await updateCustomConfig(servers[0].url, servers[0].accessToken, config) | ||
261 | 310 | ||
262 | const resVideoRequest = await request(servers[0].url) | 311 | async function channelPageTest (path: string) { |
263 | .get('/videos/watch/' + servers[0].video.uuid) | 312 | const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 }) |
264 | .set('Accept', 'text/html') | 313 | const text = res.text |
265 | .expect(HttpStatusCode.OK_200) | ||
266 | 314 | ||
267 | expect(resVideoRequest.text).to.contain('<meta property="twitter:card" content="player" />') | 315 | expect(text).to.contain('<meta property="twitter:card" content="summary" />') |
268 | expect(resVideoRequest.text).to.contain('<meta property="twitter:site" content="@Kuja" />') | 316 | expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />') |
317 | } | ||
318 | |||
319 | async function watchVideoPageTest (path: string) { | ||
320 | const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 }) | ||
321 | const text = res.text | ||
322 | |||
323 | expect(text).to.contain('<meta property="twitter:card" content="player" />') | ||
324 | expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />') | ||
325 | } | ||
269 | 326 | ||
270 | const resVideoPlaylistRequest = await request(servers[0].url) | 327 | async function watchPlaylistPageTest (path: string) { |
271 | .get('/videos/watch/playlist/' + playlistUUID) | 328 | const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', statusCodeExpected: HttpStatusCode.OK_200 }) |
272 | .set('Accept', 'text/html') | 329 | const text = res.text |
273 | .expect(HttpStatusCode.OK_200) | ||
274 | 330 | ||
275 | expect(resVideoPlaylistRequest.text).to.contain('<meta property="twitter:card" content="player" />') | 331 | expect(text).to.contain('<meta property="twitter:card" content="player" />') |
276 | expect(resVideoPlaylistRequest.text).to.contain('<meta property="twitter:site" content="@Kuja" />') | 332 | expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />') |
333 | } | ||
277 | 334 | ||
278 | const resAccountRequest = await request(servers[0].url) | 335 | it('Should have valid twitter card on the watch video page', async function () { |
279 | .get('/accounts/' + account.name) | 336 | await watchVideoPageTest('/videos/watch/' + servers[0].video.id) |
280 | .set('Accept', 'text/html') | 337 | await watchVideoPageTest('/videos/watch/' + servers[0].video.uuid) |
281 | .expect(HttpStatusCode.OK_200) | 338 | await watchVideoPageTest('/w/' + servers[0].video.uuid) |
339 | await watchVideoPageTest('/w/' + servers[0].video.id) | ||
340 | }) | ||
282 | 341 | ||
283 | expect(resAccountRequest.text).to.contain('<meta property="twitter:card" content="summary" />') | 342 | it('Should have valid twitter card on the watch playlist page', async function () { |
284 | expect(resAccountRequest.text).to.contain('<meta property="twitter:site" content="@Kuja" />') | 343 | await watchPlaylistPageTest('/videos/watch/playlist/' + playlistUUID) |
344 | await watchPlaylistPageTest('/w/p/' + playlistUUID) | ||
345 | }) | ||
285 | 346 | ||
286 | const resChannelRequest = await request(servers[0].url) | 347 | it('Should have valid twitter card on the account page', async function () { |
287 | .get('/video-channels/' + servers[0].videoChannel.name) | 348 | await accountPageTest('/accounts/' + account.name) |
288 | .set('Accept', 'text/html') | 349 | await accountPageTest('/a/' + account.name) |
289 | .expect(HttpStatusCode.OK_200) | 350 | await accountPageTest('/@' + account.name) |
351 | }) | ||
290 | 352 | ||
291 | expect(resChannelRequest.text).to.contain('<meta property="twitter:card" content="summary" />') | 353 | it('Should have valid twitter card on the channel page', async function () { |
292 | expect(resChannelRequest.text).to.contain('<meta property="twitter:site" content="@Kuja" />') | 354 | await channelPageTest('/video-channels/' + servers[0].videoChannel.name) |
355 | await channelPageTest('/c/' + servers[0].videoChannel.name) | ||
356 | await channelPageTest('/@' + servers[0].videoChannel.name) | ||
357 | }) | ||
293 | }) | 358 | }) |
294 | }) | 359 | }) |
295 | 360 | ||
296 | describe('Index HTML', function () { | 361 | describe('Index HTML', function () { |
297 | 362 | ||
298 | it('Should have valid index html tags (title, description...)', async function () { | 363 | it('Should have valid index html tags (title, description...)', async function () { |
364 | const resConfig = await getConfig(servers[0].url) | ||
299 | const res = await makeHTMLRequest(servers[0].url, '/videos/trending') | 365 | const res = await makeHTMLRequest(servers[0].url, '/videos/trending') |
300 | 366 | ||
301 | const description = 'PeerTube, an ActivityPub-federated video streaming platform using P2P directly in your web browser.' | 367 | const description = 'PeerTube, an ActivityPub-federated video streaming platform using P2P directly in your web browser.' |
302 | checkIndexTags(res.text, 'PeerTube', description, '') | 368 | checkIndexTags(res.text, 'PeerTube', description, '', resConfig.body) |
303 | }) | 369 | }) |
304 | 370 | ||
305 | it('Should update the customized configuration and have the correct index html tags', async function () { | 371 | it('Should update the customized configuration and have the correct index html tags', async function () { |
@@ -318,35 +384,61 @@ describe('Test a client controllers', function () { | |||
318 | } | 384 | } |
319 | }) | 385 | }) |
320 | 386 | ||
387 | const resConfig = await getConfig(servers[0].url) | ||
321 | const res = await makeHTMLRequest(servers[0].url, '/videos/trending') | 388 | const res = await makeHTMLRequest(servers[0].url, '/videos/trending') |
322 | 389 | ||
323 | checkIndexTags(res.text, 'PeerTube updated', 'my short description', 'body { background-color: red; }') | 390 | checkIndexTags(res.text, 'PeerTube updated', 'my short description', 'body { background-color: red; }', resConfig.body) |
324 | }) | 391 | }) |
325 | 392 | ||
326 | it('Should have valid index html updated tags (title, description...)', async function () { | 393 | it('Should have valid index html updated tags (title, description...)', async function () { |
394 | const resConfig = await getConfig(servers[0].url) | ||
327 | const res = await makeHTMLRequest(servers[0].url, '/videos/trending') | 395 | const res = await makeHTMLRequest(servers[0].url, '/videos/trending') |
328 | 396 | ||
329 | checkIndexTags(res.text, 'PeerTube updated', 'my short description', 'body { background-color: red; }') | 397 | checkIndexTags(res.text, 'PeerTube updated', 'my short description', 'body { background-color: red; }', resConfig.body) |
330 | }) | 398 | }) |
331 | 399 | ||
332 | it('Should use the original video URL for the canonical tag', async function () { | 400 | it('Should use the original video URL for the canonical tag', async function () { |
333 | const res = await makeHTMLRequest(servers[1].url, '/videos/watch/' + servers[0].video.uuid) | 401 | for (const basePath of watchVideoBasePaths) { |
334 | expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/videos/watch/${servers[0].video.uuid}" />`) | 402 | const res = await makeHTMLRequest(servers[1].url, basePath + servers[0].video.uuid) |
403 | expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/videos/watch/${servers[0].video.uuid}" />`) | ||
404 | } | ||
335 | }) | 405 | }) |
336 | 406 | ||
337 | it('Should use the original account URL for the canonical tag', async function () { | 407 | it('Should use the original account URL for the canonical tag', async function () { |
338 | const res = await makeHTMLRequest(servers[1].url, '/accounts/root@' + servers[0].host) | 408 | const accountURLtest = (res) => { |
339 | expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/accounts/root" />`) | 409 | expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/accounts/root" />`) |
410 | } | ||
411 | |||
412 | accountURLtest(await makeHTMLRequest(servers[1].url, '/accounts/root@' + servers[0].host)) | ||
413 | accountURLtest(await makeHTMLRequest(servers[1].url, '/a/root@' + servers[0].host)) | ||
414 | accountURLtest(await makeHTMLRequest(servers[1].url, '/@root@' + servers[0].host)) | ||
340 | }) | 415 | }) |
341 | 416 | ||
342 | it('Should use the original channel URL for the canonical tag', async function () { | 417 | it('Should use the original channel URL for the canonical tag', async function () { |
343 | const res = await makeHTMLRequest(servers[1].url, '/video-channels/root_channel@' + servers[0].host) | 418 | const channelURLtests = (res) => { |
344 | expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/video-channels/root_channel" />`) | 419 | expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/video-channels/root_channel" />`) |
420 | } | ||
421 | |||
422 | channelURLtests(await makeHTMLRequest(servers[1].url, '/video-channels/root_channel@' + servers[0].host)) | ||
423 | channelURLtests(await makeHTMLRequest(servers[1].url, '/c/root_channel@' + servers[0].host)) | ||
424 | channelURLtests(await makeHTMLRequest(servers[1].url, '/@root_channel@' + servers[0].host)) | ||
345 | }) | 425 | }) |
346 | 426 | ||
347 | it('Should use the original playlist URL for the canonical tag', async function () { | 427 | it('Should use the original playlist URL for the canonical tag', async function () { |
348 | const res = await makeHTMLRequest(servers[1].url, '/videos/watch/playlist/' + playlistUUID) | 428 | for (const basePath of watchPlaylistBasePaths) { |
349 | expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/video-playlists/${playlistUUID}" />`) | 429 | const res = await makeHTMLRequest(servers[1].url, basePath + playlistUUID) |
430 | expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/video-playlists/${playlistUUID}" />`) | ||
431 | } | ||
432 | }) | ||
433 | }) | ||
434 | |||
435 | describe('Embed HTML', function () { | ||
436 | |||
437 | it('Should have the correct embed html tags', async function () { | ||
438 | const resConfig = await getConfig(servers[0].url) | ||
439 | const res = await makeHTMLRequest(servers[0].url, servers[0].video.embedPath) | ||
440 | |||
441 | checkIndexTags(res.text, 'PeerTube updated', 'my short description', 'body { background-color: red; }', resConfig.body) | ||
350 | }) | 442 | }) |
351 | }) | 443 | }) |
352 | 444 | ||
diff --git a/server/tests/plugins/filter-hooks.ts b/server/tests/plugins/filter-hooks.ts index 7d4f7abb4..1d6bb6cf4 100644 --- a/server/tests/plugins/filter-hooks.ts +++ b/server/tests/plugins/filter-hooks.ts | |||
@@ -38,6 +38,7 @@ import { | |||
38 | import { cleanupTests, flushAndRunMultipleServers, ServerInfo, waitUntilLog } from '../../../shared/extra-utils/server/servers' | 38 | import { cleanupTests, flushAndRunMultipleServers, ServerInfo, waitUntilLog } from '../../../shared/extra-utils/server/servers' |
39 | import { getGoodVideoUrl, getMyVideoImports, importVideo } from '../../../shared/extra-utils/videos/video-imports' | 39 | import { getGoodVideoUrl, getMyVideoImports, importVideo } from '../../../shared/extra-utils/videos/video-imports' |
40 | import { | 40 | import { |
41 | VideoCommentThreadTree, | ||
41 | VideoDetails, | 42 | VideoDetails, |
42 | VideoImport, | 43 | VideoImport, |
43 | VideoImportState, | 44 | VideoImportState, |
@@ -45,7 +46,6 @@ import { | |||
45 | VideoPlaylistPrivacy, | 46 | VideoPlaylistPrivacy, |
46 | VideoPrivacy | 47 | VideoPrivacy |
47 | } from '../../../shared/models/videos' | 48 | } from '../../../shared/models/videos' |
48 | import { VideoCommentThreadTree } from '../../../shared/models/videos/video-comment.model' | ||
49 | 49 | ||
50 | const expect = chai.expect | 50 | const expect = chai.expect |
51 | 51 | ||
diff --git a/server/tools/peertube-import-videos.ts b/server/tools/peertube-import-videos.ts index 915995031..b3f57a8f9 100644 --- a/server/tools/peertube-import-videos.ts +++ b/server/tools/peertube-import-videos.ts | |||
@@ -11,9 +11,9 @@ import { promisify } from 'util' | |||
11 | import { advancedVideosSearch, getClient, getVideoCategories, login, uploadVideo } from '../../shared/extra-utils/index' | 11 | import { advancedVideosSearch, getClient, getVideoCategories, login, uploadVideo } from '../../shared/extra-utils/index' |
12 | import { sha256 } from '../helpers/core-utils' | 12 | import { sha256 } from '../helpers/core-utils' |
13 | import { doRequestAndSaveToFile } from '../helpers/requests' | 13 | import { doRequestAndSaveToFile } from '../helpers/requests' |
14 | import { buildOriginallyPublishedAt, getYoutubeDLVideoFormat, safeGetYoutubeDL } from '../helpers/youtube-dl' | ||
15 | import { CONSTRAINTS_FIELDS } from '../initializers/constants' | 14 | import { CONSTRAINTS_FIELDS } from '../initializers/constants' |
16 | import { buildCommonVideoOptions, buildVideoAttributesFromCommander, getLogger, getServerCredentials } from './cli' | 15 | import { buildCommonVideoOptions, buildVideoAttributesFromCommander, getLogger, getServerCredentials } from './cli' |
16 | import { YoutubeDL } from '@server/helpers/youtube-dl' | ||
17 | 17 | ||
18 | type UserInfo = { | 18 | type UserInfo = { |
19 | username: string | 19 | username: string |
@@ -74,9 +74,9 @@ async function run (url: string, user: UserInfo) { | |||
74 | user.password = await promptPassword() | 74 | user.password = await promptPassword() |
75 | } | 75 | } |
76 | 76 | ||
77 | const youtubeDL = await safeGetYoutubeDL() | 77 | const youtubeDLBinary = await YoutubeDL.safeGetYoutubeDL() |
78 | 78 | ||
79 | let info = await getYoutubeDLInfo(youtubeDL, options.targetUrl, command.args) | 79 | let info = await getYoutubeDLInfo(youtubeDLBinary, options.targetUrl, command.args) |
80 | 80 | ||
81 | if (!Array.isArray(info)) info = [ info ] | 81 | if (!Array.isArray(info)) info = [ info ] |
82 | 82 | ||
@@ -86,7 +86,7 @@ async function run (url: string, user: UserInfo) { | |||
86 | if (uploadsObject) { | 86 | if (uploadsObject) { |
87 | console.log('Fixing URL to %s.', uploadsObject.url) | 87 | console.log('Fixing URL to %s.', uploadsObject.url) |
88 | 88 | ||
89 | info = await getYoutubeDLInfo(youtubeDL, uploadsObject.url, command.args) | 89 | info = await getYoutubeDLInfo(youtubeDLBinary, uploadsObject.url, command.args) |
90 | } | 90 | } |
91 | 91 | ||
92 | let infoArray: any[] | 92 | let infoArray: any[] |
@@ -130,13 +130,14 @@ async function processVideo (parameters: { | |||
130 | youtubeInfo: any | 130 | youtubeInfo: any |
131 | }) { | 131 | }) { |
132 | const { youtubeInfo, cwd, url, user } = parameters | 132 | const { youtubeInfo, cwd, url, user } = parameters |
133 | const youtubeDL = new YoutubeDL('', []) | ||
133 | 134 | ||
134 | log.debug('Fetching object.', youtubeInfo) | 135 | log.debug('Fetching object.', youtubeInfo) |
135 | 136 | ||
136 | const videoInfo = await fetchObject(youtubeInfo) | 137 | const videoInfo = await fetchObject(youtubeInfo) |
137 | log.debug('Fetched object.', videoInfo) | 138 | log.debug('Fetched object.', videoInfo) |
138 | 139 | ||
139 | const originallyPublishedAt = buildOriginallyPublishedAt(videoInfo) | 140 | const originallyPublishedAt = youtubeDL.buildOriginallyPublishedAt(videoInfo) |
140 | if (options.since && originallyPublishedAt && originallyPublishedAt.getTime() < options.since.getTime()) { | 141 | if (options.since && originallyPublishedAt && originallyPublishedAt.getTime() < options.since.getTime()) { |
141 | log.info('Video "%s" has been published before "%s", don\'t upload it.\n', | 142 | log.info('Video "%s" has been published before "%s", don\'t upload it.\n', |
142 | videoInfo.title, formatDate(options.since)) | 143 | videoInfo.title, formatDate(options.since)) |
@@ -161,13 +162,14 @@ async function processVideo (parameters: { | |||
161 | 162 | ||
162 | log.info('Downloading video "%s"...', videoInfo.title) | 163 | log.info('Downloading video "%s"...', videoInfo.title) |
163 | 164 | ||
164 | const youtubeDLOptions = [ '-f', getYoutubeDLVideoFormat(), ...command.args, '-o', path ] | 165 | const youtubeDLOptions = [ '-f', youtubeDL.getYoutubeDLVideoFormat(), ...command.args, '-o', path ] |
165 | try { | 166 | try { |
166 | const youtubeDL = await safeGetYoutubeDL() | 167 | const youtubeDLBinary = await YoutubeDL.safeGetYoutubeDL() |
167 | const youtubeDLExec = promisify(youtubeDL.exec).bind(youtubeDL) | 168 | const youtubeDLExec = promisify(youtubeDLBinary.exec).bind(youtubeDLBinary) |
168 | const output = await youtubeDLExec(videoInfo.url, youtubeDLOptions, processOptions) | 169 | const output = await youtubeDLExec(videoInfo.url, youtubeDLOptions, processOptions) |
169 | log.info(output.join('\n')) | 170 | log.info(output.join('\n')) |
170 | await uploadVideoOnPeerTube({ | 171 | await uploadVideoOnPeerTube({ |
172 | youtubeDL, | ||
171 | cwd, | 173 | cwd, |
172 | url, | 174 | url, |
173 | user, | 175 | user, |
@@ -180,13 +182,14 @@ async function processVideo (parameters: { | |||
180 | } | 182 | } |
181 | 183 | ||
182 | async function uploadVideoOnPeerTube (parameters: { | 184 | async function uploadVideoOnPeerTube (parameters: { |
185 | youtubeDL: YoutubeDL | ||
183 | videoInfo: any | 186 | videoInfo: any |
184 | videoPath: string | 187 | videoPath: string |
185 | cwd: string | 188 | cwd: string |
186 | url: string | 189 | url: string |
187 | user: { username: string, password: string } | 190 | user: { username: string, password: string } |
188 | }) { | 191 | }) { |
189 | const { videoInfo, videoPath, cwd, url, user } = parameters | 192 | const { youtubeDL, videoInfo, videoPath, cwd, url, user } = parameters |
190 | 193 | ||
191 | const category = await getCategory(videoInfo.categories, url) | 194 | const category = await getCategory(videoInfo.categories, url) |
192 | const licence = getLicence(videoInfo.license) | 195 | const licence = getLicence(videoInfo.license) |
@@ -205,7 +208,7 @@ async function uploadVideoOnPeerTube (parameters: { | |||
205 | await doRequestAndSaveToFile(videoInfo.thumbnail, thumbnailfile) | 208 | await doRequestAndSaveToFile(videoInfo.thumbnail, thumbnailfile) |
206 | } | 209 | } |
207 | 210 | ||
208 | const originallyPublishedAt = buildOriginallyPublishedAt(videoInfo) | 211 | const originallyPublishedAt = youtubeDL.buildOriginallyPublishedAt(videoInfo) |
209 | 212 | ||
210 | const defaultAttributes = { | 213 | const defaultAttributes = { |
211 | name: truncate(videoInfo.title, { | 214 | name: truncate(videoInfo.title, { |
@@ -304,7 +307,7 @@ function fetchObject (info: any) { | |||
304 | const url = buildUrl(info) | 307 | const url = buildUrl(info) |
305 | 308 | ||
306 | return new Promise<any>(async (res, rej) => { | 309 | return new Promise<any>(async (res, rej) => { |
307 | const youtubeDL = await safeGetYoutubeDL() | 310 | const youtubeDL = await YoutubeDL.safeGetYoutubeDL() |
308 | youtubeDL.getInfo(url, undefined, processOptions, (err, videoInfo) => { | 311 | youtubeDL.getInfo(url, undefined, processOptions, (err, videoInfo) => { |
309 | if (err) return rej(err) | 312 | if (err) return rej(err) |
310 | 313 | ||
diff --git a/server/tools/peertube-plugins.ts b/server/tools/peertube-plugins.ts index c8a576844..cb591377b 100644 --- a/server/tools/peertube-plugins.ts +++ b/server/tools/peertube-plugins.ts | |||
@@ -4,10 +4,9 @@ import { registerTSPaths } from '../helpers/register-ts-paths' | |||
4 | registerTSPaths() | 4 | registerTSPaths() |
5 | 5 | ||
6 | import * as program from 'commander' | 6 | import * as program from 'commander' |
7 | import { PluginType } from '../../shared/models/plugins/plugin.type' | ||
8 | import { installPlugin, listPlugins, uninstallPlugin, updatePlugin } from '../../shared/extra-utils/server/plugins' | 7 | import { installPlugin, listPlugins, uninstallPlugin, updatePlugin } from '../../shared/extra-utils/server/plugins' |
9 | import { getAdminTokenOrDie, getServerCredentials } from './cli' | 8 | import { getAdminTokenOrDie, getServerCredentials } from './cli' |
10 | import { PeerTubePlugin } from '../../shared/models/plugins/peertube-plugin.model' | 9 | import { PeerTubePlugin, PluginType } from '../../shared/models' |
11 | import { isAbsolute } from 'path' | 10 | import { isAbsolute } from 'path' |
12 | import * as CliTable3 from 'cli-table3' | 11 | import * as CliTable3 from 'cli-table3' |
13 | import commander = require('commander') | 12 | import commander = require('commander') |
diff --git a/server/tools/peertube-watch.ts b/server/tools/peertube-watch.ts index 6d9cfa3b7..3ca3e242a 100644 --- a/server/tools/peertube-watch.ts +++ b/server/tools/peertube-watch.ts | |||
@@ -30,7 +30,7 @@ function run (url: string, options: program.OptionValues) { | |||
30 | 30 | ||
31 | const cmd = 'node ' + join(__dirname, 'node_modules', 'webtorrent-hybrid', 'bin', 'cmd.js') | 31 | const cmd = 'node ' + join(__dirname, 'node_modules', 'webtorrent-hybrid', 'bin', 'cmd.js') |
32 | const args = ` --${options.gui} ` + | 32 | const args = ` --${options.gui} ` + |
33 | url.replace('videos/watch', 'download/torrents') + | 33 | url.replace(/(\/videos\/watch\/)|\/w\//, '/download/torrents/') + |
34 | `-${options.resolution}.torrent` | 34 | `-${options.resolution}.torrent` |
35 | 35 | ||
36 | try { | 36 | try { |
diff --git a/server/types/models/moderation/abuse-message.ts b/server/types/models/abuse/abuse-message.ts index 565eca706..565eca706 100644 --- a/server/types/models/moderation/abuse-message.ts +++ b/server/types/models/abuse/abuse-message.ts | |||
diff --git a/server/types/models/moderation/abuse.ts b/server/types/models/abuse/abuse.ts index 6fd83684c..6fd83684c 100644 --- a/server/types/models/moderation/abuse.ts +++ b/server/types/models/abuse/abuse.ts | |||
diff --git a/server/types/models/moderation/index.ts b/server/types/models/abuse/index.ts index 1ed91b249..1ed91b249 100644 --- a/server/types/models/moderation/index.ts +++ b/server/types/models/abuse/index.ts | |||
diff --git a/server/types/models/account/account.ts b/server/types/models/account/account.ts index 9513acad8..984841291 100644 --- a/server/types/models/account/account.ts +++ b/server/types/models/account/account.ts | |||
@@ -1,7 +1,5 @@ | |||
1 | import { FunctionProperties, PickWith } from '@shared/core-utils' | 1 | import { FunctionProperties, PickWith } from '@shared/core-utils' |
2 | import { AccountModel } from '../../../models/account/account' | 2 | import { AccountModel } from '../../../models/account/account' |
3 | import { MChannelDefault } from '../video/video-channels' | ||
4 | import { MAccountBlocklistId } from './account-blocklist' | ||
5 | import { | 3 | import { |
6 | MActor, | 4 | MActor, |
7 | MActorAPAccount, | 5 | MActorAPAccount, |
@@ -15,7 +13,9 @@ import { | |||
15 | MActorSummary, | 13 | MActorSummary, |
16 | MActorSummaryFormattable, | 14 | MActorSummaryFormattable, |
17 | MActorUrl | 15 | MActorUrl |
18 | } from './actor' | 16 | } from '../actor' |
17 | import { MChannelDefault } from '../video/video-channels' | ||
18 | import { MAccountBlocklistId } from './account-blocklist' | ||
19 | 19 | ||
20 | type Use<K extends keyof AccountModel, M> = PickWith<AccountModel, K, M> | 20 | type Use<K extends keyof AccountModel, M> = PickWith<AccountModel, K, M> |
21 | 21 | ||
diff --git a/server/types/models/account/actor-custom-page.ts b/server/types/models/account/actor-custom-page.ts new file mode 100644 index 000000000..2cb8aa7e4 --- /dev/null +++ b/server/types/models/account/actor-custom-page.ts | |||
@@ -0,0 +1,4 @@ | |||
1 | |||
2 | import { ActorCustomPageModel } from '../../../models/account/actor-custom-page' | ||
3 | |||
4 | export type MActorCustomPage = Omit<ActorCustomPageModel, 'Actor'> | ||
diff --git a/server/types/models/account/index.ts b/server/types/models/account/index.ts index e3fc00f94..9679c01e4 100644 --- a/server/types/models/account/index.ts +++ b/server/types/models/account/index.ts | |||
@@ -1,5 +1,3 @@ | |||
1 | export * from './account' | 1 | export * from './account' |
2 | export * from './actor-custom-page' | ||
2 | export * from './account-blocklist' | 3 | export * from './account-blocklist' |
3 | export * from './actor-follow' | ||
4 | export * from './actor-image' | ||
5 | export * from './actor' | ||
diff --git a/server/types/models/account/actor-follow.ts b/server/types/models/actor/actor-follow.ts index 8e19c6140..98a6ca8a5 100644 --- a/server/types/models/account/actor-follow.ts +++ b/server/types/models/actor/actor-follow.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import { PickWith } from '@shared/core-utils' | 1 | import { PickWith } from '@shared/core-utils' |
2 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' | 2 | import { ActorFollowModel } from '../../../models/actor/actor-follow' |
3 | import { | 3 | import { |
4 | MActor, | 4 | MActor, |
5 | MActorChannelAccountActor, | 5 | MActorChannelAccountActor, |
diff --git a/server/types/models/account/actor-image.ts b/server/types/models/actor/actor-image.ts index e59f8b141..89adb01ae 100644 --- a/server/types/models/account/actor-image.ts +++ b/server/types/models/actor/actor-image.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import { ActorImageModel } from '../../../models/account/actor-image' | ||
2 | import { FunctionProperties } from '@shared/core-utils' | 1 | import { FunctionProperties } from '@shared/core-utils' |
2 | import { ActorImageModel } from '../../../models/actor/actor-image' | ||
3 | 3 | ||
4 | export type MActorImage = ActorImageModel | 4 | export type MActorImage = ActorImageModel |
5 | 5 | ||
diff --git a/server/types/models/account/actor.ts b/server/types/models/actor/actor.ts index 0b620872e..b3a70cbce 100644 --- a/server/types/models/account/actor.ts +++ b/server/types/models/actor/actor.ts | |||
@@ -1,9 +1,8 @@ | |||
1 | |||
2 | import { FunctionProperties, PickWith, PickWithOpt } from '@shared/core-utils' | 1 | import { FunctionProperties, PickWith, PickWithOpt } from '@shared/core-utils' |
3 | import { ActorModel } from '../../../models/activitypub/actor' | 2 | import { ActorModel } from '../../../models/actor/actor' |
3 | import { MAccount, MAccountDefault, MAccountId, MAccountIdActor } from '../account' | ||
4 | import { MServer, MServerHost, MServerHostBlocks, MServerRedundancyAllowed } from '../server' | 4 | import { MServer, MServerHost, MServerHostBlocks, MServerRedundancyAllowed } from '../server' |
5 | import { MChannel, MChannelAccountActor, MChannelAccountDefault, MChannelId, MChannelIdActor } from '../video' | 5 | import { MChannel, MChannelAccountActor, MChannelAccountDefault, MChannelId, MChannelIdActor } from '../video' |
6 | import { MAccount, MAccountDefault, MAccountId, MAccountIdActor } from './account' | ||
7 | import { MActorImage, MActorImageFormattable } from './actor-image' | 6 | import { MActorImage, MActorImageFormattable } from './actor-image' |
8 | 7 | ||
9 | type Use<K extends keyof ActorModel, M> = PickWith<ActorModel, K, M> | 8 | type Use<K extends keyof ActorModel, M> = PickWith<ActorModel, K, M> |
diff --git a/server/types/models/actor/index.ts b/server/types/models/actor/index.ts new file mode 100644 index 000000000..b27815255 --- /dev/null +++ b/server/types/models/actor/index.ts | |||
@@ -0,0 +1,3 @@ | |||
1 | export * from './actor-follow' | ||
2 | export * from './actor-image' | ||
3 | export * from './actor' | ||
diff --git a/server/types/models/index.ts b/server/types/models/index.ts index b4fdb1ff3..704cb9844 100644 --- a/server/types/models/index.ts +++ b/server/types/models/index.ts | |||
@@ -1,6 +1,7 @@ | |||
1 | export * from './abuse' | ||
1 | export * from './account' | 2 | export * from './account' |
3 | export * from './actor' | ||
2 | export * from './application' | 4 | export * from './application' |
3 | export * from './moderation' | ||
4 | export * from './oauth' | 5 | export * from './oauth' |
5 | export * from './server' | 6 | export * from './server' |
6 | export * from './user' | 7 | export * from './user' |
diff --git a/server/types/models/user/user-notification-setting.ts b/server/types/models/user/user-notification-setting.ts index c674add1b..d1db645e7 100644 --- a/server/types/models/user/user-notification-setting.ts +++ b/server/types/models/user/user-notification-setting.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | import { UserNotificationSettingModel } from '@server/models/account/user-notification-setting' | 1 | import { UserNotificationSettingModel } from '@server/models/user/user-notification-setting' |
2 | 2 | ||
3 | export type MNotificationSetting = Omit<UserNotificationSettingModel, 'User'> | 3 | export type MNotificationSetting = Omit<UserNotificationSettingModel, 'User'> |
4 | 4 | ||
diff --git a/server/types/models/user/user-notification.ts b/server/types/models/user/user-notification.ts index 7ebb0485d..918614dd1 100644 --- a/server/types/models/user/user-notification.ts +++ b/server/types/models/user/user-notification.ts | |||
@@ -2,13 +2,13 @@ import { VideoAbuseModel } from '@server/models/abuse/video-abuse' | |||
2 | import { VideoCommentAbuseModel } from '@server/models/abuse/video-comment-abuse' | 2 | import { VideoCommentAbuseModel } from '@server/models/abuse/video-comment-abuse' |
3 | import { ApplicationModel } from '@server/models/application/application' | 3 | import { ApplicationModel } from '@server/models/application/application' |
4 | import { PluginModel } from '@server/models/server/plugin' | 4 | import { PluginModel } from '@server/models/server/plugin' |
5 | import { UserNotificationModel } from '@server/models/user/user-notification' | ||
5 | import { PickWith, PickWithOpt } from '@shared/core-utils' | 6 | import { PickWith, PickWithOpt } from '@shared/core-utils' |
6 | import { AbuseModel } from '../../../models/abuse/abuse' | 7 | import { AbuseModel } from '../../../models/abuse/abuse' |
7 | import { AccountModel } from '../../../models/account/account' | 8 | import { AccountModel } from '../../../models/account/account' |
8 | import { ActorImageModel } from '../../../models/account/actor-image' | 9 | import { ActorModel } from '../../../models/actor/actor' |
9 | import { UserNotificationModel } from '../../../models/account/user-notification' | 10 | import { ActorFollowModel } from '../../../models/actor/actor-follow' |
10 | import { ActorModel } from '../../../models/activitypub/actor' | 11 | import { ActorImageModel } from '../../../models/actor/actor-image' |
11 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' | ||
12 | import { ServerModel } from '../../../models/server/server' | 12 | import { ServerModel } from '../../../models/server/server' |
13 | import { VideoModel } from '../../../models/video/video' | 13 | import { VideoModel } from '../../../models/video/video' |
14 | import { VideoBlacklistModel } from '../../../models/video/video-blacklist' | 14 | import { VideoBlacklistModel } from '../../../models/video/video-blacklist' |
diff --git a/server/types/models/user/user-video-history.ts b/server/types/models/user/user-video-history.ts index 62673ab1b..34e2930e7 100644 --- a/server/types/models/user/user-video-history.ts +++ b/server/types/models/user/user-video-history.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | import { UserVideoHistoryModel } from '../../../models/account/user-video-history' | 1 | import { UserVideoHistoryModel } from '../../../models/user/user-video-history' |
2 | 2 | ||
3 | export type MUserVideoHistory = Omit<UserVideoHistoryModel, 'Video' | 'User'> | 3 | export type MUserVideoHistory = Omit<UserVideoHistoryModel, 'Video' | 'User'> |
4 | 4 | ||
diff --git a/server/types/models/user/user.ts b/server/types/models/user/user.ts index fa7de9c52..f79220e11 100644 --- a/server/types/models/user/user.ts +++ b/server/types/models/user/user.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import { AccountModel } from '@server/models/account/account' | 1 | import { AccountModel } from '@server/models/account/account' |
2 | import { UserModel } from '@server/models/user/user' | ||
2 | import { MVideoPlaylist } from '@server/types/models' | 3 | import { MVideoPlaylist } from '@server/types/models' |
3 | import { PickWith, PickWithOpt } from '@shared/core-utils' | 4 | import { PickWith, PickWithOpt } from '@shared/core-utils' |
4 | import { UserModel } from '../../../models/account/user' | ||
5 | import { | 5 | import { |
6 | MAccount, | 6 | MAccount, |
7 | MAccountDefault, | 7 | MAccountDefault, |
diff --git a/server/types/models/video/video-channels.ts b/server/types/models/video/video-channels.ts index f577807ca..c147567d9 100644 --- a/server/types/models/video/video-channels.ts +++ b/server/types/models/video/video-channels.ts | |||
@@ -9,7 +9,9 @@ import { | |||
9 | MAccountSummaryBlocks, | 9 | MAccountSummaryBlocks, |
10 | MAccountSummaryFormattable, | 10 | MAccountSummaryFormattable, |
11 | MAccountUrl, | 11 | MAccountUrl, |
12 | MAccountUserId, | 12 | MAccountUserId |
13 | } from '../account' | ||
14 | import { | ||
13 | MActor, | 15 | MActor, |
14 | MActorAccountChannelId, | 16 | MActorAccountChannelId, |
15 | MActorAPChannel, | 17 | MActorAPChannel, |
@@ -23,7 +25,7 @@ import { | |||
23 | MActorSummary, | 25 | MActorSummary, |
24 | MActorSummaryFormattable, | 26 | MActorSummaryFormattable, |
25 | MActorUrl | 27 | MActorUrl |
26 | } from '../account' | 28 | } from '../actor' |
27 | import { MVideo } from './video' | 29 | import { MVideo } from './video' |
28 | 30 | ||
29 | type Use<K extends keyof VideoChannelModel, M> = PickWith<VideoChannelModel, K, M> | 31 | type Use<K extends keyof VideoChannelModel, M> = PickWith<VideoChannelModel, K, M> |
diff --git a/server/types/models/video/video-share.ts b/server/types/models/video/video-share.ts index b7a783bb6..78f44e58c 100644 --- a/server/types/models/video/video-share.ts +++ b/server/types/models/video/video-share.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import { VideoShareModel } from '../../../models/video/video-share' | ||
2 | import { PickWith } from '@shared/core-utils' | 1 | import { PickWith } from '@shared/core-utils' |
3 | import { MActorDefault } from '../account' | 2 | import { VideoShareModel } from '../../../models/video/video-share' |
3 | import { MActorDefault } from '../actor' | ||
4 | import { MVideo } from './video' | 4 | import { MVideo } from './video' |
5 | 5 | ||
6 | type Use<K extends keyof VideoShareModel, M> = PickWith<VideoShareModel, K, M> | 6 | type Use<K extends keyof VideoShareModel, M> = PickWith<VideoShareModel, K, M> |
diff --git a/server/types/plugins/register-server-option.model.ts b/server/types/plugins/register-server-option.model.ts index 2432b7ac4..8774bcd8c 100644 --- a/server/types/plugins/register-server-option.model.ts +++ b/server/types/plugins/register-server-option.model.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import { Router, Response } from 'express' | 1 | import { Response, Router } from 'express' |
2 | import { Logger } from 'winston' | 2 | import { Logger } from 'winston' |
3 | import { ActorModel } from '@server/models/activitypub/actor' | 3 | import { ActorModel } from '@server/models/actor/actor' |
4 | import { | 4 | import { |
5 | PluginPlaylistPrivacyManager, | 5 | PluginPlaylistPrivacyManager, |
6 | PluginSettingsManager, | 6 | PluginSettingsManager, |
diff --git a/server/types/sequelize.ts b/server/types/sequelize.ts index 9cd83612d..535113d01 100644 --- a/server/types/sequelize.ts +++ b/server/types/sequelize.ts | |||
@@ -1,4 +1,5 @@ | |||
1 | import { Model } from 'sequelize-typescript' | 1 | import { AttributesOnly } from '@shared/core-utils' |
2 | import { Model } from 'sequelize' | ||
2 | 3 | ||
3 | // Thanks to sequelize-typescript: https://github.com/RobinBuschmann/sequelize-typescript | 4 | // Thanks to sequelize-typescript: https://github.com/RobinBuschmann/sequelize-typescript |
4 | 5 | ||
@@ -9,7 +10,7 @@ export type Omit<T, K extends keyof T> = { [P in Diff<keyof T, K>]: T[P] } | |||
9 | 10 | ||
10 | export type RecursivePartial<T> = { [P in keyof T]?: RecursivePartial<T[P]> } | 11 | export type RecursivePartial<T> = { [P in keyof T]?: RecursivePartial<T[P]> } |
11 | 12 | ||
12 | export type FilteredModelAttributes<T extends Model<T>> = RecursivePartial<Omit<T, keyof Model<any>>> & { | 13 | export type FilteredModelAttributes<T extends Model<any>> = Partial<AttributesOnly<T>> & { |
13 | id?: number | any | 14 | id?: number | any |
14 | createdAt?: Date | any | 15 | createdAt?: Date | any |
15 | updatedAt?: Date | any | 16 | updatedAt?: Date | any |