diff options
Diffstat (limited to 'server')
-rw-r--r-- | server/initializers/constants.ts | 2 | ||||
-rw-r--r-- | server/middlewares/sort.ts | 2 | ||||
-rw-r--r-- | server/models/video/video-query-builder.ts | 42 | ||||
-rw-r--r-- | server/models/video/video.ts | 2 |
4 files changed, 42 insertions, 6 deletions
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index 84a515857..89491708e 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts | |||
@@ -72,7 +72,7 @@ const SORTABLE_COLUMNS = { | |||
72 | FOLLOWERS: [ 'createdAt', 'state', 'score' ], | 72 | FOLLOWERS: [ 'createdAt', 'state', 'score' ], |
73 | FOLLOWING: [ 'createdAt', 'redundancyAllowed', 'state' ], | 73 | FOLLOWING: [ 'createdAt', 'redundancyAllowed', 'state' ], |
74 | 74 | ||
75 | VIDEOS: [ 'name', 'duration', 'createdAt', 'publishedAt', 'originallyPublishedAt', 'views', 'likes', 'trending' ], | 75 | VIDEOS: [ 'name', 'duration', 'createdAt', 'publishedAt', 'originallyPublishedAt', 'views', 'likes', 'trending', 'hot' ], |
76 | 76 | ||
77 | // Don't forget to update peertube-search-index with the same values | 77 | // Don't forget to update peertube-search-index with the same values |
78 | VIDEOS_SEARCH: [ 'name', 'duration', 'createdAt', 'publishedAt', 'originallyPublishedAt', 'views', 'likes', 'match' ], | 78 | VIDEOS_SEARCH: [ 'name', 'duration', 'createdAt', 'publishedAt', 'originallyPublishedAt', 'views', 'likes', 'match' ], |
diff --git a/server/middlewares/sort.ts b/server/middlewares/sort.ts index 609046a46..0600ccd15 100644 --- a/server/middlewares/sort.ts +++ b/server/middlewares/sort.ts | |||
@@ -16,7 +16,7 @@ function setBlacklistSort (req: express.Request, res: express.Response, next: ex | |||
16 | // Set model we want to sort onto | 16 | // Set model we want to sort onto |
17 | if (req.query.sort === '-createdAt' || req.query.sort === 'createdAt' || | 17 | if (req.query.sort === '-createdAt' || req.query.sort === 'createdAt' || |
18 | req.query.sort === '-id' || req.query.sort === 'id') { | 18 | req.query.sort === '-id' || req.query.sort === 'id') { |
19 | // If we want to sort onto the BlacklistedVideos relation, we won't specify it in the query parameter ... | 19 | // If we want to sort onto the BlacklistedVideos relation, we won't specify it in the query parameter... |
20 | newSort.sortModel = undefined | 20 | newSort.sortModel = undefined |
21 | } else { | 21 | } else { |
22 | newSort.sortModel = 'Video' | 22 | newSort.sortModel = 'Video' |
diff --git a/server/models/video/video-query-builder.ts b/server/models/video/video-query-builder.ts index 9e5b6febb..65b72fe1c 100644 --- a/server/models/video/video-query-builder.ts +++ b/server/models/video/video-query-builder.ts | |||
@@ -32,6 +32,8 @@ export type BuildVideosQueryOptions = { | |||
32 | videoPlaylistId?: number | 32 | videoPlaylistId?: number |
33 | 33 | ||
34 | trendingDays?: number | 34 | trendingDays?: number |
35 | hot?: boolean | ||
36 | |||
35 | user?: MUserAccountId | 37 | user?: MUserAccountId |
36 | historyOfUser?: MUserId | 38 | historyOfUser?: MUserId |
37 | 39 | ||
@@ -239,14 +241,46 @@ function buildListQuery (model: typeof Model, options: BuildVideosQueryOptions) | |||
239 | } | 241 | } |
240 | } | 242 | } |
241 | 243 | ||
242 | // We don't exclude results in this if so if we do a count we don't need to add this complex clauses | 244 | // We don't exclude results in this so if we do a count we don't need to add this complex clause |
243 | if (options.trendingDays && options.isCount !== true) { | 245 | if (options.trendingDays && options.isCount !== true) { |
244 | const viewsGteDate = new Date(new Date().getTime() - (24 * 3600 * 1000) * options.trendingDays) | 246 | const viewsGteDate = new Date(new Date().getTime() - (24 * 3600 * 1000) * options.trendingDays) |
245 | 247 | ||
246 | joins.push('LEFT JOIN "videoView" ON "video"."id" = "videoView"."videoId" AND "videoView"."startDate" >= :viewsGteDate') | 248 | joins.push('LEFT JOIN "videoView" ON "video"."id" = "videoView"."videoId" AND "videoView"."startDate" >= :viewsGteDate') |
247 | replacements.viewsGteDate = viewsGteDate | 249 | replacements.viewsGteDate = viewsGteDate |
248 | 250 | ||
249 | attributes.push('COALESCE(SUM("videoView"."views"), 0) AS "videoViewsSum"') | 251 | attributes.push('COALESCE(SUM("videoView"."views"), 0) AS "score"') |
252 | |||
253 | group = 'GROUP BY "video"."id"' | ||
254 | } else if (options.hot && options.isCount !== true) { | ||
255 | /** | ||
256 | * "Hotness" is a measure based on absolute view/comment/like/dislike numbers, | ||
257 | * with fixed weights only applied to their log values. | ||
258 | * | ||
259 | * This algorithm gives little chance for an old video to have a good score, | ||
260 | * for which recent spikes in interactions could be a sign of "hotness" and | ||
261 | * justify a better score. However there are multiple ways to achieve that | ||
262 | * goal, which is left for later. Yes, this is a TODO :) | ||
263 | * | ||
264 | * note: weights and base score are in number of half-days. | ||
265 | * see https://github.com/reddit-archive/reddit/blob/master/r2/r2/lib/db/_sorts.pyx#L47-L58 | ||
266 | */ | ||
267 | const weights = { | ||
268 | like: 3, | ||
269 | dislike: 3, | ||
270 | view: 1 / 12, | ||
271 | comment: 6 | ||
272 | } | ||
273 | |||
274 | joins.push('LEFT JOIN "videoComment" ON "video"."id" = "videoComment"."videoId"') | ||
275 | |||
276 | attributes.push( | ||
277 | `LOG(GREATEST(1, "video"."likes" - 1)) * ${weights.like} ` + // likes (+) | ||
278 | `- LOG(GREATEST(1, "video"."dislikes" - 1)) * ${weights.dislike} ` + // dislikes (-) | ||
279 | `+ LOG("video"."views" + 1) * ${weights.view} ` + // views (+) | ||
280 | `+ LOG(GREATEST(1, COUNT(DISTINCT "videoComment"."id") - 1)) * ${weights.comment} ` + // comments (+) | ||
281 | '+ (SELECT EXTRACT(epoch FROM "video"."publishedAt") / 47000) ' + // base score (in number of half-days) | ||
282 | 'AS "score"' | ||
283 | ) | ||
250 | 284 | ||
251 | group = 'GROUP BY "video"."id"' | 285 | group = 'GROUP BY "video"."id"' |
252 | } | 286 | } |
@@ -372,8 +406,8 @@ function buildOrder (value: string) { | |||
372 | 406 | ||
373 | if (field.toLowerCase() === 'random') return 'ORDER BY RANDOM()' | 407 | if (field.toLowerCase() === 'random') return 'ORDER BY RANDOM()' |
374 | 408 | ||
375 | if (field.toLowerCase() === 'trending') { // Sort by aggregation | 409 | if ([ 'trending', 'hot' ].includes(field.toLowerCase())) { // Sort by aggregation |
376 | return `ORDER BY "videoViewsSum" ${direction}, "video"."views" ${direction}` | 410 | return `ORDER BY "score" ${direction}, "video"."views" ${direction}` |
377 | } | 411 | } |
378 | 412 | ||
379 | let firstSort: string | 413 | let firstSort: string |
diff --git a/server/models/video/video.ts b/server/models/video/video.ts index 9b0aa809e..c56fbfbf2 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts | |||
@@ -1090,6 +1090,7 @@ export class VideoModel extends Model { | |||
1090 | const trendingDays = options.sort.endsWith('trending') | 1090 | const trendingDays = options.sort.endsWith('trending') |
1091 | ? CONFIG.TRENDING.VIDEOS.INTERVAL_DAYS | 1091 | ? CONFIG.TRENDING.VIDEOS.INTERVAL_DAYS |
1092 | : undefined | 1092 | : undefined |
1093 | const hot = options.sort.endsWith('hot') | ||
1093 | 1094 | ||
1094 | const serverActor = await getServerActor() | 1095 | const serverActor = await getServerActor() |
1095 | 1096 | ||
@@ -1119,6 +1120,7 @@ export class VideoModel extends Model { | |||
1119 | user: options.user, | 1120 | user: options.user, |
1120 | historyOfUser: options.historyOfUser, | 1121 | historyOfUser: options.historyOfUser, |
1121 | trendingDays, | 1122 | trendingDays, |
1123 | hot, | ||
1122 | search: options.search | 1124 | search: options.search |
1123 | } | 1125 | } |
1124 | 1126 | ||