]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/commitdiff
give admins access to edit all channels
authorkontrollanten <6680299+kontrollanten@users.noreply.github.com>
Mon, 6 Dec 2021 04:54:26 +0000 (05:54 +0100)
committerkontrollanten <6680299+kontrollanten@users.noreply.github.com>
Mon, 6 Dec 2021 05:43:21 +0000 (06:43 +0100)
closes #4598

16 files changed:
client/src/app/+my-library/+my-video-channels/my-video-channel-edit.component.html [deleted file]
client/src/app/+my-library/+my-video-channels/my-video-channels-routing.module.ts
client/src/app/+my-library/+my-video-channels/my-video-channels.component.html
client/src/app/+my-library/+my-video-channels/my-video-channels.module.ts
client/src/app/+video-channels/video-channel-edit/video-channel-create.component.ts [moved from client/src/app/+my-library/+my-video-channels/my-video-channel-create.component.ts with 94% similarity]
client/src/app/+video-channels/video-channel-edit/video-channel-edit.component.html [new file with mode: 0644]
client/src/app/+video-channels/video-channel-edit/video-channel-edit.component.scss [moved from client/src/app/+my-library/+my-video-channels/my-video-channel-edit.component.scss with 96% similarity]
client/src/app/+video-channels/video-channel-edit/video-channel-edit.ts [moved from client/src/app/+my-library/+my-video-channels/my-video-channel-edit.ts with 100% similarity]
client/src/app/+video-channels/video-channel-edit/video-channel-update.component.ts [moved from client/src/app/+my-library/+my-video-channels/my-video-channel-update.component.ts with 92% similarity]
client/src/app/+video-channels/video-channels-routing.module.ts
client/src/app/+video-channels/video-channels.component.html
client/src/app/+video-channels/video-channels.component.ts
client/src/app/+video-channels/video-channels.module.ts
server/controllers/api/video-channel.ts
server/middlewares/user-right.ts
shared/models/users/user-right.enum.ts

diff --git a/client/src/app/+my-library/+my-video-channels/my-video-channel-edit.component.html b/client/src/app/+my-library/+my-video-channels/my-video-channel-edit.component.html
deleted file mode 100644 (file)
index 2910dff..0000000
+++ /dev/null
@@ -1,112 +0,0 @@
-<nav aria-label="breadcrumb">
-  <ol class="breadcrumb">
-    <li class="breadcrumb-item">
-      <a routerLink="/my-library/video-channels" i18n>My Channels</a>
-    </li>
-
-    <ng-container *ngIf="isCreation()">
-      <li class="breadcrumb-item active" i18n>Create</li>
-    </ng-container>
-    <ng-container *ngIf="!isCreation()">
-      <li class="breadcrumb-item active" i18n>Edit</li>
-      <li class="breadcrumb-item active" aria-current="page">
-        <a *ngIf="videoChannel" [routerLink]="[ '/my-library/video-channels/update', videoChannel?.nameWithHost ]">{{ videoChannel?.displayName }}</a>
-      </li>
-    </ng-container>
-  </ol>
-</nav>
-
-<div *ngIf="error" class="alert alert-danger">{{ error }}</div>
-
-<form role="form" (ngSubmit)="formValidated()" [formGroup]="form">
-
-  <div class="form-row"> <!-- channel grid -->
-    <div class="form-group col-12 col-lg-4 col-xl-3">
-      <div *ngIf="isCreation()" class="video-channel-title" i18n>NEW CHANNEL</div>
-      <div *ngIf="!isCreation() && videoChannel" class="video-channel-title" i18n>CHANNEL</div>
-    </div>
-
-    <div class="form-group col-12 col-lg-8 col-xl-9">
-      <h6 i18n>Banner image of your channel</h6>
-
-      <my-actor-banner-edit
-        *ngIf="videoChannel" [previewImage]="isCreation()"
-        [actor]="videoChannel" (bannerChange)="onBannerChange($event)" (bannerDelete)="onBannerDelete()"
-      ></my-actor-banner-edit>
-
-      <my-actor-avatar-edit
-        *ngIf="videoChannel" [previewImage]="isCreation()"
-        [actor]="videoChannel" (avatarChange)="onAvatarChange($event)" (avatarDelete)="onAvatarDelete()"
-        [displayUsername]="!isCreation()" [displaySubscribers]="!isCreation()"
-      ></my-actor-avatar-edit>
-
-      <div class="form-group" *ngIf="isCreation()">
-        <label i18n for="name">Name</label>
-        <div class="input-group">
-          <input
-            type="text" id="name" i18n-placeholder placeholder="Example: my_channel"
-            formControlName="name" [ngClass]="{ 'input-error': formErrors['name'] }" class="form-control"
-          >
-          <div class="input-group-append">
-            <span class="input-group-text">@{{ instanceHost }}</span>
-          </div>
-        </div>
-        <div *ngIf="formErrors['name']" class="form-error">
-          {{ formErrors['name'] }}
-        </div>
-      </div>
-
-      <div class="form-group">
-        <label i18n for="display-name">Display name</label>
-        <input
-          type="text" id="display-name" class="form-control"
-          formControlName="display-name" [ngClass]="{ 'input-error': formErrors['display-name'] }"
-        >
-        <div *ngIf="formErrors['display-name']" class="form-error">
-          {{ formErrors['display-name'] }}
-        </div>
-      </div>
-
-      <div class="form-group">
-        <label i18n for="description">Description</label>
-        <textarea
-          id="description" formControlName="description" class="form-control"
-          [ngClass]="{ 'input-error': formErrors['description'] }"
-        ></textarea>
-        <div *ngIf="formErrors.description" class="form-error">
-          {{ formErrors.description }}
-        </div>
-      </div>
-
-      <div class="form-group">
-        <label for="support">Support</label>
-        <my-help
-          helpType="markdownEnhanced" i18n-preHtml preHtml="Short text to tell people how they can support your channel (membership platform...).<br /><br />
-    When you will upload a video in this channel, the video support field will be automatically filled by this text."
-        ></my-help>
-        <my-markdown-textarea
-            id="support" formControlName="support" textareaMaxWidth="500px" markdownType="enhanced"
-            [classes]="{ 'input-error': formErrors['support'] }"
-        ></my-markdown-textarea>
-        <div *ngIf="formErrors.support" class="form-error">
-          {{ formErrors.support }}
-        </div>
-      </div>
-
-      <div class="form-group" *ngIf="isBulkUpdateVideosDisplayed()">
-        <my-peertube-checkbox
-          inputName="bulkVideosSupportUpdate" formControlName="bulkVideosSupportUpdate"
-          i18n-labelText labelText="Overwrite support field of all videos of this channel"
-        ></my-peertube-checkbox>
-      </div>
-
-    </div>
-  </div>
-
-  <div class="form-row"> <!-- submit placement block -->
-    <div class="col-md-7 col-xl-5"></div>
-    <div class="col-md-5 col-xl-5 d-inline-flex">
-      <input type="submit" value="{{ getFormButtonTitle() }}" [disabled]="!form.valid">
-    </div>
-  </div>
-</form>
index 6b8efad0b0f2e7c7feffec8be4d98ad316530883..f103bacc4287bb9662092ae5ec5386a06838404f 100644 (file)
@@ -1,7 +1,5 @@
 import { NgModule } from '@angular/core'
 import { RouterModule, Routes } from '@angular/router'
-import { MyVideoChannelUpdateComponent } from './my-video-channel-update.component'
-import { MyVideoChannelCreateComponent } from './my-video-channel-create.component'
 import { MyVideoChannelsComponent } from './my-video-channels.component'
 
 const myVideoChannelsRoutes: Routes = [
@@ -13,24 +11,6 @@ const myVideoChannelsRoutes: Routes = [
         title: $localize`My video channels`
       }
     }
-  },
-  {
-    path: 'create',
-    component: MyVideoChannelCreateComponent,
-    data: {
-      meta: {
-        title: $localize`Create a new video channel`
-      }
-    }
-  },
-  {
-    path: 'update/:videoChannelId',
-    component: MyVideoChannelUpdateComponent,
-    data: {
-      meta: {
-        title: $localize`Update video channel`
-      }
-    }
   }
 ]
 
index bbe583971ab9c76277fb8e0a0aa8442b9f2837c6..92e2345abb3bf0f1c62ab89877da2abcbe4556b7 100644 (file)
@@ -9,7 +9,7 @@
 <div class="video-channels-header d-flex justify-content-between">
   <my-advanced-input-filter (search)="onSearch($event)"></my-advanced-input-filter>
 
-  <a class="create-button" routerLink="create">
+  <a class="create-button" routerLink="/c/@create">
     <my-global-icon iconName="add" aria-hidden="true"></my-global-icon>
     <ng-container i18n>Create video channel</ng-container>
   </a>
@@ -37,7 +37,7 @@
       <div i18n class="video-channel-videos">{videoChannel.videosCount, plural, =0 {No videos} =1 {1 video} other {{{ videoChannel.videosCount }} videos}}</div>
 
       <div class="video-channel-buttons">
-        <my-edit-button label [routerLink]="[ 'update', videoChannel.nameWithHost ]"></my-edit-button>
+        <my-edit-button label [routerLink]="[ '/c', videoChannel.nameWithHost, 'update' ]"></my-edit-button>
         <my-delete-button label (click)="deleteVideoChannel(videoChannel)"></my-delete-button>
       </div>
 
index c775bfdee46e931a917092394dea19b7316c213e..a17eb9f1069d3a1c43befcbfb98c688ca604416f 100644 (file)
@@ -1,11 +1,8 @@
 import { ChartModule } from 'primeng/chart'
 import { NgModule } from '@angular/core'
-import { SharedActorImageEditModule } from '@app/shared/shared-actor-image-edit'
 import { SharedFormModule } from '@app/shared/shared-forms'
 import { SharedGlobalIconModule } from '@app/shared/shared-icons'
 import { SharedMainModule } from '@app/shared/shared-main'
-import { MyVideoChannelCreateComponent } from './my-video-channel-create.component'
-import { MyVideoChannelUpdateComponent } from './my-video-channel-update.component'
 import { MyVideoChannelsRoutingModule } from './my-video-channels-routing.module'
 import { MyVideoChannelsComponent } from './my-video-channels.component'
 import { SharedActorImageModule } from '@app/shared/shared-actor-image/shared-actor-image.module'
@@ -19,14 +16,11 @@ import { SharedActorImageModule } from '@app/shared/shared-actor-image/shared-ac
     SharedMainModule,
     SharedFormModule,
     SharedGlobalIconModule,
-    SharedActorImageEditModule,
     SharedActorImageModule
   ],
 
   declarations: [
-    MyVideoChannelsComponent,
-    MyVideoChannelCreateComponent,
-    MyVideoChannelUpdateComponent
+    MyVideoChannelsComponent
   ],
 
   exports: [],
similarity index 94%
rename from client/src/app/+my-library/+my-video-channels/my-video-channel-create.component.ts
rename to client/src/app/+video-channels/video-channel-edit/video-channel-create.component.ts
index fd00720d817cbb7f00928ba48f274c4c18c7ef0b..a7b365dbf4bd6f2f2ef7a48bd040c56df5c394ae 100644 (file)
@@ -12,11 +12,11 @@ import {
 import { FormValidatorService } from '@app/shared/shared-forms'
 import { VideoChannel, VideoChannelService } from '@app/shared/shared-main'
 import { HttpStatusCode, VideoChannelCreate } from '@shared/models'
-import { MyVideoChannelEdit } from './my-video-channel-edit'
+import { MyVideoChannelEdit } from './video-channel-edit'
 
 @Component({
-  templateUrl: './my-video-channel-edit.component.html',
-  styleUrls: [ './my-video-channel-edit.component.scss' ]
+  templateUrl: './video-channel-edit.component.html',
+  styleUrls: [ './video-channel-edit.component.scss' ]
 })
 export class MyVideoChannelCreateComponent extends MyVideoChannelEdit implements OnInit {
   error: string
diff --git a/client/src/app/+video-channels/video-channel-edit/video-channel-edit.component.html b/client/src/app/+video-channels/video-channel-edit/video-channel-edit.component.html
new file mode 100644 (file)
index 0000000..3751747
--- /dev/null
@@ -0,0 +1,96 @@
+<div *ngIf="error" class="alert alert-danger">{{ error }}</div>
+
+<div class="margin-content">
+  <form role="form" (ngSubmit)="formValidated()" [formGroup]="form">
+
+    <div class="form-row"> <!-- channel grid -->
+      <div class="form-group col-12 col-lg-4 col-xl-3">
+        <div *ngIf="isCreation()" class="video-channel-title" i18n>NEW CHANNEL</div>
+        <div *ngIf="!isCreation() && videoChannel" class="video-channel-title" i18n>CHANNEL</div>
+      </div>
+  
+      <div class="form-group col-12 col-lg-8 col-xl-9">
+        <h6 i18n>Banner image of the channel</h6>
+  
+        <my-actor-banner-edit
+          *ngIf="videoChannel" [previewImage]="isCreation()"
+          [actor]="videoChannel" (bannerChange)="onBannerChange($event)" (bannerDelete)="onBannerDelete()"
+        ></my-actor-banner-edit>
+  
+        <my-actor-avatar-edit
+          *ngIf="videoChannel" [previewImage]="isCreation()"
+          [actor]="videoChannel" (avatarChange)="onAvatarChange($event)" (avatarDelete)="onAvatarDelete()"
+          [displayUsername]="!isCreation()" [displaySubscribers]="!isCreation()"
+        ></my-actor-avatar-edit>
+  
+        <div class="form-group" *ngIf="isCreation()">
+          <label i18n for="name">Name</label>
+          <div class="input-group">
+            <input
+              type="text" id="name" i18n-placeholder placeholder="Example: my_channel"
+              formControlName="name" [ngClass]="{ 'input-error': formErrors['name'] }" class="form-control"
+            >
+            <div class="input-group-append">
+              <span class="input-group-text">@{{ instanceHost }}</span>
+            </div>
+          </div>
+          <div *ngIf="formErrors['name']" class="form-error">
+            {{ formErrors['name'] }}
+          </div>
+        </div>
+  
+        <div class="form-group">
+          <label i18n for="display-name">Display name</label>
+          <input
+            type="text" id="display-name" class="form-control"
+            formControlName="display-name" [ngClass]="{ 'input-error': formErrors['display-name'] }"
+          >
+          <div *ngIf="formErrors['display-name']" class="form-error">
+            {{ formErrors['display-name'] }}
+          </div>
+        </div>
+  
+        <div class="form-group">
+          <label i18n for="description">Description</label>
+          <textarea
+            id="description" formControlName="description" class="form-control"
+            [ngClass]="{ 'input-error': formErrors['description'] }"
+          ></textarea>
+          <div *ngIf="formErrors.description" class="form-error">
+            {{ formErrors.description }}
+          </div>
+        </div>
+  
+        <div class="form-group">
+          <label for="support">Support</label>
+          <my-help
+            helpType="markdownEnhanced" i18n-preHtml preHtml="Short text to tell people how they can support the channel (membership platform...).<br /><br />
+      When a video is uploaded in this channel, the video support field will be automatically filled by this text."
+          ></my-help>
+          <my-markdown-textarea
+              id="support" formControlName="support" textareaMaxWidth="500px" markdownType="enhanced"
+              [classes]="{ 'input-error': formErrors['support'] }"
+          ></my-markdown-textarea>
+          <div *ngIf="formErrors.support" class="form-error">
+            {{ formErrors.support }}
+          </div>
+        </div>
+  
+        <div class="form-group" *ngIf="isBulkUpdateVideosDisplayed()">
+          <my-peertube-checkbox
+            inputName="bulkVideosSupportUpdate" formControlName="bulkVideosSupportUpdate"
+            i18n-labelText labelText="Overwrite support field of all videos of this channel"
+          ></my-peertube-checkbox>
+        </div>
+  
+      </div>
+    </div>
+  
+    <div class="form-row"> <!-- submit placement block -->
+      <div class="col-md-7 col-xl-5"></div>
+      <div class="col-md-5 col-xl-5 d-inline-flex">
+        <input type="submit" value="{{ getFormButtonTitle() }}" [disabled]="!form.valid">
+      </div>
+    </div>
+  </form>  
+</div>
\ No newline at end of file
similarity index 96%
rename from client/src/app/+my-library/+my-video-channels/my-video-channel-edit.component.scss
rename to client/src/app/+video-channels/video-channel-edit/video-channel-edit.component.scss
index d8bfe71b6af87e3ae330b38ec043b5d3179623c5..d010d6277e1b53bc6ca8275e5382b566ecfd75d4 100644 (file)
@@ -1,6 +1,10 @@
 @use '_variables' as *;
 @use '_mixins' as *;
 
+.margin-content {
+  padding-top: 20px;
+}
+
 label {
   font-weight: $font-regular;
   font-size: 100%;
similarity index 92%
rename from client/src/app/+my-library/+my-video-channels/my-video-channel-update.component.ts
rename to client/src/app/+video-channels/video-channel-edit/video-channel-update.component.ts
index f9521b8b51afae1b852519be77d1a9e55effc9c3..96cb4c6b627c2d01ebb1567acb2f3effc886baa9 100644 (file)
@@ -12,14 +12,14 @@ import {
 import { FormValidatorService } from '@app/shared/shared-forms'
 import { VideoChannel, VideoChannelService } from '@app/shared/shared-main'
 import { HTMLServerConfig, VideoChannelUpdate } from '@shared/models'
-import { MyVideoChannelEdit } from './my-video-channel-edit'
+import { MyVideoChannelEdit } from './video-channel-edit'
 
 @Component({
   selector: 'my-video-channel-update',
-  templateUrl: './my-video-channel-edit.component.html',
-  styleUrls: [ './my-video-channel-edit.component.scss' ]
+  templateUrl: './video-channel-edit.component.html',
+  styleUrls: [ './video-channel-edit.component.scss' ]
 })
-export class MyVideoChannelUpdateComponent extends MyVideoChannelEdit implements OnInit, OnDestroy {
+export class VideoChannelUpdateComponent extends MyVideoChannelEdit implements OnInit, OnDestroy {
   error: string
   videoChannel: VideoChannel
 
@@ -50,9 +50,9 @@ export class MyVideoChannelUpdateComponent extends MyVideoChannelEdit implements
     })
 
     this.paramsSub = this.route.params.subscribe(routeParams => {
-      const videoChannelId = routeParams['videoChannelId']
+      const videoChannelName = routeParams['videoChannelName']
 
-      this.videoChannelService.getVideoChannel(videoChannelId)
+      this.videoChannelService.getVideoChannel(videoChannelName)
         .subscribe({
           next: videoChannelToUpdate => {
             this.videoChannel = videoChannelToUpdate
@@ -95,7 +95,7 @@ export class MyVideoChannelUpdateComponent extends MyVideoChannelEdit implements
 
           this.notifier.success($localize`Video channel ${videoChannelUpdate.displayName} updated.`)
 
-          this.router.navigate([ '/my-library', 'video-channels' ])
+          this.router.navigate([ '/c', this.videoChannel.name ])
         },
 
         error: err => {
index 4ee0528736c0a203c0d36496abdcb13ddf2a174f..ae5c601a3e81b8918234aadb12ec23cfd489695b 100644 (file)
@@ -1,10 +1,21 @@
 import { NgModule } from '@angular/core'
 import { RouterModule, Routes } from '@angular/router'
+import { MyVideoChannelCreateComponent } from './video-channel-edit/video-channel-create.component'
+import { VideoChannelUpdateComponent } from './video-channel-edit/video-channel-update.component'
 import { VideoChannelPlaylistsComponent } from './video-channel-playlists/video-channel-playlists.component'
 import { VideoChannelVideosComponent } from './video-channel-videos/video-channel-videos.component'
 import { VideoChannelsComponent } from './video-channels.component'
 
 const videoChannelsRoutes: Routes = [
+  {
+    path: '@create',
+    component: MyVideoChannelCreateComponent,
+    data: {
+      meta: {
+        title: $localize`Create a new video channel`
+      }
+    }
+  },
   {
     path: ':videoChannelName',
     component: VideoChannelsComponent,
@@ -37,6 +48,16 @@ const videoChannelsRoutes: Routes = [
         }
       }
     ]
+  },
+  {
+    path: ':videoChannelName/update',
+    component: VideoChannelUpdateComponent,
+
+    data: {
+      meta: {
+        title: $localize`Update video channel`
+      }
+    }
   }
 ]
 
index 064fbb6f5b6e30a60319aa622051abe279620090..861a515747882e0c82befdf82ece2f73eba94c3b 100644 (file)
@@ -6,11 +6,11 @@
   <div class="channel-info">
 
     <ng-template #buttonsTemplate>
-      <a *ngIf="isManageable()" [routerLink]="[ '/my-library/video-channels/update', videoChannel.nameWithHost ]" class="peertube-button-link orange-button" i18n>
+      <a *ngIf="isManageable()" [routerLink]="[ 'update' ]" class="peertube-button-link orange-button" i18n>
         Manage channel
       </a>
 
-      <my-subscribe-button *ngIf="!isManageable()" #subscribeButton [videoChannels]="[videoChannel]"></my-subscribe-button>
+      <my-subscribe-button *ngIf="!isOwner()" #subscribeButton [videoChannels]="[videoChannel]"></my-subscribe-button>
 
       <button *ngIf="videoChannel.support" (click)="showSupportModal()" class="support-button peertube-button orange-button-inverted">
         <my-global-icon iconName="support" aria-hidden="true"></my-global-icon>
index 272fc41d95ef08e5155cb6d6281a7d243c012242..c8a6cdd92be4f5054281be9b41c37719d9a646c5 100644 (file)
@@ -7,7 +7,7 @@ import { AuthService, MarkdownService, Notifier, RestExtractor, ScreenService }
 import { ListOverflowItem, VideoChannel, VideoChannelService, VideoService } from '@app/shared/shared-main'
 import { SupportModalComponent } from '@app/shared/shared-support-modal'
 import { SubscribeButtonComponent } from '@app/shared/shared-user-subscription'
-import { HttpStatusCode } from '@shared/models'
+import { HttpStatusCode, UserRight } from '@shared/models'
 
 @Component({
   templateUrl: './video-channels.component.html',
@@ -93,10 +93,14 @@ export class VideoChannelsComponent implements OnInit, OnDestroy {
     return this.authService.isLoggedIn()
   }
 
+  isOwner () {
+    return this.videoChannel?.ownerAccount.userId === this.authService.getUser().id
+  }
+
   isManageable () {
     if (!this.isUserLoggedIn()) return false
 
-    return this.videoChannel?.ownerAccount.userId === this.authService.getUser().id
+    return this.isOwner() || this.authService.getUser().hasRight(UserRight.MANAGE_VIDEO_CHANNELS)
   }
 
   activateCopiedMessage () {
index 35c39cc2ec12723637f1fc3924b2e0acbcedd8c3..26f89c68a2b66cd83ce980418ee6ba1804a3f518 100644 (file)
@@ -11,6 +11,9 @@ import { VideoChannelVideosComponent } from './video-channel-videos/video-channe
 import { VideoChannelsRoutingModule } from './video-channels-routing.module'
 import { VideoChannelsComponent } from './video-channels.component'
 import { SharedActorImageModule } from '../shared/shared-actor-image/shared-actor-image.module'
+import { MyVideoChannelCreateComponent } from './video-channel-edit/video-channel-create.component'
+import { VideoChannelUpdateComponent } from './video-channel-edit/video-channel-update.component'
+import { SharedActorImageEditModule } from '@app/shared/shared-actor-image-edit'
 
 @NgModule({
   imports: [
@@ -23,13 +26,16 @@ import { SharedActorImageModule } from '../shared/shared-actor-image/shared-acto
     SharedUserSubscriptionModule,
     SharedGlobalIconModule,
     SharedSupportModal,
-    SharedActorImageModule
+    SharedActorImageModule,
+    SharedActorImageEditModule
   ],
 
   declarations: [
     VideoChannelsComponent,
     VideoChannelVideosComponent,
-    VideoChannelPlaylistsComponent
+    VideoChannelPlaylistsComponent,
+    MyVideoChannelCreateComponent,
+    VideoChannelUpdateComponent
   ],
 
   exports: [
index d1a1e6473d5b6a788913f2cd24faec0dea3d13af..b2916278752aee9095a78514f180011aadab33de 100644 (file)
@@ -24,6 +24,7 @@ import {
   asyncRetryTransactionMiddleware,
   authenticate,
   commonVideosFiltersValidator,
+  ensureUserCanManageChannel,
   optionalAuthenticate,
   paginationValidator,
   setDefaultPagination,
@@ -74,7 +75,7 @@ videoChannelRouter.post('/:nameWithHost/avatar/pick',
   authenticate,
   reqAvatarFile,
   asyncMiddleware(videoChannelsNameWithHostValidator),
-  ensureAuthUserOwnsChannelValidator,
+  ensureUserCanManageChannel,
   updateAvatarValidator,
   asyncMiddleware(updateVideoChannelAvatar)
 )
@@ -83,7 +84,7 @@ videoChannelRouter.post('/:nameWithHost/banner/pick',
   authenticate,
   reqBannerFile,
   asyncMiddleware(videoChannelsNameWithHostValidator),
-  ensureAuthUserOwnsChannelValidator,
+  ensureUserCanManageChannel,
   updateBannerValidator,
   asyncMiddleware(updateVideoChannelBanner)
 )
@@ -91,21 +92,21 @@ videoChannelRouter.post('/:nameWithHost/banner/pick',
 videoChannelRouter.delete('/:nameWithHost/avatar',
   authenticate,
   asyncMiddleware(videoChannelsNameWithHostValidator),
-  ensureAuthUserOwnsChannelValidator,
+  ensureUserCanManageChannel,
   asyncMiddleware(deleteVideoChannelAvatar)
 )
 
 videoChannelRouter.delete('/:nameWithHost/banner',
   authenticate,
   asyncMiddleware(videoChannelsNameWithHostValidator),
-  ensureAuthUserOwnsChannelValidator,
+  ensureUserCanManageChannel,
   asyncMiddleware(deleteVideoChannelBanner)
 )
 
 videoChannelRouter.put('/:nameWithHost',
   authenticate,
   asyncMiddleware(videoChannelsNameWithHostValidator),
-  ensureAuthUserOwnsChannelValidator,
+  ensureUserCanManageChannel,
   videoChannelsUpdateValidator,
   asyncRetryTransactionMiddleware(updateVideoChannel)
 )
index ea95b16c2d8550f9cb6fc30636bb3b9b2d94af4e..e864d1bc54c0784ea3cba3def79be749be165d89 100644 (file)
@@ -20,8 +20,26 @@ function ensureUserHasRight (userRight: UserRight) {
   }
 }
 
+function ensureUserCanManageChannel (req: express.Request, res: express.Response, next: express.NextFunction) {
+  const user = res.locals.oauth.token.user
+  const isUserOwner = res.locals.videoChannel.Account.userId !== user.id
+
+  if (isUserOwner && user.hasRight(UserRight.MANAGE_VIDEO_CHANNELS) === false) {
+    const message = `User ${user.username} does not have right to manage channel ${req.params.nameWithHost}.`
+    logger.info(message)
+
+    return res.fail({
+      status: HttpStatusCode.FORBIDDEN_403,
+      message
+    })
+  }
+
+  return next()
+}
+
 // ---------------------------------------------------------------------------
 
 export {
-  ensureUserHasRight
+  ensureUserHasRight,
+  ensureUserCanManageChannel
 }
index 6415ca6f2ec9b6e6f7ddd897d6e46cab62776537..b35345ec96271116ca700a2da0b7932b91a2630b 100644 (file)
@@ -41,5 +41,7 @@ export const enum UserRight {
   MANAGE_VIDEOS_REDUNDANCIES,
 
   MANAGE_VIDEO_FILES,
-  RUN_VIDEO_TRANSCODING
+  RUN_VIDEO_TRANSCODING,
+
+  MANAGE_VIDEO_CHANNELS
 }