import { CONFIG } from '../../../initializers/config'
import { sequelizeTypescript } from '../../../initializers/database'
import { UserAdminFlag } from '../../../../shared/models/users/user-flag.model'
+import { UserRegister } from '../../../../shared/models/users/user-register.model'
const auditLogger = auditLoggerFactory('users')
}
async function registerUser (req: express.Request, res: express.Response) {
- const body: UserCreate = req.body
+ const body: UserRegister = req.body
const userToCreate = new UserModel({
username: body.username,
emailVerified: CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION ? false : null
})
- const { user } = await createUserAccountAndChannelAndPlaylist(userToCreate)
+ const { user } = await createUserAccountAndChannelAndPlaylist(userToCreate, body.channel)
auditLogger.create(body.username, new UserAuditView(user.toFormattedJSON()))
logger.info('User %s with its channel and account registered.', body.username)
}
const user = new UserModel(userData)
- await createUserAccountAndChannelAndPlaylist(user, validatePassword)
+ await createUserAccountAndChannelAndPlaylist(user, undefined, validatePassword)
logger.info('Username: ' + username)
logger.info('User password: ' + password)
}
import { createWatchLaterPlaylist } from './video-playlist'
import { sequelizeTypescript } from '../initializers/database'
-async function createUserAccountAndChannelAndPlaylist (userToCreate: UserModel, validateUser = true) {
+type ChannelNames = { name: string, displayName: string }
+async function createUserAccountAndChannelAndPlaylist (userToCreate: UserModel, channelNames?: ChannelNames, validateUser = true) {
const { user, account, videoChannel } = await sequelizeTypescript.transaction(async t => {
const userOptions = {
transaction: t,
const accountCreated = await createLocalAccountWithoutKeys(userCreated.username, userCreated.id, null, t)
userCreated.Account = accountCreated
- 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)
+ const channelAttributes = await buildChannelAttributes(userCreated, channelNames)
+ const videoChannel = await createVideoChannel(channelAttributes, accountCreated, t)
const videoPlaylist = await createWatchLaterPlaylist(accountCreated, t)
return UserNotificationSettingModel.create(values, { transaction: t })
}
+
+async function buildChannelAttributes (user: UserModel, channelNames?: ChannelNames) {
+ if (channelNames) return channelNames
+
+ let channelName = user.username + '_channel'
+
+ // Conflict, generate uuid instead
+ const actor = await ActorModel.loadLocalByName(channelName)
+ if (actor) channelName = uuidv4()
+
+ const videoChannelDisplayName = `Main ${user.username} channel`
+
+ return {
+ name: channelName,
+ displayName: videoChannelDisplayName
+ }
+}
import { UserModel } from '../../models/account/user'
import { areValidationErrors } from './utils'
import { ActorModel } from '../../models/activitypub/actor'
+import { isActorPreferredUsernameValid } from '../../helpers/custom-validators/activitypub/actor'
+import { isVideoChannelNameValid } from '../../helpers/custom-validators/video-channels'
+import { UserCreate } from '../../../shared/models/users'
+import { UserRegister } from '../../../shared/models/users/user-register.model'
const usersAddValidator = [
body('username').custom(isUserUsernameValid).withMessage('Should have a valid username (lowercase alphanumeric characters)'),
body('username').custom(isUserUsernameValid).withMessage('Should have a valid username'),
body('password').custom(isUserPasswordValid).withMessage('Should have a valid password'),
body('email').isEmail().withMessage('Should have a valid email'),
+ body('channel.name').optional().custom(isActorPreferredUsernameValid).withMessage('Should have a valid channel name'),
+ body('channel.displayName').optional().custom(isVideoChannelNameValid).withMessage('Should have a valid display name'),
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking usersRegister parameters', { parameters: omit(req.body, 'password') })
if (areValidationErrors(req, res)) return
if (!await checkUserNameOrEmailDoesNotAlreadyExist(req.body.username, req.body.email, res)) return
+ const body: UserRegister = req.body
+ if (body.channel) {
+ if (!body.channel.name || !body.channel.displayName) {
+ return res.status(400)
+ .send({ error: 'Channel is optional but if you specify it, channel.name and channel.displayName are required.' })
+ .end()
+ }
+
+ const existing = await ActorModel.loadLocalByName(body.channel.name)
+ if (existing) {
+ return res.status(409)
+ .send({ error: `Channel with name ${body.channel.name} already exists.` })
+ .end()
+ }
+ }
+
return next()
}
]
import { UserRole, VideoImport, VideoImportState } from '../../../../shared'
import {
+ addVideoChannel,
blockUser,
cleanupTests,
createUser,
})
})
- describe('When register a new user', function () {
+ describe('When registering a new user', function () {
const registrationPath = path + '/register'
const baseCorrectParams = {
username: 'user3',
})
})
+ it('Should fail with a bad channel name', async function () {
+ const fields = immutableAssign(baseCorrectParams, { channel: { name: '[]azf', displayName: 'toto' } })
+
+ await makePostBodyRequest({ url: server.url, path: registrationPath, token: server.accessToken, fields })
+ })
+
+ it('Should fail with a bad channel display name', async function () {
+ const fields = immutableAssign(baseCorrectParams, { channel: { name: 'toto', displayName: '' } })
+
+ await makePostBodyRequest({ url: server.url, path: registrationPath, token: server.accessToken, fields })
+ })
+
+ it('Should fail with an existing channel', async function () {
+ const videoChannelAttributesArg = { name: 'existing_channel', displayName: 'hello', description: 'super description' }
+ await addVideoChannel(server.url, server.accessToken, videoChannelAttributesArg)
+
+ const fields = immutableAssign(baseCorrectParams, { channel: { name: 'existing_channel', displayName: 'toto' } })
+
+ await makePostBodyRequest({ url: server.url, path: registrationPath, token: server.accessToken, fields, statusCodeExpected: 409 })
+ })
+
it('Should succeed with the correct params', async function () {
+ const fields = immutableAssign(baseCorrectParams, { channel: { name: 'super_channel', displayName: 'toto' } })
+
await makePostBodyRequest({
url: server.url,
path: registrationPath,
token: server.accessToken,
- fields: baseCorrectParams,
+ fields: fields,
statusCodeExpected: 204
})
})
updateMyUser,
updateUser,
uploadVideo,
- userLogin
+ userLogin,
+ registerUserWithChannel, getVideoChannel
} from '../../../../shared/extra-utils'
import { follow } from '../../../../shared/extra-utils/server/follows'
import { setAccessTokensToServers } from '../../../../shared/extra-utils/users/login'
describe('Registering a new user', function () {
it('Should register a new user', async function () {
- await registerUser(server.url, 'user_15', 'my super password')
+ const user = { username: 'user_15', password: 'my super password' }
+ const channel = { name: 'my_user_15_channel', displayName: 'my channel rocks' }
+
+ await registerUserWithChannel({ url: server.url, user, channel })
})
it('Should be able to login with this registered user', async function () {
expect(user.videoQuota).to.equal(5 * 1024 * 1024)
})
+ it('Should have created the channel', async function () {
+ const res = await getVideoChannel(server.url, 'my_user_15_channel')
+
+ expect(res.body.displayName).to.equal('my channel rocks')
+ })
+
it('Should remove me', async function () {
{
const res = await getUsersList(server.url, server.accessToken)
import * as request from 'supertest'
-import { makePostBodyRequest, makePutBodyRequest, updateAvatarRequest } from '../requests/requests'
+import { makeGetRequest, makePostBodyRequest, makePutBodyRequest, updateAvatarRequest } from '../requests/requests'
-import { UserRole } from '../../index'
+import { UserCreate, UserRole } from '../../index'
import { NSFWPolicyType } from '../../models/videos/nsfw-policy.type'
import { ServerInfo, userLogin } from '..'
import { UserAdminFlag } from '../../models/users/user-flag.model'
+import { UserRegister } from '../../models/users/user-register.model'
type CreateUserArgs = { url: string,
accessToken: string,
.expect(specialStatus)
}
+function registerUserWithChannel (options: {
+ url: string,
+ user: { username: string, password: string },
+ channel: { name: string, displayName: string }
+}) {
+ const path = '/api/v1/users/register'
+ const body: UserRegister = {
+ username: options.user.username,
+ password: options.user.password,
+ email: options.user.username + '@example.com',
+ channel: options.channel
+ }
+
+ return makePostBodyRequest({
+ url: options.url,
+ path,
+ fields: body,
+ statusCodeExpected: 204
+ })
+}
+
function getMyUserInformation (url: string, accessToken: string, specialStatus = 200) {
const path = '/api/v1/users/me'
getMyUserInformation,
getMyUserVideoRating,
deleteMe,
+ registerUserWithChannel,
getMyUserVideoQuotaUsed,
getUsersList,
getUsersListPaginationAndSort,
--- /dev/null
+export interface UserRegister {
+ username: string
+ password: string
+ email: string
+
+ channel?: {
+ name: string
+ displayName: string
+ }
+}