Added signup API endpoint

This commit is contained in:
Jori Lallo 2016-09-11 16:34:57 -07:00
parent bf84f792e2
commit 48a9a9f285
11 changed files with 265 additions and 8 deletions

View File

@ -0,0 +1,27 @@
exports[`test should require params 1`] = `
Object {
"error": "name is required",
"ok": false
}
`;
exports[`test should require unique email 1`] = `
Object {
"error": "User already exists with this email",
"ok": false
}
`;
exports[`test should require unique username 1`] = `
Object {
"error": "User already exists with this username",
"ok": false
}
`;
exports[`test should require valid email 1`] = `
Object {
"error": "email is invalid",
"ok": false
}
`;

View File

@ -8,6 +8,36 @@ import { User, Team } from '../models';
const router = new Router();
router.post('auth.signup', async (ctx) => {
const { username, name, email, password } = ctx.request.body;
ctx.assertPresent(username, 'name is required');
ctx.assertPresent(name, 'name is required');
ctx.assertPresent(email, 'email is required');
ctx.assertEmail(email, 'email is invalid');
ctx.assertPresent(password, 'password is required');
if (await User.findOne({ where: { email } })) {
throw httpErrors.BadRequest('User already exists with this email');
}
if (await User.findOne({ where: { username } })) {
throw httpErrors.BadRequest('User already exists with this username');
}
const user = await User.create({
username,
name,
email,
password,
});
ctx.body = { data: {
user: await presentUser(ctx, user),
accessToken: user.getJwtToken(),
} };
});
router.post('auth.slack', async (ctx) => {
const { code } = ctx.body;
ctx.assertPresent(code, 'code is required');

85
server/api/auth.test.js Normal file
View File

@ -0,0 +1,85 @@
import TestServer from 'fetch-test-server';
import app from '..';
import { flushdb, sequelize, seed } from '../test/support';
const server = new TestServer(app.callback());
beforeEach(flushdb);
afterAll(() => server.close());
afterAll(() => sequelize.close());
it('should signup a new user', async () => {
const res = await server.post('/api/auth.signup', {
body: {
username: 'testuser',
name: 'Test User',
email: 'new.user@example.com',
password: 'test123!',
},
});
const body = await res.json();
expect(res.status).toEqual(200);
expect(body.ok).toBe(true);
expect(body.data.user).toBeTruthy();
});
it('should require params', async () => {
const res = await server.post('/api/auth.signup', {
body: {
username: 'testuser',
},
});
const body = await res.json();
expect(res.status).toEqual(400);
expect(body).toMatchSnapshot();
});
it('should require valid email', async () => {
const res = await server.post('/api/auth.signup', {
body: {
username: 'testuser',
name: 'Test User',
email: 'example.com',
password: 'test123!',
},
});
const body = await res.json();
expect(res.status).toEqual(400);
expect(body).toMatchSnapshot();
});
it('should require unique email', async () => {
await seed();
const res = await server.post('/api/auth.signup', {
body: {
username: 'testuser',
name: 'Test User',
email: 'user1@example.com',
password: 'test123!',
},
});
const body = await res.json();
expect(res.status).toEqual(400);
expect(body).toMatchSnapshot();
});
it('should require unique username', async () => {
await seed();
const res = await server.post('/api/auth.signup', {
body: {
username: 'user1',
name: 'Test User',
email: 'userone@example.com',
password: 'test123!',
},
});
const body = await res.json();
expect(res.status).toEqual(400);
expect(body).toMatchSnapshot();
});

View File

@ -0,0 +1,46 @@
/* eslint-disable */
'use strict';
module.exports = {
up: function (queryInterface, Sequelize) {
queryInterface.changeColumn(
'users',
'slackId',
{
type: Sequelize.STRING,
unique: false,
allowNull: true,
}
);
queryInterface.changeColumn(
'teams',
'slackId',
{
type: Sequelize.STRING,
unique: false,
allowNull: true,
}
);
},
down: function (queryInterface, Sequelize) {
queryInterface.changeColumn(
'users',
'slackId',
{
type: Sequelize.STRING,
unique: true,
allowNull: false,
}
);
queryInterface.changeColumn(
'teams',
'slackId',
{
type: Sequelize.STRING,
unique: true,
allowNull: false,
}
);
},
};

View File

@ -0,0 +1,46 @@
'use strict';
module.exports = {
up: function (queryInterface, Sequelize) {
queryInterface.changeColumn(
'users',
'email',
{
type: Sequelize.STRING,
unique: true,
allowNull: false,
}
);
queryInterface.changeColumn(
'users',
'username',
{
type: Sequelize.STRING,
unique: true,
allowNull: false,
}
);
},
down: function (queryInterface, Sequelize) {
queryInterface.changeColumn(
'users',
'email',
{
type: Sequelize.STRING,
unique: false,
allowNull: true,
}
);
queryInterface.changeColumn(
'users',
'username',
{
type: Sequelize.STRING,
unique: false,
allowNull: true,
}
);
}
};

View File

@ -9,7 +9,7 @@ import User from './User';
const Team = sequelize.define('team', {
id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, primaryKey: true },
name: DataTypes.STRING,
slackId: { type: DataTypes.STRING, unique: true },
slackId: { type: DataTypes.STRING, allowNull: true },
slackData: DataTypes.JSONB,
}, {
instanceMethods: {

View File

@ -9,12 +9,12 @@ import JWT from 'jsonwebtoken';
const User = sequelize.define('user', {
id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, primaryKey: true },
email: DataTypes.STRING,
username: DataTypes.STRING,
email: { type: DataTypes.STRING, unique: true },
username: { type: DataTypes.STRING, unique: true },
name: DataTypes.STRING,
isAdmin: DataTypes.BOOLEAN,
slackAccessToken: encryptedFields.vault('slackAccessToken'),
slackId: { type: DataTypes.STRING, unique: true },
slackId: { type: DataTypes.STRING, allowNull: true },
slackData: DataTypes.JSONB,
jwtSecret: encryptedFields.vault('jwtSecret'),
}, {

View File

@ -6,3 +6,12 @@ Object {
"username": "testuser"
}
`;
exports[`test presents a user without slack data 1`] = `
Object {
"avatarUrl": null,
"id": "123",
"name": "Test User",
"username": "testuser"
}
`;

View File

@ -4,9 +4,9 @@ const presentUser = (ctx, user) => {
return new Promise(async (resolve, _reject) => {
const data = {
id: user.id,
name: user.name,
username: user.username,
avatarUrl: user.slackData.image_192,
name: user.name,
avatarUrl: user.slackData ? user.slackData.image_192 : null,
};
resolve(data);
});

View File

@ -17,3 +17,17 @@ it('presents a user', async () => {
expect(user).toMatchSnapshot();
});
it('presents a user without slack data', async () => {
const user = await presentUser(
ctx,
{
id: '123',
name: 'Test User',
username: 'testuser',
slackData: null,
},
);
expect(user).toMatchSnapshot();
});

View File

@ -21,9 +21,9 @@ function runMigrations() {
path: './server/migrations',
},
});
umzug.up()
return umzug.up()
.then(() => {
sequelize.close();
return sequelize.close();
});
}