Add ability to change the homepage
authorChocobozzz <me@florianbigard.com>
Thu, 1 Mar 2018 12:57:29 +0000 (13:57 +0100)
committerChocobozzz <me@florianbigard.com>
Thu, 1 Mar 2018 12:57:29 +0000 (13:57 +0100)
18 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/app-routing.module.ts
client/src/app/app.component.ts
client/src/app/core/core.module.ts
client/src/app/core/routing/index.ts
client/src/app/core/routing/redirect.service.ts [new file with mode: 0644]
client/src/app/core/server/server.service.ts
client/src/app/videos/+video-watch/video-watch.component.ts
config/default.yaml
config/production.yaml.example
server/controllers/api/config.ts
server/initializers/constants.ts
server/middlewares/validators/config.ts
server/tests/api/check-params/config.ts
server/tests/api/server/config.ts
shared/models/server/custom-config.model.ts
shared/models/server/server-config.model.ts

index 18ba7ba060cba248f814ed2e2740c85a0723516e..c7ddaaf010bd92d8b0ee3f49febeac262022f9a2 100644 (file)
     </div>
   </div>
 
+  <div class="form-group">
+    <label for="instanceDefaultClientRoute">Default client route</label>
+    <div class="peertube-select-container">
+      <select id="instanceDefaultClientRoute" formControlName="instanceDefaultClientRoute">
+        <option value="/videos/trending">Videos Trending</option>
+        <option value="/videos/recently-added">Videos Recently Added</option>
+      </select>
+    </div>
+  </div>
+
   <div class="inner-form-title">Cache</div>
 
   <div class="form-group">
index cf93b40602eb6e9d25b55451d3dddf420344c5b9..c38bc326acfb44a27701ab90ed24cc3971529c41 100644 (file)
@@ -46,6 +46,7 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
     instanceName: '',
     instanceDescription: '',
     instanceTerms: '',
+    instanceDefaultClientRoute: '',
     cachePreviewsSize: '',
     signupLimit: '',
     adminEmail: '',
@@ -85,6 +86,7 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
       instanceName: [ '', INSTANCE_NAME.VALIDATORS ],
       instanceDescription: [ '' ],
       instanceTerms: [ '' ],
+      instanceDefaultClientRoute: [ '' ],
       cachePreviewsSize: [ '', CACHE_PREVIEWS_SIZE.VALIDATORS ],
       signupEnabled: [ ],
       signupLimit: [ '', SIGNUP_LIMIT.VALIDATORS ],
@@ -153,11 +155,12 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
       if (confirmRes === false) return
     }
 
-    const data = {
+    const data: CustomConfig = {
       instance: {
         name: this.form.value['instanceName'],
         description: this.form.value['instanceDescription'],
         terms: this.form.value['instanceTerms'],
+        defaultClientRoute: this.form.value['instanceDefaultClientRoute'],
         customizations: {
           javascript: this.form.value['customizationJavascript'],
           css: this.form.value['customizationCSS']
@@ -213,6 +216,7 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
       instanceName: this.customConfig.instance.name,
       instanceDescription: this.customConfig.instance.description,
       instanceTerms: this.customConfig.instance.terms,
+      instanceDefaultClientRoute: this.customConfig.instance.defaultClientRoute,
       cachePreviewsSize: this.customConfig.cache.previews.size,
       signupEnabled: this.customConfig.signup.enabled,
       signupLimit: this.customConfig.signup.limit,
index f31b51e2381e8226c59e69df90164e76067a9b4f..c8a6b392429238fadf7d82b9d41e34fe1ead5c09 100644 (file)
@@ -1,14 +1,10 @@
 import { NgModule } from '@angular/core'
 import { Routes, RouterModule } from '@angular/router'
+import { RedirectService } from '@app/core/routing/redirect.service'
 
 import { PreloadSelectedModulesList } from './core'
 
 const routes: Routes = [
-  {
-    path: '',
-    redirectTo: '/videos/trending',
-    pathMatch: 'full'
-  },
   {
     path: 'admin',
     loadChildren: './+admin/admin.module#AdminModule'
@@ -22,7 +18,9 @@ const routes: Routes = [
       preloadingStrategy: PreloadSelectedModulesList
     })
   ],
-  providers: [ PreloadSelectedModulesList ],
+  providers: [
+    PreloadSelectedModulesList
+  ],
   exports: [ RouterModule ]
 })
 export class AppRoutingModule {}
index 25936146c8c6f5970b2bfcc1a2349b7fef52762e..346e966e5189f665ec14aabd9b08f8c89fa1f60c 100644 (file)
@@ -1,7 +1,7 @@
 import { Component, OnInit } from '@angular/core'
 import { DomSanitizer, SafeHtml } from '@angular/platform-browser'
 import { GuardsCheckStart, Router } from '@angular/router'
-import { AuthService, ServerService } from '@app/core'
+import { AuthService, RedirectService, ServerService } from '@app/core'
 import { isInSmallView } from '@app/shared/misc/utils'
 
 @Component({
@@ -31,7 +31,8 @@ export class AppComponent implements OnInit {
     private router: Router,
     private authService: AuthService,
     private serverService: ServerService,
-    private domSanitizer: DomSanitizer
+    private domSanitizer: DomSanitizer,
+    private redirectService: RedirectService
   ) {}
 
   get serverVersion () {
@@ -43,6 +44,10 @@ export class AppComponent implements OnInit {
   }
 
   ngOnInit () {
+    if (this.router.url === '/') {
+      this.redirectService.redirectToHomepage()
+    }
+
     this.authService.loadClientCredentials()
 
     if (this.authService.isLoggedIn()) {
index 708831965574dd3c9bd9ecbca2830bae43ab8ead..c2de2084e87427a6c8726c74e22f60791f4dc8a6 100644 (file)
@@ -13,7 +13,7 @@ import { ModalModule } from 'ngx-bootstrap/modal'
 import { AuthService } from './auth'
 import { ConfirmComponent, ConfirmService } from './confirm'
 import { throwIfAlreadyLoaded } from './module-import-guard'
-import { LoginGuard, UserRightGuard } from './routing'
+import { LoginGuard, RedirectService, UserRightGuard } from './routing'
 import { ServerService } from './server'
 
 @NgModule({
@@ -48,7 +48,8 @@ import { ServerService } from './server'
     ConfirmService,
     ServerService,
     LoginGuard,
-    UserRightGuard
+    UserRightGuard,
+    RedirectService
   ]
 })
 export class CoreModule {
index d1b9828344662074d81c2659579da767f36a6dea..9f0b4eac5498ad214b43e6cd5c9b715c002f0292 100644 (file)
@@ -1,3 +1,4 @@
 export * from './login-guard.service'
 export * from './user-right-guard.service'
 export * from './preload-selected-modules-list'
+export * from './redirect.service'
diff --git a/client/src/app/core/routing/redirect.service.ts b/client/src/app/core/routing/redirect.service.ts
new file mode 100644 (file)
index 0000000..a0125e0
--- /dev/null
@@ -0,0 +1,48 @@
+import { Injectable } from '@angular/core'
+import { Router } from '@angular/router'
+import { ServerService } from '../server'
+
+@Injectable()
+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
+
+  constructor (
+    private router: Router,
+    private serverService: ServerService
+  ) {
+    // The config is first loaded from the cache so try to get the default route
+    const config = this.serverService.getConfig()
+    if (config && config.instance && config.instance.defaultClientRoute) {
+      RedirectService.DEFAULT_ROUTE = config.instance.defaultClientRoute
+    }
+
+    this.serverService.configLoaded
+      .subscribe(() => {
+        const defaultRouteConfig = this.serverService.getConfig().instance.defaultClientRoute
+
+        if (defaultRouteConfig) {
+          RedirectService.DEFAULT_ROUTE = defaultRouteConfig
+        }
+      })
+  }
+
+  redirectToHomepage () {
+    console.log('Redirecting to %s...', RedirectService.DEFAULT_ROUTE)
+
+    this.router.navigate([ RedirectService.DEFAULT_ROUTE ])
+      .catch(() => {
+        console.error(
+          'Cannot navigate to %s, resetting default route to %s.',
+          RedirectService.DEFAULT_ROUTE,
+          RedirectService.INIT_DEFAULT_ROUTE
+        )
+
+        RedirectService.DEFAULT_ROUTE = RedirectService.INIT_DEFAULT_ROUTE
+        return this.router.navigate([ RedirectService.DEFAULT_ROUTE ])
+      })
+
+  }
+
+}
index 984738948295ff63e9c0fce7fc75cfce9e09025a..2135c3268e843a9662bd74cf512531fbfb26ed6f 100644 (file)
@@ -21,6 +21,7 @@ export class ServerService {
   private config: ServerConfig = {
     instance: {
       name: 'PeerTube',
+      defaultClientRoute: '',
       customizations: {
         javascript: '',
         css: ''
index 585ab2e00502f172a8c8fc92dab1171a0a67980e..66ef0399ac063d1f74ace2d09e161c999b994615 100644 (file)
@@ -1,9 +1,9 @@
 import { Component, ElementRef, NgZone, OnDestroy, OnInit, ViewChild } from '@angular/core'
 import { ActivatedRoute, Router } from '@angular/router'
+import { RedirectService } from '@app/core/routing/redirect.service'
 import { VideoSupportComponent } from '@app/videos/+video-watch/modal/video-support.component'
 import { MetaService } from '@ngx-meta/core'
 import { NotificationsService } from 'angular2-notifications'
-import { Observable } from 'rxjs/Observable'
 import { Subscription } from 'rxjs/Subscription'
 import * as videojs from 'video.js'
 import 'videojs-hotkeys'
@@ -64,7 +64,8 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
     private authService: AuthService,
     private notificationsService: NotificationsService,
     private markdownService: MarkdownService,
-    private zone: NgZone
+    private zone: NgZone,
+    private redirectService: RedirectService
   ) {}
 
   get user () {
@@ -142,7 +143,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
                               .subscribe(
                                 status => {
                                   this.notificationsService.success('Success', `Video ${this.video.name} had been blacklisted.`)
-                                  this.router.navigate(['/videos/list'])
+                                  this.redirectService.redirectToHomepage()
                                 },
 
                                 error => this.notificationsService.error('Error', error.message)
@@ -247,7 +248,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
           this.notificationsService.success('Success', `Video ${this.video.name} deleted.`)
 
           // Go back to the video-list.
-          this.router.navigate([ '/videos/list' ])
+          this.redirectService.redirectToHomepage()
         },
 
         error => this.notificationsService.error('Error', error.message)
@@ -313,7 +314,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
         'This video contains mature or explicit content. Are you sure you want to watch it?',
         'Mature or explicit content'
       )
-      if (res === false) return this.router.navigate([ '/videos/list' ])
+      if (res === false) return this.redirectService.redirectToHomepage()
     }
 
     if (!this.hasAlreadyAcceptedPrivacyConcern()) {
@@ -323,7 +324,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
         'Privacy concern',
         'I accept!'
       )
-      if (res === false) return this.router.navigate([ '/videos/list' ])
+      if (res === false) return this.redirectService.redirectToHomepage()
     }
 
     this.acceptedPrivacyConcern()
index a634be61c15519d34fe44f670e2f61e2f83ef499..5389f116413ec834639ce6b7885bb375e06576d7 100644 (file)
@@ -74,6 +74,7 @@ instance:
   name: 'PeerTube'
   description: 'Welcome to this PeerTube instance!' # Support markdown
   terms: 'No terms for now.' # Support markdown
+  default_client_route: '/videos/trending'
   customizations:
     javascript: '' # Directly your JavaScript code (without <script> tags). Will be eval at runtime
     css: '' # Directly your CSS code (without <style> tags). Will be injected at runtime
index e70201668c66b9590a0b2280c556f871f23cb726..2f28501b3ae9427d32cd16d072588b986adcd033 100644 (file)
@@ -87,6 +87,7 @@ instance:
   name: 'PeerTube'
   description: '' # Support markdown
   terms: '' # Support markdown
+  default_client_route: '/videos/trending'
   customizations:
     javascript: '' # Directly your JavaScript code (without <script> tags). Will be eval at runtime
     css: '' # Directly your CSS code (without <style> tags). Will be injected at runtime
index 42712581035512e049f11a441c5d337dbd1a5685..a25d7a1577ba4c04df0abb845238561227d5b0eb 100644 (file)
@@ -44,6 +44,7 @@ async function getConfig (req: express.Request, res: express.Response, next: exp
   const json: ServerConfig = {
     instance: {
       name: CONFIG.INSTANCE.NAME,
+      defaultClientRoute: CONFIG.INSTANCE.DEFAULT_CLIENT_ROUTE,
       customizations: {
         javascript: CONFIG.INSTANCE.CUSTOMIZATIONS.JAVASCRIPT,
         css: CONFIG.INSTANCE.CUSTOMIZATIONS.CSS
@@ -114,7 +115,9 @@ async function updateCustomConfig (req: express.Request, res: express.Response,
   // Need to change the videoQuota key a little bit
   const toUpdateJSON = omit(toUpdate, 'videoQuota')
   toUpdateJSON.user['video_quota'] = toUpdate.user.videoQuota
+  toUpdateJSON.instance['default_client_route'] = toUpdate.instance.defaultClientRoute
   delete toUpdate.user.videoQuota
+  delete toUpdate.instance.defaultClientRoute
 
   await writeFilePromise(CONFIG.CUSTOM_FILE, JSON.stringify(toUpdateJSON, undefined, 2))
 
@@ -138,6 +141,7 @@ function customConfig (): CustomConfig {
       name: CONFIG.INSTANCE.NAME,
       description: CONFIG.INSTANCE.DESCRIPTION,
       terms: CONFIG.INSTANCE.TERMS,
+      defaultClientRoute: CONFIG.INSTANCE.DEFAULT_CLIENT_ROUTE,
       customizations: {
         css: CONFIG.INSTANCE.CUSTOMIZATIONS.CSS,
         javascript: CONFIG.INSTANCE.CUSTOMIZATIONS.JAVASCRIPT
index 318df48bf04af079ce8fa3eb28b4eb389c6eb828..5946bcd11ce53b54477d54f78e79e3728f51aafa 100644 (file)
@@ -159,6 +159,7 @@ const CONFIG = {
     get NAME () { return config.get<string>('instance.name') },
     get DESCRIPTION () { return config.get<string>('instance.description') },
     get TERMS () { return config.get<string>('instance.terms') },
+    get DEFAULT_CLIENT_ROUTE () { return config.get<string>('instance.default_client_route') },
     CUSTOMIZATIONS: {
       get JAVASCRIPT () { return config.get<string>('instance.customizations.javascript') },
       get CSS () { return config.get<string>('instance.customizations.css') }
index 800aaf107495acfd6ddae3555fa84a47586a71c3..ee6f6efa4d3de1a29f9f9481de3793c521ffd456 100644 (file)
@@ -5,6 +5,12 @@ import { logger } from '../../helpers/logger'
 import { areValidationErrors } from './utils'
 
 const customConfigUpdateValidator = [
+  body('instance.name').exists().withMessage('Should have a valid instance name'),
+  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.customizations.css').exists().withMessage('Should have a valid instance CSS customization'),
+  body('instance.customizations.javascript').exists().withMessage('Should have a valid instance JavaScript customization'),
   body('cache.previews.size').isInt().withMessage('Should have a valid previews size'),
   body('signup.enabled').isBoolean().withMessage('Should have a valid signup enabled boolean'),
   body('signup.limit').isInt().withMessage('Should have a valid signup limit'),
index a66e51a6a53f502cc203832b2fee6800e4a87016..ca82392700bd33aeb6eaee85049a769b871f1269 100644 (file)
@@ -18,6 +18,7 @@ describe('Test config API validators', function () {
       name: 'PeerTube updated',
       description: 'my super description',
       terms: 'my super terms',
+      defaultClientRoute: '/videos/recently-added',
       customizations: {
         javascript: 'alert("coucou")',
         css: 'body { background-color: red; }'
index 3d90580d8782b509963e97c04b64219d2cc9ca93..271a57275f0ecb90636feeb3cca7501523c09e7c 100644 (file)
@@ -54,6 +54,7 @@ describe('Test config', function () {
     expect(data.instance.name).to.equal('PeerTube')
     expect(data.instance.description).to.equal('Welcome to this PeerTube instance!')
     expect(data.instance.terms).to.equal('No terms for now.')
+    expect(data.instance.defaultClientRoute).to.equal('/videos/trending')
     expect(data.instance.customizations.css).to.be.empty
     expect(data.instance.customizations.javascript).to.be.empty
     expect(data.cache.previews.size).to.equal(1)
@@ -76,6 +77,7 @@ describe('Test config', function () {
         name: 'PeerTube updated',
         description: 'my super description',
         terms: 'my super terms',
+        defaultClientRoute: '/videos/recently-added',
         customizations: {
           javascript: 'alert("coucou")',
           css: 'body { background-color: red; }'
@@ -116,6 +118,7 @@ describe('Test config', function () {
     expect(data.instance.name).to.equal('PeerTube updated')
     expect(data.instance.description).to.equal('my super description')
     expect(data.instance.terms).to.equal('my super terms')
+    expect(data.instance.defaultClientRoute).to.equal('/videos/recently-added')
     expect(data.instance.customizations.javascript).to.equal('alert("coucou")')
     expect(data.instance.customizations.css).to.equal('body { background-color: red; }')
     expect(data.cache.previews.size).to.equal(2)
@@ -145,6 +148,7 @@ describe('Test config', function () {
     expect(data.instance.name).to.equal('PeerTube updated')
     expect(data.instance.description).to.equal('my super description')
     expect(data.instance.terms).to.equal('my super terms')
+    expect(data.instance.defaultClientRoute).to.equal('/videos/recently-added')
     expect(data.instance.customizations.javascript).to.equal('alert("coucou")')
     expect(data.instance.customizations.css).to.equal('body { background-color: red; }')
     expect(data.cache.previews.size).to.equal(2)
@@ -181,6 +185,7 @@ describe('Test config', function () {
     expect(data.instance.name).to.equal('PeerTube')
     expect(data.instance.description).to.equal('Welcome to this PeerTube instance!')
     expect(data.instance.terms).to.equal('No terms for now.')
+    expect(data.instance.defaultClientRoute).to.equal('/videos/trending')
     expect(data.instance.customizations.css).to.be.empty
     expect(data.instance.customizations.javascript).to.be.empty
     expect(data.cache.previews.size).to.equal(1)
index 46d0a86efa31c77e8b8fb81a730bf9478557bafa..7f3e2df0272f29456ea854f1ebc0ec38e4f7fd3c 100644 (file)
@@ -3,6 +3,7 @@ export interface CustomConfig {
     name: string
     description: string
     terms: string
+    defaultClientRoute: string
     customizations: {
       javascript?: string
       css?: string
index 004cf6ddb909ce5812eaca8b8e219c6a2d971d9a..a30c24eb9ae89ea331658da86317a7127b3f0499 100644 (file)
@@ -2,7 +2,8 @@ export interface ServerConfig {
   serverVersion: string
 
   instance: {
-    name: string;
+    name: string
+    defaultClientRoute: string
     customizations: {
       javascript: string
       css: string