Add ability to set a name to a channel
authorChocobozzz <me@florianbigard.com>
Fri, 17 Aug 2018 13:45:42 +0000 (15:45 +0200)
committerChocobozzz <me@florianbigard.com>
Mon, 27 Aug 2018 07:41:54 +0000 (09:41 +0200)
45 files changed:
client/src/app/+accounts/account-video-channels/account-video-channels.component.html
client/src/app/+accounts/account-video-channels/account-video-channels.component.ts
client/src/app/+my-account/my-account-video-channels/my-account-video-channel-create.component.ts
client/src/app/+my-account/my-account-video-channels/my-account-video-channel-edit.component.html
client/src/app/+my-account/my-account-video-channels/my-account-video-channel-edit.component.scss
client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.html
client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.scss
client/src/app/+video-channels/video-channels.component.html
client/src/app/shared/forms/form-validators/video-channel-validators.service.ts
client/src/app/shared/video-channel/video-channel.model.ts
client/src/app/shared/video-channel/video-channel.service.ts
client/src/app/shared/video/video.service.ts
client/src/app/signup/signup.component.html
client/src/app/signup/signup.component.scss
client/src/app/signup/signup.component.ts
client/src/app/videos/+video-watch/video-watch.component.html
client/src/sass/include/_mixins.scss
server/controllers/activitypub/client.ts
server/controllers/api/accounts.ts
server/controllers/api/video-channel.ts
server/helpers/custom-validators/accounts.ts
server/helpers/custom-validators/activitypub/actor.ts
server/helpers/custom-validators/video-channels.ts
server/lib/activitypub/url.ts
server/lib/user.ts
server/lib/video-channel.ts
server/middlewares/validators/feeds.ts
server/middlewares/validators/video-channels.ts
server/models/account/account.ts
server/models/activitypub/actor.ts
server/models/video/video-channel.ts
server/tests/api/check-params/user-subscriptions.ts
server/tests/api/check-params/video-channels.ts
server/tests/api/server/follows.ts
server/tests/api/server/handle-down.ts
server/tests/api/users/user-subscriptions.ts
server/tests/api/users/users-multiple-servers.ts
server/tests/api/videos/multiple-servers.ts
server/tests/api/videos/single-server.ts
server/tests/api/videos/video-channels.ts
server/tests/cli/update-host.ts
server/tests/real-world/populate-database.ts
server/tests/utils/videos/video-channels.ts
server/tests/utils/videos/videos.ts
shared/models/videos/channel/video-channel-create.model.ts

index bcd3beaf0a86259f18040ef6902b6d3531c2c297..114a9e517142419263da62828c31ec94e5ff049f 100644 (file)
@@ -1,6 +1,6 @@
 <div *ngIf="account" class="row">
   <a
-    *ngFor="let videoChannel of videoChannels" [routerLink]="[ '/video-channels', videoChannel.uuid ]"
+    *ngFor="let videoChannel of videoChannels" [routerLink]="[ '/video-channels', videoChannel.name ]"
     class="video-channel" i18n-title title="See this video channel"
   >
     <img [src]="videoChannel.avatarUrl" alt="Avatar" />
index ebc6711136e2bd27248471c32099d04c712a118f..44f5626bb810f3961329285190cc059fb49a2ed3 100644 (file)
@@ -2,10 +2,10 @@ import { Component, OnDestroy, OnInit } from '@angular/core'
 import { ActivatedRoute } from '@angular/router'
 import { Account } from '@app/shared/account/account.model'
 import { AccountService } from '@app/shared/account/account.service'
-import { VideoChannel } from '../../../../../shared/models/videos'
 import { VideoChannelService } from '@app/shared/video-channel/video-channel.service'
 import { flatMap, map, tap } from 'rxjs/operators'
 import { Subscription } from 'rxjs'
+import { VideoChannel } from '@app/shared/video-channel/video-channel.model'
 
 @Component({
   selector: 'my-account-video-channels',
index c0eaa476377c8d467ca941ffaf09ce014d2d14d1..79ac07c93f4470a7b880036a402b8686c2fc9510 100644 (file)
@@ -29,8 +29,13 @@ export class MyAccountVideoChannelCreateComponent extends MyAccountVideoChannelE
     super()
   }
 
+  get instanceHost () {
+    return window.location.host
+  }
+
   ngOnInit () {
     this.buildForm({
+      name: this.videoChannelValidatorsService.VIDEO_CHANNEL_NAME,
       'display-name': this.videoChannelValidatorsService.VIDEO_CHANNEL_DISPLAY_NAME,
       description: this.videoChannelValidatorsService.VIDEO_CHANNEL_DESCRIPTION,
       support: this.videoChannelValidatorsService.VIDEO_CHANNEL_SUPPORT
@@ -42,6 +47,7 @@ export class MyAccountVideoChannelCreateComponent extends MyAccountVideoChannelE
 
     const body = this.form.value
     const videoChannelCreate: VideoChannelCreate = {
+      name: body.name,
       displayName: body['display-name'],
       description: body.description || null,
       support: body.support || null
index f7ca2ec4317cd06856f6c74cfda6777dfa825b54..81fb11f45e197855eaad0bb92a3e1465b2a13f18 100644 (file)
@@ -8,6 +8,22 @@
 <div *ngIf="error" class="alert alert-danger">{{ error }}</div>
 
 <form role="form" (ngSubmit)="formValidated()" [formGroup]="form">
+  <div class="form-group" *ngIf="isCreation() === true">
+    <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'] }"
+      >
+      <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
index 86c2598b79fb0f82382da631b7c70004ade15ee0..833fda45057a70bc57f5ff0d5cc1c72f876db6c8 100644 (file)
@@ -10,10 +10,19 @@ my-actor-avatar-info {
   margin-bottom: 20px;
 }
 
+.input-group {
+  @include peertube-input-group(340px);
+}
+
 input[type=text] {
   @include peertube-input-text(340px);
 
   display: block;
+
+  &#name {
+    width: auto;
+    flex-grow: 1;
+  }
 }
 
 textarea {
index d27c3b4ec6c722c1b734bd56d7272e0e7b599cf8..548645a76bc2b34ff1e2367a4dcf0a8bcea41f36 100644 (file)
@@ -7,15 +7,14 @@
 
 <div class="video-channels">
   <div *ngFor="let videoChannel of videoChannels" class="video-channel">
-    <a [routerLink]="[ '/video-channels', videoChannel.uuid ]">
+    <a [routerLink]="[ '/video-channels', videoChannel.name ]">
       <img [src]="videoChannel.avatarUrl" alt="Avatar" />
     </a>
 
     <div class="video-channel-info">
-      <a [routerLink]="[ '/video-channels', videoChannel.uuid ]" class="video-channel-names" i18n-title title="Go to the channel">
+      <a [routerLink]="[ '/video-channels', videoChannel.name ]" class="video-channel-names" i18n-title title="Go to the channel">
         <div class="video-channel-display-name">{{ videoChannel.displayName }}</div>
-        <!-- Hide the name for now, because it's an UUID not very friendly -->
-        <!--<div class="video-channel-name">{{ videoChannel.name }}</div>-->
+        <div class="video-channel-name">{{ videoChannel.name }}</div>
       </a>
 
       <div i18n class="video-channel-followers">{{ videoChannel.followersCount }} subscribers</div>
@@ -24,7 +23,7 @@
     <div class="video-channel-buttons">
       <my-delete-button (click)="deleteVideoChannel(videoChannel)"></my-delete-button>
 
-      <my-edit-button [routerLink]="[ 'update', videoChannel.uuid ]"></my-edit-button>
+      <my-edit-button [routerLink]="[ 'update', videoChannel.name ]"></my-edit-button>
     </div>
   </div>
 </div>
index f047bb4110569f9216ae912ed9c0a3c3e7cbc25f..f8fd2684e7b74e80b10f4ab47ef663286f53eac7 100644 (file)
@@ -30,7 +30,9 @@
     a.video-channel-names {
       @include disable-default-a-behaviour;
 
+      width: fit-content;
       display: flex;
+      align-items: baseline;
       color: #000;
 
       .video-channel-display-name {
@@ -41,6 +43,7 @@
       .video-channel-name {
         font-size: 14px;
         color: #777272;
+        margin-left: 5px;
       }
     }
   }
index a52894cac2b2f01235690a9384e9ce6a9deba5a3..5a69a82a04ee69c7fcee535dc01e099172ddb58b 100644 (file)
@@ -7,6 +7,7 @@
       <div class="actor-info">
         <div class="actor-names">
           <div class="actor-display-name">{{ videoChannel.displayName }}</div>
+          <div class="actor-name">{{ videoChannel.nameWithHost }}</div>
         </div>
         <div i18n class="actor-followers">{{ videoChannel.followersCount }} subscribers</div>
 
index 28b063f89f1e22796ce2cfeb2c934576eec70307..1ce3a0dcaceab81086a22022474a8c058afd1396 100644 (file)
@@ -5,11 +5,27 @@ import { BuildFormValidator } from '@app/shared'
 
 @Injectable()
 export class VideoChannelValidatorsService {
+  readonly VIDEO_CHANNEL_NAME: BuildFormValidator
   readonly VIDEO_CHANNEL_DISPLAY_NAME: BuildFormValidator
   readonly VIDEO_CHANNEL_DESCRIPTION: BuildFormValidator
   readonly VIDEO_CHANNEL_SUPPORT: BuildFormValidator
 
   constructor (private i18n: I18n) {
+    this.VIDEO_CHANNEL_NAME = {
+      VALIDATORS: [
+        Validators.required,
+        Validators.minLength(3),
+        Validators.maxLength(20),
+        Validators.pattern(/^[a-z0-9._]+$/)
+      ],
+      MESSAGES: {
+        'required': this.i18n('Name is required.'),
+        'minlength': this.i18n('Name must be at least 3 characters long.'),
+        'maxlength': this.i18n('Name cannot be more than 20 characters long.'),
+        'pattern': this.i18n('Name should be only lowercase alphanumeric characters.')
+      }
+    }
+
     this.VIDEO_CHANNEL_DISPLAY_NAME = {
       VALIDATORS: [
         Validators.required,
index b6862b681b20968cc180cbbf1cc360b9b44d8bfa..309b614aef8cda1bf502eeb49f6ca3bdd3050da7 100644 (file)
@@ -7,6 +7,7 @@ export class VideoChannel extends Actor implements ServerVideoChannel {
   description: string
   support: string
   isLocal: boolean
+  nameWithHost: string
   ownerAccount?: Account
   ownerBy?: string
   ownerAvatarUrl?: string
@@ -18,6 +19,7 @@ export class VideoChannel extends Actor implements ServerVideoChannel {
     this.description = hash.description
     this.support = hash.support
     this.isLocal = hash.isLocal
+    this.nameWithHost = Actor.CREATE_BY_STRING(this.name, this.host)
 
     if (hash.ownerAccount) {
       this.ownerAccount = hash.ownerAccount
index 8c000665f097761d36047be9f8b3bb2bd9dbfddf..510dc9c3d31a5dd08c422dd5584c5f5448e6eb65 100644 (file)
@@ -22,8 +22,8 @@ export class VideoChannelService {
     private restExtractor: RestExtractor
   ) {}
 
-  getVideoChannel (videoChannelUUID: string) {
-    return this.authHttp.get<VideoChannel>(VideoChannelService.BASE_VIDEO_CHANNEL_URL + videoChannelUUID)
+  getVideoChannel (videoChannelName: string) {
+    return this.authHttp.get<VideoChannel>(VideoChannelService.BASE_VIDEO_CHANNEL_URL + videoChannelName)
                .pipe(
                  map(videoChannelHash => new VideoChannel(videoChannelHash)),
                  tap(videoChannel => this.videoChannelLoaded.next(videoChannel)),
index e2a62c701c56ed7a2932e58a6d33d0f4e3e4c615..e44f1ee65a2e7de096608b3f6c0f1405040bed8c 100644 (file)
@@ -150,7 +150,7 @@ export class VideoService {
     params = this.restService.addRestGetParams(params, pagination, sort)
 
     return this.authHttp
-               .get<ResultList<Video>>(VideoChannelService.BASE_VIDEO_CHANNEL_URL + videoChannel.uuid + '/videos', { params })
+               .get<ResultList<Video>>(VideoChannelService.BASE_VIDEO_CHANNEL_URL + videoChannel.name + '/videos', { params })
                .pipe(
                  switchMap(res => this.extractVideos(res)),
                  catchError(err => this.restExtractor.handleError(err))
index 565b695d9281d6f71aae5e37a55eca27b4af1911..5fd630b095949ea619867b2b3d1168bed91ee4d1 100644 (file)
   <form role="form" (ngSubmit)="signup()" [formGroup]="form">
     <div class="form-group">
       <label for="username" i18n>Username</label>
-      <input
-        type="text" id="username" i18n-placeholder placeholder="Username"
-        formControlName="username" [ngClass]="{ 'input-error': formErrors['username'] }"
-      >
+
+      <div class="input-group">
+        <input
+          type="text" id="username" i18n-placeholder placeholder="Example: neil_amstrong"
+          formControlName="username" [ngClass]="{ 'input-error': formErrors['username'] }"
+        >
+        <div class="input-group-append">
+          <span class="input-group-text">@{{ instanceHost }}</span>
+        </div>
+      </div>
+
       <div *ngIf="formErrors.username" class="form-error">
         {{ formErrors.username }}
       </div>
index e6d4842979b43d6edbea895a1f7cf5b732cae1c0..1c992faf5d22bd1d3e598804c55a8e30bcef0f14 100644 (file)
   margin: 30px 0;
 }
 
+.input-group {
+  @include peertube-input-group(340px);
+}
+
 input:not([type=submit]) {
   @include peertube-input-text(340px);
   display: block;
+
+  &#username {
+    width: auto;
+    flex-grow: 1;
+  }
 }
 
 input[type=submit] {
index 076dac4547737db563b1e46c8c1abfb372b9c870..ed68487ae10ae9aa524eece634e1fb2b6ff1d687 100644 (file)
@@ -34,6 +34,10 @@ export class SignupComponent extends FormReactive implements OnInit {
     return this.serverService.getConfig().user.videoQuota
   }
 
+  get instanceHost () {
+    return window.location.host
+  }
+
   ngOnInit () {
     this.buildForm({
       username: this.userValidatorsService.USER_USERNAME,
index 8d4a4a5ca7257a47413e7ca916badb043dae027b..c275258effc1b04785125a19b1e3c0b71e487285 100644 (file)
@@ -37,7 +37,7 @@
           </div>
 
           <div class="video-info-channel">
-            <a [routerLink]="[ '/video-channels', video.channel.uuid ]" i18n-title title="Go the channel page">
+            <a [routerLink]="[ '/video-channels', video.channel.name ]" i18n-title title="Go the channel page">
               {{ video.channel.displayName }}
 
               <img [src]="video.videoChannelAvatarUrl" alt="Video channel avatar" />
index 3d518394aaf6f53ca067b922cbfb0b43e3f6f377..b0b0f544c2f8e939e12351407c438aecf93f9fa3 100644 (file)
   border-radius: 3px;
   padding-left: 15px;
   padding-right: 15px;
+}
 
-  &::placeholder {
-    color: #585858;
+@mixin peertube-input-group($width) {
+  width: $width;
+  height: $button-height;
+  padding-top: 0;
+  padding-bottom: 0;
+
+  .input-group-text{
+    font-size: 14px;
   }
 }
 
index c90c3f931956cdfcb38623b34f67ec950f105111..54cf44419f6f0a99b90e26b2387c9593e9bf929b 100644 (file)
@@ -6,8 +6,8 @@ import { CONFIG, ROUTE_CACHE_LIFETIME } from '../../initializers'
 import { buildVideoAnnounce } from '../../lib/activitypub/send'
 import { audiencify, getAudience } from '../../lib/activitypub/audience'
 import { createActivityData } from '../../lib/activitypub/send/send-create'
-import { asyncMiddleware, executeIfActivityPub, localAccountValidator } from '../../middlewares'
-import { videoChannelsGetValidator, videosGetValidator, videosShareValidator } from '../../middlewares/validators'
+import { asyncMiddleware, executeIfActivityPub, localAccountValidator, localVideoChannelValidator } from '../../middlewares'
+import { videosGetValidator, videosShareValidator } from '../../middlewares/validators'
 import { videoCommentGetValidator } from '../../middlewares/validators/video-comments'
 import { AccountModel } from '../../models/account/account'
 import { ActorModel } from '../../models/activitypub/actor'
@@ -80,16 +80,16 @@ activityPubClientRouter.get('/videos/watch/:videoId/comments/:commentId/activity
   executeIfActivityPub(asyncMiddleware(videoCommentController))
 )
 
-activityPubClientRouter.get('/video-channels/:id',
-  executeIfActivityPub(asyncMiddleware(videoChannelsGetValidator)),
+activityPubClientRouter.get('/video-channels/:name',
+  executeIfActivityPub(asyncMiddleware(localVideoChannelValidator)),
   executeIfActivityPub(asyncMiddleware(videoChannelController))
 )
-activityPubClientRouter.get('/video-channels/:id/followers',
-  executeIfActivityPub(asyncMiddleware(videoChannelsGetValidator)),
+activityPubClientRouter.get('/video-channels/:name/followers',
+  executeIfActivityPub(asyncMiddleware(localVideoChannelValidator)),
   executeIfActivityPub(asyncMiddleware(videoChannelFollowersController))
 )
-activityPubClientRouter.get('/video-channels/:id/following',
-  executeIfActivityPub(asyncMiddleware(videoChannelsGetValidator)),
+activityPubClientRouter.get('/video-channels/:name/following',
+  executeIfActivityPub(asyncMiddleware(localVideoChannelValidator)),
   executeIfActivityPub(asyncMiddleware(videoChannelFollowingController))
 )
 
index 308970abc1270741b1369de4c931431a8815e7d1..7b7e5e740f58aaa71ccc4e34c167d56a64a03dfb 100644 (file)
@@ -78,7 +78,7 @@ async function listAccountVideos (req: express.Request, res: express.Response, n
     start: req.query.start,
     count: req.query.count,
     sort: req.query.sort,
-    includeLocalVideos: false,
+    includeLocalVideos: true,
     categoryOneOf: req.query.categoryOneOf,
     licenceOneOf: req.query.licenceOneOf,
     languageOneOf: req.query.languageOneOf,
index 6ffc09f87e29acb1b3303aab9d14c20bde6e8bd5..3f51f03f4fcb9816ffed70f8ba205a9501de9c41 100644 (file)
@@ -10,13 +10,12 @@ import {
   setDefaultPagination,
   setDefaultSort,
   videoChannelsAddValidator,
-  videoChannelsGetValidator,
   videoChannelsRemoveValidator,
   videoChannelsSortValidator,
   videoChannelsUpdateValidator
 } from '../../middlewares'
 import { VideoChannelModel } from '../../models/video/video-channel'
-import { videosSortValidator } from '../../middlewares/validators'
+import { videoChannelsNameWithHostValidator, videosSortValidator } from '../../middlewares/validators'
 import { sendUpdateActor } from '../../lib/activitypub/send'
 import { VideoChannelCreate, VideoChannelUpdate } from '../../../shared'
 import { createVideoChannel } from '../../lib/video-channel'
@@ -50,7 +49,7 @@ videoChannelRouter.post('/',
   asyncRetryTransactionMiddleware(addVideoChannel)
 )
 
-videoChannelRouter.post('/:id/avatar/pick',
+videoChannelRouter.post('/:nameWithHost/avatar/pick',
   authenticate,
   reqAvatarFile,
   // Check the rights
@@ -59,25 +58,25 @@ videoChannelRouter.post('/:id/avatar/pick',
   asyncMiddleware(updateVideoChannelAvatar)
 )
 
-videoChannelRouter.put('/:id',
+videoChannelRouter.put('/:nameWithHost',
   authenticate,
   asyncMiddleware(videoChannelsUpdateValidator),
   asyncRetryTransactionMiddleware(updateVideoChannel)
 )
 
-videoChannelRouter.delete('/:id',
+videoChannelRouter.delete('/:nameWithHost',
   authenticate,
   asyncMiddleware(videoChannelsRemoveValidator),
   asyncRetryTransactionMiddleware(removeVideoChannel)
 )
 
-videoChannelRouter.get('/:id',
-  asyncMiddleware(videoChannelsGetValidator),
+videoChannelRouter.get('/:nameWithHost',
+  asyncMiddleware(videoChannelsNameWithHostValidator),
   asyncMiddleware(getVideoChannel)
 )
 
-videoChannelRouter.get('/:id/videos',
-  asyncMiddleware(videoChannelsGetValidator),
+videoChannelRouter.get('/:nameWithHost/videos',
+  asyncMiddleware(videoChannelsNameWithHostValidator),
   paginationValidator,
   videosSortValidator,
   setDefaultSort,
@@ -215,7 +214,7 @@ async function listVideoChannelVideos (req: express.Request, res: express.Respon
     start: req.query.start,
     count: req.query.count,
     sort: req.query.sort,
-    includeLocalVideos: false,
+    includeLocalVideos: true,
     categoryOneOf: req.query.categoryOneOf,
     licenceOneOf: req.query.licenceOneOf,
     languageOneOf: req.query.languageOneOf,
index 0607d661c2c41d6d586362e7c0aecb2acd8cfa08..191de1496eec4b545e04a0438a1b85f95881b986 100644 (file)
@@ -42,7 +42,7 @@ function isAccountNameWithHostExist (nameWithDomain: string, res: Response, send
 
   let promise: Bluebird<AccountModel>
   if (!host || host === CONFIG.WEBSERVER.HOST) promise = AccountModel.loadLocalByName(accountName)
-  else promise = AccountModel.loadLocalByNameAndHost(accountName, host)
+  else promise = AccountModel.loadByNameAndHost(accountName, host)
 
   return isAccountExist(promise, res, sendNotFound)
 }
index ae5014f8fef448f0123714af7b98842263338131..c3a62c12d3389a0c9771bacaa22fb0469d2d17f0 100644 (file)
@@ -27,7 +27,7 @@ function isActorPublicKeyValid (publicKey: string) {
     validator.isLength(publicKey, CONSTRAINTS_FIELDS.ACTORS.PUBLIC_KEY)
 }
 
-const actorNameRegExp = new RegExp('[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_]+')
+const actorNameRegExp = new RegExp('^[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\\-_]+$')
 function isActorPreferredUsernameValid (preferredUsername: string) {
   return exists(preferredUsername) && validator.matches(preferredUsername, actorNameRegExp)
 }
index 32faf36f757a45f5b0b4dcbf57613f8e096ff73e..f13519c1dd0de4fe1604d482e83da2fdadda541b 100644 (file)
@@ -2,10 +2,9 @@ import * as express from 'express'
 import 'express-validator'
 import 'multer'
 import * as validator from 'validator'
-import { CONSTRAINTS_FIELDS } from '../../initializers'
+import { CONFIG, CONSTRAINTS_FIELDS } from '../../initializers'
 import { VideoChannelModel } from '../../models/video/video-channel'
 import { exists } from './misc'
-import { Response } from 'express'
 
 const VIDEO_CHANNELS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_CHANNELS
 
@@ -21,13 +20,13 @@ function isVideoChannelSupportValid (value: string) {
   return value === null || (exists(value) && validator.isLength(value, VIDEO_CHANNELS_CONSTRAINTS_FIELDS.SUPPORT))
 }
 
-async function isLocalVideoChannelNameExist (name: string, res: Response) {
-  const videoChannel = await VideoChannelModel.loadLocalByName(name)
+async function isLocalVideoChannelNameExist (name: string, res: express.Response) {
+  const videoChannel = await VideoChannelModel.loadLocalByNameAndPopulateAccount(name)
 
   return processVideoChannelExist(videoChannel, res)
 }
 
-async function isVideoChannelExist (id: string, res: express.Response) {
+async function isVideoChannelIdExist (id: string, res: express.Response) {
   let videoChannel: VideoChannelModel
   if (validator.isInt(id)) {
     videoChannel = await VideoChannelModel.loadAndPopulateAccount(+id)
@@ -38,14 +37,25 @@ async function isVideoChannelExist (id: string, res: express.Response) {
   return processVideoChannelExist(videoChannel, res)
 }
 
+async function isVideoChannelNameWithHostExist (nameWithDomain: string, res: express.Response) {
+  const [ name, host ] = nameWithDomain.split('@')
+  let videoChannel: VideoChannelModel
+
+  if (!host || host === CONFIG.WEBSERVER.HOST) videoChannel = await VideoChannelModel.loadLocalByNameAndPopulateAccount(name)
+  else videoChannel = await VideoChannelModel.loadByNameAndHostAndPopulateAccount(name, host)
+
+  return processVideoChannelExist(videoChannel, res)
+}
+
 // ---------------------------------------------------------------------------
 
 export {
+  isVideoChannelNameWithHostExist,
   isLocalVideoChannelNameExist,
   isVideoChannelDescriptionValid,
   isVideoChannelNameValid,
   isVideoChannelSupportValid,
-  isVideoChannelExist
+  isVideoChannelIdExist
 }
 
 function processVideoChannelExist (videoChannel: VideoChannelModel, res: express.Response) {
index ba3bf688d634d036f0877eab1d8c865f78da9a9e..262463310bd0ad3fa21ff9961c895140e60e7c1a 100644 (file)
@@ -13,8 +13,8 @@ function getVideoCommentActivityPubUrl (video: VideoModel, videoComment: VideoCo
   return CONFIG.WEBSERVER.URL + '/videos/watch/' + video.uuid + '/comments/' + videoComment.id
 }
 
-function getVideoChannelActivityPubUrl (videoChannelUUID: string) {
-  return CONFIG.WEBSERVER.URL + '/video-channels/' + videoChannelUUID
+function getVideoChannelActivityPubUrl (videoChannelName: string) {
+  return CONFIG.WEBSERVER.URL + '/video-channels/' + videoChannelName
 }
 
 function getAccountActivityPubUrl (accountName: string) {
index e7a45f5aa72b4c96bcdeb6880facb43eb02c1dbb..db29469eb39942df22702aa1af275751f79537c0 100644 (file)
@@ -1,4 +1,5 @@
 import * as Sequelize from 'sequelize'
+import * as uuidv4 from 'uuid/v4'
 import { ActivityPubActorType } from '../../shared/models/activitypub'
 import { sequelizeTypescript, SERVER_ACTOR_NAME } from '../initializers'
 import { AccountModel } from '../models/account/account'
@@ -7,6 +8,7 @@ import { buildActorInstance, getAccountActivityPubUrl, setAsyncActorKeys } from
 import { createVideoChannel } from './video-channel'
 import { VideoChannelModel } from '../models/video/video-channel'
 import { FilteredModelAttributes } from 'sequelize-typescript/lib/models/Model'
+import { ActorModel } from '../models/activitypub/actor'
 
 async function createUserAccountAndChannel (userToCreate: UserModel, validateUser = true) {
   const { user, account, videoChannel } = await sequelizeTypescript.transaction(async t => {
@@ -19,8 +21,15 @@ async function createUserAccountAndChannel (userToCreate: UserModel, validateUse
     const accountCreated = await createLocalAccountWithoutKeys(userToCreate.username, userToCreate.id, null, t)
     userCreated.Account = accountCreated
 
-    const videoChannelDisplayName = `Default ${userCreated.username} channel`
+    let channelName = userCreated.username + '_channel'
+
+    // Conflict, generate uuid instead
+    const actor = await ActorModel.loadLocalByName(channelName)
+    if (actor) channelName = uuidv4()
+
+    const videoChannelDisplayName = `Main ${userCreated.username} channel`
     const videoChannelInfo = {
+      name: channelName,
       displayName: videoChannelDisplayName
     }
     const videoChannel = await createVideoChannel(videoChannelInfo, accountCreated, t)
index 600316cda66019008e211cf4e8e41551901ae37c..0fe95ca0986d41fc863ea6afb2f8f60daee37751 100644 (file)
@@ -7,9 +7,8 @@ import { buildActorInstance, getVideoChannelActivityPubUrl } from './activitypub
 
 async function createVideoChannel (videoChannelInfo: VideoChannelCreate, account: AccountModel, t: Sequelize.Transaction) {
   const uuid = uuidv4()
-  const url = getVideoChannelActivityPubUrl(uuid)
-  // We use the name as uuid
-  const actorInstance = buildActorInstance('Group', url, uuid, uuid)
+  const url = getVideoChannelActivityPubUrl(videoChannelInfo.name)
+  const actorInstance = buildActorInstance('Group', url, videoChannelInfo.name, uuid)
 
   const actorInstanceCreated = await actorInstance.save({ transaction: t })
 
index 3c8532bd93355d6e7910959518cfbf94ce867820..c1054ad9b28b013fe1dba483712aca87cad3478b 100644 (file)
@@ -1,13 +1,13 @@
 import * as express from 'express'
 import { param, query } from 'express-validator/check'
-import { isAccountIdExist, isAccountNameValid } from '../../helpers/custom-validators/accounts'
-import { join } from 'path'
+import { isAccountIdExist, isAccountNameValid, isAccountNameWithHostExist } from '../../helpers/custom-validators/accounts'
 import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc'
 import { logger } from '../../helpers/logger'
 import { areValidationErrors } from './utils'
 import { isValidRSSFeed } from '../../helpers/custom-validators/feeds'
-import { isVideoChannelExist } from '../../helpers/custom-validators/video-channels'
+import { isVideoChannelIdExist, isVideoChannelNameWithHostExist } from '../../helpers/custom-validators/video-channels'
 import { isVideoExist } from '../../helpers/custom-validators/videos'
+import { isActorPreferredUsernameValid } from '../../helpers/custom-validators/activitypub/actor'
 
 const videoFeedsValidator = [
   param('format').optional().custom(isValidRSSFeed).withMessage('Should have a valid format (rss, atom, json)'),
@@ -15,6 +15,7 @@ const videoFeedsValidator = [
   query('accountId').optional().custom(isIdOrUUIDValid),
   query('accountName').optional().custom(isAccountNameValid),
   query('videoChannelId').optional().custom(isIdOrUUIDValid),
+  query('videoChannelName').optional().custom(isActorPreferredUsernameValid),
 
   async (req: express.Request, res: express.Response, next: express.NextFunction) => {
     logger.debug('Checking feeds parameters', { parameters: req.query })
@@ -22,7 +23,9 @@ const videoFeedsValidator = [
     if (areValidationErrors(req, res)) return
 
     if (req.query.accountId && !await isAccountIdExist(req.query.accountId, res)) return
-    if (req.query.videoChannelId && !await isVideoChannelExist(req.query.videoChannelId, res)) return
+    if (req.query.videoChannelName && !await isVideoChannelIdExist(req.query.videoChannelName, res)) return
+    if (req.query.accountName && !await isAccountNameWithHostExist(req.query.accountName, res)) return
+    if (req.query.videoChannelName && !await isVideoChannelNameWithHostExist(req.query.videoChannelName, res)) return
 
     return next()
   }
index d354c7e059669e945c08dcf38d53a449ab524283..79587b0284aa4fef262c4036c68050d2ebf28856 100644 (file)
@@ -2,18 +2,18 @@ import * as express from 'express'
 import { body, param } from 'express-validator/check'
 import { UserRight } from '../../../shared'
 import { isAccountNameWithHostExist } from '../../helpers/custom-validators/accounts'
-import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc'
 import {
   isLocalVideoChannelNameExist,
   isVideoChannelDescriptionValid,
-  isVideoChannelExist,
   isVideoChannelNameValid,
+  isVideoChannelNameWithHostExist,
   isVideoChannelSupportValid
 } from '../../helpers/custom-validators/video-channels'
 import { logger } from '../../helpers/logger'
 import { UserModel } from '../../models/account/user'
 import { VideoChannelModel } from '../../models/video/video-channel'
 import { areValidationErrors } from './utils'
+import { isActorPreferredUsernameValid } from '../../helpers/custom-validators/activitypub/actor'
 
 const listVideoAccountChannelsValidator = [
   param('accountName').exists().withMessage('Should have a valid account name'),
@@ -29,6 +29,7 @@ const listVideoAccountChannelsValidator = [
 ]
 
 const videoChannelsAddValidator = [
+  body('name').custom(isActorPreferredUsernameValid).withMessage('Should have a valid channel name'),
   body('displayName').custom(isVideoChannelNameValid).withMessage('Should have a valid display name'),
   body('description').optional().custom(isVideoChannelDescriptionValid).withMessage('Should have a valid description'),
   body('support').optional().custom(isVideoChannelSupportValid).withMessage('Should have a valid support text'),
@@ -43,7 +44,7 @@ const videoChannelsAddValidator = [
 ]
 
 const videoChannelsUpdateValidator = [
-  param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
+  param('nameWithHost').exists().withMessage('Should have an video channel name with host'),
   body('displayName').optional().custom(isVideoChannelNameValid).withMessage('Should have a valid display name'),
   body('description').optional().custom(isVideoChannelDescriptionValid).withMessage('Should have a valid description'),
   body('support').optional().custom(isVideoChannelSupportValid).withMessage('Should have a valid support text'),
@@ -52,7 +53,7 @@ const videoChannelsUpdateValidator = [
     logger.debug('Checking videoChannelsUpdate parameters', { parameters: req.body })
 
     if (areValidationErrors(req, res)) return
-    if (!await isVideoChannelExist(req.params.id, res)) return
+    if (!await isVideoChannelNameWithHostExist(req.params.nameWithHost, res)) return
 
     // We need to make additional checks
     if (res.locals.videoChannel.Actor.isOwned() === false) {
@@ -72,13 +73,13 @@ const videoChannelsUpdateValidator = [
 ]
 
 const videoChannelsRemoveValidator = [
-  param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
+  param('nameWithHost').exists().withMessage('Should have an video channel name with host'),
 
   async (req: express.Request, res: express.Response, next: express.NextFunction) => {
     logger.debug('Checking videoChannelsRemove parameters', { parameters: req.params })
 
     if (areValidationErrors(req, res)) return
-    if (!await isVideoChannelExist(req.params.id, res)) return
+    if (!await isVideoChannelNameWithHostExist(req.params.nameWithHost, res)) return
 
     if (!checkUserCanDeleteVideoChannel(res.locals.oauth.token.User, res.locals.videoChannel, res)) return
     if (!await checkVideoChannelIsNotTheLastOne(res)) return
@@ -87,15 +88,15 @@ const videoChannelsRemoveValidator = [
   }
 ]
 
-const videoChannelsGetValidator = [
-  param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
+const videoChannelsNameWithHostValidator = [
+  param('nameWithHost').exists().withMessage('Should have an video channel name with host'),
 
   async (req: express.Request, res: express.Response, next: express.NextFunction) => {
-    logger.debug('Checking videoChannelsGet parameters', { parameters: req.params })
+    logger.debug('Checking videoChannelsNameWithHostValidator parameters', { parameters: req.params })
 
     if (areValidationErrors(req, res)) return
 
-    if (!await isVideoChannelExist(req.params.id, res)) return
+    if (!await isVideoChannelNameWithHostExist(req.params.nameWithHost, res)) return
 
     return next()
   }
@@ -121,7 +122,7 @@ export {
   videoChannelsAddValidator,
   videoChannelsUpdateValidator,
   videoChannelsRemoveValidator,
-  videoChannelsGetValidator,
+  videoChannelsNameWithHostValidator,
   localVideoChannelValidator
 }
 
index 66f5dcf2ef3d22d269769bc008cde0a782a1a4e5..07539a04eb561e640291dfa971ee862a8ecc8eed 100644 (file)
@@ -194,7 +194,7 @@ export class AccountModel extends Model<AccountModel> {
     return AccountModel.findOne(query)
   }
 
-  static loadLocalByNameAndHost (name: string, host: string) {
+  static loadByNameAndHost (name: string, host: string) {
     const query = {
       include: [
         {
index 35d7c35e8635bcba7c3c1e3f20b8c05f4cbe5303..2abf4071373ff4504b993fc214a181018f3384c6 100644 (file)
@@ -260,12 +260,13 @@ export class ActorModel extends Model<ActorModel> {
     return ActorModel.scope(ScopeNames.FULL).findAll(query)
   }
 
-  static loadLocalByName (preferredUsername: string) {
+  static loadLocalByName (preferredUsername: string, transaction?: Sequelize.Transaction) {
     const query = {
       where: {
         preferredUsername,
         serverId: null
-      }
+      },
+      transaction
     }
 
     return ActorModel.scope(ScopeNames.FULL).findOne(query)
index 0273fab13c2958856b6eb5f3718444df2881f83e..9f80e0b8daab4b8e6629736a7b307f159bf48e37 100644 (file)
@@ -29,6 +29,7 @@ import { getSort, throwIfNotValid } from '../utils'
 import { VideoModel } from './video'
 import { CONSTRAINTS_FIELDS } from '../../initializers'
 import { AvatarModel } from '../avatar/avatar'
+import { ServerModel } from '../server/server'
 
 enum ScopeNames {
   WITH_ACCOUNT = 'WITH_ACCOUNT',
@@ -206,7 +207,7 @@ export class VideoChannelModel extends Model<VideoChannelModel> {
   }
 
   static loadByIdAndAccount (id: number, accountId: number) {
-    const options = {
+    const query = {
       where: {
         id,
         accountId
@@ -215,7 +216,7 @@ export class VideoChannelModel extends Model<VideoChannelModel> {
 
     return VideoChannelModel
       .scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT ])
-      .findOne(options)
+      .findOne(query)
   }
 
   static loadAndPopulateAccount (id: number) {
@@ -225,7 +226,7 @@ export class VideoChannelModel extends Model<VideoChannelModel> {
   }
 
   static loadByUUIDAndPopulateAccount (uuid: string) {
-    const options = {
+    const query = {
       include: [
         {
           model: ActorModel,
@@ -239,36 +240,63 @@ export class VideoChannelModel extends Model<VideoChannelModel> {
 
     return VideoChannelModel
       .scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT ])
-      .findOne(options)
+      .findOne(query)
   }
 
-  static loadAndPopulateAccountAndVideos (id: number) {
-    const options = {
+  static loadLocalByNameAndPopulateAccount (name: string) {
+    const query = {
       include: [
-        VideoModel
+        {
+          model: ActorModel,
+          required: true,
+          where: {
+            preferredUsername: name,
+            serverId: null
+          }
+        }
       ]
     }
 
     return VideoChannelModel
-      .scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT, ScopeNames.WITH_VIDEOS ])
-      .findById(id, options)
+      .scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT ])
+      .findOne(query)
   }
 
-  static loadLocalByName (name: string) {
+  static loadByNameAndHostAndPopulateAccount (name: string, host: string) {
     const query = {
       include: [
         {
           model: ActorModel,
           required: true,
           where: {
-            preferredUsername: name,
-            serverId: null
-          }
+            preferredUsername: name
+          },
+          include: [
+            {
+              model: ServerModel,
+              required: true,
+              where: { host }
+            }
+          ]
         }
       ]
     }
 
-    return VideoChannelModel.findOne(query)
+    return VideoChannelModel
+      .scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT ])
+      .findOne(query)
+  }
+
+  static loadAndPopulateAccountAndVideos (id: number) {
+    const options = {
+      include: [
+        VideoModel
+      ]
+    }
+
+    return VideoChannelModel
+      .scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT, ScopeNames.WITH_VIDEOS ])
+      .findById(id, options)
   }
 
   toFormattedJSON (): VideoChannel {
index 9f7d15b27aa65c5af8b79a9a95a159201f77cb31..628a744760bb1f93e6f6dbccda6d240685aeb3bd 100644 (file)
@@ -5,7 +5,6 @@ import 'mocha'
 import {
   createUser,
   flushTests,
-  getMyUserInformation,
   killallServers,
   makeDeleteRequest,
   makeGetRequest,
@@ -21,7 +20,6 @@ describe('Test user subscriptions API validators', function () {
   const path = '/api/v1/users/me/subscriptions'
   let server: ServerInfo
   let userAccessToken = ''
-  let userChannelUUID: string
 
   // ---------------------------------------------------------------
 
@@ -40,11 +38,6 @@ describe('Test user subscriptions API validators', function () {
     }
     await createUser(server.url, server.accessToken, user.username, user.password)
     userAccessToken = await userLogin(server, user)
-
-    {
-      const res = await getMyUserInformation(server.url, server.accessToken)
-      userChannelUUID = res.body.videoChannels[ 0 ].uuid
-    }
   })
 
   describe('When listing my subscriptions', function () {
@@ -69,7 +62,7 @@ describe('Test user subscriptions API validators', function () {
     })
 
     it('Should success with the correct parameters', async function () {
-      await await makeGetRequest({
+      await makeGetRequest({
         url: server.url,
         path,
         token: userAccessToken,
@@ -102,7 +95,7 @@ describe('Test user subscriptions API validators', function () {
     })
 
     it('Should success with the correct parameters', async function () {
-      await await makeGetRequest({
+      await makeGetRequest({
         url: server.url,
         path,
         token: userAccessToken,
@@ -116,7 +109,7 @@ describe('Test user subscriptions API validators', function () {
       await makePostBodyRequest({
         url: server.url,
         path,
-        fields: { uri: userChannelUUID + '@localhost:9001' },
+        fields: { uri: 'user1_channel@localhost:9001' },
         statusCodeExpected: 401
       })
     })
@@ -152,7 +145,7 @@ describe('Test user subscriptions API validators', function () {
         url: server.url,
         path,
         token: server.accessToken,
-        fields: { uri: userChannelUUID + '@localhost:9001' },
+        fields: { uri: 'user1_channel@localhost:9001' },
         statusCodeExpected: 204
       })
     })
@@ -162,7 +155,7 @@ describe('Test user subscriptions API validators', function () {
     it('Should fail with a non authenticated user', async function () {
       await makeDeleteRequest({
         url: server.url,
-        path: path + '/' + userChannelUUID + '@localhost:9001',
+        path: path + '/user1_channel@localhost:9001',
         statusCodeExpected: 401
       })
     })
@@ -202,7 +195,7 @@ describe('Test user subscriptions API validators', function () {
     it('Should success with the correct parameters', async function () {
       await makeDeleteRequest({
         url: server.url,
-        path: path + '/' + userChannelUUID + '@localhost:9001',
+        path: path + '/user1_channel@localhost:9001',
         token: server.accessToken,
         statusCodeExpected: 204
       })
index 0980de73b8d972f5b3b67c7294fc95999504ea90..bcf4b7473e319857d1d0be42f752e1a47063c1b6 100644 (file)
@@ -31,7 +31,6 @@ describe('Test video channels API validator', function () {
   const videoChannelPath = '/api/v1/video-channels'
   let server: ServerInfo
   let accessTokenUser: string
-  let videoChannelUUID: string
 
   // ---------------------------------------------------------------
 
@@ -53,12 +52,6 @@ describe('Test video channels API validator', function () {
       await createUser(server.url, server.accessToken, user.username, user.password)
       accessTokenUser = await userLogin(server, user)
     }
-
-    {
-      const res = await getMyUserInformation(server.url, server.accessToken)
-      const user: User = res.body
-      videoChannelUUID = user.videoChannels[0].uuid
-    }
   })
 
   describe('When listing a video channels', function () {
@@ -83,6 +76,7 @@ describe('Test video channels API validator', function () {
 
   describe('When adding a video channel', function () {
     const baseCorrectParams = {
+      name: 'super_channel',
       displayName: 'hello',
       description: 'super description',
       support: 'super support text'
@@ -103,6 +97,16 @@ describe('Test video channels API validator', function () {
       await makePostBodyRequest({ url: server.url, path: videoChannelPath, token: server.accessToken, fields })
     })
 
+    it('Should fail without a name', async function () {
+      const fields = omit(baseCorrectParams, 'name')
+      await makePostBodyRequest({ url: server.url, path: videoChannelPath, token: server.accessToken, fields })
+    })
+
+    it('Should fail with a bad name', async function () {
+      const fields = immutableAssign(baseCorrectParams, { name: 'super name' })
+      await makePostBodyRequest({ url: server.url, path: videoChannelPath, token: server.accessToken, fields })
+    })
+
     it('Should fail without a name', async function () {
       const fields = omit(baseCorrectParams, 'displayName')
       await makePostBodyRequest({ url: server.url, path: videoChannelPath, token: server.accessToken, fields })
@@ -142,7 +146,7 @@ describe('Test video channels API validator', function () {
     let path: string
 
     before(async function () {
-      path = videoChannelPath + '/' + videoChannelUUID
+      path = videoChannelPath + '/super_channel'
     })
 
     it('Should fail with a non authenticated user', async function () {
@@ -195,7 +199,7 @@ describe('Test video channels API validator', function () {
     let path: string
 
     before(async function () {
-      path = videoChannelPath + '/' + videoChannelUUID
+      path = videoChannelPath + '/super_channel'
     })
 
     it('Should fail with an incorrect input file', async function () {
@@ -255,18 +259,10 @@ describe('Test video channels API validator', function () {
       expect(res.body.data).to.be.an('array')
     })
 
-    it('Should fail without a correct uuid', async function () {
-      await makeGetRequest({
-        url: server.url,
-        path: videoChannelPath + '/coucou',
-        statusCodeExpected: 400
-      })
-    })
-
     it('Should return 404 with an incorrect video channel', async function () {
       await makeGetRequest({
         url: server.url,
-        path: videoChannelPath + '/4da6fde3-88f7-4d16-b119-108df5630b06',
+        path: videoChannelPath + '/super_channel2',
         statusCodeExpected: 404
       })
     })
@@ -274,7 +270,7 @@ describe('Test video channels API validator', function () {
     it('Should succeed with the correct parameters', async function () {
       await makeGetRequest({
         url: server.url,
-        path: videoChannelPath + '/' + videoChannelUUID,
+        path: videoChannelPath + '/super_channel',
         statusCodeExpected: 200
       })
     })
@@ -282,26 +278,23 @@ describe('Test video channels API validator', function () {
 
   describe('When deleting a video channel', function () {
     it('Should fail with a non authenticated user', async function () {
-      await deleteVideoChannel(server.url, 'coucou', videoChannelUUID, 401)
+      await deleteVideoChannel(server.url, 'coucou', 'super_channel', 401)
     })
 
     it('Should fail with another authenticated user', async function () {
-      await deleteVideoChannel(server.url, accessTokenUser, videoChannelUUID, 403)
+      await deleteVideoChannel(server.url, accessTokenUser, 'super_channel', 403)
     })
 
     it('Should fail with an unknown video channel id', async function () {
-      await deleteVideoChannel(server.url, server.accessToken,454554, 404)
+      await deleteVideoChannel(server.url, server.accessToken,'super_channel2', 404)
     })
 
     it('Should succeed with the correct parameters', async function () {
-      await deleteVideoChannel(server.url, server.accessToken, videoChannelUUID)
+      await deleteVideoChannel(server.url, server.accessToken, 'super_channel')
     })
 
     it('Should fail to delete the last user video channel', async function () {
-      const res = await getVideoChannelsList(server.url, 0, 1)
-      const lastVideoChannelUUID = res.body.data[0].uuid
-
-      await deleteVideoChannel(server.url, server.accessToken, lastVideoChannelUUID, 409)
+      await deleteVideoChannel(server.url, server.accessToken, 'root_channel', 409)
     })
   })
 
index 1cad8998cf5cdf9ede92df030d6264d482aa687e..243fcd4e702136e99427b8479674e1af30659b47 100644 (file)
@@ -311,7 +311,7 @@ describe('Test follows', function () {
         likes: 1,
         dislikes: 1,
         channel: {
-          name: 'Default root channel',
+          name: 'Main root channel',
           description: '',
           isLocal
         },
index 18a0d9ce3187d47d8bce71725adb221762de6841..df35b36ebb491793720ed03e2b879b372c495e6c 100644 (file)
@@ -71,7 +71,7 @@ describe('Test handle downs', function () {
     privacy: VideoPrivacy.PUBLIC,
     commentsEnabled: true,
     channel: {
-      name: 'Default root channel',
+      name: 'Main root channel',
       description: '',
       isLocal: false
     },
index 2ba6cdfafee443daa7c4c3124a3dbf1fd2fa1bcd..ba59a9a608bd41906098af2c7ce5ec39202f354a 100644 (file)
@@ -3,7 +3,7 @@
 import * as chai from 'chai'
 import 'mocha'
 import { createUser, doubleFollow, flushAndRunMultipleServers, follow, getVideosList, unfollow, userLogin } from '../../utils'
-import { getMyUserInformation, killallServers, ServerInfo, uploadVideo } from '../../utils/index'
+import { killallServers, ServerInfo, uploadVideo } from '../../utils/index'
 import { setAccessTokensToServers } from '../../utils/users/login'
 import { Video, VideoChannel } from '../../../../shared/models/videos'
 import { waitJobs } from '../../utils/server/jobs'
@@ -18,8 +18,7 @@ const expect = chai.expect
 
 describe('Test users subscriptions', function () {
   let servers: ServerInfo[] = []
-  const users: { accessToken: string, videoChannelName: string }[] = []
-  let rootChannelNameServer1: string
+  const users: { accessToken: string }[] = []
 
   before(async function () {
     this.timeout(120000)
@@ -32,19 +31,13 @@ describe('Test users subscriptions', function () {
     // Server 1 and server 2 follow each other
     await doubleFollow(servers[0], servers[1])
 
-    const res = await getMyUserInformation(servers[0].url, servers[0].accessToken)
-    rootChannelNameServer1 = res.body.videoChannels[0].name
-
     {
       for (const server of servers) {
         const user = { username: 'user' + server.serverNumber, password: 'password' }
         await createUser(server.url, server.accessToken, user.username, user.password)
 
         const accessToken = await userLogin(server, user)
-        const res = await getMyUserInformation(server.url, accessToken)
-        const videoChannels: VideoChannel[] = res.body.videoChannels
-
-        users.push({ accessToken, videoChannelName: videoChannels[0].name })
+        users.push({ accessToken })
 
         const videoName1 = 'video 1-' + server.serverNumber
         await uploadVideo(server.url, accessToken, { name: videoName1 })
@@ -64,10 +57,10 @@ describe('Test users subscriptions', function () {
   })
 
   it('User of server 1 should follow user of server 3 and root of server 1', async function () {
-    this.timeout(30000)
+    this.timeout(60000)
 
-    await addUserSubscription(servers[0].url, users[0].accessToken, users[2].videoChannelName + '@localhost:9003')
-    await addUserSubscription(servers[0].url, users[0].accessToken, rootChannelNameServer1 + '@localhost:9001')
+    await addUserSubscription(servers[0].url, users[0].accessToken, 'user3_channel@localhost:9003')
+    await addUserSubscription(servers[0].url, users[0].accessToken, 'root_channel@localhost:9001')
 
     await waitJobs(servers)
 
@@ -103,8 +96,8 @@ describe('Test users subscriptions', function () {
       expect(subscriptions).to.be.an('array')
       expect(subscriptions).to.have.lengthOf(2)
 
-      expect(subscriptions[0].name).to.equal(users[2].videoChannelName)
-      expect(subscriptions[1].name).to.equal(rootChannelNameServer1)
+      expect(subscriptions[0].name).to.equal('user3_channel')
+      expect(subscriptions[1].name).to.equal('root_channel')
     }
   })
 
@@ -131,7 +124,7 @@ describe('Test users subscriptions', function () {
   })
 
   it('Should upload a video by root on server 1 and see it in the subscription videos', async function () {
-    this.timeout(30000)
+    this.timeout(60000)
 
     const videoName = 'video server 1 added after follow'
     await uploadVideo(servers[0].url, servers[0].accessToken, { name: videoName })
@@ -172,7 +165,7 @@ describe('Test users subscriptions', function () {
   })
 
   it('Should have server 1 follow server 3 and display server 3 videos', async function () {
-    this.timeout(30000)
+    this.timeout(60000)
 
     await follow(servers[0].url, [ servers[2].url ], servers[0].accessToken)
 
@@ -190,7 +183,7 @@ describe('Test users subscriptions', function () {
   })
 
   it('Should remove follow server 1 -> server 3 and hide server 3 videos', async function () {
-    this.timeout(30000)
+    this.timeout(60000)
 
     await unfollow(servers[0].url, servers[0].accessToken, servers[2])
 
@@ -230,7 +223,7 @@ describe('Test users subscriptions', function () {
   })
 
   it('Should remove user of server 3 subscription', async function () {
-    await removeUserSubscription(servers[0].url, users[0].accessToken, users[2].videoChannelName + '@localhost:9003')
+    await removeUserSubscription(servers[0].url, users[0].accessToken, 'user3_channel@localhost:9003')
 
     await waitJobs(servers)
   })
@@ -249,7 +242,7 @@ describe('Test users subscriptions', function () {
   })
 
   it('Should remove the root subscription and not display the videos anymore', async function () {
-    await removeUserSubscription(servers[0].url, users[0].accessToken, rootChannelNameServer1 + '@localhost:9001')
+    await removeUserSubscription(servers[0].url, users[0].accessToken, 'root_channel@localhost:9001')
 
     await waitJobs(servers)
 
@@ -275,9 +268,9 @@ describe('Test users subscriptions', function () {
   })
 
   it('Should follow user of server 3 again', async function () {
-    this.timeout(30000)
+    this.timeout(60000)
 
-    await addUserSubscription(servers[0].url, users[0].accessToken, users[2].videoChannelName + '@localhost:9003')
+    await addUserSubscription(servers[0].url, users[0].accessToken, 'user3_channel@localhost:9003')
 
     await waitJobs(servers)
 
index 81489021bfbe44560d463bdefca036af5539855d..575e04546d776d21de36c5dbf02ab58c16a845bd 100644 (file)
@@ -12,10 +12,9 @@ import {
   getVideoChannelsList,
   removeUser,
   updateMyUser,
-  userLogin,
-  wait
+  userLogin
 } from '../../utils'
-import { flushTests, getMyUserInformation, killallServers, ServerInfo, testImage, updateMyAvatar, uploadVideo } from '../../utils/index'
+import { getMyUserInformation, killallServers, ServerInfo, testImage, updateMyAvatar, uploadVideo } from '../../utils/index'
 import { checkActorFilesWereRemoved, getAccount, getAccountsList } from '../../utils/users/accounts'
 import { setAccessTokensToServers } from '../../utils/users/login'
 import { User } from '../../../../shared/models/users'
@@ -172,7 +171,7 @@ describe('Test users with multiple servers', function () {
 
       const resVideoChannels = await getVideoChannelsList(server.url, 0, 10)
       const videoChannelDeleted = resVideoChannels.body.data.find(a => {
-        return a.displayName === 'Default user1 channel' && a.host === 'localhost:9001'
+        return a.displayName === 'Main user1 channel' && a.host === 'localhost:9001'
       }) as VideoChannel
       expect(videoChannelDeleted).not.to.be.undefined
     }
@@ -189,7 +188,7 @@ describe('Test users with multiple servers', function () {
 
       const resVideoChannels = await getVideoChannelsList(server.url, 0, 10)
       const videoChannelDeleted = resVideoChannels.body.data.find(a => {
-        return a.name === 'Default user1 channel' && a.host === 'localhost:9001'
+        return a.name === 'Main user1 channel' && a.host === 'localhost:9001'
       }) as VideoChannel
       expect(videoChannelDeleted).to.be.undefined
     }
index cab096a1250e2136e17cae987aad686b2d105431..3c383933864c2436195fd7bb07e11685dd93cca4 100644 (file)
@@ -6,7 +6,6 @@ import { join } from 'path'
 import * as request from 'supertest'
 import { VideoPrivacy } from '../../../../shared/models/videos'
 import { VideoComment, VideoCommentThreadTree } from '../../../../shared/models/videos/video-comment.model'
-
 import {
   addVideoChannel,
   checkVideoFilesWereRemoved,
@@ -60,6 +59,7 @@ describe('Test multiple servers', function () {
 
     {
       const videoChannel = {
+        name: 'super_channel_name',
         displayName: 'my channel',
         description: 'super channel'
       }
@@ -201,7 +201,7 @@ describe('Test multiple servers', function () {
           tags: [ 'tag1p2', 'tag2p2', 'tag3p2' ],
           privacy: VideoPrivacy.PUBLIC,
           channel: {
-            name: 'Default user1 channel',
+            name: 'Main user1 channel',
             description: 'super channel',
             isLocal
           },
@@ -307,7 +307,7 @@ describe('Test multiple servers', function () {
           tags: [ 'tag1p3' ],
           privacy: VideoPrivacy.PUBLIC,
           channel: {
-            name: 'Default root channel',
+            name: 'Main root channel',
             description: '',
             isLocal
           },
@@ -339,7 +339,7 @@ describe('Test multiple servers', function () {
           tags: [ 'tag2p3', 'tag3p3', 'tag4p3' ],
           privacy: VideoPrivacy.PUBLIC,
           channel: {
-            name: 'Default root channel',
+            name: 'Main root channel',
             description: '',
             isLocal
           },
@@ -647,7 +647,7 @@ describe('Test multiple servers', function () {
           tags: [ 'tag_up_1', 'tag_up_2' ],
           privacy: VideoPrivacy.PUBLIC,
           channel: {
-            name: 'Default root channel',
+            name: 'Main root channel',
             description: '',
             isLocal
           },
@@ -967,7 +967,7 @@ describe('Test multiple servers', function () {
           tags: [ ],
           privacy: VideoPrivacy.PUBLIC,
           channel: {
-            name: 'Default root channel',
+            name: 'Main root channel',
             description: '',
             isLocal
           },
index ba4920d1b36fef518c1c3c0b0ef5063df06c5971..12181ad671033992836844b4c9c1580e3f6dbc98 100644 (file)
@@ -56,7 +56,7 @@ describe('Test a single server', function () {
     privacy: VideoPrivacy.PUBLIC,
     commentsEnabled: true,
     channel: {
-      name: 'Default root channel',
+      name: 'Main root channel',
       description: '',
       isLocal: true
     },
@@ -87,7 +87,7 @@ describe('Test a single server', function () {
     duration: 5,
     commentsEnabled: false,
     channel: {
-      name: 'Default root channel',
+      name: 'Main root channel',
       description: '',
       isLocal: true
     },
index e4e3ce9d9e78a2498b8d197e81eb4118e5fa6fd1..8138c65d60528015c2b274641232f9e958ed71ab 100644 (file)
@@ -4,12 +4,13 @@ import * as chai from 'chai'
 import 'mocha'
 import { User, Video } from '../../../../shared/index'
 import {
+  createUser,
   doubleFollow,
   flushAndRunMultipleServers,
-  getVideoChannelVideos, testImage,
+  getVideoChannelVideos, serverLogin, testImage,
   updateVideo,
   updateVideoChannelAvatar,
-  uploadVideo, wait
+  uploadVideo, wait, userLogin
 } from '../../utils'
 import {
   addVideoChannel,
@@ -33,9 +34,7 @@ describe('Test video channels', function () {
   let userInfo: User
   let accountUUID: string
   let firstVideoChannelId: number
-  let firstVideoChannelUUID: string
   let secondVideoChannelId: number
-  let secondVideoChannelUUID: string
   let videoUUID: string
 
   before(async function () {
@@ -54,7 +53,6 @@ describe('Test video channels', function () {
       accountUUID = user.account.uuid
 
       firstVideoChannelId = user.videoChannels[0].id
-      firstVideoChannelUUID = user.videoChannels[0].uuid
     }
 
     await waitJobs(servers)
@@ -73,13 +71,13 @@ describe('Test video channels', function () {
 
     {
       const videoChannel = {
+        name: 'second_video_channel',
         displayName: 'second video channel',
         description: 'super video channel description',
         support: 'super video channel support text'
       }
       const res = await addVideoChannel(servers[ 0 ].url, servers[ 0 ].accessToken, videoChannel)
       secondVideoChannelId = res.body.videoChannel.id
-      secondVideoChannelUUID = res.body.videoChannel.uuid
     }
 
     // The channel is 1 is propagated to servers 2
@@ -99,7 +97,10 @@ describe('Test video channels', function () {
     expect(userInfo.videoChannels).to.have.lengthOf(2)
 
     const videoChannels = userInfo.videoChannels
-    expect(videoChannels[0].displayName).to.equal('Default root channel')
+    expect(videoChannels[0].name).to.equal('root_channel')
+    expect(videoChannels[0].displayName).to.equal('Main root channel')
+
+    expect(videoChannels[1].name).to.equal('second_video_channel')
     expect(videoChannels[1].displayName).to.equal('second video channel')
     expect(videoChannels[1].description).to.equal('super video channel description')
     expect(videoChannels[1].support).to.equal('super video channel support text')
@@ -112,7 +113,10 @@ describe('Test video channels', function () {
     expect(res.body.data).to.have.lengthOf(2)
 
     const videoChannels = res.body.data
-    expect(videoChannels[0].displayName).to.equal('Default root channel')
+    expect(videoChannels[0].name).to.equal('root_channel')
+    expect(videoChannels[0].displayName).to.equal('Main root channel')
+
+    expect(videoChannels[1].name).to.equal('second_video_channel')
     expect(videoChannels[1].displayName).to.equal('second video channel')
     expect(videoChannels[1].description).to.equal('super video channel description')
     expect(videoChannels[1].support).to.equal('super video channel support text')
@@ -125,6 +129,7 @@ describe('Test video channels', function () {
     expect(res.body.data).to.have.lengthOf(1)
 
     const videoChannels = res.body.data
+    expect(videoChannels[0].name).to.equal('second_video_channel')
     expect(videoChannels[0].displayName).to.equal('second video channel')
     expect(videoChannels[0].description).to.equal('super video channel description')
     expect(videoChannels[0].support).to.equal('super video channel support text')
@@ -136,7 +141,8 @@ describe('Test video channels', function () {
     expect(res.body.total).to.equal(2)
     expect(res.body.data).to.be.an('array')
     expect(res.body.data).to.have.lengthOf(1)
-    expect(res.body.data[0].displayName).to.equal('Default root channel')
+    expect(res.body.data[0].name).to.equal('root_channel')
+    expect(res.body.data[0].displayName).to.equal('Main root channel')
   })
 
   it('Should update video channel', async function () {
@@ -148,7 +154,7 @@ describe('Test video channels', function () {
       support: 'video channel support text updated'
     }
 
-    await updateVideoChannel(servers[0].url, servers[0].accessToken, secondVideoChannelId, videoChannelAttributes)
+    await updateVideoChannel(servers[0].url, servers[0].accessToken, 'second_video_channel', videoChannelAttributes)
 
     await waitJobs(servers)
   })
@@ -160,6 +166,7 @@ describe('Test video channels', function () {
       expect(res.body.total).to.equal(2)
       expect(res.body.data).to.be.an('array')
       expect(res.body.data).to.have.lengthOf(1)
+      expect(res.body.data[0].name).to.equal('second_video_channel')
       expect(res.body.data[0].displayName).to.equal('video channel updated')
       expect(res.body.data[0].description).to.equal('video channel description updated')
       expect(res.body.data[0].support).to.equal('video channel support text updated')
@@ -174,7 +181,7 @@ describe('Test video channels', function () {
     await updateVideoChannelAvatar({
       url: servers[0].url,
       accessToken: servers[0].accessToken,
-      videoChannelId: secondVideoChannelId,
+      videoChannelName: 'second_video_channel',
       fixture
     })
 
@@ -192,9 +199,10 @@ describe('Test video channels', function () {
   })
 
   it('Should get video channel', async function () {
-    const res = await getVideoChannel(servers[0].url, secondVideoChannelId)
+    const res = await getVideoChannel(servers[0].url, 'second_video_channel')
 
     const videoChannel = res.body
+    expect(videoChannel.name).to.equal('second_video_channel')
     expect(videoChannel.displayName).to.equal('video channel updated')
     expect(videoChannel.description).to.equal('video channel description updated')
     expect(videoChannel.support).to.equal('video channel support text updated')
@@ -204,7 +212,8 @@ describe('Test video channels', function () {
     this.timeout(10000)
 
     for (const server of servers) {
-      const res1 = await getVideoChannelVideos(server.url, server.accessToken, secondVideoChannelUUID, 0, 5)
+      const channelURI = 'second_video_channel@localhost:9001'
+      const res1 = await getVideoChannelVideos(server.url, server.accessToken, channelURI, 0, 5)
       expect(res1.body.total).to.equal(1)
       expect(res1.body.data).to.be.an('array')
       expect(res1.body.data).to.have.lengthOf(1)
@@ -224,10 +233,12 @@ describe('Test video channels', function () {
     this.timeout(10000)
 
     for (const server of servers) {
-      const res1 = await getVideoChannelVideos(server.url, server.accessToken, secondVideoChannelUUID, 0, 5)
+      const secondChannelURI = 'second_video_channel@localhost:9001'
+      const res1 = await getVideoChannelVideos(server.url, server.accessToken, secondChannelURI, 0, 5)
       expect(res1.body.total).to.equal(0)
 
-      const res2 = await getVideoChannelVideos(server.url, server.accessToken, firstVideoChannelUUID, 0, 5)
+      const channelURI = 'root_channel@localhost:9001'
+      const res2 = await getVideoChannelVideos(server.url, server.accessToken, channelURI, 0, 5)
       expect(res2.body.total).to.equal(1)
 
       const videos: Video[] = res2.body.data
@@ -238,7 +249,7 @@ describe('Test video channels', function () {
   })
 
   it('Should delete video channel', async function () {
-    await deleteVideoChannel(servers[0].url, servers[0].accessToken, secondVideoChannelId)
+    await deleteVideoChannel(servers[0].url, servers[0].accessToken, 'second_video_channel')
   })
 
   it('Should have video channel deleted', async function () {
@@ -247,7 +258,23 @@ describe('Test video channels', function () {
     expect(res.body.total).to.equal(1)
     expect(res.body.data).to.be.an('array')
     expect(res.body.data).to.have.lengthOf(1)
-    expect(res.body.data[0].displayName).to.equal('Default root channel')
+    expect(res.body.data[0].displayName).to.equal('Main root channel')
+  })
+
+  it('Should create the main channel with an uuid if there is a conflict', async function () {
+    {
+      const videoChannel = { name: 'toto_channel', displayName: 'My toto channel' }
+      await addVideoChannel(servers[ 0 ].url, servers[ 0 ].accessToken, videoChannel)
+    }
+
+    {
+      await createUser(servers[ 0 ].url, servers[ 0 ].accessToken, 'toto', 'password')
+      const accessToken = await userLogin(servers[ 0 ], { username: 'toto', password: 'password' })
+
+      const res = await getMyUserInformation(servers[ 0 ].url, accessToken)
+      const videoChannel = res.body.videoChannels[ 0 ]
+      expect(videoChannel.name).to.match(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/)
+    }
   })
 
   after(async function () {
index 968b7bd7f62ae6d6c5a95ad6eb9bd7fec3bccec7..7f54c0e70827b436b0c2e22645bb4316189ebab2 100644 (file)
@@ -54,6 +54,7 @@ describe('Test update host scripts', function () {
 
     // Create channel
     const videoChannel = {
+      name: 'second_channel',
       displayName: 'second video channel',
       description: 'super video channel description'
     }
index d41ac8d3633b5be286b221e14e6305e3934fd5be..a7fdbd1dc8fccaa8cd2255413c6ab4244ac461e1 100644 (file)
@@ -66,6 +66,7 @@ function getRandomInt (min, max) {
 
 function createCustomChannel (server: ServerInfo) {
   const videoChannel = {
+    name: Date.now().toString(),
     displayName: Date.now().toString(),
     description: Date.now().toString()
   }
index 3ca39469c5b7b88e2db27ae1e051d917497efa7d..1eea22b31d865f0854d20c070ab62afe6aa4964f 100644 (file)
@@ -97,10 +97,10 @@ function updateVideoChannelAvatar (options: {
   url: string,
   accessToken: string,
   fixture: string,
-  videoChannelId: string | number
+  videoChannelName: string | number
 }) {
 
-  const path = '/api/v1/video-channels/' + options.videoChannelId + '/avatar/pick'
+  const path = '/api/v1/video-channels/' + options.videoChannelName + '/avatar/pick'
 
   return updateAvatarRequest(Object.assign(options, { path }))
 }
index b280cccda32223c72f01c1a736b6beb25b459f67..59224814425f8221b7cd031bb0f6318b8e8d6cfa 100644 (file)
@@ -199,13 +199,13 @@ function getAccountVideos (
 function getVideoChannelVideos (
   url: string,
   accessToken: string,
-  videoChannelId: number | string,
+  videoChannelName: string,
   start: number,
   count: number,
   sort?: string,
   query: { nsfw?: boolean } = {}
 ) {
-  const path = '/api/v1/video-channels/' + videoChannelId + '/videos'
+  const path = '/api/v1/video-channels/' + videoChannelName + '/videos'
 
   return makeGetRequest({
     url,
index 08cd5fb84739546540dd5df9b21a3f0680cf2e3b..da8ce620c333597cb9b2b756b9fd6cdc0e29fd6f 100644 (file)
@@ -1,4 +1,5 @@
 export interface VideoChannelCreate {
+  name: string
   displayName: string
   description?: string
   support?: string