diff options
Diffstat (limited to 'server')
49 files changed, 324 insertions, 151 deletions
diff --git a/server/controllers/api/index.ts b/server/controllers/api/index.ts index 5f49336b1..d1d4ef765 100644 --- a/server/controllers/api/index.ts +++ b/server/controllers/api/index.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import cors from 'cors' | 1 | import cors from 'cors' |
2 | import express from 'express' | 2 | import express from 'express' |
3 | import RateLimit from 'express-rate-limit' | 3 | import { buildRateLimiter } from '@server/middlewares' |
4 | import { HttpStatusCode } from '../../../shared/models' | 4 | import { HttpStatusCode } from '../../../shared/models' |
5 | import { badRequest } from '../../helpers/express-utils' | 5 | import { badRequest } from '../../helpers/express-utils' |
6 | import { CONFIG } from '../../initializers/config' | 6 | import { CONFIG } from '../../initializers/config' |
@@ -29,7 +29,7 @@ apiRouter.use(cors({ | |||
29 | credentials: true | 29 | credentials: true |
30 | })) | 30 | })) |
31 | 31 | ||
32 | const apiRateLimiter = RateLimit({ | 32 | const apiRateLimiter = buildRateLimiter({ |
33 | windowMs: CONFIG.RATES_LIMIT.API.WINDOW_MS, | 33 | windowMs: CONFIG.RATES_LIMIT.API.WINDOW_MS, |
34 | max: CONFIG.RATES_LIMIT.API.MAX | 34 | max: CONFIG.RATES_LIMIT.API.MAX |
35 | }) | 35 | }) |
diff --git a/server/controllers/api/users/index.ts b/server/controllers/api/users/index.ts index 8a06bfe93..46e80d56d 100644 --- a/server/controllers/api/users/index.ts +++ b/server/controllers/api/users/index.ts | |||
@@ -1,5 +1,4 @@ | |||
1 | import express from 'express' | 1 | import express from 'express' |
2 | import RateLimit from 'express-rate-limit' | ||
3 | import { tokensRouter } from '@server/controllers/api/users/token' | 2 | import { tokensRouter } from '@server/controllers/api/users/token' |
4 | import { Hooks } from '@server/lib/plugins/hooks' | 3 | import { Hooks } from '@server/lib/plugins/hooks' |
5 | import { OAuthTokenModel } from '@server/models/oauth/oauth-token' | 4 | import { OAuthTokenModel } from '@server/models/oauth/oauth-token' |
@@ -17,9 +16,11 @@ import { Notifier } from '../../../lib/notifier' | |||
17 | import { Redis } from '../../../lib/redis' | 16 | import { Redis } from '../../../lib/redis' |
18 | import { buildUser, createUserAccountAndChannelAndPlaylist, sendVerifyUserEmail } from '../../../lib/user' | 17 | import { buildUser, createUserAccountAndChannelAndPlaylist, sendVerifyUserEmail } from '../../../lib/user' |
19 | import { | 18 | import { |
19 | adminUsersSortValidator, | ||
20 | asyncMiddleware, | 20 | asyncMiddleware, |
21 | asyncRetryTransactionMiddleware, | 21 | asyncRetryTransactionMiddleware, |
22 | authenticate, | 22 | authenticate, |
23 | buildRateLimiter, | ||
23 | ensureUserHasRight, | 24 | ensureUserHasRight, |
24 | ensureUserRegistrationAllowed, | 25 | ensureUserRegistrationAllowed, |
25 | ensureUserRegistrationAllowedForIP, | 26 | ensureUserRegistrationAllowedForIP, |
@@ -32,7 +33,6 @@ import { | |||
32 | usersListValidator, | 33 | usersListValidator, |
33 | usersRegisterValidator, | 34 | usersRegisterValidator, |
34 | usersRemoveValidator, | 35 | usersRemoveValidator, |
35 | usersSortValidator, | ||
36 | usersUpdateValidator | 36 | usersUpdateValidator |
37 | } from '../../../middlewares' | 37 | } from '../../../middlewares' |
38 | import { | 38 | import { |
@@ -54,13 +54,13 @@ import { myVideoPlaylistsRouter } from './my-video-playlists' | |||
54 | 54 | ||
55 | const auditLogger = auditLoggerFactory('users') | 55 | const auditLogger = auditLoggerFactory('users') |
56 | 56 | ||
57 | const signupRateLimiter = RateLimit({ | 57 | const signupRateLimiter = buildRateLimiter({ |
58 | windowMs: CONFIG.RATES_LIMIT.SIGNUP.WINDOW_MS, | 58 | windowMs: CONFIG.RATES_LIMIT.SIGNUP.WINDOW_MS, |
59 | max: CONFIG.RATES_LIMIT.SIGNUP.MAX, | 59 | max: CONFIG.RATES_LIMIT.SIGNUP.MAX, |
60 | skipFailedRequests: true | 60 | skipFailedRequests: true |
61 | }) | 61 | }) |
62 | 62 | ||
63 | const askSendEmailLimiter = RateLimit({ | 63 | const askSendEmailLimiter = buildRateLimiter({ |
64 | windowMs: CONFIG.RATES_LIMIT.ASK_SEND_EMAIL.WINDOW_MS, | 64 | windowMs: CONFIG.RATES_LIMIT.ASK_SEND_EMAIL.WINDOW_MS, |
65 | max: CONFIG.RATES_LIMIT.ASK_SEND_EMAIL.MAX | 65 | max: CONFIG.RATES_LIMIT.ASK_SEND_EMAIL.MAX |
66 | }) | 66 | }) |
@@ -84,7 +84,7 @@ usersRouter.get('/', | |||
84 | authenticate, | 84 | authenticate, |
85 | ensureUserHasRight(UserRight.MANAGE_USERS), | 85 | ensureUserHasRight(UserRight.MANAGE_USERS), |
86 | paginationValidator, | 86 | paginationValidator, |
87 | usersSortValidator, | 87 | adminUsersSortValidator, |
88 | setDefaultSort, | 88 | setDefaultSort, |
89 | setDefaultPagination, | 89 | setDefaultPagination, |
90 | usersListValidator, | 90 | usersListValidator, |
@@ -277,7 +277,7 @@ async function autocompleteUsers (req: express.Request, res: express.Response) { | |||
277 | } | 277 | } |
278 | 278 | ||
279 | async function listUsers (req: express.Request, res: express.Response) { | 279 | async function listUsers (req: express.Request, res: express.Response) { |
280 | const resultList = await UserModel.listForApi({ | 280 | const resultList = await UserModel.listForAdminApi({ |
281 | start: req.query.start, | 281 | start: req.query.start, |
282 | count: req.query.count, | 282 | count: req.query.count, |
283 | sort: req.query.sort, | 283 | sort: req.query.sort, |
diff --git a/server/controllers/api/users/token.ts b/server/controllers/api/users/token.ts index 258b50fe9..012a49791 100644 --- a/server/controllers/api/users/token.ts +++ b/server/controllers/api/users/token.ts | |||
@@ -1,18 +1,17 @@ | |||
1 | import express from 'express' | 1 | import express from 'express' |
2 | import RateLimit from 'express-rate-limit' | ||
3 | import { logger } from '@server/helpers/logger' | 2 | import { logger } from '@server/helpers/logger' |
4 | import { CONFIG } from '@server/initializers/config' | 3 | import { CONFIG } from '@server/initializers/config' |
5 | import { getAuthNameFromRefreshGrant, getBypassFromExternalAuth, getBypassFromPasswordGrant } from '@server/lib/auth/external-auth' | 4 | import { getAuthNameFromRefreshGrant, getBypassFromExternalAuth, getBypassFromPasswordGrant } from '@server/lib/auth/external-auth' |
6 | import { handleOAuthToken } from '@server/lib/auth/oauth' | 5 | import { handleOAuthToken } from '@server/lib/auth/oauth' |
7 | import { BypassLogin, revokeToken } from '@server/lib/auth/oauth-model' | 6 | import { BypassLogin, revokeToken } from '@server/lib/auth/oauth-model' |
8 | import { Hooks } from '@server/lib/plugins/hooks' | 7 | import { Hooks } from '@server/lib/plugins/hooks' |
9 | import { asyncMiddleware, authenticate, openapiOperationDoc } from '@server/middlewares' | 8 | import { asyncMiddleware, authenticate, buildRateLimiter, openapiOperationDoc } from '@server/middlewares' |
10 | import { buildUUID } from '@shared/extra-utils' | 9 | import { buildUUID } from '@shared/extra-utils' |
11 | import { ScopedToken } from '@shared/models/users/user-scoped-token' | 10 | import { ScopedToken } from '@shared/models/users/user-scoped-token' |
12 | 11 | ||
13 | const tokensRouter = express.Router() | 12 | const tokensRouter = express.Router() |
14 | 13 | ||
15 | const loginRateLimiter = RateLimit({ | 14 | const loginRateLimiter = buildRateLimiter({ |
16 | windowMs: CONFIG.RATES_LIMIT.LOGIN.WINDOW_MS, | 15 | windowMs: CONFIG.RATES_LIMIT.LOGIN.WINDOW_MS, |
17 | max: CONFIG.RATES_LIMIT.LOGIN.MAX | 16 | max: CONFIG.RATES_LIMIT.LOGIN.MAX |
18 | }) | 17 | }) |
diff --git a/server/controllers/api/videos/view.ts b/server/controllers/api/videos/view.ts index db1091f2d..dee1ec67c 100644 --- a/server/controllers/api/videos/view.ts +++ b/server/controllers/api/videos/view.ts | |||
@@ -26,7 +26,7 @@ export { | |||
26 | // --------------------------------------------------------------------------- | 26 | // --------------------------------------------------------------------------- |
27 | 27 | ||
28 | async function viewVideo (req: express.Request, res: express.Response) { | 28 | async function viewVideo (req: express.Request, res: express.Response) { |
29 | const video = res.locals.onlyVideo | 29 | const video = res.locals.onlyImmutableVideo |
30 | 30 | ||
31 | const body = req.body as VideoView | 31 | const body = req.body as VideoView |
32 | 32 | ||
diff --git a/server/controllers/feeds.ts b/server/controllers/feeds.ts index c929a6726..9eb31ed93 100644 --- a/server/controllers/feeds.ts +++ b/server/controllers/feeds.ts | |||
@@ -1,13 +1,13 @@ | |||
1 | import express from 'express' | 1 | import express from 'express' |
2 | import { Feed } from '@peertube/feed' | ||
3 | import { extname } from 'path' | 2 | import { extname } from 'path' |
3 | import { Feed } from '@peertube/feed' | ||
4 | import { mdToOneLinePlainText, toSafeHtml } from '@server/helpers/markdown' | 4 | import { mdToOneLinePlainText, toSafeHtml } from '@server/helpers/markdown' |
5 | import { getServerActor } from '@server/models/application/application' | 5 | import { getServerActor } from '@server/models/application/application' |
6 | import { getCategoryLabel } from '@server/models/video/formatter/video-format-utils' | 6 | import { getCategoryLabel } from '@server/models/video/formatter/video-format-utils' |
7 | import { VideoInclude } from '@shared/models' | 7 | import { VideoInclude } from '@shared/models' |
8 | import { buildNSFWFilter } from '../helpers/express-utils' | 8 | import { buildNSFWFilter } from '../helpers/express-utils' |
9 | import { CONFIG } from '../initializers/config' | 9 | import { CONFIG } from '../initializers/config' |
10 | import { FEEDS, MIMETYPES, PREVIEWS_SIZE, ROUTE_CACHE_LIFETIME, WEBSERVER } from '../initializers/constants' | 10 | import { MIMETYPES, PREVIEWS_SIZE, ROUTE_CACHE_LIFETIME, WEBSERVER } from '../initializers/constants' |
11 | import { | 11 | import { |
12 | asyncMiddleware, | 12 | asyncMiddleware, |
13 | commonVideosFiltersValidator, | 13 | commonVideosFiltersValidator, |
@@ -76,7 +76,7 @@ async function generateVideoCommentsFeed (req: express.Request, res: express.Res | |||
76 | 76 | ||
77 | const comments = await VideoCommentModel.listForFeed({ | 77 | const comments = await VideoCommentModel.listForFeed({ |
78 | start, | 78 | start, |
79 | count: FEEDS.COUNT, | 79 | count: CONFIG.FEEDS.COMMENTS.COUNT, |
80 | videoId: video ? video.id : undefined, | 80 | videoId: video ? video.id : undefined, |
81 | accountId: account ? account.id : undefined, | 81 | accountId: account ? account.id : undefined, |
82 | videoChannelId: videoChannel ? videoChannel.id : undefined | 82 | videoChannelId: videoChannel ? videoChannel.id : undefined |
@@ -166,7 +166,7 @@ async function generateVideoFeed (req: express.Request, res: express.Response) { | |||
166 | const server = await getServerActor() | 166 | const server = await getServerActor() |
167 | const { data } = await VideoModel.listForApi({ | 167 | const { data } = await VideoModel.listForApi({ |
168 | start, | 168 | start, |
169 | count: FEEDS.COUNT, | 169 | count: CONFIG.FEEDS.VIDEOS.COUNT, |
170 | sort: req.query.sort, | 170 | sort: req.query.sort, |
171 | displayOnlyForFollower: { | 171 | displayOnlyForFollower: { |
172 | actorId: server.id, | 172 | actorId: server.id, |
@@ -202,7 +202,7 @@ async function generateVideoFeedForSubscriptions (req: express.Request, res: exp | |||
202 | 202 | ||
203 | const { data } = await VideoModel.listForApi({ | 203 | const { data } = await VideoModel.listForApi({ |
204 | start, | 204 | start, |
205 | count: FEEDS.COUNT, | 205 | count: CONFIG.FEEDS.VIDEOS.COUNT, |
206 | sort: req.query.sort, | 206 | sort: req.query.sort, |
207 | nsfw, | 207 | nsfw, |
208 | 208 | ||
diff --git a/server/controllers/lazy-static.ts b/server/controllers/lazy-static.ts index 55bf02660..8a180b5bc 100644 --- a/server/controllers/lazy-static.ts +++ b/server/controllers/lazy-static.ts | |||
@@ -1,9 +1,10 @@ | |||
1 | import cors from 'cors' | 1 | import cors from 'cors' |
2 | import express from 'express' | 2 | import express from 'express' |
3 | import { VideosTorrentCache } from '@server/lib/files-cache/videos-torrent-cache' | 3 | import { VideosTorrentCache } from '@server/lib/files-cache/videos-torrent-cache' |
4 | import { MActorImage } from '@server/types/models' | ||
4 | import { HttpStatusCode } from '../../shared/models/http/http-error-codes' | 5 | import { HttpStatusCode } from '../../shared/models/http/http-error-codes' |
5 | import { logger } from '../helpers/logger' | 6 | import { logger } from '../helpers/logger' |
6 | import { LAZY_STATIC_PATHS, STATIC_MAX_AGE } from '../initializers/constants' | 7 | import { ACTOR_IMAGES_SIZE, LAZY_STATIC_PATHS, STATIC_MAX_AGE } from '../initializers/constants' |
7 | import { VideosCaptionCache, VideosPreviewCache } from '../lib/files-cache' | 8 | import { VideosCaptionCache, VideosPreviewCache } from '../lib/files-cache' |
8 | import { actorImagePathUnsafeCache, pushActorImageProcessInQueue } from '../lib/local-actor' | 9 | import { actorImagePathUnsafeCache, pushActorImageProcessInQueue } from '../lib/local-actor' |
9 | import { asyncMiddleware } from '../middlewares' | 10 | import { asyncMiddleware } from '../middlewares' |
@@ -67,10 +68,7 @@ async function getActorImage (req: express.Request, res: express.Response, next: | |||
67 | await pushActorImageProcessInQueue({ | 68 | await pushActorImageProcessInQueue({ |
68 | filename: image.filename, | 69 | filename: image.filename, |
69 | fileUrl: image.fileUrl, | 70 | fileUrl: image.fileUrl, |
70 | size: { | 71 | size: getActorImageSize(image), |
71 | height: image.height, | ||
72 | width: image.width | ||
73 | }, | ||
74 | type: image.type | 72 | type: image.type |
75 | }) | 73 | }) |
76 | } catch (err) { | 74 | } catch (err) { |
@@ -94,7 +92,7 @@ async function getActorImage (req: express.Request, res: express.Response, next: | |||
94 | if (err.status === HttpStatusCode.NOT_FOUND_404 && !image.isOwned()) { | 92 | if (err.status === HttpStatusCode.NOT_FOUND_404 && !image.isOwned()) { |
95 | logger.error('Cannot lazy serve actor image %s.', filename, { err }) | 93 | logger.error('Cannot lazy serve actor image %s.', filename, { err }) |
96 | 94 | ||
97 | actorImagePathUnsafeCache.del(filename) | 95 | actorImagePathUnsafeCache.delete(filename) |
98 | 96 | ||
99 | image.onDisk = false | 97 | image.onDisk = false |
100 | image.save() | 98 | image.save() |
@@ -105,6 +103,17 @@ async function getActorImage (req: express.Request, res: express.Response, next: | |||
105 | }) | 103 | }) |
106 | } | 104 | } |
107 | 105 | ||
106 | function getActorImageSize (image: MActorImage): { width: number, height: number } { | ||
107 | if (image.width && image.height) { | ||
108 | return { | ||
109 | height: image.height, | ||
110 | width: image.width | ||
111 | } | ||
112 | } | ||
113 | |||
114 | return ACTOR_IMAGES_SIZE[image.type][0] | ||
115 | } | ||
116 | |||
108 | async function getPreview (req: express.Request, res: express.Response) { | 117 | async function getPreview (req: express.Request, res: express.Response) { |
109 | const result = await VideosPreviewCache.Instance.getFilePath(req.params.filename) | 118 | const result = await VideosPreviewCache.Instance.getFilePath(req.params.filename) |
110 | if (!result) return res.status(HttpStatusCode.NOT_FOUND_404).end() | 119 | if (!result) return res.status(HttpStatusCode.NOT_FOUND_404).end() |
diff --git a/server/helpers/audit-logger.ts b/server/helpers/audit-logger.ts index 79ef44be1..076b7f11d 100644 --- a/server/helpers/audit-logger.ts +++ b/server/helpers/audit-logger.ts | |||
@@ -120,7 +120,7 @@ const videoKeysToKeep = [ | |||
120 | 'downloadEnabled' | 120 | 'downloadEnabled' |
121 | ] | 121 | ] |
122 | class VideoAuditView extends EntityAuditView { | 122 | class VideoAuditView extends EntityAuditView { |
123 | constructor (private readonly video: VideoDetails) { | 123 | constructor (video: VideoDetails) { |
124 | super(videoKeysToKeep, 'video', video) | 124 | super(videoKeysToKeep, 'video', video) |
125 | } | 125 | } |
126 | } | 126 | } |
@@ -131,7 +131,7 @@ const videoImportKeysToKeep = [ | |||
131 | 'video-name' | 131 | 'video-name' |
132 | ] | 132 | ] |
133 | class VideoImportAuditView extends EntityAuditView { | 133 | class VideoImportAuditView extends EntityAuditView { |
134 | constructor (private readonly videoImport: VideoImport) { | 134 | constructor (videoImport: VideoImport) { |
135 | super(videoImportKeysToKeep, 'video-import', videoImport) | 135 | super(videoImportKeysToKeep, 'video-import', videoImport) |
136 | } | 136 | } |
137 | } | 137 | } |
@@ -150,7 +150,7 @@ const commentKeysToKeep = [ | |||
150 | 'account-name' | 150 | 'account-name' |
151 | ] | 151 | ] |
152 | class CommentAuditView extends EntityAuditView { | 152 | class CommentAuditView extends EntityAuditView { |
153 | constructor (private readonly comment: VideoComment) { | 153 | constructor (comment: VideoComment) { |
154 | super(commentKeysToKeep, 'comment', comment) | 154 | super(commentKeysToKeep, 'comment', comment) |
155 | } | 155 | } |
156 | } | 156 | } |
@@ -179,7 +179,7 @@ const userKeysToKeep = [ | |||
179 | 'videoChannels' | 179 | 'videoChannels' |
180 | ] | 180 | ] |
181 | class UserAuditView extends EntityAuditView { | 181 | class UserAuditView extends EntityAuditView { |
182 | constructor (private readonly user: User) { | 182 | constructor (user: User) { |
183 | super(userKeysToKeep, 'user', user) | 183 | super(userKeysToKeep, 'user', user) |
184 | } | 184 | } |
185 | } | 185 | } |
@@ -205,7 +205,7 @@ const channelKeysToKeep = [ | |||
205 | 'ownerAccount-displayedName' | 205 | 'ownerAccount-displayedName' |
206 | ] | 206 | ] |
207 | class VideoChannelAuditView extends EntityAuditView { | 207 | class VideoChannelAuditView extends EntityAuditView { |
208 | constructor (private readonly channel: VideoChannel) { | 208 | constructor (channel: VideoChannel) { |
209 | super(channelKeysToKeep, 'channel', channel) | 209 | super(channelKeysToKeep, 'channel', channel) |
210 | } | 210 | } |
211 | } | 211 | } |
@@ -217,7 +217,7 @@ const abuseKeysToKeep = [ | |||
217 | 'createdAt' | 217 | 'createdAt' |
218 | ] | 218 | ] |
219 | class AbuseAuditView extends EntityAuditView { | 219 | class AbuseAuditView extends EntityAuditView { |
220 | constructor (private readonly abuse: AdminAbuse) { | 220 | constructor (abuse: AdminAbuse) { |
221 | super(abuseKeysToKeep, 'abuse', abuse) | 221 | super(abuseKeysToKeep, 'abuse', abuse) |
222 | } | 222 | } |
223 | } | 223 | } |
diff --git a/server/helpers/core-utils.ts b/server/helpers/core-utils.ts index 0ec45eb2e..6ebe8e2ac 100644 --- a/server/helpers/core-utils.ts +++ b/server/helpers/core-utils.ts | |||
@@ -56,6 +56,7 @@ const timeTable = { | |||
56 | export function parseDurationToMs (duration: number | string): number { | 56 | export function parseDurationToMs (duration: number | string): number { |
57 | if (duration === null) return null | 57 | if (duration === null) return null |
58 | if (typeof duration === 'number') return duration | 58 | if (typeof duration === 'number') return duration |
59 | if (!isNaN(+duration)) return +duration | ||
59 | 60 | ||
60 | if (typeof duration === 'string') { | 61 | if (typeof duration === 'string') { |
61 | const split = duration.match(/^([\d.,]+)\s?(\w+)$/) | 62 | const split = duration.match(/^([\d.,]+)\s?(\w+)$/) |
@@ -76,6 +77,7 @@ export function parseDurationToMs (duration: number | string): number { | |||
76 | 77 | ||
77 | export function parseBytes (value: string | number): number { | 78 | export function parseBytes (value: string | number): number { |
78 | if (typeof value === 'number') return value | 79 | if (typeof value === 'number') return value |
80 | if (!isNaN(+value)) return +value | ||
79 | 81 | ||
80 | const tgm = /^(\d+)\s*TB\s*(\d+)\s*GB\s*(\d+)\s*MB$/ | 82 | const tgm = /^(\d+)\s*TB\s*(\d+)\s*GB\s*(\d+)\s*MB$/ |
81 | const tg = /^(\d+)\s*TB\s*(\d+)\s*GB$/ | 83 | const tg = /^(\d+)\s*TB\s*(\d+)\s*GB$/ |
@@ -85,40 +87,55 @@ export function parseBytes (value: string | number): number { | |||
85 | const g = /^(\d+)\s*GB$/ | 87 | const g = /^(\d+)\s*GB$/ |
86 | const m = /^(\d+)\s*MB$/ | 88 | const m = /^(\d+)\s*MB$/ |
87 | const b = /^(\d+)\s*B$/ | 89 | const b = /^(\d+)\s*B$/ |
88 | let match | 90 | |
91 | let match: RegExpMatchArray | ||
89 | 92 | ||
90 | if (value.match(tgm)) { | 93 | if (value.match(tgm)) { |
91 | match = value.match(tgm) | 94 | match = value.match(tgm) |
92 | return parseInt(match[1], 10) * 1024 * 1024 * 1024 * 1024 + | 95 | return parseInt(match[1], 10) * 1024 * 1024 * 1024 * 1024 + |
93 | parseInt(match[2], 10) * 1024 * 1024 * 1024 + | 96 | parseInt(match[2], 10) * 1024 * 1024 * 1024 + |
94 | parseInt(match[3], 10) * 1024 * 1024 | 97 | parseInt(match[3], 10) * 1024 * 1024 |
95 | } else if (value.match(tg)) { | 98 | } |
99 | |||
100 | if (value.match(tg)) { | ||
96 | match = value.match(tg) | 101 | match = value.match(tg) |
97 | return parseInt(match[1], 10) * 1024 * 1024 * 1024 * 1024 + | 102 | return parseInt(match[1], 10) * 1024 * 1024 * 1024 * 1024 + |
98 | parseInt(match[2], 10) * 1024 * 1024 * 1024 | 103 | parseInt(match[2], 10) * 1024 * 1024 * 1024 |
99 | } else if (value.match(tm)) { | 104 | } |
105 | |||
106 | if (value.match(tm)) { | ||
100 | match = value.match(tm) | 107 | match = value.match(tm) |
101 | return parseInt(match[1], 10) * 1024 * 1024 * 1024 * 1024 + | 108 | return parseInt(match[1], 10) * 1024 * 1024 * 1024 * 1024 + |
102 | parseInt(match[2], 10) * 1024 * 1024 | 109 | parseInt(match[2], 10) * 1024 * 1024 |
103 | } else if (value.match(gm)) { | 110 | } |
111 | |||
112 | if (value.match(gm)) { | ||
104 | match = value.match(gm) | 113 | match = value.match(gm) |
105 | return parseInt(match[1], 10) * 1024 * 1024 * 1024 + | 114 | return parseInt(match[1], 10) * 1024 * 1024 * 1024 + |
106 | parseInt(match[2], 10) * 1024 * 1024 | 115 | parseInt(match[2], 10) * 1024 * 1024 |
107 | } else if (value.match(t)) { | 116 | } |
117 | |||
118 | if (value.match(t)) { | ||
108 | match = value.match(t) | 119 | match = value.match(t) |
109 | return parseInt(match[1], 10) * 1024 * 1024 * 1024 * 1024 | 120 | return parseInt(match[1], 10) * 1024 * 1024 * 1024 * 1024 |
110 | } else if (value.match(g)) { | 121 | } |
122 | |||
123 | if (value.match(g)) { | ||
111 | match = value.match(g) | 124 | match = value.match(g) |
112 | return parseInt(match[1], 10) * 1024 * 1024 * 1024 | 125 | return parseInt(match[1], 10) * 1024 * 1024 * 1024 |
113 | } else if (value.match(m)) { | 126 | } |
127 | |||
128 | if (value.match(m)) { | ||
114 | match = value.match(m) | 129 | match = value.match(m) |
115 | return parseInt(match[1], 10) * 1024 * 1024 | 130 | return parseInt(match[1], 10) * 1024 * 1024 |
116 | } else if (value.match(b)) { | 131 | } |
132 | |||
133 | if (value.match(b)) { | ||
117 | match = value.match(b) | 134 | match = value.match(b) |
118 | return parseInt(match[1], 10) * 1024 | 135 | return parseInt(match[1], 10) * 1024 |
119 | } else { | ||
120 | return parseInt(value, 10) | ||
121 | } | 136 | } |
137 | |||
138 | return parseInt(value, 10) | ||
122 | } | 139 | } |
123 | 140 | ||
124 | // --------------------------------------------------------------------------- | 141 | // --------------------------------------------------------------------------- |
diff --git a/server/helpers/image-utils.ts b/server/helpers/image-utils.ts index 7d6451db9..ebb102a0d 100644 --- a/server/helpers/image-utils.ts +++ b/server/helpers/image-utils.ts | |||
@@ -114,7 +114,7 @@ async function autoResize (options: { | |||
114 | }) { | 114 | }) { |
115 | const { sourceImage, newSize, destination } = options | 115 | const { sourceImage, newSize, destination } = options |
116 | 116 | ||
117 | // Portrait mode targetting a landscape, apply some effect on the image | 117 | // Portrait mode targeting a landscape, apply some effect on the image |
118 | const sourceIsPortrait = sourceImage.getWidth() < sourceImage.getHeight() | 118 | const sourceIsPortrait = sourceImage.getWidth() < sourceImage.getHeight() |
119 | const destIsPortraitOrSquare = newSize.width <= newSize.height | 119 | const destIsPortraitOrSquare = newSize.width <= newSize.height |
120 | 120 | ||
diff --git a/server/helpers/upload.ts b/server/helpers/upload.ts index c94c7ab82..3cb17edd0 100644 --- a/server/helpers/upload.ts +++ b/server/helpers/upload.ts | |||
@@ -1,5 +1,4 @@ | |||
1 | import { join } from 'path' | 1 | import { join } from 'path' |
2 | import { JobQueue } from '@server/lib/job-queue' | ||
3 | import { RESUMABLE_UPLOAD_DIRECTORY } from '../initializers/constants' | 2 | import { RESUMABLE_UPLOAD_DIRECTORY } from '../initializers/constants' |
4 | 3 | ||
5 | function getResumableUploadPath (filename?: string) { | 4 | function getResumableUploadPath (filename?: string) { |
@@ -8,14 +7,8 @@ function getResumableUploadPath (filename?: string) { | |||
8 | return RESUMABLE_UPLOAD_DIRECTORY | 7 | return RESUMABLE_UPLOAD_DIRECTORY |
9 | } | 8 | } |
10 | 9 | ||
11 | function scheduleDeleteResumableUploadMetaFile (filepath: string) { | ||
12 | const payload = { filepath } | ||
13 | JobQueue.Instance.createJob({ type: 'delete-resumable-upload-meta-file', payload }, { delay: 900 * 1000 }) // executed in 15 min | ||
14 | } | ||
15 | |||
16 | // --------------------------------------------------------------------------- | 10 | // --------------------------------------------------------------------------- |
17 | 11 | ||
18 | export { | 12 | export { |
19 | getResumableUploadPath, | 13 | getResumableUploadPath |
20 | scheduleDeleteResumableUploadMetaFile | ||
21 | } | 14 | } |
diff --git a/server/initializers/checker-before-init.ts b/server/initializers/checker-before-init.ts index 794303743..359f0c31d 100644 --- a/server/initializers/checker-before-init.ts +++ b/server/initializers/checker-before-init.ts | |||
@@ -31,8 +31,8 @@ function checkMissedConfig () { | |||
31 | 'transcoding.resolutions.0p', 'transcoding.resolutions.144p', 'transcoding.resolutions.240p', 'transcoding.resolutions.360p', | 31 | 'transcoding.resolutions.0p', 'transcoding.resolutions.144p', 'transcoding.resolutions.240p', 'transcoding.resolutions.360p', |
32 | 'transcoding.resolutions.480p', 'transcoding.resolutions.720p', 'transcoding.resolutions.1080p', 'transcoding.resolutions.1440p', | 32 | 'transcoding.resolutions.480p', 'transcoding.resolutions.720p', 'transcoding.resolutions.1080p', 'transcoding.resolutions.1440p', |
33 | 'transcoding.resolutions.2160p', 'video_studio.enabled', | 33 | 'transcoding.resolutions.2160p', 'video_studio.enabled', |
34 | 'import.videos.http.enabled', 'import.videos.torrent.enabled', 'import.videos.concurrency', 'auto_blacklist.videos.of_users.enabled', | 34 | 'import.videos.http.enabled', 'import.videos.torrent.enabled', 'import.videos.concurrency', 'import.videos.timeout', |
35 | 'trending.videos.interval_days', | 35 | 'auto_blacklist.videos.of_users.enabled', 'trending.videos.interval_days', |
36 | 'client.videos.miniature.display_author_avatar', | 36 | 'client.videos.miniature.display_author_avatar', |
37 | 'client.videos.miniature.prefer_author_display_name', 'client.menu.login.redirect_on_single_external_auth', | 37 | 'client.videos.miniature.prefer_author_display_name', 'client.menu.login.redirect_on_single_external_auth', |
38 | 'defaults.publish.download_enabled', 'defaults.publish.comments_enabled', 'defaults.publish.privacy', 'defaults.publish.licence', | 38 | 'defaults.publish.download_enabled', 'defaults.publish.comments_enabled', 'defaults.publish.privacy', 'defaults.publish.licence', |
@@ -44,6 +44,7 @@ function checkMissedConfig () { | |||
44 | 'history.videos.max_age', 'views.videos.remote.max_age', 'views.videos.local_buffer_update_interval', 'views.videos.ip_view_expiration', | 44 | 'history.videos.max_age', 'views.videos.remote.max_age', 'views.videos.local_buffer_update_interval', 'views.videos.ip_view_expiration', |
45 | 'rates_limit.login.window', 'rates_limit.login.max', 'rates_limit.ask_send_email.window', 'rates_limit.ask_send_email.max', | 45 | 'rates_limit.login.window', 'rates_limit.login.max', 'rates_limit.ask_send_email.window', 'rates_limit.ask_send_email.max', |
46 | 'theme.default', | 46 | 'theme.default', |
47 | 'feeds.videos.count', 'feeds.comments.count', | ||
47 | 'geo_ip.enabled', 'geo_ip.country.database_url', | 48 | 'geo_ip.enabled', 'geo_ip.country.database_url', |
48 | 'remote_redundancy.videos.accept_from', | 49 | 'remote_redundancy.videos.accept_from', |
49 | 'federation.videos.federate_unlisted', 'federation.videos.cleanup_remote_interactions', | 50 | 'federation.videos.federate_unlisted', 'federation.videos.cleanup_remote_interactions', |
diff --git a/server/initializers/config.ts b/server/initializers/config.ts index 59a65d6a5..c76a839bc 100644 --- a/server/initializers/config.ts +++ b/server/initializers/config.ts | |||
@@ -247,6 +247,14 @@ const CONFIG = { | |||
247 | } | 247 | } |
248 | } | 248 | } |
249 | }, | 249 | }, |
250 | FEEDS: { | ||
251 | VIDEOS: { | ||
252 | COUNT: config.get<number>('feeds.videos.count') | ||
253 | }, | ||
254 | COMMENTS: { | ||
255 | COUNT: config.get<number>('feeds.comments.count') | ||
256 | } | ||
257 | }, | ||
250 | ADMIN: { | 258 | ADMIN: { |
251 | get EMAIL () { return config.get<string>('admin.email') } | 259 | get EMAIL () { return config.get<string>('admin.email') } |
252 | }, | 260 | }, |
@@ -349,6 +357,7 @@ const CONFIG = { | |||
349 | IMPORT: { | 357 | IMPORT: { |
350 | VIDEOS: { | 358 | VIDEOS: { |
351 | get CONCURRENCY () { return config.get<number>('import.videos.concurrency') }, | 359 | get CONCURRENCY () { return config.get<number>('import.videos.concurrency') }, |
360 | get TIMEOUT () { return parseDurationToMs(config.get<string>('import.videos.timeout')) }, | ||
352 | 361 | ||
353 | HTTP: { | 362 | HTTP: { |
354 | get ENABLED () { return config.get<boolean>('import.videos.http.enabled') }, | 363 | get ENABLED () { return config.get<boolean>('import.videos.http.enabled') }, |
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index 9201f95b3..f54ce9506 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts | |||
@@ -58,7 +58,7 @@ const WEBSERVER = { | |||
58 | 58 | ||
59 | // Sortable columns per schema | 59 | // Sortable columns per schema |
60 | const SORTABLE_COLUMNS = { | 60 | const SORTABLE_COLUMNS = { |
61 | USERS: [ 'id', 'username', 'videoQuotaUsed', 'createdAt', 'lastLoginDate', 'role' ], | 61 | ADMIN_USERS: [ 'id', 'username', 'videoQuotaUsed', 'createdAt', 'lastLoginDate', 'role' ], |
62 | USER_SUBSCRIPTIONS: [ 'id', 'createdAt' ], | 62 | USER_SUBSCRIPTIONS: [ 'id', 'createdAt' ], |
63 | ACCOUNTS: [ 'createdAt' ], | 63 | ACCOUNTS: [ 'createdAt' ], |
64 | JOBS: [ 'createdAt' ], | 64 | JOBS: [ 'createdAt' ], |
@@ -186,7 +186,7 @@ const JOB_TTL: { [id in JobType]: number } = { | |||
186 | 'video-file-import': 1000 * 3600, // 1 hour | 186 | 'video-file-import': 1000 * 3600, // 1 hour |
187 | 'video-transcoding': 1000 * 3600 * 48, // 2 days, transcoding could be long | 187 | 'video-transcoding': 1000 * 3600 * 48, // 2 days, transcoding could be long |
188 | 'video-studio-edition': 1000 * 3600 * 10, // 10 hours | 188 | 'video-studio-edition': 1000 * 3600 * 10, // 10 hours |
189 | 'video-import': 1000 * 3600 * 2, // 2 hours | 189 | 'video-import': CONFIG.IMPORT.VIDEOS.TIMEOUT, |
190 | 'email': 60000 * 10, // 10 minutes | 190 | 'email': 60000 * 10, // 10 minutes |
191 | 'actor-keys': 60000 * 20, // 20 minutes | 191 | 'actor-keys': 60000 * 20, // 20 minutes |
192 | 'videos-views-stats': undefined, // Unlimited | 192 | 'videos-views-stats': undefined, // Unlimited |
@@ -213,7 +213,7 @@ const CRAWL_REQUEST_CONCURRENCY = 1 // How many requests in parallel to fetch re | |||
213 | 213 | ||
214 | const AP_CLEANER = { | 214 | const AP_CLEANER = { |
215 | CONCURRENCY: 10, // How many requests in parallel we do in activitypub-cleaner job | 215 | CONCURRENCY: 10, // How many requests in parallel we do in activitypub-cleaner job |
216 | UNAVAILABLE_TRESHOLD: 3, // How many attemps we do before removing an unavailable remote resource | 216 | UNAVAILABLE_TRESHOLD: 3, // How many attempts we do before removing an unavailable remote resource |
217 | PERIOD: parseDurationToMs('1 week') // /!\ Has to be sync with REPEAT_JOBS | 217 | PERIOD: parseDurationToMs('1 week') // /!\ Has to be sync with REPEAT_JOBS |
218 | } | 218 | } |
219 | 219 | ||
@@ -734,12 +734,14 @@ const VIDEO_LIVE = { | |||
734 | const MEMOIZE_TTL = { | 734 | const MEMOIZE_TTL = { |
735 | OVERVIEWS_SAMPLE: 1000 * 3600 * 4, // 4 hours | 735 | OVERVIEWS_SAMPLE: 1000 * 3600 * 4, // 4 hours |
736 | INFO_HASH_EXISTS: 1000 * 3600 * 12, // 12 hours | 736 | INFO_HASH_EXISTS: 1000 * 3600 * 12, // 12 hours |
737 | VIDEO_DURATION: 1000 * 10, // 10 seconds | ||
737 | LIVE_ABLE_TO_UPLOAD: 1000 * 60, // 1 minute | 738 | LIVE_ABLE_TO_UPLOAD: 1000 * 60, // 1 minute |
738 | LIVE_CHECK_SOCKET_HEALTH: 1000 * 60 // 1 minute | 739 | LIVE_CHECK_SOCKET_HEALTH: 1000 * 60 // 1 minute |
739 | } | 740 | } |
740 | 741 | ||
741 | const MEMOIZE_LENGTH = { | 742 | const MEMOIZE_LENGTH = { |
742 | INFO_HASH_EXISTS: 200 | 743 | INFO_HASH_EXISTS: 200, |
744 | VIDEO_DURATION: 200 | ||
743 | } | 745 | } |
744 | 746 | ||
745 | const QUEUE_CONCURRENCY = { | 747 | const QUEUE_CONCURRENCY = { |
@@ -769,12 +771,6 @@ const CUSTOM_HTML_TAG_COMMENTS = { | |||
769 | SERVER_CONFIG: '<!-- server config -->' | 771 | SERVER_CONFIG: '<!-- server config -->' |
770 | } | 772 | } |
771 | 773 | ||
772 | // --------------------------------------------------------------------------- | ||
773 | |||
774 | const FEEDS = { | ||
775 | COUNT: 20 | ||
776 | } | ||
777 | |||
778 | const MAX_LOGS_OUTPUT_CHARACTERS = 10 * 1000 * 1000 | 774 | const MAX_LOGS_OUTPUT_CHARACTERS = 10 * 1000 * 1000 |
779 | const LOG_FILENAME = 'peertube.log' | 775 | const LOG_FILENAME = 'peertube.log' |
780 | const AUDIT_LOG_FILENAME = 'peertube-audit.log' | 776 | const AUDIT_LOG_FILENAME = 'peertube-audit.log' |
@@ -818,7 +814,7 @@ const STATS_TIMESERIE = { | |||
818 | // --------------------------------------------------------------------------- | 814 | // --------------------------------------------------------------------------- |
819 | 815 | ||
820 | // Special constants for a test instance | 816 | // Special constants for a test instance |
821 | if (isTestInstance() === true) { | 817 | if (isTestInstance() === true && process.env.PRODUCTION_CONSTANTS !== 'true') { |
822 | PRIVATE_RSA_KEY_SIZE = 1024 | 818 | PRIVATE_RSA_KEY_SIZE = 1024 |
823 | 819 | ||
824 | ACTOR_FOLLOW_SCORE.BASE = 20 | 820 | ACTOR_FOLLOW_SCORE.BASE = 20 |
@@ -942,7 +938,6 @@ export { | |||
942 | ROUTE_CACHE_LIFETIME, | 938 | ROUTE_CACHE_LIFETIME, |
943 | SORTABLE_COLUMNS, | 939 | SORTABLE_COLUMNS, |
944 | HLS_STREAMING_PLAYLIST_DIRECTORY, | 940 | HLS_STREAMING_PLAYLIST_DIRECTORY, |
945 | FEEDS, | ||
946 | JOB_TTL, | 941 | JOB_TTL, |
947 | DEFAULT_THEME_NAME, | 942 | DEFAULT_THEME_NAME, |
948 | NSFW_POLICY_TYPES, | 943 | NSFW_POLICY_TYPES, |
diff --git a/server/lib/activitypub/process/process-create.ts b/server/lib/activitypub/process/process-create.ts index 3e7931bb2..76ed37aae 100644 --- a/server/lib/activitypub/process/process-create.ts +++ b/server/lib/activitypub/process/process-create.ts | |||
@@ -124,7 +124,7 @@ async function processCreateVideoComment (activity: ActivityCreate, byActor: MAc | |||
124 | return | 124 | return |
125 | } | 125 | } |
126 | 126 | ||
127 | // Try to not forward unwanted commments on our videos | 127 | // Try to not forward unwanted comments on our videos |
128 | if (video.isOwned()) { | 128 | if (video.isOwned()) { |
129 | if (await isBlockedByServerOrAccount(comment.Account, video.VideoChannel.Account)) { | 129 | if (await isBlockedByServerOrAccount(comment.Account, video.VideoChannel.Account)) { |
130 | logger.info('Skip comment forward from blocked account or server %s.', comment.Account.Actor.url) | 130 | logger.info('Skip comment forward from blocked account or server %s.', comment.Account.Actor.url) |
diff --git a/server/lib/activitypub/videos/shared/creator.ts b/server/lib/activitypub/videos/shared/creator.ts index 688bcbb53..07252fea2 100644 --- a/server/lib/activitypub/videos/shared/creator.ts +++ b/server/lib/activitypub/videos/shared/creator.ts | |||
@@ -24,7 +24,7 @@ export class APVideoCreator extends APVideoAbstractBuilder { | |||
24 | const channel = channelActor.VideoChannel | 24 | const channel = channelActor.VideoChannel |
25 | 25 | ||
26 | const videoData = getVideoAttributesFromObject(channel, this.videoObject, this.videoObject.to) | 26 | const videoData = getVideoAttributesFromObject(channel, this.videoObject, this.videoObject.to) |
27 | const video = VideoModel.build(videoData) as MVideoThumbnail | 27 | const video = VideoModel.build({ ...videoData, likes: 0, dislikes: 0 }) as MVideoThumbnail |
28 | 28 | ||
29 | const promiseThumbnail = this.tryToGenerateThumbnail(video) | 29 | const promiseThumbnail = this.tryToGenerateThumbnail(video) |
30 | 30 | ||
diff --git a/server/lib/activitypub/videos/shared/object-to-model-attributes.ts b/server/lib/activitypub/videos/shared/object-to-model-attributes.ts index f02b9cba6..86699c5b8 100644 --- a/server/lib/activitypub/videos/shared/object-to-model-attributes.ts +++ b/server/lib/activitypub/videos/shared/object-to-model-attributes.ts | |||
@@ -210,8 +210,6 @@ function getVideoAttributesFromObject (videoChannel: MChannelId, videoObject: Vi | |||
210 | 210 | ||
211 | updatedAt: new Date(videoObject.updated), | 211 | updatedAt: new Date(videoObject.updated), |
212 | views: videoObject.views, | 212 | views: videoObject.views, |
213 | likes: 0, | ||
214 | dislikes: 0, | ||
215 | remote: true, | 213 | remote: true, |
216 | privacy | 214 | privacy |
217 | } | 215 | } |
diff --git a/server/lib/client-html.ts b/server/lib/client-html.ts index 337364ac9..1e8d03023 100644 --- a/server/lib/client-html.ts +++ b/server/lib/client-html.ts | |||
@@ -30,6 +30,7 @@ import { MAccountActor, MChannelActor } from '../types/models' | |||
30 | import { getActivityStreamDuration } from './activitypub/activity' | 30 | import { getActivityStreamDuration } from './activitypub/activity' |
31 | import { getBiggestActorImage } from './actor-image' | 31 | import { getBiggestActorImage } from './actor-image' |
32 | import { ServerConfigManager } from './server-config-manager' | 32 | import { ServerConfigManager } from './server-config-manager' |
33 | import { isTestInstance } from '@server/helpers/core-utils' | ||
33 | 34 | ||
34 | type Tags = { | 35 | type Tags = { |
35 | ogType: string | 36 | ogType: string |
@@ -232,7 +233,10 @@ class ClientHtml { | |||
232 | static async getEmbedHTML () { | 233 | static async getEmbedHTML () { |
233 | const path = ClientHtml.getEmbedPath() | 234 | const path = ClientHtml.getEmbedPath() |
234 | 235 | ||
235 | if (ClientHtml.htmlCache[path]) return ClientHtml.htmlCache[path] | 236 | // Disable HTML cache in dev mode because webpack can regenerate JS files |
237 | if (!isTestInstance() && ClientHtml.htmlCache[path]) { | ||
238 | return ClientHtml.htmlCache[path] | ||
239 | } | ||
236 | 240 | ||
237 | const buffer = await readFile(path) | 241 | const buffer = await readFile(path) |
238 | const serverConfig = await ServerConfigManager.Instance.getHTMLServerConfig() | 242 | const serverConfig = await ServerConfigManager.Instance.getHTMLServerConfig() |
diff --git a/server/lib/emailer.ts b/server/lib/emailer.ts index aebca04fe..edc99057c 100644 --- a/server/lib/emailer.ts +++ b/server/lib/emailer.ts | |||
@@ -179,7 +179,7 @@ class Emailer { | |||
179 | } | 179 | } |
180 | } | 180 | } |
181 | 181 | ||
182 | // overriden/new variables given for a specific template in the payload | 182 | // overridden/new variables given for a specific template in the payload |
183 | const sendOptions = merge(baseOptions, options) | 183 | const sendOptions = merge(baseOptions, options) |
184 | 184 | ||
185 | await email.send(sendOptions) | 185 | await email.send(sendOptions) |
diff --git a/server/lib/job-queue/handlers/move-to-object-storage.ts b/server/lib/job-queue/handlers/move-to-object-storage.ts index f480b32cd..49064052c 100644 --- a/server/lib/job-queue/handlers/move-to-object-storage.ts +++ b/server/lib/job-queue/handlers/move-to-object-storage.ts | |||
@@ -48,15 +48,24 @@ export async function processMoveToObjectStorage (job: Job) { | |||
48 | await doAfterLastJob({ video, previousVideoState: payload.previousVideoState, isNewVideo: payload.isNewVideo }) | 48 | await doAfterLastJob({ video, previousVideoState: payload.previousVideoState, isNewVideo: payload.isNewVideo }) |
49 | } | 49 | } |
50 | } catch (err) { | 50 | } catch (err) { |
51 | logger.error('Cannot move video %s to object storage.', video.url, { err, ...lTags }) | 51 | await onMoveToObjectStorageFailure(job, err) |
52 | |||
53 | await moveToFailedMoveToObjectStorageState(video) | ||
54 | await VideoJobInfoModel.abortAllTasks(video.uuid, 'pendingMove') | ||
55 | } | 52 | } |
56 | 53 | ||
57 | return payload.videoUUID | 54 | return payload.videoUUID |
58 | } | 55 | } |
59 | 56 | ||
57 | export async function onMoveToObjectStorageFailure (job: Job, err: any) { | ||
58 | const payload = job.data as MoveObjectStoragePayload | ||
59 | |||
60 | const video = await VideoModel.loadWithFiles(payload.videoUUID) | ||
61 | if (!video) return | ||
62 | |||
63 | logger.error('Cannot move video %s to object storage.', video.url, { err, ...lTagsBase(video.uuid, video.url) }) | ||
64 | |||
65 | await moveToFailedMoveToObjectStorageState(video) | ||
66 | await VideoJobInfoModel.abortAllTasks(video.uuid, 'pendingMove') | ||
67 | } | ||
68 | |||
60 | // --------------------------------------------------------------------------- | 69 | // --------------------------------------------------------------------------- |
61 | 70 | ||
62 | async function moveWebTorrentFiles (video: MVideoWithAllFiles) { | 71 | async function moveWebTorrentFiles (video: MVideoWithAllFiles) { |
diff --git a/server/lib/job-queue/job-queue.ts b/server/lib/job-queue/job-queue.ts index f339e9135..ce24763f1 100644 --- a/server/lib/job-queue/job-queue.ts +++ b/server/lib/job-queue/job-queue.ts | |||
@@ -33,7 +33,7 @@ import { refreshAPObject } from './handlers/activitypub-refresher' | |||
33 | import { processActorKeys } from './handlers/actor-keys' | 33 | import { processActorKeys } from './handlers/actor-keys' |
34 | import { processEmail } from './handlers/email' | 34 | import { processEmail } from './handlers/email' |
35 | import { processManageVideoTorrent } from './handlers/manage-video-torrent' | 35 | import { processManageVideoTorrent } from './handlers/manage-video-torrent' |
36 | import { processMoveToObjectStorage } from './handlers/move-to-object-storage' | 36 | import { onMoveToObjectStorageFailure, processMoveToObjectStorage } from './handlers/move-to-object-storage' |
37 | import { processVideoFileImport } from './handlers/video-file-import' | 37 | import { processVideoFileImport } from './handlers/video-file-import' |
38 | import { processVideoImport } from './handlers/video-import' | 38 | import { processVideoImport } from './handlers/video-import' |
39 | import { processVideoLiveEnding } from './handlers/video-live-ending' | 39 | import { processVideoLiveEnding } from './handlers/video-live-ending' |
@@ -88,6 +88,10 @@ const handlers: { [id in JobType]: (job: Job) => Promise<any> } = { | |||
88 | 'video-studio-edition': processVideoStudioEdition | 88 | 'video-studio-edition': processVideoStudioEdition |
89 | } | 89 | } |
90 | 90 | ||
91 | const errorHandlers: { [id in JobType]?: (job: Job, err: any) => Promise<any> } = { | ||
92 | 'move-to-object-storage': onMoveToObjectStorageFailure | ||
93 | } | ||
94 | |||
91 | const jobTypes: JobType[] = [ | 95 | const jobTypes: JobType[] = [ |
92 | 'activitypub-follow', | 96 | 'activitypub-follow', |
93 | 'activitypub-http-broadcast', | 97 | 'activitypub-http-broadcast', |
@@ -162,6 +166,11 @@ class JobQueue { | |||
162 | : 'error' | 166 | : 'error' |
163 | 167 | ||
164 | logger.log(logLevel, 'Cannot execute job %d in queue %s.', job.id, handlerName, { payload: job.data, err }) | 168 | logger.log(logLevel, 'Cannot execute job %d in queue %s.', job.id, handlerName, { payload: job.data, err }) |
169 | |||
170 | if (errorHandlers[job.name]) { | ||
171 | errorHandlers[job.name](job, err) | ||
172 | .catch(err => logger.error('Cannot run error handler for job failure %d in queue %s.', job.id, handlerName, { err })) | ||
173 | } | ||
165 | }) | 174 | }) |
166 | 175 | ||
167 | queue.on('error', err => { | 176 | queue.on('error', err => { |
diff --git a/server/lib/notifier/shared/abuse/abstract-new-abuse-message.ts b/server/lib/notifier/shared/abuse/abstract-new-abuse-message.ts index daefa25bd..a7292de69 100644 --- a/server/lib/notifier/shared/abuse/abstract-new-abuse-message.ts +++ b/server/lib/notifier/shared/abuse/abstract-new-abuse-message.ts | |||
@@ -5,7 +5,7 @@ import { MAbuseFull, MAbuseMessage, MAccountDefault, MUserWithNotificationSettin | |||
5 | import { UserNotificationType } from '@shared/models' | 5 | import { UserNotificationType } from '@shared/models' |
6 | import { AbstractNotification } from '../common/abstract-notification' | 6 | import { AbstractNotification } from '../common/abstract-notification' |
7 | 7 | ||
8 | export type NewAbuseMessagePayload = { | 8 | type NewAbuseMessagePayload = { |
9 | abuse: MAbuseFull | 9 | abuse: MAbuseFull |
10 | message: MAbuseMessage | 10 | message: MAbuseMessage |
11 | } | 11 | } |
diff --git a/server/lib/redis.ts b/server/lib/redis.ts index d052de786..158f3c080 100644 --- a/server/lib/redis.ts +++ b/server/lib/redis.ts | |||
@@ -308,7 +308,7 @@ class Redis { | |||
308 | return this.deleteKey('resumable-upload-' + uploadId) | 308 | return this.deleteKey('resumable-upload-' + uploadId) |
309 | } | 309 | } |
310 | 310 | ||
311 | /* ************ AP ressource unavailability ************ */ | 311 | /* ************ AP resource unavailability ************ */ |
312 | 312 | ||
313 | async addAPUnavailability (url: string) { | 313 | async addAPUnavailability (url: string) { |
314 | const key = this.generateAPUnavailabilityKey(url) | 314 | const key = this.generateAPUnavailabilityKey(url) |
diff --git a/server/lib/schedulers/geo-ip-update-scheduler.ts b/server/lib/schedulers/geo-ip-update-scheduler.ts index 9dda6d76c..b06f5a9b5 100644 --- a/server/lib/schedulers/geo-ip-update-scheduler.ts +++ b/server/lib/schedulers/geo-ip-update-scheduler.ts | |||
@@ -6,7 +6,7 @@ export class GeoIPUpdateScheduler extends AbstractScheduler { | |||
6 | 6 | ||
7 | private static instance: AbstractScheduler | 7 | private static instance: AbstractScheduler |
8 | 8 | ||
9 | protected schedulerIntervalMs = SCHEDULER_INTERVALS_MS.YOUTUBE_DL_UPDATE | 9 | protected schedulerIntervalMs = SCHEDULER_INTERVALS_MS.GEO_IP_UPDATE |
10 | 10 | ||
11 | private constructor () { | 11 | private constructor () { |
12 | super() | 12 | super() |
diff --git a/server/lib/signup.ts b/server/lib/signup.ts index 3c1397a12..f094531eb 100644 --- a/server/lib/signup.ts +++ b/server/lib/signup.ts | |||
@@ -26,7 +26,7 @@ function isSignupAllowedForCurrentIP (ip: string) { | |||
26 | const excludeList = [ 'blacklist' ] | 26 | const excludeList = [ 'blacklist' ] |
27 | let matched = '' | 27 | let matched = '' |
28 | 28 | ||
29 | // if there is a valid, non-empty whitelist, we exclude all unknown adresses too | 29 | // if there is a valid, non-empty whitelist, we exclude all unknown addresses too |
30 | if (CONFIG.SIGNUP.FILTERS.CIDR.WHITELIST.filter(cidr => isCidr(cidr)).length > 0) { | 30 | if (CONFIG.SIGNUP.FILTERS.CIDR.WHITELIST.filter(cidr => isCidr(cidr)).length > 0) { |
31 | excludeList.push('unknown') | 31 | excludeList.push('unknown') |
32 | } | 32 | } |
diff --git a/server/lib/video.ts b/server/lib/video.ts index a98e45c60..86718abbe 100644 --- a/server/lib/video.ts +++ b/server/lib/video.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import { UploadFiles } from 'express' | 1 | import { UploadFiles } from 'express' |
2 | import { Transaction } from 'sequelize/types' | 2 | import { Transaction } from 'sequelize/types' |
3 | import { DEFAULT_AUDIO_RESOLUTION, JOB_PRIORITY } from '@server/initializers/constants' | 3 | import { DEFAULT_AUDIO_RESOLUTION, JOB_PRIORITY, MEMOIZE_LENGTH, MEMOIZE_TTL } from '@server/initializers/constants' |
4 | import { TagModel } from '@server/models/video/tag' | 4 | import { TagModel } from '@server/models/video/tag' |
5 | import { VideoModel } from '@server/models/video/video' | 5 | import { VideoModel } from '@server/models/video/video' |
6 | import { VideoJobInfoModel } from '@server/models/video/video-job-info' | 6 | import { VideoJobInfoModel } from '@server/models/video/video-job-info' |
@@ -10,6 +10,7 @@ import { ThumbnailType, VideoCreate, VideoPrivacy, VideoState, VideoTranscodingP | |||
10 | import { CreateJobOptions, JobQueue } from './job-queue/job-queue' | 10 | import { CreateJobOptions, JobQueue } from './job-queue/job-queue' |
11 | import { updateVideoMiniatureFromExisting } from './thumbnail' | 11 | import { updateVideoMiniatureFromExisting } from './thumbnail' |
12 | import { CONFIG } from '@server/initializers/config' | 12 | import { CONFIG } from '@server/initializers/config' |
13 | import memoizee from 'memoizee' | ||
13 | 14 | ||
14 | function buildLocalVideoFromReq (videoInfo: VideoCreate, channelId: number): FilteredModelAttributes<VideoModel> { | 15 | function buildLocalVideoFromReq (videoInfo: VideoCreate, channelId: number): FilteredModelAttributes<VideoModel> { |
15 | return { | 16 | return { |
@@ -150,6 +151,24 @@ async function addMoveToObjectStorageJob (options: { | |||
150 | 151 | ||
151 | // --------------------------------------------------------------------------- | 152 | // --------------------------------------------------------------------------- |
152 | 153 | ||
154 | async function getVideoDuration (videoId: number | string) { | ||
155 | const video = await VideoModel.load(videoId) | ||
156 | |||
157 | const duration = video.isLive | ||
158 | ? undefined | ||
159 | : video.duration | ||
160 | |||
161 | return { duration, isLive: video.isLive } | ||
162 | } | ||
163 | |||
164 | const getCachedVideoDuration = memoizee(getVideoDuration, { | ||
165 | promise: true, | ||
166 | max: MEMOIZE_LENGTH.VIDEO_DURATION, | ||
167 | maxAge: MEMOIZE_TTL.VIDEO_DURATION | ||
168 | }) | ||
169 | |||
170 | // --------------------------------------------------------------------------- | ||
171 | |||
153 | export { | 172 | export { |
154 | buildLocalVideoFromReq, | 173 | buildLocalVideoFromReq, |
155 | buildVideoThumbnailsFromReq, | 174 | buildVideoThumbnailsFromReq, |
@@ -157,5 +176,6 @@ export { | |||
157 | addOptimizeOrMergeAudioJob, | 176 | addOptimizeOrMergeAudioJob, |
158 | addTranscodingJob, | 177 | addTranscodingJob, |
159 | addMoveToObjectStorageJob, | 178 | addMoveToObjectStorageJob, |
160 | getTranscodingJobPriority | 179 | getTranscodingJobPriority, |
180 | getCachedVideoDuration | ||
161 | } | 181 | } |
diff --git a/server/lib/views/shared/video-viewer-counters.ts b/server/lib/views/shared/video-viewer-counters.ts index 5158f8f93..cf3fa5882 100644 --- a/server/lib/views/shared/video-viewer-counters.ts +++ b/server/lib/views/shared/video-viewer-counters.ts | |||
@@ -5,7 +5,7 @@ import { sendView } from '@server/lib/activitypub/send/send-view' | |||
5 | import { PeerTubeSocket } from '@server/lib/peertube-socket' | 5 | import { PeerTubeSocket } from '@server/lib/peertube-socket' |
6 | import { getServerActor } from '@server/models/application/application' | 6 | import { getServerActor } from '@server/models/application/application' |
7 | import { VideoModel } from '@server/models/video/video' | 7 | import { VideoModel } from '@server/models/video/video' |
8 | import { MVideo } from '@server/types/models' | 8 | import { MVideo, MVideoImmutable } from '@server/types/models' |
9 | import { buildUUID, sha256 } from '@shared/extra-utils' | 9 | import { buildUUID, sha256 } from '@shared/extra-utils' |
10 | 10 | ||
11 | const lTags = loggerTagsFactory('views') | 11 | const lTags = loggerTagsFactory('views') |
@@ -33,7 +33,7 @@ export class VideoViewerCounters { | |||
33 | // --------------------------------------------------------------------------- | 33 | // --------------------------------------------------------------------------- |
34 | 34 | ||
35 | async addLocalViewer (options: { | 35 | async addLocalViewer (options: { |
36 | video: MVideo | 36 | video: MVideoImmutable |
37 | ip: string | 37 | ip: string |
38 | }) { | 38 | }) { |
39 | const { video, ip } = options | 39 | const { video, ip } = options |
@@ -86,7 +86,7 @@ export class VideoViewerCounters { | |||
86 | // --------------------------------------------------------------------------- | 86 | // --------------------------------------------------------------------------- |
87 | 87 | ||
88 | private async addViewerToVideo (options: { | 88 | private async addViewerToVideo (options: { |
89 | video: MVideo | 89 | video: MVideoImmutable |
90 | viewerId: string | 90 | viewerId: string |
91 | viewerExpires?: Date | 91 | viewerExpires?: Date |
92 | }) { | 92 | }) { |
@@ -162,7 +162,7 @@ export class VideoViewerCounters { | |||
162 | return sha256(this.salt + '-' + ip + '-' + videoUUID) | 162 | return sha256(this.salt + '-' + ip + '-' + videoUUID) |
163 | } | 163 | } |
164 | 164 | ||
165 | private async federateViewerIfNeeded (video: MVideo, viewer: Viewer) { | 165 | private async federateViewerIfNeeded (video: MVideoImmutable, viewer: Viewer) { |
166 | // Federate the viewer if it's been a "long" time we did not | 166 | // Federate the viewer if it's been a "long" time we did not |
167 | const now = new Date().getTime() | 167 | const now = new Date().getTime() |
168 | const federationLimit = now - (VIEW_LIFETIME.VIEWER_COUNTER * 0.75) | 168 | const federationLimit = now - (VIEW_LIFETIME.VIEWER_COUNTER * 0.75) |
diff --git a/server/lib/views/shared/video-viewer-stats.ts b/server/lib/views/shared/video-viewer-stats.ts index a9ba25b47..a56c20559 100644 --- a/server/lib/views/shared/video-viewer-stats.ts +++ b/server/lib/views/shared/video-viewer-stats.ts | |||
@@ -10,7 +10,7 @@ import { Redis } from '@server/lib/redis' | |||
10 | import { VideoModel } from '@server/models/video/video' | 10 | import { VideoModel } from '@server/models/video/video' |
11 | import { LocalVideoViewerModel } from '@server/models/view/local-video-viewer' | 11 | import { LocalVideoViewerModel } from '@server/models/view/local-video-viewer' |
12 | import { LocalVideoViewerWatchSectionModel } from '@server/models/view/local-video-viewer-watch-section' | 12 | import { LocalVideoViewerWatchSectionModel } from '@server/models/view/local-video-viewer-watch-section' |
13 | import { MVideo } from '@server/types/models' | 13 | import { MVideo, MVideoImmutable } from '@server/types/models' |
14 | import { VideoViewEvent } from '@shared/models' | 14 | import { VideoViewEvent } from '@shared/models' |
15 | 15 | ||
16 | const lTags = loggerTagsFactory('views') | 16 | const lTags = loggerTagsFactory('views') |
@@ -41,7 +41,7 @@ export class VideoViewerStats { | |||
41 | // --------------------------------------------------------------------------- | 41 | // --------------------------------------------------------------------------- |
42 | 42 | ||
43 | async addLocalViewer (options: { | 43 | async addLocalViewer (options: { |
44 | video: MVideo | 44 | video: MVideoImmutable |
45 | currentTime: number | 45 | currentTime: number |
46 | ip: string | 46 | ip: string |
47 | viewEvent?: VideoViewEvent | 47 | viewEvent?: VideoViewEvent |
@@ -64,7 +64,7 @@ export class VideoViewerStats { | |||
64 | // --------------------------------------------------------------------------- | 64 | // --------------------------------------------------------------------------- |
65 | 65 | ||
66 | private async updateLocalViewerStats (options: { | 66 | private async updateLocalViewerStats (options: { |
67 | video: MVideo | 67 | video: MVideoImmutable |
68 | ip: string | 68 | ip: string |
69 | currentTime: number | 69 | currentTime: number |
70 | viewEvent?: VideoViewEvent | 70 | viewEvent?: VideoViewEvent |
diff --git a/server/lib/views/shared/video-views.ts b/server/lib/views/shared/video-views.ts index 275f7a014..e563287e1 100644 --- a/server/lib/views/shared/video-views.ts +++ b/server/lib/views/shared/video-views.ts | |||
@@ -1,7 +1,8 @@ | |||
1 | import { logger, loggerTagsFactory } from '@server/helpers/logger' | 1 | import { logger, loggerTagsFactory } from '@server/helpers/logger' |
2 | import { sendView } from '@server/lib/activitypub/send/send-view' | 2 | import { sendView } from '@server/lib/activitypub/send/send-view' |
3 | import { getCachedVideoDuration } from '@server/lib/video' | ||
3 | import { getServerActor } from '@server/models/application/application' | 4 | import { getServerActor } from '@server/models/application/application' |
4 | import { MVideo } from '@server/types/models' | 5 | import { MVideo, MVideoImmutable } from '@server/types/models' |
5 | import { buildUUID } from '@shared/extra-utils' | 6 | import { buildUUID } from '@shared/extra-utils' |
6 | import { Redis } from '../../redis' | 7 | import { Redis } from '../../redis' |
7 | 8 | ||
@@ -10,7 +11,7 @@ const lTags = loggerTagsFactory('views') | |||
10 | export class VideoViews { | 11 | export class VideoViews { |
11 | 12 | ||
12 | async addLocalView (options: { | 13 | async addLocalView (options: { |
13 | video: MVideo | 14 | video: MVideoImmutable |
14 | ip: string | 15 | ip: string |
15 | watchTime: number | 16 | watchTime: number |
16 | }) { | 17 | }) { |
@@ -18,7 +19,7 @@ export class VideoViews { | |||
18 | 19 | ||
19 | logger.debug('Adding local view to video %s.', video.uuid, { watchTime, ...lTags(video.uuid) }) | 20 | logger.debug('Adding local view to video %s.', video.uuid, { watchTime, ...lTags(video.uuid) }) |
20 | 21 | ||
21 | if (!this.hasEnoughWatchTime(video, watchTime)) return false | 22 | if (!await this.hasEnoughWatchTime(video, watchTime)) return false |
22 | 23 | ||
23 | const viewExists = await Redis.Instance.doesVideoIPViewExist(ip, video.uuid) | 24 | const viewExists = await Redis.Instance.doesVideoIPViewExist(ip, video.uuid) |
24 | if (viewExists) return false | 25 | if (viewExists) return false |
@@ -46,7 +47,7 @@ export class VideoViews { | |||
46 | 47 | ||
47 | // --------------------------------------------------------------------------- | 48 | // --------------------------------------------------------------------------- |
48 | 49 | ||
49 | private async addView (video: MVideo) { | 50 | private async addView (video: MVideoImmutable) { |
50 | const promises: Promise<any>[] = [] | 51 | const promises: Promise<any>[] = [] |
51 | 52 | ||
52 | if (video.isOwned()) { | 53 | if (video.isOwned()) { |
@@ -58,10 +59,12 @@ export class VideoViews { | |||
58 | await Promise.all(promises) | 59 | await Promise.all(promises) |
59 | } | 60 | } |
60 | 61 | ||
61 | private hasEnoughWatchTime (video: MVideo, watchTime: number) { | 62 | private async hasEnoughWatchTime (video: MVideoImmutable, watchTime: number) { |
62 | if (video.isLive || video.duration >= 30) return watchTime >= 30 | 63 | const { duration, isLive } = await getCachedVideoDuration(video.id) |
64 | |||
65 | if (isLive || duration >= 30) return watchTime >= 30 | ||
63 | 66 | ||
64 | // Check more than 50% of the video is watched | 67 | // Check more than 50% of the video is watched |
65 | return video.duration / watchTime < 2 | 68 | return duration / watchTime < 2 |
66 | } | 69 | } |
67 | } | 70 | } |
diff --git a/server/lib/views/video-views-manager.ts b/server/lib/views/video-views-manager.ts index ea3b35c6c..86758e8d8 100644 --- a/server/lib/views/video-views-manager.ts +++ b/server/lib/views/video-views-manager.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import { logger, loggerTagsFactory } from '@server/helpers/logger' | 1 | import { logger, loggerTagsFactory } from '@server/helpers/logger' |
2 | import { MVideo } from '@server/types/models' | 2 | import { MVideo, MVideoImmutable } from '@server/types/models' |
3 | import { VideoViewEvent } from '@shared/models' | 3 | import { VideoViewEvent } from '@shared/models' |
4 | import { VideoViewerCounters, VideoViewerStats, VideoViews } from './shared' | 4 | import { VideoViewerCounters, VideoViewerStats, VideoViews } from './shared' |
5 | 5 | ||
@@ -41,7 +41,7 @@ export class VideoViewsManager { | |||
41 | } | 41 | } |
42 | 42 | ||
43 | async processLocalView (options: { | 43 | async processLocalView (options: { |
44 | video: MVideo | 44 | video: MVideoImmutable |
45 | currentTime: number | 45 | currentTime: number |
46 | ip: string | null | 46 | ip: string | null |
47 | viewEvent?: VideoViewEvent | 47 | viewEvent?: VideoViewEvent |
diff --git a/server/middlewares/index.ts b/server/middlewares/index.ts index d2ed079b6..b40f864ce 100644 --- a/server/middlewares/index.ts +++ b/server/middlewares/index.ts | |||
@@ -4,6 +4,7 @@ export * from './activitypub' | |||
4 | export * from './async' | 4 | export * from './async' |
5 | export * from './auth' | 5 | export * from './auth' |
6 | export * from './pagination' | 6 | export * from './pagination' |
7 | export * from './rate-limiter' | ||
7 | export * from './robots' | 8 | export * from './robots' |
8 | export * from './servers' | 9 | export * from './servers' |
9 | export * from './sort' | 10 | export * from './sort' |
diff --git a/server/middlewares/rate-limiter.ts b/server/middlewares/rate-limiter.ts new file mode 100644 index 000000000..bc9513969 --- /dev/null +++ b/server/middlewares/rate-limiter.ts | |||
@@ -0,0 +1,31 @@ | |||
1 | import { UserRole } from '@shared/models' | ||
2 | import RateLimit from 'express-rate-limit' | ||
3 | import { optionalAuthenticate } from './auth' | ||
4 | |||
5 | const whitelistRoles = new Set([ UserRole.ADMINISTRATOR, UserRole.MODERATOR ]) | ||
6 | |||
7 | function buildRateLimiter (options: { | ||
8 | windowMs: number | ||
9 | max: number | ||
10 | skipFailedRequests?: boolean | ||
11 | }) { | ||
12 | return RateLimit({ | ||
13 | windowMs: options.windowMs, | ||
14 | max: options.max, | ||
15 | skipFailedRequests: options.skipFailedRequests, | ||
16 | |||
17 | handler: (req, res, next, options) => { | ||
18 | return optionalAuthenticate(req, res, () => { | ||
19 | if (res.locals.authenticated === true && whitelistRoles.has(res.locals.oauth.token.User.role)) { | ||
20 | return next() | ||
21 | } | ||
22 | |||
23 | return res.status(options.statusCode).send(options.message) | ||
24 | }) | ||
25 | } | ||
26 | }) | ||
27 | } | ||
28 | |||
29 | export { | ||
30 | buildRateLimiter | ||
31 | } | ||
diff --git a/server/middlewares/validators/sort.ts b/server/middlewares/validators/sort.ts index 3ba668460..c9978e3b4 100644 --- a/server/middlewares/validators/sort.ts +++ b/server/middlewares/validators/sort.ts | |||
@@ -28,7 +28,7 @@ function createSortableColumns (sortableColumns: string[]) { | |||
28 | return sortableColumns.concat(sortableColumnDesc) | 28 | return sortableColumns.concat(sortableColumnDesc) |
29 | } | 29 | } |
30 | 30 | ||
31 | const usersSortValidator = checkSortFactory(SORTABLE_COLUMNS.USERS) | 31 | const adminUsersSortValidator = checkSortFactory(SORTABLE_COLUMNS.ADMIN_USERS) |
32 | const accountsSortValidator = checkSortFactory(SORTABLE_COLUMNS.ACCOUNTS) | 32 | const accountsSortValidator = checkSortFactory(SORTABLE_COLUMNS.ACCOUNTS) |
33 | const jobsSortValidator = checkSortFactory(SORTABLE_COLUMNS.JOBS, [ 'jobs' ]) | 33 | const jobsSortValidator = checkSortFactory(SORTABLE_COLUMNS.JOBS, [ 'jobs' ]) |
34 | const abusesSortValidator = checkSortFactory(SORTABLE_COLUMNS.ABUSES) | 34 | const abusesSortValidator = checkSortFactory(SORTABLE_COLUMNS.ABUSES) |
@@ -59,7 +59,7 @@ const videoChannelsFollowersSortValidator = checkSortFactory(SORTABLE_COLUMNS.CH | |||
59 | // --------------------------------------------------------------------------- | 59 | // --------------------------------------------------------------------------- |
60 | 60 | ||
61 | export { | 61 | export { |
62 | usersSortValidator, | 62 | adminUsersSortValidator, |
63 | abusesSortValidator, | 63 | abusesSortValidator, |
64 | videoChannelsSortValidator, | 64 | videoChannelsSortValidator, |
65 | videoImportsSortValidator, | 65 | videoImportsSortValidator, |
diff --git a/server/middlewares/validators/users.ts b/server/middlewares/validators/users.ts index bc6007c6d..6d306121e 100644 --- a/server/middlewares/validators/users.ts +++ b/server/middlewares/validators/users.ts | |||
@@ -486,7 +486,7 @@ const ensureAuthUserOwnsAccountValidator = [ | |||
486 | if (res.locals.account.id !== user.Account.id) { | 486 | if (res.locals.account.id !== user.Account.id) { |
487 | return res.fail({ | 487 | return res.fail({ |
488 | status: HttpStatusCode.FORBIDDEN_403, | 488 | status: HttpStatusCode.FORBIDDEN_403, |
489 | message: 'Only owner of this account can access this ressource.' | 489 | message: 'Only owner of this account can access this resource.' |
490 | }) | 490 | }) |
491 | } | 491 | } |
492 | 492 | ||
diff --git a/server/middlewares/validators/videos/video-view.ts b/server/middlewares/validators/videos/video-view.ts index 7a4994e8a..2edcd140f 100644 --- a/server/middlewares/validators/videos/video-view.ts +++ b/server/middlewares/validators/videos/video-view.ts | |||
@@ -6,6 +6,7 @@ import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes' | |||
6 | import { exists, isIdValid, isIntOrNull, toIntOrNull } from '../../../helpers/custom-validators/misc' | 6 | import { exists, isIdValid, isIntOrNull, toIntOrNull } from '../../../helpers/custom-validators/misc' |
7 | import { logger } from '../../../helpers/logger' | 7 | import { logger } from '../../../helpers/logger' |
8 | import { areValidationErrors, doesVideoExist, isValidVideoIdParam } from '../shared' | 8 | import { areValidationErrors, doesVideoExist, isValidVideoIdParam } from '../shared' |
9 | import { getCachedVideoDuration } from '@server/lib/video' | ||
9 | 10 | ||
10 | const getVideoLocalViewerValidator = [ | 11 | const getVideoLocalViewerValidator = [ |
11 | param('localViewerId') | 12 | param('localViewerId') |
@@ -42,20 +43,18 @@ const videoViewValidator = [ | |||
42 | logger.debug('Checking videoView parameters', { parameters: req.body }) | 43 | logger.debug('Checking videoView parameters', { parameters: req.body }) |
43 | 44 | ||
44 | if (areValidationErrors(req, res)) return | 45 | if (areValidationErrors(req, res)) return |
45 | if (!await doesVideoExist(req.params.videoId, res, 'only-video')) return | 46 | if (!await doesVideoExist(req.params.videoId, res, 'only-immutable-attributes')) return |
46 | 47 | ||
47 | const video = res.locals.onlyVideo | 48 | const video = res.locals.onlyImmutableVideo |
48 | const videoDuration = video.isLive | 49 | const { duration } = await getCachedVideoDuration(video.id) |
49 | ? undefined | ||
50 | : video.duration | ||
51 | 50 | ||
52 | if (!exists(req.body.currentTime)) { // TODO: remove in a few versions, introduced in 4.2 | 51 | if (!exists(req.body.currentTime)) { // TODO: remove in a few versions, introduced in 4.2 |
53 | req.body.currentTime = Math.min(videoDuration ?? 0, 30) | 52 | req.body.currentTime = Math.min(duration ?? 0, 30) |
54 | } | 53 | } |
55 | 54 | ||
56 | const currentTime: number = req.body.currentTime | 55 | const currentTime: number = req.body.currentTime |
57 | 56 | ||
58 | if (!isVideoTimeValid(currentTime, videoDuration)) { | 57 | if (!isVideoTimeValid(currentTime, duration)) { |
59 | return res.fail({ | 58 | return res.fail({ |
60 | status: HttpStatusCode.BAD_REQUEST_400, | 59 | status: HttpStatusCode.BAD_REQUEST_400, |
61 | message: 'Current time is invalid' | 60 | message: 'Current time is invalid' |
diff --git a/server/models/abuse/abuse-query-builder.ts b/server/models/abuse/abuse-query-builder.ts index 025e6ba55..cfc924ba4 100644 --- a/server/models/abuse/abuse-query-builder.ts +++ b/server/models/abuse/abuse-query-builder.ts | |||
@@ -13,7 +13,7 @@ export type BuildAbusesQueryOptions = { | |||
13 | searchReporter?: string | 13 | searchReporter?: string |
14 | searchReportee?: string | 14 | searchReportee?: string |
15 | 15 | ||
16 | // video releated | 16 | // video related |
17 | searchVideo?: string | 17 | searchVideo?: string |
18 | searchVideoChannel?: string | 18 | searchVideoChannel?: string |
19 | videoIs?: AbuseVideoIs | 19 | videoIs?: AbuseVideoIs |
diff --git a/server/models/shared/abstract-run-query.ts b/server/models/shared/abstract-run-query.ts index f1182c7be..7f27a0c4b 100644 --- a/server/models/shared/abstract-run-query.ts +++ b/server/models/shared/abstract-run-query.ts | |||
@@ -2,7 +2,7 @@ import { QueryTypes, Sequelize, Transaction } from 'sequelize' | |||
2 | 2 | ||
3 | /** | 3 | /** |
4 | * | 4 | * |
5 | * Abstact builder to run video SQL queries | 5 | * Abstract builder to run video SQL queries |
6 | * | 6 | * |
7 | */ | 7 | */ |
8 | 8 | ||
diff --git a/server/models/user/user.ts b/server/models/user/user.ts index 68b2bf523..a25551ecd 100644 --- a/server/models/user/user.ts +++ b/server/models/user/user.ts | |||
@@ -66,7 +66,7 @@ import { ActorModel } from '../actor/actor' | |||
66 | import { ActorFollowModel } from '../actor/actor-follow' | 66 | import { ActorFollowModel } from '../actor/actor-follow' |
67 | import { ActorImageModel } from '../actor/actor-image' | 67 | import { ActorImageModel } from '../actor/actor-image' |
68 | import { OAuthTokenModel } from '../oauth/oauth-token' | 68 | import { OAuthTokenModel } from '../oauth/oauth-token' |
69 | import { getSort, throwIfNotValid } from '../utils' | 69 | import { getAdminUsersSort, throwIfNotValid } from '../utils' |
70 | import { VideoModel } from '../video/video' | 70 | import { VideoModel } from '../video/video' |
71 | import { VideoChannelModel } from '../video/video-channel' | 71 | import { VideoChannelModel } from '../video/video-channel' |
72 | import { VideoImportModel } from '../video/video-import' | 72 | import { VideoImportModel } from '../video/video-import' |
@@ -461,7 +461,7 @@ export class UserModel extends Model<Partial<AttributesOnly<UserModel>>> { | |||
461 | return this.count() | 461 | return this.count() |
462 | } | 462 | } |
463 | 463 | ||
464 | static listForApi (parameters: { | 464 | static listForAdminApi (parameters: { |
465 | start: number | 465 | start: number |
466 | count: number | 466 | count: number |
467 | sort: string | 467 | sort: string |
@@ -497,7 +497,7 @@ export class UserModel extends Model<Partial<AttributesOnly<UserModel>>> { | |||
497 | const query: FindOptions = { | 497 | const query: FindOptions = { |
498 | offset: start, | 498 | offset: start, |
499 | limit: count, | 499 | limit: count, |
500 | order: getSort(sort), | 500 | order: getAdminUsersSort(sort), |
501 | where | 501 | where |
502 | } | 502 | } |
503 | 503 | ||
diff --git a/server/models/utils.ts b/server/models/utils.ts index b57290aff..c468f748d 100644 --- a/server/models/utils.ts +++ b/server/models/utils.ts | |||
@@ -11,8 +11,6 @@ function getSort (value: string, lastSort: OrderItem = [ 'id', 'ASC' ]): OrderIt | |||
11 | 11 | ||
12 | if (field.toLowerCase() === 'match') { // Search | 12 | if (field.toLowerCase() === 'match') { // Search |
13 | finalField = Sequelize.col('similarity') | 13 | finalField = Sequelize.col('similarity') |
14 | } else if (field === 'videoQuotaUsed') { // Users list | ||
15 | finalField = Sequelize.col('videoQuotaUsed') | ||
16 | } else { | 14 | } else { |
17 | finalField = field | 15 | finalField = field |
18 | } | 16 | } |
@@ -20,6 +18,25 @@ function getSort (value: string, lastSort: OrderItem = [ 'id', 'ASC' ]): OrderIt | |||
20 | return [ [ finalField, direction ], lastSort ] | 18 | return [ [ finalField, direction ], lastSort ] |
21 | } | 19 | } |
22 | 20 | ||
21 | function getAdminUsersSort (value: string): OrderItem[] { | ||
22 | const { direction, field } = buildDirectionAndField(value) | ||
23 | |||
24 | let finalField: string | ReturnType<typeof Sequelize.col> | ||
25 | |||
26 | if (field === 'videoQuotaUsed') { // Users list | ||
27 | finalField = Sequelize.col('videoQuotaUsed') | ||
28 | } else { | ||
29 | finalField = field | ||
30 | } | ||
31 | |||
32 | const nullPolicy = direction === 'ASC' | ||
33 | ? 'NULLS FIRST' | ||
34 | : 'NULLS LAST' | ||
35 | |||
36 | // FIXME: typings | ||
37 | return [ [ finalField as any, direction, nullPolicy ], [ 'id', 'ASC' ] ] | ||
38 | } | ||
39 | |||
23 | function getPlaylistSort (value: string, lastSort: OrderItem = [ 'id', 'ASC' ]): OrderItem[] { | 40 | function getPlaylistSort (value: string, lastSort: OrderItem = [ 'id', 'ASC' ]): OrderItem[] { |
24 | const { direction, field } = buildDirectionAndField(value) | 41 | const { direction, field } = buildDirectionAndField(value) |
25 | 42 | ||
@@ -102,7 +119,7 @@ function getInstanceFollowsSort (value: string, lastSort: OrderItem = [ 'id', 'A | |||
102 | 119 | ||
103 | function isOutdated (model: { createdAt: Date, updatedAt: Date }, refreshInterval: number) { | 120 | function isOutdated (model: { createdAt: Date, updatedAt: Date }, refreshInterval: number) { |
104 | if (!model.createdAt || !model.updatedAt) { | 121 | if (!model.createdAt || !model.updatedAt) { |
105 | throw new Error('Miss createdAt & updatedAt attribuets to model') | 122 | throw new Error('Miss createdAt & updatedAt attributes to model') |
106 | } | 123 | } |
107 | 124 | ||
108 | const now = Date.now() | 125 | const now = Date.now() |
@@ -260,6 +277,7 @@ export { | |||
260 | buildLocalAccountIdsIn, | 277 | buildLocalAccountIdsIn, |
261 | getSort, | 278 | getSort, |
262 | getCommentSort, | 279 | getCommentSort, |
280 | getAdminUsersSort, | ||
263 | getVideoSort, | 281 | getVideoSort, |
264 | getBlacklistSort, | 282 | getBlacklistSort, |
265 | createSimilarityAttribute, | 283 | createSimilarityAttribute, |
diff --git a/server/models/video/video-channel.ts b/server/models/video/video-channel.ts index d6dd1b8bb..91dafbcf1 100644 --- a/server/models/video/video-channel.ts +++ b/server/models/video/video-channel.ts | |||
@@ -311,6 +311,16 @@ export type SummaryOptions = { | |||
311 | ')' | 311 | ')' |
312 | ), | 312 | ), |
313 | 'viewsPerDay' | 313 | 'viewsPerDay' |
314 | ], | ||
315 | [ | ||
316 | literal( | ||
317 | '(' + | ||
318 | 'SELECT COALESCE(SUM("video".views), 0) AS totalViews ' + | ||
319 | 'FROM "video" ' + | ||
320 | 'WHERE "video"."channelId" = "VideoChannelModel"."id"' + | ||
321 | ')' | ||
322 | ), | ||
323 | 'totalViews' | ||
314 | ] | 324 | ] |
315 | ] | 325 | ] |
316 | } | 326 | } |
@@ -766,6 +776,8 @@ ON "Account->Actor"."serverId" = "Account->Actor->Server"."id"` | |||
766 | }) | 776 | }) |
767 | } | 777 | } |
768 | 778 | ||
779 | const totalViews = this.get('totalViews') as number | ||
780 | |||
769 | const actor = this.Actor.toFormattedJSON() | 781 | const actor = this.Actor.toFormattedJSON() |
770 | const videoChannel = { | 782 | const videoChannel = { |
771 | id: this.id, | 783 | id: this.id, |
@@ -779,6 +791,7 @@ ON "Account->Actor"."serverId" = "Account->Actor->Server"."id"` | |||
779 | 791 | ||
780 | videosCount, | 792 | videosCount, |
781 | viewsPerDay, | 793 | viewsPerDay, |
794 | totalViews, | ||
782 | 795 | ||
783 | avatars: actor.avatars, | 796 | avatars: actor.avatars, |
784 | 797 | ||
diff --git a/server/tests/api/check-params/abuses.ts b/server/tests/api/check-params/abuses.ts index c4b051723..bc2cc640f 100644 --- a/server/tests/api/check-params/abuses.ts +++ b/server/tests/api/check-params/abuses.ts | |||
@@ -269,7 +269,7 @@ describe('Test abuses API validators', function () { | |||
269 | await makePostBodyRequest({ url: server.url, path, token: userToken, fields }) | 269 | await makePostBodyRequest({ url: server.url, path, token: userToken, fields }) |
270 | }) | 270 | }) |
271 | 271 | ||
272 | it('Should succeed with the corret parameters (advanced)', async function () { | 272 | it('Should succeed with the correct parameters (advanced)', async function () { |
273 | const fields: AbuseCreate = { | 273 | const fields: AbuseCreate = { |
274 | video: { | 274 | video: { |
275 | id: server.store.videoCreated.id, | 275 | id: server.store.videoCreated.id, |
@@ -333,7 +333,7 @@ describe('Test abuses API validators', function () { | |||
333 | await command.addMessage({ token: userToken, abuseId, message: 'a'.repeat(5000), expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) | 333 | await command.addMessage({ token: userToken, abuseId, message: 'a'.repeat(5000), expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) |
334 | }) | 334 | }) |
335 | 335 | ||
336 | it('Should suceed with the correct params', async function () { | 336 | it('Should succeed with the correct params', async function () { |
337 | const res = await command.addMessage({ token: userToken, abuseId, message }) | 337 | const res = await command.addMessage({ token: userToken, abuseId, message }) |
338 | messageId = res.body.abuseMessage.id | 338 | messageId = res.body.abuseMessage.id |
339 | }) | 339 | }) |
diff --git a/server/tests/api/live/live.ts b/server/tests/api/live/live.ts index 9b8fbe3e2..b9caf394d 100644 --- a/server/tests/api/live/live.ts +++ b/server/tests/api/live/live.ts | |||
@@ -610,7 +610,7 @@ describe('Test live', function () { | |||
610 | } | 610 | } |
611 | 611 | ||
612 | before(async function () { | 612 | before(async function () { |
613 | this.timeout(160000) | 613 | this.timeout(300000) |
614 | 614 | ||
615 | liveVideoId = await createLiveWrapper({ saveReplay: false, permanent: false }) | 615 | liveVideoId = await createLiveWrapper({ saveReplay: false, permanent: false }) |
616 | liveVideoReplayId = await createLiveWrapper({ saveReplay: true, permanent: false }) | 616 | liveVideoReplayId = await createLiveWrapper({ saveReplay: true, permanent: false }) |
@@ -654,7 +654,7 @@ describe('Test live', function () { | |||
654 | }) | 654 | }) |
655 | 655 | ||
656 | it('Should save a non permanent live replay', async function () { | 656 | it('Should save a non permanent live replay', async function () { |
657 | this.timeout(120000) | 657 | this.timeout(240000) |
658 | 658 | ||
659 | await commands[0].waitUntilPublished({ videoId: liveVideoReplayId }) | 659 | await commands[0].waitUntilPublished({ videoId: liveVideoReplayId }) |
660 | 660 | ||
diff --git a/server/tests/api/moderation/abuses.ts b/server/tests/api/moderation/abuses.ts index 7bf49c7ec..568fbefcf 100644 --- a/server/tests/api/moderation/abuses.ts +++ b/server/tests/api/moderation/abuses.ts | |||
@@ -168,7 +168,7 @@ describe('Test abuses', function () { | |||
168 | expect(abuse2.reporterAccount.name).to.equal('root') | 168 | expect(abuse2.reporterAccount.name).to.equal('root') |
169 | expect(abuse2.reporterAccount.host).to.equal(servers[0].host) | 169 | expect(abuse2.reporterAccount.host).to.equal(servers[0].host) |
170 | 170 | ||
171 | expect(abuse2.video.id).to.equal(servers[1].store.videoCreated.id) | 171 | expect(abuse2.video.uuid).to.equal(servers[1].store.videoCreated.uuid) |
172 | 172 | ||
173 | expect(abuse2.comment).to.be.null | 173 | expect(abuse2.comment).to.be.null |
174 | 174 | ||
@@ -530,7 +530,7 @@ describe('Test abuses', function () { | |||
530 | it('Should keep the comment abuse when deleting the comment', async function () { | 530 | it('Should keep the comment abuse when deleting the comment', async function () { |
531 | this.timeout(10000) | 531 | this.timeout(10000) |
532 | 532 | ||
533 | const commentServer2 = await getComment(servers[0], servers[1].store.videoCreated.id) | 533 | const commentServer2 = await getComment(servers[0], servers[1].store.videoCreated.uuid) |
534 | 534 | ||
535 | await servers[0].comments.delete({ videoId: servers[1].store.videoCreated.uuid, commentId: commentServer2.id }) | 535 | await servers[0].comments.delete({ videoId: servers[1].store.videoCreated.uuid, commentId: commentServer2.id }) |
536 | 536 | ||
diff --git a/server/tests/api/notifications/user-notifications.ts b/server/tests/api/notifications/user-notifications.ts index a7cc529f8..a11289236 100644 --- a/server/tests/api/notifications/user-notifications.ts +++ b/server/tests/api/notifications/user-notifications.ts | |||
@@ -545,7 +545,7 @@ describe('Test user notifications', function () { | |||
545 | await servers[1].subscriptions.remove({ uri: 'user_1_channel@localhost:' + servers[0].port }) | 545 | await servers[1].subscriptions.remove({ uri: 'user_1_channel@localhost:' + servers[0].port }) |
546 | }) | 546 | }) |
547 | 547 | ||
548 | // PeerTube does not support accout -> account follows | 548 | // PeerTube does not support account -> account follows |
549 | // it('Should notify when a local account is following one of our channel', async function () { | 549 | // it('Should notify when a local account is following one of our channel', async function () { |
550 | // this.timeout(50000) | 550 | // this.timeout(50000) |
551 | // | 551 | // |
diff --git a/server/tests/api/search/search-index.ts b/server/tests/api/search/search-index.ts index 287abe455..53b91e1cb 100644 --- a/server/tests/api/search/search-index.ts +++ b/server/tests/api/search/search-index.ts | |||
@@ -136,14 +136,14 @@ describe('Test index search', function () { | |||
136 | expect(video.account.url).to.equal('https://framatube.org/accounts/framasoft') | 136 | expect(video.account.url).to.equal('https://framatube.org/accounts/framasoft') |
137 | // TODO: remove, deprecated in 4.2 | 137 | // TODO: remove, deprecated in 4.2 |
138 | expect(video.account.avatar).to.exist | 138 | expect(video.account.avatar).to.exist |
139 | expect(video.account.avatars.length).to.equal(1, 'Account should have one avatar image') | 139 | expect(video.account.avatars.length).to.equal(2, 'Account should have one avatar image') |
140 | 140 | ||
141 | expect(video.channel.host).to.equal('framatube.org') | 141 | expect(video.channel.host).to.equal('framatube.org') |
142 | expect(video.channel.name).to.equal('joinpeertube') | 142 | expect(video.channel.name).to.equal('joinpeertube') |
143 | expect(video.channel.url).to.equal('https://framatube.org/video-channels/joinpeertube') | 143 | expect(video.channel.url).to.equal('https://framatube.org/video-channels/joinpeertube') |
144 | // TODO: remove, deprecated in 4.2 | 144 | // TODO: remove, deprecated in 4.2 |
145 | expect(video.channel.avatar).to.exist | 145 | expect(video.channel.avatar).to.exist |
146 | expect(video.channel.avatars.length).to.equal(1, 'Channel should have one avatar image') | 146 | expect(video.channel.avatars.length).to.equal(2, 'Channel should have one avatar image') |
147 | } | 147 | } |
148 | 148 | ||
149 | const baseSearch: VideosSearchQuery = { | 149 | const baseSearch: VideosSearchQuery = { |
@@ -322,7 +322,7 @@ describe('Test index search', function () { | |||
322 | expect(videoChannel.host).to.equal('framatube.org') | 322 | expect(videoChannel.host).to.equal('framatube.org') |
323 | // TODO: remove, deprecated in 4.2 | 323 | // TODO: remove, deprecated in 4.2 |
324 | expect(videoChannel.avatar).to.exist | 324 | expect(videoChannel.avatar).to.exist |
325 | expect(videoChannel.avatars.length).to.equal(1, 'Channel should have two avatar images') | 325 | expect(videoChannel.avatars.length).to.equal(2, 'Channel should have two avatar images') |
326 | expect(videoChannel.displayName).to.exist | 326 | expect(videoChannel.displayName).to.exist |
327 | 327 | ||
328 | expect(videoChannel.ownerAccount.url).to.equal('https://framatube.org/accounts/framasoft') | 328 | expect(videoChannel.ownerAccount.url).to.equal('https://framatube.org/accounts/framasoft') |
@@ -330,7 +330,7 @@ describe('Test index search', function () { | |||
330 | expect(videoChannel.ownerAccount.host).to.equal('framatube.org') | 330 | expect(videoChannel.ownerAccount.host).to.equal('framatube.org') |
331 | // TODO: remove, deprecated in 4.2 | 331 | // TODO: remove, deprecated in 4.2 |
332 | expect(videoChannel.ownerAccount.avatar).to.exist | 332 | expect(videoChannel.ownerAccount.avatar).to.exist |
333 | expect(videoChannel.ownerAccount.avatars.length).to.equal(1, 'Account should have two avatar images') | 333 | expect(videoChannel.ownerAccount.avatars.length).to.equal(2, 'Account should have two avatar images') |
334 | } | 334 | } |
335 | 335 | ||
336 | it('Should make a simple search and not have results', async function () { | 336 | it('Should make a simple search and not have results', async function () { |
diff --git a/server/tests/api/server/contact-form.ts b/server/tests/api/server/contact-form.ts index 4f01f6fd5..d6165b293 100644 --- a/server/tests/api/server/contact-form.ts +++ b/server/tests/api/server/contact-form.ts | |||
@@ -61,7 +61,7 @@ describe('Test contact form', function () { | |||
61 | expect(email['text']).contains('my super message') | 61 | expect(email['text']).contains('my super message') |
62 | }) | 62 | }) |
63 | 63 | ||
64 | it('Should not have duplicated email adress in text message', async function () { | 64 | it('Should not have duplicated email address in text message', async function () { |
65 | const text = emails[0]['text'] as string | 65 | const text = emails[0]['text'] as string |
66 | 66 | ||
67 | const matches = text.match(/toto@example.com/g) | 67 | const matches = text.match(/toto@example.com/g) |
diff --git a/server/tests/api/server/reverse-proxy.ts b/server/tests/api/server/reverse-proxy.ts index fa2063536..0a1565faf 100644 --- a/server/tests/api/server/reverse-proxy.ts +++ b/server/tests/api/server/reverse-proxy.ts | |||
@@ -7,6 +7,7 @@ import { cleanupTests, createSingleServer, PeerTubeServer, setAccessTokensToServ | |||
7 | 7 | ||
8 | describe('Test application behind a reverse proxy', function () { | 8 | describe('Test application behind a reverse proxy', function () { |
9 | let server: PeerTubeServer | 9 | let server: PeerTubeServer |
10 | let userAccessToken: string | ||
10 | let videoId: string | 11 | let videoId: string |
11 | 12 | ||
12 | before(async function () { | 13 | before(async function () { |
@@ -34,6 +35,8 @@ describe('Test application behind a reverse proxy', function () { | |||
34 | server = await createSingleServer(1, config) | 35 | server = await createSingleServer(1, config) |
35 | await setAccessTokensToServers([ server ]) | 36 | await setAccessTokensToServers([ server ]) |
36 | 37 | ||
38 | userAccessToken = await server.users.generateUserAndToken('user') | ||
39 | |||
37 | const { uuid } = await server.videos.upload() | 40 | const { uuid } = await server.videos.upload() |
38 | videoId = uuid | 41 | videoId = uuid |
39 | }) | 42 | }) |
@@ -93,7 +96,7 @@ describe('Test application behind a reverse proxy', function () { | |||
93 | it('Should rate limit logins', async function () { | 96 | it('Should rate limit logins', async function () { |
94 | const user = { username: 'root', password: 'fail' } | 97 | const user = { username: 'root', password: 'fail' } |
95 | 98 | ||
96 | for (let i = 0; i < 19; i++) { | 99 | for (let i = 0; i < 18; i++) { |
97 | await server.login.login({ user, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) | 100 | await server.login.login({ user, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) |
98 | } | 101 | } |
99 | 102 | ||
@@ -141,6 +144,12 @@ describe('Test application behind a reverse proxy', function () { | |||
141 | await server.videos.get({ id: videoId, expectedStatus: HttpStatusCode.TOO_MANY_REQUESTS_429 }) | 144 | await server.videos.get({ id: videoId, expectedStatus: HttpStatusCode.TOO_MANY_REQUESTS_429 }) |
142 | }) | 145 | }) |
143 | 146 | ||
147 | it('Should rate limit API calls with a user but not with an admin', async function () { | ||
148 | await server.videos.get({ id: videoId, token: userAccessToken, expectedStatus: HttpStatusCode.TOO_MANY_REQUESTS_429 }) | ||
149 | |||
150 | await server.videos.get({ id: videoId, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 }) | ||
151 | }) | ||
152 | |||
144 | after(async function () { | 153 | after(async function () { |
145 | await cleanupTests([ server ]) | 154 | await cleanupTests([ server ]) |
146 | }) | 155 | }) |
diff --git a/server/tests/api/users/users.ts b/server/tests/api/users/users.ts index 01b4c2eab..d15daeba5 100644 --- a/server/tests/api/users/users.ts +++ b/server/tests/api/users/users.ts | |||
@@ -380,7 +380,7 @@ describe('Test users', function () { | |||
380 | }) | 380 | }) |
381 | 381 | ||
382 | it('Should disable webtorrent, enable HLS, and update my quota', async function () { | 382 | it('Should disable webtorrent, enable HLS, and update my quota', async function () { |
383 | this.timeout(60000) | 383 | this.timeout(160000) |
384 | 384 | ||
385 | { | 385 | { |
386 | const config = await server.config.getCustomConfig() | 386 | const config = await server.config.getCustomConfig() |
diff --git a/server/tests/api/videos/video-channels.ts b/server/tests/api/videos/video-channels.ts index 6f495c42d..42e0cf431 100644 --- a/server/tests/api/videos/video-channels.ts +++ b/server/tests/api/videos/video-channels.ts | |||
@@ -478,6 +478,25 @@ describe('Test video channels', function () { | |||
478 | } | 478 | } |
479 | }) | 479 | }) |
480 | 480 | ||
481 | it('Should report correct total views count', async function () { | ||
482 | // check if there's the property | ||
483 | { | ||
484 | const { data } = await servers[0].channels.listByAccount({ accountName, withStats: true }) | ||
485 | |||
486 | for (const channel of data) { | ||
487 | expect(channel).to.haveOwnProperty('totalViews') | ||
488 | expect(channel.totalViews).to.be.a('number') | ||
489 | } | ||
490 | } | ||
491 | |||
492 | // Check if the totalViews count can be updated | ||
493 | { | ||
494 | const { data } = await servers[0].channels.listByAccount({ accountName, withStats: true }) | ||
495 | const channelWithView = data.find(channel => channel.id === servers[0].store.channel.id) | ||
496 | expect(channelWithView.totalViews).to.equal(2) | ||
497 | } | ||
498 | }) | ||
499 | |||
481 | it('Should report correct videos count', async function () { | 500 | it('Should report correct videos count', async function () { |
482 | const { data } = await servers[0].channels.listByAccount({ accountName, withStats: true }) | 501 | const { data } = await servers[0].channels.listByAccount({ accountName, withStats: true }) |
483 | 502 | ||
diff --git a/server/tests/helpers/core-utils.ts b/server/tests/helpers/core-utils.ts index fa0a71341..e3e5eb45c 100644 --- a/server/tests/helpers/core-utils.ts +++ b/server/tests/helpers/core-utils.ts | |||
@@ -6,47 +6,64 @@ import { snakeCase } from 'lodash' | |||
6 | import validator from 'validator' | 6 | import validator from 'validator' |
7 | import { getAverageBitrate, getMaxBitrate } from '@shared/core-utils' | 7 | import { getAverageBitrate, getMaxBitrate } from '@shared/core-utils' |
8 | import { VideoResolution } from '@shared/models' | 8 | import { VideoResolution } from '@shared/models' |
9 | import { objectConverter, parseBytes } from '../../helpers/core-utils' | 9 | import { objectConverter, parseBytes, parseDurationToMs } from '../../helpers/core-utils' |
10 | 10 | ||
11 | const expect = chai.expect | 11 | const expect = chai.expect |
12 | 12 | ||
13 | describe('Parse Bytes', function () { | 13 | describe('Parse Bytes', function () { |
14 | 14 | ||
15 | it('Should pass when given valid value', async function () { | 15 | it('Should pass on valid value', async function () { |
16 | // just return it | 16 | // just return it |
17 | expect(parseBytes(1024)).to.be.eq(1024) | 17 | expect(parseBytes(-1024)).to.equal(-1024) |
18 | expect(parseBytes(1048576)).to.be.eq(1048576) | 18 | expect(parseBytes(1024)).to.equal(1024) |
19 | expect(parseBytes('1024')).to.be.eq(1024) | 19 | expect(parseBytes(1048576)).to.equal(1048576) |
20 | expect(parseBytes('1048576')).to.be.eq(1048576) | 20 | expect(parseBytes('1024')).to.equal(1024) |
21 | expect(parseBytes('1048576')).to.equal(1048576) | ||
21 | 22 | ||
22 | // sizes | 23 | // sizes |
23 | expect(parseBytes('1B')).to.be.eq(1024) | 24 | expect(parseBytes('1B')).to.equal(1024) |
24 | expect(parseBytes('1MB')).to.be.eq(1048576) | 25 | expect(parseBytes('1MB')).to.equal(1048576) |
25 | expect(parseBytes('1GB')).to.be.eq(1073741824) | 26 | expect(parseBytes('1GB')).to.equal(1073741824) |
26 | expect(parseBytes('1TB')).to.be.eq(1099511627776) | 27 | expect(parseBytes('1TB')).to.equal(1099511627776) |
27 | 28 | ||
28 | expect(parseBytes('5GB')).to.be.eq(5368709120) | 29 | expect(parseBytes('5GB')).to.equal(5368709120) |
29 | expect(parseBytes('5TB')).to.be.eq(5497558138880) | 30 | expect(parseBytes('5TB')).to.equal(5497558138880) |
30 | 31 | ||
31 | expect(parseBytes('1024B')).to.be.eq(1048576) | 32 | expect(parseBytes('1024B')).to.equal(1048576) |
32 | expect(parseBytes('1024MB')).to.be.eq(1073741824) | 33 | expect(parseBytes('1024MB')).to.equal(1073741824) |
33 | expect(parseBytes('1024GB')).to.be.eq(1099511627776) | 34 | expect(parseBytes('1024GB')).to.equal(1099511627776) |
34 | expect(parseBytes('1024TB')).to.be.eq(1125899906842624) | 35 | expect(parseBytes('1024TB')).to.equal(1125899906842624) |
35 | 36 | ||
36 | // with whitespace | 37 | // with whitespace |
37 | expect(parseBytes('1 GB')).to.be.eq(1073741824) | 38 | expect(parseBytes('1 GB')).to.equal(1073741824) |
38 | expect(parseBytes('1\tGB')).to.be.eq(1073741824) | 39 | expect(parseBytes('1\tGB')).to.equal(1073741824) |
39 | 40 | ||
40 | // sum value | 41 | // sum value |
41 | expect(parseBytes('1TB 1024MB')).to.be.eq(1100585369600) | 42 | expect(parseBytes('1TB 1024MB')).to.equal(1100585369600) |
42 | expect(parseBytes('4GB 1024MB')).to.be.eq(5368709120) | 43 | expect(parseBytes('4GB 1024MB')).to.equal(5368709120) |
43 | expect(parseBytes('4TB 1024GB')).to.be.eq(5497558138880) | 44 | expect(parseBytes('4TB 1024GB')).to.equal(5497558138880) |
44 | expect(parseBytes('4TB 1024GB 0MB')).to.be.eq(5497558138880) | 45 | expect(parseBytes('4TB 1024GB 0MB')).to.equal(5497558138880) |
45 | expect(parseBytes('1024TB 1024GB 1024MB')).to.be.eq(1127000492212224) | 46 | expect(parseBytes('1024TB 1024GB 1024MB')).to.equal(1127000492212224) |
47 | }) | ||
48 | |||
49 | it('Should be invalid when given invalid value', async function () { | ||
50 | expect(parseBytes('6GB 1GB')).to.equal(6) | ||
51 | }) | ||
52 | }) | ||
53 | |||
54 | describe('Parse duration', function () { | ||
55 | |||
56 | it('Should pass when given valid value', async function () { | ||
57 | expect(parseDurationToMs(35)).to.equal(35) | ||
58 | expect(parseDurationToMs(-35)).to.equal(-35) | ||
59 | expect(parseDurationToMs('35 seconds')).to.equal(35 * 1000) | ||
60 | expect(parseDurationToMs('1 minute')).to.equal(60 * 1000) | ||
61 | expect(parseDurationToMs('1 hour')).to.equal(3600 * 1000) | ||
62 | expect(parseDurationToMs('35 hours')).to.equal(3600 * 35 * 1000) | ||
46 | }) | 63 | }) |
47 | 64 | ||
48 | it('Should be invalid when given invalid value', async function () { | 65 | it('Should be invalid when given invalid value', async function () { |
49 | expect(parseBytes('6GB 1GB')).to.be.eq(6) | 66 | expect(parseBytes('35m 5s')).to.equal(35) |
50 | }) | 67 | }) |
51 | }) | 68 | }) |
52 | 69 | ||