Add ability to specify channel on registration
This commit is contained in:
parent
cce1b3dfd3
commit
e590b4a512
|
@ -46,6 +46,7 @@ import { mySubscriptionsRouter } from './my-subscriptions'
|
||||||
import { CONFIG } from '../../../initializers/config'
|
import { CONFIG } from '../../../initializers/config'
|
||||||
import { sequelizeTypescript } from '../../../initializers/database'
|
import { sequelizeTypescript } from '../../../initializers/database'
|
||||||
import { UserAdminFlag } from '../../../../shared/models/users/user-flag.model'
|
import { UserAdminFlag } from '../../../../shared/models/users/user-flag.model'
|
||||||
|
import { UserRegister } from '../../../../shared/models/users/user-register.model'
|
||||||
|
|
||||||
const auditLogger = auditLoggerFactory('users')
|
const auditLogger = auditLoggerFactory('users')
|
||||||
|
|
||||||
|
@ -197,7 +198,7 @@ async function createUser (req: express.Request, res: express.Response) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function registerUser (req: express.Request, res: express.Response) {
|
async function registerUser (req: express.Request, res: express.Response) {
|
||||||
const body: UserCreate = req.body
|
const body: UserRegister = req.body
|
||||||
|
|
||||||
const userToCreate = new UserModel({
|
const userToCreate = new UserModel({
|
||||||
username: body.username,
|
username: body.username,
|
||||||
|
@ -211,7 +212,7 @@ async function registerUser (req: express.Request, res: express.Response) {
|
||||||
emailVerified: CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION ? false : null
|
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()))
|
auditLogger.create(body.username, new UserAuditView(user.toFormattedJSON()))
|
||||||
logger.info('User %s with its channel and account registered.', body.username)
|
logger.info('User %s with its channel and account registered.', body.username)
|
||||||
|
|
|
@ -146,7 +146,7 @@ async function createOAuthAdminIfNotExist () {
|
||||||
}
|
}
|
||||||
const user = new UserModel(userData)
|
const user = new UserModel(userData)
|
||||||
|
|
||||||
await createUserAccountAndChannelAndPlaylist(user, validatePassword)
|
await createUserAccountAndChannelAndPlaylist(user, undefined, validatePassword)
|
||||||
logger.info('Username: ' + username)
|
logger.info('Username: ' + username)
|
||||||
logger.info('User password: ' + password)
|
logger.info('User password: ' + password)
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,8 @@ import { UserNotificationSetting, UserNotificationSettingValue } from '../../sha
|
||||||
import { createWatchLaterPlaylist } from './video-playlist'
|
import { createWatchLaterPlaylist } from './video-playlist'
|
||||||
import { sequelizeTypescript } from '../initializers/database'
|
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 { user, account, videoChannel } = await sequelizeTypescript.transaction(async t => {
|
||||||
const userOptions = {
|
const userOptions = {
|
||||||
transaction: t,
|
transaction: t,
|
||||||
|
@ -26,18 +27,8 @@ async function createUserAccountAndChannelAndPlaylist (userToCreate: UserModel,
|
||||||
const accountCreated = await createLocalAccountWithoutKeys(userCreated.username, userCreated.id, null, t)
|
const accountCreated = await createLocalAccountWithoutKeys(userCreated.username, userCreated.id, null, t)
|
||||||
userCreated.Account = accountCreated
|
userCreated.Account = accountCreated
|
||||||
|
|
||||||
let channelName = userCreated.username + '_channel'
|
const channelAttributes = await buildChannelAttributes(userCreated, channelNames)
|
||||||
|
const videoChannel = await createVideoChannel(channelAttributes, accountCreated, t)
|
||||||
// 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 videoPlaylist = await createWatchLaterPlaylist(accountCreated, t)
|
const videoPlaylist = await createWatchLaterPlaylist(accountCreated, t)
|
||||||
|
|
||||||
|
@ -116,3 +107,20 @@ function createDefaultUserNotificationSettings (user: UserModel, t: Sequelize.Tr
|
||||||
|
|
||||||
return UserNotificationSettingModel.create(values, { transaction: 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -25,6 +25,10 @@ import { Redis } from '../../lib/redis'
|
||||||
import { UserModel } from '../../models/account/user'
|
import { UserModel } from '../../models/account/user'
|
||||||
import { areValidationErrors } from './utils'
|
import { areValidationErrors } from './utils'
|
||||||
import { ActorModel } from '../../models/activitypub/actor'
|
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 = [
|
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 (lowercase alphanumeric characters)'),
|
||||||
|
@ -49,6 +53,8 @@ const usersRegisterValidator = [
|
||||||
body('username').custom(isUserUsernameValid).withMessage('Should have a valid username'),
|
body('username').custom(isUserUsernameValid).withMessage('Should have a valid username'),
|
||||||
body('password').custom(isUserPasswordValid).withMessage('Should have a valid password'),
|
body('password').custom(isUserPasswordValid).withMessage('Should have a valid password'),
|
||||||
body('email').isEmail().withMessage('Should have a valid email'),
|
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) => {
|
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||||
logger.debug('Checking usersRegister parameters', { parameters: omit(req.body, 'password') })
|
logger.debug('Checking usersRegister parameters', { parameters: omit(req.body, 'password') })
|
||||||
|
@ -56,6 +62,22 @@ const usersRegisterValidator = [
|
||||||
if (areValidationErrors(req, res)) return
|
if (areValidationErrors(req, res)) return
|
||||||
if (!await checkUserNameOrEmailDoesNotAlreadyExist(req.body.username, req.body.email, 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()
|
return next()
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { join } from 'path'
|
||||||
import { UserRole, VideoImport, VideoImportState } from '../../../../shared'
|
import { UserRole, VideoImport, VideoImportState } from '../../../../shared'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
addVideoChannel,
|
||||||
blockUser,
|
blockUser,
|
||||||
cleanupTests,
|
cleanupTests,
|
||||||
createUser,
|
createUser,
|
||||||
|
@ -638,7 +639,7 @@ describe('Test users API validators', function () {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('When register a new user', function () {
|
describe('When registering a new user', function () {
|
||||||
const registrationPath = path + '/register'
|
const registrationPath = path + '/register'
|
||||||
const baseCorrectParams = {
|
const baseCorrectParams = {
|
||||||
username: 'user3',
|
username: 'user3',
|
||||||
|
@ -724,12 +725,35 @@ describe('Test users API validators', function () {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
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 () {
|
it('Should succeed with the correct params', async function () {
|
||||||
|
const fields = immutableAssign(baseCorrectParams, { channel: { name: 'super_channel', displayName: 'toto' } })
|
||||||
|
|
||||||
await makePostBodyRequest({
|
await makePostBodyRequest({
|
||||||
url: server.url,
|
url: server.url,
|
||||||
path: registrationPath,
|
path: registrationPath,
|
||||||
token: server.accessToken,
|
token: server.accessToken,
|
||||||
fields: baseCorrectParams,
|
fields: fields,
|
||||||
statusCodeExpected: 204
|
statusCodeExpected: 204
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -31,7 +31,8 @@ import {
|
||||||
updateMyUser,
|
updateMyUser,
|
||||||
updateUser,
|
updateUser,
|
||||||
uploadVideo,
|
uploadVideo,
|
||||||
userLogin
|
userLogin,
|
||||||
|
registerUserWithChannel, getVideoChannel
|
||||||
} from '../../../../shared/extra-utils'
|
} from '../../../../shared/extra-utils'
|
||||||
import { follow } from '../../../../shared/extra-utils/server/follows'
|
import { follow } from '../../../../shared/extra-utils/server/follows'
|
||||||
import { setAccessTokensToServers } from '../../../../shared/extra-utils/users/login'
|
import { setAccessTokensToServers } from '../../../../shared/extra-utils/users/login'
|
||||||
|
@ -617,7 +618,10 @@ describe('Test users', function () {
|
||||||
|
|
||||||
describe('Registering a new user', function () {
|
describe('Registering a new user', function () {
|
||||||
it('Should register a new user', async 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 () {
|
it('Should be able to login with this registered user', async function () {
|
||||||
|
@ -636,6 +640,12 @@ describe('Test users', function () {
|
||||||
expect(user.videoQuota).to.equal(5 * 1024 * 1024)
|
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 () {
|
it('Should remove me', async function () {
|
||||||
{
|
{
|
||||||
const res = await getUsersList(server.url, server.accessToken)
|
const res = await getUsersList(server.url, server.accessToken)
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import * as request from 'supertest'
|
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 { NSFWPolicyType } from '../../models/videos/nsfw-policy.type'
|
||||||
import { ServerInfo, userLogin } from '..'
|
import { ServerInfo, userLogin } from '..'
|
||||||
import { UserAdminFlag } from '../../models/users/user-flag.model'
|
import { UserAdminFlag } from '../../models/users/user-flag.model'
|
||||||
|
import { UserRegister } from '../../models/users/user-register.model'
|
||||||
|
|
||||||
type CreateUserArgs = { url: string,
|
type CreateUserArgs = { url: string,
|
||||||
accessToken: string,
|
accessToken: string,
|
||||||
|
@ -70,6 +71,27 @@ function registerUser (url: string, username: string, password: string, specialS
|
||||||
.expect(specialStatus)
|
.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) {
|
function getMyUserInformation (url: string, accessToken: string, specialStatus = 200) {
|
||||||
const path = '/api/v1/users/me'
|
const path = '/api/v1/users/me'
|
||||||
|
|
||||||
|
@ -312,6 +334,7 @@ export {
|
||||||
getMyUserInformation,
|
getMyUserInformation,
|
||||||
getMyUserVideoRating,
|
getMyUserVideoRating,
|
||||||
deleteMe,
|
deleteMe,
|
||||||
|
registerUserWithChannel,
|
||||||
getMyUserVideoQuotaUsed,
|
getMyUserVideoQuotaUsed,
|
||||||
getUsersList,
|
getUsersList,
|
||||||
getUsersListPaginationAndSort,
|
getUsersListPaginationAndSort,
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
export interface UserRegister {
|
||||||
|
username: string
|
||||||
|
password: string
|
||||||
|
email: string
|
||||||
|
|
||||||
|
channel?: {
|
||||||
|
name: string
|
||||||
|
displayName: string
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue