]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/commitdiff
add default trending page choice, revert comments count for hot strategy
authorRigel Kent <sendmemail@rigelk.eu>
Tue, 26 Jan 2021 00:53:13 +0000 (01:53 +0100)
committerChocobozzz <chocobozzz@cpy.re>
Thu, 28 Jan 2021 14:55:34 +0000 (15:55 +0100)
30 files changed:
client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html
client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts
client/src/app/+videos/video-list/trending/index.ts
client/src/app/+videos/video-list/trending/video-most-viewed.component.ts [moved from client/src/app/+videos/video-list/trending/video-trending.component.ts with 95% similarity]
client/src/app/+videos/video-list/trending/video-trending-header.component.html
client/src/app/+videos/video-list/trending/video-trending-header.component.ts
client/src/app/+videos/videos-routing.module.ts
client/src/app/+videos/videos.module.ts
client/src/app/core/core.module.ts
client/src/app/core/routing/index.ts
client/src/app/core/routing/redirect.service.ts
client/src/app/core/routing/trending-guard.service.ts [new file with mode: 0644]
client/src/app/core/server/server.service.ts
client/src/app/menu/menu.component.html
client/src/app/shared/shared-video-miniature/abstract-video-list.scss
client/src/app/shared/shared-video-miniature/video-list-header.component.html [new file with mode: 0644]
client/src/app/shared/shared-video-miniature/video-list-header.component.ts
config/default.yaml
config/production.yaml.example
server/controllers/api/config.ts
server/helpers/audit-logger.ts
server/initializers/config.ts
server/middlewares/validators/config.ts
server/models/video/video-query-builder.ts
server/tests/api/check-params/config.ts
server/tests/api/server/config.ts
server/tests/client.ts
shared/extra-utils/server/config.ts
shared/models/server/custom-config.model.ts
shared/models/server/server-config.model.ts

index 9e46916708fb3817fbc8c92caa5998757d0d83f0..83b1c6a3103810b4ff82a7677abc696ca95d60a4 100644 (file)
               <div class="peertube-select-container">
                 <select id="instanceDefaultClientRoute" formControlName="defaultClientRoute" class="form-control">
                   <option i18n value="/videos/overview">Discover videos</option>
-                  <option i18n value="/videos/trending">Trending videos</option>
-                  <option i18n value="/videos/hot">Hot videos</option>
-                  <option i18n value="/videos/most-liked">Most liked videos</option>
+                  <optgroup i18n-label label="Trending pages">
+                    <option i18n value="/videos/trending">Default trending page</option>
+                    <option i18n value="/videos/hot" *ngIf="isTrendingHotEnabled()">Hot videos</option>
+                    <option i18n value="/videos/hot" *ngIf="!isTrendingHotEnabled()" disabled>Hot videos</option>
+                    <option i18n value="/videos/most-viewed">Most viewed videos</option>
+                    <option i18n value="/videos/most-liked">Most liked videos</option>
+                  </optgroup>
                   <option i18n value="/videos/recently-added">Recently added videos</option>
                   <option i18n value="/videos/local">Local videos</option>
                 </select>
               <div *ngIf="formErrors.instance.defaultClientRoute" class="form-error">{{ formErrors.instance.defaultClientRoute }}</div>
             </div>
 
+            <div class="form-group" formGroupName="instance">
+              <label i18n for="instanceDefaultTrendingRoute">Default trending page</label>
+              <div class="peertube-select-container">
+                <select id="instanceDefaultTrendingRoute" formControlName="defaultTrendingRoute" class="form-control">
+                  <option i18n value="/videos/hot" *ngIf="isTrendingHotEnabled()">Hot videos</option>
+                  <option i18n value="/videos/hot" *ngIf="!isTrendingHotEnabled()" disabled>Hot videos</option>
+                  <option i18n value="/videos/trending">Most viewed videos</option>
+                  <option i18n value="/videos/most-liked">Most liked videos</option>
+                </select>
+              </div>
+              <div *ngIf="formErrors.instance.defaultTrendingRoute" class="form-error">{{ formErrors.instance.defaultTrendingRoute }}</div>
+            </div>
+
           </div>
         </div>
 
index 330ab075a91bc1c59d35de5e339f0687e7a02ab9..e6fc4582b8d7b0f5d7c804046ebc698e888f6765 100644 (file)
@@ -186,6 +186,12 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit, A
         languages: null,
 
         defaultClientRoute: null,
+        defaultTrendingRoute: null,
+        pages: {
+          hot: {
+            enabled: null
+          }
+        },
 
         customizations: {
           javascript: null,
@@ -364,6 +370,10 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit, A
     return this.form.value['followings']['instance']['autoFollowIndex']['enabled'] === true
   }
 
+  isTrendingHotEnabled () {
+    return this.form.value['instance']['pages']['hot']['enabled'] === true
+  }
+
   async formValidated () {
     const value: CustomConfig = this.form.getRawValue()
 
index 8bae205a5e1513b4c90133addcf27133da2446c2..93f4b1df66bc00fd589ab7a81fe446dd34fadb75 100644 (file)
@@ -1,4 +1,4 @@
 export * from './video-trending-header.component'
-export * from './video-trending.component'
 export * from './video-hot.component'
+export * from './video-most-viewed.component'
 export * from './video-most-liked.component'
similarity index 95%
rename from client/src/app/+videos/video-list/trending/video-trending.component.ts
rename to client/src/app/+videos/video-list/trending/video-most-viewed.component.ts
index e7723158648c96d594978542922037f308c561e7..98ced42d691b2f3fb49b3fa1c8759fd1d630aa33 100644 (file)
@@ -9,11 +9,11 @@ import { VideoSortField } from '@shared/models'
 import { VideoTrendingHeaderComponent } from './video-trending-header.component'
 
 @Component({
-  selector: 'my-videos-trending',
+  selector: 'my-videos-most-viewed',
   styleUrls: [ '../../../shared/shared-video-miniature/abstract-video-list.scss' ],
   templateUrl: '../../../shared/shared-video-miniature/abstract-video-list.html'
 })
-export class VideoTrendingComponent extends AbstractVideoList implements OnInit, OnDestroy {
+export class VideoMostViewedComponent extends AbstractVideoList implements OnInit, OnDestroy {
   HeaderComponent = VideoTrendingHeaderComponent
   titlePage: string
   defaultSort: VideoSortField = '-trending'
index 6319ee6d38f616bf44cf3dacd571b42cef83df29..a025bf1a2a426096e9ba9f0a3fed929591629636 100644 (file)
@@ -1,5 +1,5 @@
 <div class="btn-group btn-group-toggle" ngbRadioGroup name="radioBasic" [(ngModel)]="data.model" (ngModelChange)="setSort()">
-  <label *ngFor="let button of buttons" ngbButtonLabel class="btn-light" placement="bottom" [ngbTooltip]="button.tooltip" container="body">
+  <label *ngFor="let button of visibleButtons" ngbButtonLabel class="btn-light" placement="bottom" [ngbTooltip]="button.tooltip" container="body">
     <my-global-icon [iconName]="button.iconName"></my-global-icon>
     <input ngbButton type="radio" [value]="button.value"> {{ button.label }}
   </label>
index 125f14e33b40b81f45f9fb7fc4a4b7931c8f182e..e49b61c68736688cc9189a697d01f3e7c1c86521 100644 (file)
@@ -1,8 +1,9 @@
-import { Component, Inject } from '@angular/core'
+import { Component, Inject, OnInit } from '@angular/core'
 import { Router } from '@angular/router'
 import { VideoListHeaderComponent } from '@app/shared/shared-video-miniature'
 import { GlobalIconName } from '@app/shared/shared-icons'
 import { VideoSortField } from '@shared/models'
+import { ServerService } from '@app/core/server/server.service'
 
 interface VideoTrendingHeaderItem {
   label: string
@@ -10,6 +11,7 @@ interface VideoTrendingHeaderItem {
   value: VideoSortField
   path: string
   tooltip?: string
+  hidden?: boolean
 }
 
 @Component({
@@ -18,12 +20,13 @@ interface VideoTrendingHeaderItem {
   styleUrls: [ './video-trending-header.component.scss' ],
   templateUrl: './video-trending-header.component.html'
 })
-export class VideoTrendingHeaderComponent extends VideoListHeaderComponent {
+export class VideoTrendingHeaderComponent extends VideoListHeaderComponent implements OnInit {
   buttons: VideoTrendingHeaderItem[]
 
   constructor (
     @Inject('data') public data: any,
-    private router: Router
+    private router: Router,
+    private serverService: ServerService
   ) {
     super(data)
 
@@ -34,16 +37,17 @@ export class VideoTrendingHeaderComponent extends VideoListHeaderComponent {
         value: '-hot',
         path: 'hot',
         tooltip: $localize`Videos totalizing the most interactions for recent videos`,
+        hidden: true
       },
       {
         label: $localize`:Main variant of Trending videos based on number of recent views:Views`,
         iconName: 'trending',
         value: '-trending',
-        path: 'trending',
+        path: 'most-viewed',
         tooltip: $localize`Videos totalizing the most views during the last 24 hours`,
       },
       {
-        label: $localize`:a variant of Trending videos based on the number of likes:Likes`,
+        label: $localize`:A variant of Trending videos based on the number of likes:Likes`,
         iconName: 'like',
         value: '-likes',
         path: 'most-liked',
@@ -52,6 +56,21 @@ export class VideoTrendingHeaderComponent extends VideoListHeaderComponent {
     ]
   }
 
+  ngOnInit () {
+    this.serverService.getConfig()
+        .subscribe(config => {
+          // don't filter if auto-blacklist is not enabled as this will be the only list
+          if (config.instance.pages.hot.enabled) {
+            const index = this.buttons.findIndex(b => b.path === 'hot')
+            this.buttons[index].hidden = false
+          }
+        })
+  }
+
+  get visibleButtons () {
+    return this.buttons.filter(b => !b.hidden)
+  }
+
   setSort () {
     const path = this.buttons.find(b => b.value === this.data.model).path
     this.router.navigate([ `/videos/${path}` ])
index b6850b436fd8d17b896befc51b73b070c8ff5db0..973935af8fef3cde144f4e339284638a35824180 100644 (file)
@@ -1,11 +1,11 @@
 import { NgModule } from '@angular/core'
 import { RouterModule, Routes } from '@angular/router'
-import { LoginGuard } from '@app/core'
+import { LoginGuard, TrendingGuard } from '@app/core'
 import { MetaGuard } from '@ngx-meta/core'
 import { VideoOverviewComponent } from './video-list/overview/video-overview.component'
 import { VideoHotComponent } from './video-list/trending/video-hot.component'
 import { VideoMostLikedComponent } from './video-list/trending/video-most-liked.component'
-import { VideoTrendingComponent } from './video-list/trending/video-trending.component'
+import { VideoMostViewedComponent } from './video-list/trending/video-most-viewed.component'
 import { VideoLocalComponent } from './video-list/video-local.component'
 import { VideoRecentlyAddedComponent } from './video-list/video-recently-added.component'
 import { VideoUserSubscriptionsComponent } from './video-list/video-user-subscriptions.component'
@@ -28,27 +28,31 @@ const videosRoutes: Routes = [
       },
       {
         path: 'trending',
-        component: VideoTrendingComponent,
+        canActivate: [ TrendingGuard ]
+      },
+      {
+        path: 'hot',
+        component: VideoHotComponent,
         data: {
           meta: {
-            title: $localize`Trending videos`
+            title: $localize`Hot videos`
           },
           reuse: {
             enabled: true,
-            key: 'trending-videos-list'
+            key: 'hot-videos-list'
           }
         }
       },
       {
-        path: 'hot',
-        component: VideoHotComponent,
+        path: 'most-viewed',
+        component: VideoMostViewedComponent,
         data: {
           meta: {
-            title: $localize`Hot videos`
+            title: $localize`Most viewed videos`
           },
           reuse: {
             enabled: true,
-            key: 'hot-videos-list'
+            key: 'most-viewed-videos-list'
           }
         }
       },
index 4c88a039721ea383f12652cdbff4162e44326e57..ae9c680eb6bf398d68c67f1b4e3fbdf5db16fb1f 100644 (file)
@@ -9,7 +9,7 @@ import { OverviewService } from './video-list'
 import { VideoOverviewComponent } from './video-list/overview/video-overview.component'
 import { VideoTrendingHeaderComponent } from './video-list/trending/video-trending-header.component'
 import { VideoHotComponent } from './video-list/trending/video-hot.component'
-import { VideoTrendingComponent } from './video-list/trending/video-trending.component'
+import { VideoMostViewedComponent } from './video-list/trending/video-most-viewed.component'
 import { VideoMostLikedComponent } from './video-list/trending/video-most-liked.component'
 import { VideoLocalComponent } from './video-list/video-local.component'
 import { VideoRecentlyAddedComponent } from './video-list/video-recently-added.component'
@@ -32,7 +32,7 @@ import { VideosComponent } from './videos.component'
     VideosComponent,
 
     VideoTrendingHeaderComponent,
-    VideoTrendingComponent,
+    VideoMostViewedComponent,
     VideoHotComponent,
     VideoMostLikedComponent,
     VideoRecentlyAddedComponent,
index c4fc9995e689d7af16fc86f937ce861acd19743e..32dfc8f36cdba65646aca607fce1f094dd08f00b 100644 (file)
@@ -7,7 +7,6 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations'
 import { PeerTubeSocket } from '@app/core/notification/peertube-socket.service'
 import { HooksService } from '@app/core/plugins/hooks.service'
 import { PluginService } from '@app/core/plugins/plugin.service'
-import { UnloggedGuard } from '@app/core/routing/unlogged-guard.service'
 import { AuthService } from './auth'
 import { ConfirmService } from './confirm'
 import { CheatSheetComponent } from './hotkeys'
@@ -16,7 +15,7 @@ import { throwIfAlreadyLoaded } from './module-import-guard'
 import { Notifier } from './notification'
 import { HtmlRendererService, LinkifierService, MarkdownService } from './renderer'
 import { RestExtractor, RestService } from './rest'
-import { LoginGuard, RedirectService, UserRightGuard } from './routing'
+import { LoginGuard, RedirectService, UserRightGuard, UnloggedGuard, TrendingGuard } from './routing'
 import { CanDeactivateGuard } from './routing/can-deactivate-guard.service'
 import { ServerConfigResolver } from './routing/server-config-resolver.service'
 import { ScopedTokensService } from './scoped-tokens'
@@ -57,6 +56,7 @@ import { LocalStorageService, ScreenService, SessionStorageService } from './wra
     LoginGuard,
     UserRightGuard,
     UnloggedGuard,
+    TrendingGuard,
 
     PluginService,
     HooksService,
index 239c27caf9bd67dbc9d0f96f7b74c3d5b9efb729..b3985d87006e85492c446d721ff3d8956fb7a596 100644 (file)
@@ -8,3 +8,4 @@ export * from './redirect.service'
 export * from './server-config-resolver.service'
 export * from './unlogged-guard.service'
 export * from './user-right-guard.service'
+export * from './trending-guard.service'
index 3218040bf1c43479ad617be79b7323f288aeab95..76e28e4611b7d35790a5870c1715ffc46b5c1e25 100644 (file)
@@ -7,11 +7,14 @@ export class RedirectService {
   // Default route could change according to the instance configuration
   static INIT_DEFAULT_ROUTE = '/videos/trending'
   static DEFAULT_ROUTE = RedirectService.INIT_DEFAULT_ROUTE
+  static INIT_DEFAULT_TRENDING_ROUTE = '/videos/most-viewed'
+  static DEFAULT_TRENDING_ROUTE = RedirectService.INIT_DEFAULT_TRENDING_ROUTE
 
   private previousUrl: string
   private currentUrl: string
 
   private redirectingToHomepage = false
+  private redirectingToTrending = false
 
   constructor (
     private router: Router,
@@ -19,18 +22,28 @@ export class RedirectService {
   ) {
     // The config is first loaded from the cache so try to get the default route
     const tmpConfig = this.serverService.getTmpConfig()
-    if (tmpConfig && tmpConfig.instance && tmpConfig.instance.defaultClientRoute) {
-      RedirectService.DEFAULT_ROUTE = tmpConfig.instance.defaultClientRoute
+    if (tmpConfig && tmpConfig.instance) {
+      if (tmpConfig.instance.defaultClientRoute) {
+        RedirectService.DEFAULT_ROUTE = tmpConfig.instance.defaultClientRoute
+      }
+      if (tmpConfig.instance.defaultTrendingRoute) {
+        RedirectService.DEFAULT_TRENDING_ROUTE = tmpConfig.instance.defaultTrendingRoute
+      }
     }
 
     // Load default route
     this.serverService.getConfig()
         .subscribe(config => {
           const defaultRouteConfig = config.instance.defaultClientRoute
+          const defaultTrendingConfig = config.instance.defaultTrendingRoute
 
           if (defaultRouteConfig) {
             RedirectService.DEFAULT_ROUTE = defaultRouteConfig
           }
+
+          if (defaultTrendingConfig) {
+            RedirectService.DEFAULT_TRENDING_ROUTE = defaultTrendingConfig
+          }
         })
 
     // Track previous url
@@ -57,6 +70,15 @@ export class RedirectService {
     return this.redirectToHomepage()
   }
 
+  redirectToTrending () {
+    if (this.redirectingToTrending) return
+
+    this.redirectingToTrending = true
+
+    this.router.navigate([ RedirectService.DEFAULT_TRENDING_ROUTE ])
+        .then(() => this.redirectingToTrending = false)
+  }
+
   redirectToHomepage (skipLocationChange = false) {
     if (this.redirectingToHomepage) return
 
diff --git a/client/src/app/core/routing/trending-guard.service.ts b/client/src/app/core/routing/trending-guard.service.ts
new file mode 100644 (file)
index 0000000..7db7fe9
--- /dev/null
@@ -0,0 +1,14 @@
+import { Injectable } from '@angular/core'
+import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from '@angular/router'
+import { RedirectService } from './redirect.service'
+
+@Injectable()
+export class TrendingGuard implements CanActivate {
+
+  constructor (private redirectService: RedirectService) {}
+
+  canActivate (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
+    this.redirectService.redirectToTrending()
+    return false
+  }
+}
index b1d8fcf8339b6dd46a9809b765143c39666b0a9b..5f13190b4ddc7923f4490999d0c1c8b1456e9815 100644 (file)
@@ -36,9 +36,15 @@ export class ServerService {
       name: 'PeerTube',
       shortDescription: 'PeerTube, a federated (ActivityPub) video streaming platform  ' +
                         'using P2P (BitTorrent) directly in the web browser with WebTorrent and Angular.',
-      defaultClientRoute: '',
       isNSFW: false,
       defaultNSFWPolicy: 'do_not_list' as 'do_not_list',
+      defaultClientRoute: '',
+      defaultTrendingRoute: '',
+      pages: {
+        hot: {
+          enabled: true
+        }
+      },
       customizations: {
         javascript: '',
         css: ''
index 9aa397edd843b80e6499641ed4bca41bf206886b..fc57b970b0d3abb0075859a28f464f44d7dec10b 100644 (file)
           <ng-container i18n>Discover</ng-container>
         </a>
 
-        <a routerLink="/videos/trending" routerLinkActive="active">
+        <a routerLink="/videos/trending" routerLinkActive="active" [ngClass]="{ 'active': hot.isActive || mostViewed.isActive || mostLiked.isActive }">
           <my-global-icon iconName="trending" aria-hidden="true"></my-global-icon>
           <ng-container i18n>Trending</ng-container>
         </a>
+        <a routerLink="/videos/hot" routerLinkActive #hot="routerLinkActive" hidden></a>
+        <a routerLink="/videos/most-viewed" routerLinkActive #mostViewed="routerLinkActive" hidden></a>
+        <a routerLink="/videos/most-liked" routerLinkActive #mostLiked="routerLinkActive" hidden></a>
+
 
         <a routerLink="/videos/recently-added" routerLinkActive="active">
           <my-global-icon iconName="recently-added" aria-hidden="true"></my-global-icon>
index 2eaf0dc7096e5779d820bdabd9abc0e2011dd0f1..0a8aa8fa4ef9b91e5075b0d1bfdd8271a38f6309 100644 (file)
@@ -5,7 +5,7 @@
 
 $iconSize: 16px;
 
-::ng-deep .title-page.title-page-single {
+::ng-deep my-video-list-header {
   display: flex;
   flex-grow: 1;
 }
diff --git a/client/src/app/shared/shared-video-miniature/video-list-header.component.html b/client/src/app/shared/shared-video-miniature/video-list-header.component.html
new file mode 100644 (file)
index 0000000..58db437
--- /dev/null
@@ -0,0 +1,5 @@
+<h1 class="title-page title-page-single">
+  <div placement="bottom" [ngbTooltip]="data.titleTooltip" container="body">
+    {{ data.titlePage }}
+  </div>
+</h1>
\ No newline at end of file
index a07248b96b2b77322c5bdc9ec4679cac865d57f5..67bbf7d7a61c627c0421a26dfa1078c866da94a4 100644 (file)
@@ -1,17 +1,13 @@
-import { Component, Inject } from '@angular/core'
+import { Component, Inject, ViewEncapsulation } from '@angular/core'
 
 export abstract class GenericHeaderComponent {
   constructor (@Inject('data') public data: any) {}
 }
 
 @Component({
-  selector: 'h1',
-  host: { 'class': 'title-page title-page-single' },
-  template: `
-<div placement="bottom" [ngbTooltip]="data.titleTooltip" container="body">
-  {{ data.titlePage }}
-</div>
-  `
+  selector: 'my-video-list-header',
+  encapsulation: ViewEncapsulation.None,
+  templateUrl: './video-list-header.component.html'
 })
 export class VideoListHeaderComponent extends GenericHeaderComponent {
   constructor (@Inject('data') public data: any) {
index e4a5ee727437ce0babcac3574f3431cebb5ff65c..43c7f4a53ee6c63cbe5edbd9c6033d26b8dc183b 100644 (file)
@@ -366,6 +366,10 @@ instance:
 #    - 18 # Food
 
   default_client_route: '/videos/trending'
+  default_trending_route: '/videos/most-viewed'
+  pages:
+    hot:
+      enabled: true
 
   # Whether or not the instance is dedicated to NSFW content
   # Enabling it will allow other administrators to know that you are mainly federating sensitive content
index f7b56cc4a5c8b66844135854f96fd1e8b4e6f443..f9f3abc182ae766ff345ceb727ad475feda35156 100644 (file)
@@ -380,6 +380,10 @@ instance:
 #    - 18 # Food
 
   default_client_route: '/videos/trending'
+  default_trending_route: '/videos/most-viewed'
+  pages:
+    hot:
+      enabled: true
 
   # Whether or not the instance is dedicated to NSFW content
   # Enabling it will allow other administrators to know that you are mainly federating sensitive content
index 44f3d3ef7a2f55fe3434596a98e65b120d6bb1cd..24e7601ec14255468dc202a2d4d72f49c5b3b471 100644 (file)
@@ -65,9 +65,15 @@ async function getConfig (req: express.Request, res: express.Response) {
     instance: {
       name: CONFIG.INSTANCE.NAME,
       shortDescription: CONFIG.INSTANCE.SHORT_DESCRIPTION,
-      defaultClientRoute: CONFIG.INSTANCE.DEFAULT_CLIENT_ROUTE,
       isNSFW: CONFIG.INSTANCE.IS_NSFW,
       defaultNSFWPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY,
+      defaultClientRoute: CONFIG.INSTANCE.DEFAULT_CLIENT_ROUTE,
+      defaultTrendingRoute: CONFIG.INSTANCE.DEFAULT_TRENDING_ROUTE,
+      pages: {
+        hot: {
+          enabled: CONFIG.INSTANCE.PAGES.HOT.ENABLED
+        }
+      },
       customizations: {
         javascript: CONFIG.INSTANCE.CUSTOMIZATIONS.JAVASCRIPT,
         css: CONFIG.INSTANCE.CUSTOMIZATIONS.CSS
@@ -362,8 +368,16 @@ function customConfig (): CustomConfig {
       categories: CONFIG.INSTANCE.CATEGORIES,
 
       isNSFW: CONFIG.INSTANCE.IS_NSFW,
-      defaultClientRoute: CONFIG.INSTANCE.DEFAULT_CLIENT_ROUTE,
       defaultNSFWPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY,
+
+      defaultClientRoute: CONFIG.INSTANCE.DEFAULT_CLIENT_ROUTE,
+      defaultTrendingRoute: CONFIG.INSTANCE.DEFAULT_TRENDING_ROUTE,
+      pages: {
+        hot: {
+          enabled: CONFIG.INSTANCE.PAGES.HOT.ENABLED
+        }
+      },
+
       customizations: {
         css: CONFIG.INSTANCE.CUSTOMIZATIONS.CSS,
         javascript: CONFIG.INSTANCE.CUSTOMIZATIONS.JAVASCRIPT
index 6aae5e82112725fc6d843d91c68315e4469741e8..e474959b2b900ecf345538021e7aa9369b10b40a 100644 (file)
@@ -230,6 +230,7 @@ const customConfigKeysToKeep = [
   'instance-description',
   'instance-terms',
   'instance-defaultClientRoute',
+  'instance-defaultTrendingRoute',
   'instance-defaultNSFWPolicy',
   'instance-customizations-javascript',
   'instance-customizations-css',
index c7ef9b49705872a49fd227b2219cca64a85eae59..e1f8077528e6caa9427352133fd2231f3c238602 100644 (file)
@@ -278,8 +278,16 @@ const CONFIG = {
     get CATEGORIES () { return config.get<number[]>('instance.categories') || [] },
 
     get IS_NSFW () { return config.get<boolean>('instance.is_nsfw') },
-    get DEFAULT_CLIENT_ROUTE () { return config.get<string>('instance.default_client_route') },
     get DEFAULT_NSFW_POLICY () { return config.get<NSFWPolicyType>('instance.default_nsfw_policy') },
+
+    get DEFAULT_CLIENT_ROUTE () { return config.get<string>('instance.default_client_route') },
+    get DEFAULT_TRENDING_ROUTE () { return config.get<string>('instance.default_trending_route') },
+    PAGES: {
+      HOT: {
+        get ENABLED () { return config.get<boolean>('instance.pages.hot.enabled') }
+      }
+    },
+
     CUSTOMIZATIONS: {
       get JAVASCRIPT () { return config.get<string>('instance.customizations.javascript') },
       get CSS () { return config.get<string>('instance.customizations.css') }
index faabf17d7156c8684f9b895cc65b3fea578a688b..0efe1157ff9457a142f0a8ccf805716df08fe182 100644 (file)
@@ -15,8 +15,9 @@ const customConfigUpdateValidator = [
   body('instance.shortDescription').exists().withMessage('Should have a valid instance short description'),
   body('instance.description').exists().withMessage('Should have a valid instance description'),
   body('instance.terms').exists().withMessage('Should have a valid instance terms'),
-  body('instance.defaultClientRoute').exists().withMessage('Should have a valid instance default client route'),
   body('instance.defaultNSFWPolicy').custom(isUserNSFWPolicyValid).withMessage('Should have a valid NSFW policy'),
+  body('instance.defaultClientRoute').exists().withMessage('Should have a valid instance default client route'),
+  body('instance.defaultTrendingRoute').exists().withMessage('Should have a valid instance default trending route'),
   body('instance.customizations.css').exists().withMessage('Should have a valid instance CSS customization'),
   body('instance.customizations.javascript').exists().withMessage('Should have a valid instance JavaScript customization'),
 
index 8e0965244a63e59f8aa4e9b6745a3d7d6be16b0a..3f31ac86273e479c8cb9121880b34d1c6026ac37 100644 (file)
@@ -242,64 +242,49 @@ function buildListQuery (model: typeof Model, options: BuildVideosQueryOptions)
   }
 
   // We don't exclude results in this so if we do a count we don't need to add this complex clause
-  if (options.trendingDays && options.isCount !== true) {
-    const viewsGteDate = new Date(new Date().getTime() - (24 * 3600 * 1000) * options.trendingDays)
-
-    joins.push('LEFT JOIN "videoView" ON "video"."id" = "videoView"."videoId" AND "videoView"."startDate" >= :viewsGteDate')
-    replacements.viewsGteDate = viewsGteDate
-
-    attributes.push('COALESCE(SUM("videoView"."views"), 0) AS "score"')
-
-    group = 'GROUP BY "video"."id"'
-  } else if (options.hot && options.isCount !== true) {
-    /**
-     * "Hotness" is a measure based on absolute view/comment/like/dislike numbers,
-     * with fixed weights only applied to their log values.
-     *
-     * This algorithm gives little chance for an old video to have a good score,
-     * for which recent spikes in interactions could be a sign of "hotness" and
-     * justify a better score. However there are multiple ways to achieve that
-     * goal, which is left for later. Yes, this is a TODO :)
-     *
-     * note: weights and base score are in number of half-days.
-     * see https://github.com/reddit-archive/reddit/blob/master/r2/r2/lib/db/_sorts.pyx#L47-L58
-     */
-    const weights = {
-      like: 3,
-      dislike: 3,
-      view: 1 / 12,
-      comment: 2 // a comment takes more time than a like to do, but can be done multiple times
-    }
-
-    cte.push( // TODO: exclude blocklisted comments
-      '"totalCommentsWithoutVideoAuthor" AS (' +
-        'SELECT "video"."id", ' +
-                'COUNT("replies"."id") - (' +
-                  'SELECT COUNT("authorReplies"."id") ' +
-                  'FROM "videoComment" AS "authorReplies" ' +
-                  'LEFT JOIN "account" ON "account"."id" = "authorReplies"."accountId" ' +
-                  'LEFT JOIN "videoChannel" ON "videoChannel"."accountId" = "account"."id" ' +
-                  'WHERE "video"."channelId" = "videoChannel"."id" ' +
-                ') as "value" ' +
-        'FROM "videoComment" AS "replies" ' +
-        'LEFT JOIN "video" ON "video"."id" = "replies"."videoId" ' +
-        'WHERE "replies"."videoId" = "video"."id" ' +
-        'GROUP BY "video"."id"' +
-      ')'
-    )
+  if (options.isCount !== true) {
+    if (options.trendingDays) {
+      const viewsGteDate = new Date(new Date().getTime() - (24 * 3600 * 1000) * options.trendingDays)
+
+      joins.push('LEFT JOIN "videoView" ON "video"."id" = "videoView"."videoId" AND "videoView"."startDate" >= :viewsGteDate')
+      replacements.viewsGteDate = viewsGteDate
+
+      attributes.push('COALESCE(SUM("videoView"."views"), 0) AS "score"')
+
+      group = 'GROUP BY "video"."id"'
+    } else if (options.hot) {
+      /**
+       * "Hotness" is a measure based on absolute view/comment/like/dislike numbers,
+       * with fixed weights only applied to their log values.
+       *
+       * This algorithm gives little chance for an old video to have a good score,
+       * for which recent spikes in interactions could be a sign of "hotness" and
+       * justify a better score. However there are multiple ways to achieve that
+       * goal, which is left for later. Yes, this is a TODO :)
+       *
+       * note: weights and base score are in number of half-days.
+       * see https://github.com/reddit-archive/reddit/blob/master/r2/r2/lib/db/_sorts.pyx#L47-L58
+       */
+      const weights = {
+        like: 3,
+        dislike: 3,
+        view: 1 / 12,
+        comment: 2 // a comment takes more time than a like to do, but can be done multiple times
+      }
 
-    joins.push('LEFT JOIN "totalCommentsWithoutVideoAuthor" ON "video"."id" = "totalCommentsWithoutVideoAuthor"."id"')
+      joins.push('LEFT JOIN "videoComment" ON "video"."id" = "videoComment"."videoId"')
 
-    attributes.push(
-      `LOG(GREATEST(1, "video"."likes" - 1)) * ${weights.like} ` + // likes (+)
-      `- LOG(GREATEST(1, "video"."dislikes" - 1)) * ${weights.dislike} ` + // dislikes (-)
-      `+ LOG("video"."views" + 1) * ${weights.view} ` + // views (+)
-      `+ LOG(GREATEST(1, "totalCommentsWithoutVideoAuthor"."value")) * ${weights.comment} ` + // comments (+)
-      '+ (SELECT EXTRACT(epoch FROM "video"."publishedAt") / 47000) ' + // base score (in number of half-days)
-      'AS "score"'
-    )
+      attributes.push(
+        `LOG(GREATEST(1, "video"."likes" - 1)) * ${weights.like} ` + // likes (+)
+        `- LOG(GREATEST(1, "video"."dislikes" - 1)) * ${weights.dislike} ` + // dislikes (-)
+        `+ LOG("video"."views" + 1) * ${weights.view} ` + // views (+)
+        `+ LOG(GREATEST(1, COUNT(DISTINCT "videoComment"."id"))) * ${weights.comment} ` + // comments (+)
+        '+ (SELECT EXTRACT(epoch FROM "video"."publishedAt") / 47000) ' + // base score (in number of half-days)
+        'AS "score"'
+      )
 
-    group = 'GROUP BY "video"."id", "totalCommentsWithoutVideoAuthor"."value"'
+      group = 'GROUP BY "video"."id"'
+    }
   }
 
   if (options.historyOfUser) {
index e36cdeab2cefd661418e4707a4ea9a18c76a62f9..e58e0cd9f0524654ab694d79ad8a8e2a41fe2704 100644 (file)
@@ -41,8 +41,16 @@ describe('Test config API validators', function () {
       categories: [ 1, 2 ],
 
       isNSFW: true,
-      defaultClientRoute: '/videos/recently-added',
       defaultNSFWPolicy: 'blur',
+
+      defaultClientRoute: '/videos/recently-added',
+      defaultTrendingRoute: '/videos/trending',
+      pages: {
+        hot: {
+          enabled: true
+        }
+      },
+
       customizations: {
         javascript: 'alert("coucou")',
         css: 'body { background-color: red; }'
index af25f4800276106e763a7a4a0827619e5145eb50..328f4852aa8c8be4b29035b7075e99507517fd26 100644 (file)
@@ -272,9 +272,17 @@ describe('Test config', function () {
         languages: [ 'en', 'es' ],
         categories: [ 1, 2 ],
 
-        defaultClientRoute: '/videos/recently-added',
         isNSFW: true,
         defaultNSFWPolicy: 'blur' as 'blur',
+
+        defaultClientRoute: '/videos/recently-added',
+        defaultTrendingRoute: '/videos/trending',
+        pages: {
+          hot: {
+            enabled: true
+          }
+        },
+
         customizations: {
           javascript: 'alert("coucou")',
           css: 'body { background-color: red; }'
index 7572fd34a5bb48c7c3b558a946bd59b517f2c670..d608764eed708664f21b5cd8b5765eb594b7dcd8 100644 (file)
@@ -308,8 +308,8 @@ describe('Test a client controllers', function () {
           shortDescription: 'my short description',
           description: 'my super description',
           terms: 'my super terms',
-          defaultClientRoute: '/videos/recently-added',
           defaultNSFWPolicy: 'blur',
+          defaultClientRoute: '/videos/recently-added',
           customizations: {
             javascript: 'alert("coucou")',
             css: 'body { background-color: red; }'
index f7c488c0bbea34a49824391aa36e7fd847ff608c..5152ec693218499fa43cdf9e6be2ad3e8f4145d1 100644 (file)
@@ -65,9 +65,17 @@ function updateCustomSubConfig (url: string, token: string, newConfig: DeepParti
       languages: [ 'en', 'es' ],
       categories: [ 1, 2 ],
 
-      defaultClientRoute: '/videos/recently-added',
       isNSFW: true,
       defaultNSFWPolicy: 'blur',
+
+      defaultClientRoute: '/videos/recently-added',
+      defaultTrendingRoute: '/videos/trending',
+      pages: {
+        hot: {
+          enabled: true
+        }
+      },
+
       customizations: {
         javascript: 'alert("coucou")',
         css: 'body { background-color: red; }'
index 9a6a249231e6c5fcd76cb29cb582a64f715abbff..fcc29e5d70ba9ce6a536a3a3e1e62f823b2d0956 100644 (file)
@@ -30,8 +30,16 @@ export interface CustomConfig {
     categories: number[]
 
     isNSFW: boolean
-    defaultClientRoute: string
     defaultNSFWPolicy: NSFWPolicyType
+
+    defaultClientRoute: string
+    defaultTrendingRoute: string
+    pages: {
+      hot: {
+        enabled: boolean
+      }
+    }
+
     customizations: {
       javascript?: string
       css?: string
index 2dcf98f4f3183891c4331c5c644621062fb4888a..a2d93ce738c3029bca8563ff92570562635a6bf4 100644 (file)
@@ -36,9 +36,15 @@ export interface ServerConfig {
   instance: {
     name: string
     shortDescription: string
-    defaultClientRoute: string
     isNSFW: boolean
     defaultNSFWPolicy: NSFWPolicyType
+    defaultClientRoute: string
+    defaultTrendingRoute: string
+    pages: {
+      hot: {
+        enabled: boolean
+      }
+    }
     customizations: {
       javascript: string
       css: string