<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" />
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',
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
const body = this.form.value
const videoChannelCreate: VideoChannelCreate = {
+ name: body.name,
displayName: body['display-name'],
description: body.description || null,
support: body.support || null
<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
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 {
<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>
<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>
a.video-channel-names {
@include disable-default-a-behaviour;
+ width: fit-content;
display: flex;
+ align-items: baseline;
color: #000;
.video-channel-display-name {
.video-channel-name {
font-size: 14px;
color: #777272;
+ margin-left: 5px;
}
}
}
<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>
@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,
description: string
support: string
isLocal: boolean
+ nameWithHost: string
ownerAccount?: Account
ownerBy?: string
ownerAvatarUrl?: string
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
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)),
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))
<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>
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] {
return this.serverService.getConfig().user.videoQuota
}
+ get instanceHost () {
+ return window.location.host
+ }
+
ngOnInit () {
this.buildForm({
username: this.userValidatorsService.USER_USERNAME,
</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" />
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;
}
}
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'
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))
)
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,
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'
asyncRetryTransactionMiddleware(addVideoChannel)
)
-videoChannelRouter.post('/:id/avatar/pick',
+videoChannelRouter.post('/:nameWithHost/avatar/pick',
authenticate,
reqAvatarFile,
// Check the rights
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,
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,
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)
}
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)
}
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
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)
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) {
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) {
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'
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 => {
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)
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 })
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)'),
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 })
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()
}
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'),
]
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'),
]
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'),
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) {
]
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
}
]
-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()
}
videoChannelsAddValidator,
videoChannelsUpdateValidator,
videoChannelsRemoveValidator,
- videoChannelsGetValidator,
+ videoChannelsNameWithHostValidator,
localVideoChannelValidator
}
return AccountModel.findOne(query)
}
- static loadLocalByNameAndHost (name: string, host: string) {
+ static loadByNameAndHost (name: string, host: string) {
const query = {
include: [
{
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)
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',
}
static loadByIdAndAccount (id: number, accountId: number) {
- const options = {
+ const query = {
where: {
id,
accountId
return VideoChannelModel
.scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT ])
- .findOne(options)
+ .findOne(query)
}
static loadAndPopulateAccount (id: number) {
}
static loadByUUIDAndPopulateAccount (uuid: string) {
- const options = {
+ const query = {
include: [
{
model: ActorModel,
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 {
import {
createUser,
flushTests,
- getMyUserInformation,
killallServers,
makeDeleteRequest,
makeGetRequest,
const path = '/api/v1/users/me/subscriptions'
let server: ServerInfo
let userAccessToken = ''
- let userChannelUUID: string
// ---------------------------------------------------------------
}
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 () {
})
it('Should success with the correct parameters', async function () {
- await await makeGetRequest({
+ await makeGetRequest({
url: server.url,
path,
token: userAccessToken,
})
it('Should success with the correct parameters', async function () {
- await await makeGetRequest({
+ await makeGetRequest({
url: server.url,
path,
token: userAccessToken,
await makePostBodyRequest({
url: server.url,
path,
- fields: { uri: userChannelUUID + '@localhost:9001' },
+ fields: { uri: 'user1_channel@localhost:9001' },
statusCodeExpected: 401
})
})
url: server.url,
path,
token: server.accessToken,
- fields: { uri: userChannelUUID + '@localhost:9001' },
+ fields: { uri: 'user1_channel@localhost:9001' },
statusCodeExpected: 204
})
})
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
})
})
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
})
const videoChannelPath = '/api/v1/video-channels'
let server: ServerInfo
let accessTokenUser: string
- let videoChannelUUID: string
// ---------------------------------------------------------------
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 () {
describe('When adding a video channel', function () {
const baseCorrectParams = {
+ name: 'super_channel',
displayName: 'hello',
description: 'super description',
support: 'super support text'
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 })
let path: string
before(async function () {
- path = videoChannelPath + '/' + videoChannelUUID
+ path = videoChannelPath + '/super_channel'
})
it('Should fail with a non authenticated user', async function () {
let path: string
before(async function () {
- path = videoChannelPath + '/' + videoChannelUUID
+ path = videoChannelPath + '/super_channel'
})
it('Should fail with an incorrect input file', async 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
})
})
it('Should succeed with the correct parameters', async function () {
await makeGetRequest({
url: server.url,
- path: videoChannelPath + '/' + videoChannelUUID,
+ path: videoChannelPath + '/super_channel',
statusCodeExpected: 200
})
})
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)
})
})
likes: 1,
dislikes: 1,
channel: {
- name: 'Default root channel',
+ name: 'Main root channel',
description: '',
isLocal
},
privacy: VideoPrivacy.PUBLIC,
commentsEnabled: true,
channel: {
- name: 'Default root channel',
+ name: 'Main root channel',
description: '',
isLocal: false
},
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'
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)
// 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 })
})
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)
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')
}
})
})
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 })
})
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)
})
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])
})
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)
})
})
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)
})
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)
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'
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
}
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
}
import * as request from 'supertest'
import { VideoPrivacy } from '../../../../shared/models/videos'
import { VideoComment, VideoCommentThreadTree } from '../../../../shared/models/videos/video-comment.model'
-
import {
addVideoChannel,
checkVideoFilesWereRemoved,
{
const videoChannel = {
+ name: 'super_channel_name',
displayName: 'my channel',
description: 'super channel'
}
tags: [ 'tag1p2', 'tag2p2', 'tag3p2' ],
privacy: VideoPrivacy.PUBLIC,
channel: {
- name: 'Default user1 channel',
+ name: 'Main user1 channel',
description: 'super channel',
isLocal
},
tags: [ 'tag1p3' ],
privacy: VideoPrivacy.PUBLIC,
channel: {
- name: 'Default root channel',
+ name: 'Main root channel',
description: '',
isLocal
},
tags: [ 'tag2p3', 'tag3p3', 'tag4p3' ],
privacy: VideoPrivacy.PUBLIC,
channel: {
- name: 'Default root channel',
+ name: 'Main root channel',
description: '',
isLocal
},
tags: [ 'tag_up_1', 'tag_up_2' ],
privacy: VideoPrivacy.PUBLIC,
channel: {
- name: 'Default root channel',
+ name: 'Main root channel',
description: '',
isLocal
},
tags: [ ],
privacy: VideoPrivacy.PUBLIC,
channel: {
- name: 'Default root channel',
+ name: 'Main root channel',
description: '',
isLocal
},
privacy: VideoPrivacy.PUBLIC,
commentsEnabled: true,
channel: {
- name: 'Default root channel',
+ name: 'Main root channel',
description: '',
isLocal: true
},
duration: 5,
commentsEnabled: false,
channel: {
- name: 'Default root channel',
+ name: 'Main root channel',
description: '',
isLocal: true
},
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,
let userInfo: User
let accountUUID: string
let firstVideoChannelId: number
- let firstVideoChannelUUID: string
let secondVideoChannelId: number
- let secondVideoChannelUUID: string
let videoUUID: string
before(async function () {
accountUUID = user.account.uuid
firstVideoChannelId = user.videoChannels[0].id
- firstVideoChannelUUID = user.videoChannels[0].uuid
}
await waitJobs(servers)
{
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
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')
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')
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')
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 () {
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)
})
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')
await updateVideoChannelAvatar({
url: servers[0].url,
accessToken: servers[0].accessToken,
- videoChannelId: secondVideoChannelId,
+ videoChannelName: 'second_video_channel',
fixture
})
})
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')
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)
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
})
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 () {
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 () {
// Create channel
const videoChannel = {
+ name: 'second_channel',
displayName: 'second video channel',
description: 'super video channel description'
}
function createCustomChannel (server: ServerInfo) {
const videoChannel = {
+ name: Date.now().toString(),
displayName: Date.now().toString(),
description: Date.now().toString()
}
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 }))
}
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,
export interface VideoChannelCreate {
+ name: string
displayName: string
description?: string
support?: string