Admin endpoints
This commit is contained in:
@ -161,7 +161,7 @@
|
|||||||
"redis-lock": "^0.1.0",
|
"redis-lock": "^0.1.0",
|
||||||
"rimraf": "^2.5.4",
|
"rimraf": "^2.5.4",
|
||||||
"safestart": "1.1.0",
|
"safestart": "1.1.0",
|
||||||
"sequelize": "^4.3.1",
|
"sequelize": "4.28.6",
|
||||||
"sequelize-cli": "^2.7.0",
|
"sequelize-cli": "^2.7.0",
|
||||||
"sequelize-encrypted": "0.1.0",
|
"sequelize-encrypted": "0.1.0",
|
||||||
"slate": "^0.31.5",
|
"slate": "^0.31.5",
|
||||||
|
88
server/api/__snapshots__/team.test.js.snap
Normal file
88
server/api/__snapshots__/team.test.js.snap
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`#team.addAdmin should promote a new admin 1`] = `
|
||||||
|
Object {
|
||||||
|
"avatarUrl": "http://example.com/avatar.png",
|
||||||
|
"email": "user1@example.com",
|
||||||
|
"id": "46fde1d4-0050-428f-9f0b-0bf77f4bdf61",
|
||||||
|
"isAdmin": true,
|
||||||
|
"name": "User 1",
|
||||||
|
"ok": true,
|
||||||
|
"status": 200,
|
||||||
|
"username": "user1",
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`#team.addAdmin should require admin 1`] = `
|
||||||
|
Object {
|
||||||
|
"error": "only_available_for_admins",
|
||||||
|
"message": "Only available for admins",
|
||||||
|
"ok": false,
|
||||||
|
"status": 403,
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`#team.removeAdmin should demote an admin 1`] = `
|
||||||
|
Object {
|
||||||
|
"avatarUrl": null,
|
||||||
|
"ok": true,
|
||||||
|
"status": 200,
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`#team.removeAdmin should require admin 1`] = `
|
||||||
|
Object {
|
||||||
|
"error": "only_available_for_admins",
|
||||||
|
"message": "Only available for admins",
|
||||||
|
"ok": false,
|
||||||
|
"status": 403,
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`#team.removeAdmin shouldn't demote admins if only one available 1`] = `
|
||||||
|
Object {
|
||||||
|
"error": "at_least_one_admin_is_required",
|
||||||
|
"message": "At least one admin is required",
|
||||||
|
"ok": false,
|
||||||
|
"status": 400,
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`#team.users should require admin 1`] = `
|
||||||
|
Object {
|
||||||
|
"error": "only_available_for_admins",
|
||||||
|
"message": "Only available for admins",
|
||||||
|
"ok": false,
|
||||||
|
"status": 403,
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`#team.users should return teams paginated user list 1`] = `
|
||||||
|
Object {
|
||||||
|
"data": Array [
|
||||||
|
Object {
|
||||||
|
"avatarUrl": "http://example.com/avatar.png",
|
||||||
|
"email": "admin@example.com",
|
||||||
|
"id": "fa952cff-fa64-4d42-a6ea-6955c9689046",
|
||||||
|
"isAdmin": true,
|
||||||
|
"name": "Admin User",
|
||||||
|
"username": "admin",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"avatarUrl": "http://example.com/avatar.png",
|
||||||
|
"email": "user1@example.com",
|
||||||
|
"id": "46fde1d4-0050-428f-9f0b-0bf77f4bdf61",
|
||||||
|
"isAdmin": false,
|
||||||
|
"name": "User 1",
|
||||||
|
"username": "user1",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"ok": true,
|
||||||
|
"pagination": Object {
|
||||||
|
"limit": 15,
|
||||||
|
"nextPath": "/api/team.users?limit=15&offset=15",
|
||||||
|
"offset": 0,
|
||||||
|
},
|
||||||
|
"status": 200,
|
||||||
|
}
|
||||||
|
`;
|
@ -13,7 +13,6 @@ exports[`#user.info should return known user 1`] = `
|
|||||||
Object {
|
Object {
|
||||||
"data": Object {
|
"data": Object {
|
||||||
"avatarUrl": "http://example.com/avatar.png",
|
"avatarUrl": "http://example.com/avatar.png",
|
||||||
"email": "user1@example.com",
|
|
||||||
"id": "46fde1d4-0050-428f-9f0b-0bf77f4bdf61",
|
"id": "46fde1d4-0050-428f-9f0b-0bf77f4bdf61",
|
||||||
"name": "User 1",
|
"name": "User 1",
|
||||||
"username": "user1",
|
"username": "user1",
|
||||||
|
@ -13,7 +13,7 @@ router.post('auth.info', auth(), async ctx => {
|
|||||||
|
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
data: {
|
data: {
|
||||||
user: await presentUser(ctx, user),
|
user: await presentUser(ctx, user, { includeDetails: true }),
|
||||||
team: await presentTeam(ctx, team),
|
team: await presentTeam(ctx, team),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -51,9 +51,14 @@ router.post('auth.slack', async ctx => {
|
|||||||
name: data.user.name,
|
name: data.user.name,
|
||||||
email: data.user.email,
|
email: data.user.email,
|
||||||
teamId: team.id,
|
teamId: team.id,
|
||||||
|
isAdmin: !teamExisted,
|
||||||
slackData: data.user,
|
slackData: data.user,
|
||||||
slackAccessToken: data.access_token,
|
slackAccessToken: data.access_token,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Set initial avatar
|
||||||
|
await user.updateAvatar();
|
||||||
|
await user.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!teamExisted) {
|
if (!teamExisted) {
|
||||||
@ -68,10 +73,6 @@ router.post('auth.slack', async ctx => {
|
|||||||
expires: new Date('2100'),
|
expires: new Date('2100'),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update user's avatar
|
|
||||||
await user.updateAvatar();
|
|
||||||
await user.save();
|
|
||||||
|
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
data: {
|
data: {
|
||||||
user: await presentUser(ctx, user),
|
user: await presentUser(ctx, user),
|
||||||
|
@ -59,6 +59,8 @@ router.post('hooks.slack', async ctx => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!user) throw httpErrors.BadRequest('Invalid user');
|
if (!user) throw httpErrors.BadRequest('Invalid user');
|
||||||
|
if (!user.isAdmin)
|
||||||
|
throw httpErrors.BadRequest('Only admins can add integrations');
|
||||||
|
|
||||||
const documents = await Document.searchForUser(user, text, {
|
const documents = await Document.searchForUser(user, text, {
|
||||||
limit: 5,
|
limit: 5,
|
||||||
|
70
server/api/team.js
Normal file
70
server/api/team.js
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
// @flow
|
||||||
|
import Router from 'koa-router';
|
||||||
|
import httpErrors from 'http-errors';
|
||||||
|
|
||||||
|
import User from '../models/User';
|
||||||
|
import Team from '../models/Team';
|
||||||
|
|
||||||
|
import auth from './middlewares/authentication';
|
||||||
|
import pagination from './middlewares/pagination';
|
||||||
|
import { presentUser } from '../presenters';
|
||||||
|
|
||||||
|
const router = new Router();
|
||||||
|
router.use(auth({ adminOnly: true }));
|
||||||
|
|
||||||
|
router.post('team.users', pagination(), async ctx => {
|
||||||
|
const user = ctx.state.user;
|
||||||
|
|
||||||
|
const users = await User.findAll({
|
||||||
|
where: {
|
||||||
|
teamId: user.teamId,
|
||||||
|
},
|
||||||
|
order: [['createdAt', 'DESC']],
|
||||||
|
offset: ctx.state.pagination.offset,
|
||||||
|
limit: ctx.state.pagination.limit,
|
||||||
|
});
|
||||||
|
|
||||||
|
ctx.body = {
|
||||||
|
pagination: ctx.state.pagination,
|
||||||
|
data: users.map(user => presentUser(ctx, user, { includeDetails: true })),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('team.addAdmin', async ctx => {
|
||||||
|
const { user } = ctx.body;
|
||||||
|
const admin = ctx.state.user;
|
||||||
|
ctx.assertPresent(user, 'id is required');
|
||||||
|
|
||||||
|
const team = await Team.findById(admin.teamId);
|
||||||
|
const promotedUser = await User.findOne({
|
||||||
|
where: { id: user, teamId: admin.teamId },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!promotedUser) throw httpErrors.NotFound();
|
||||||
|
|
||||||
|
await team.addAdmin(promotedUser);
|
||||||
|
|
||||||
|
ctx.body = presentUser(ctx, promotedUser, { includeDetails: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('team.removeAdmin', async ctx => {
|
||||||
|
const { user } = ctx.body;
|
||||||
|
const admin = ctx.state.user;
|
||||||
|
ctx.assertPresent(user, 'id is required');
|
||||||
|
|
||||||
|
const team = await Team.findById(admin.teamId);
|
||||||
|
const demotedUser = await User.findOne({
|
||||||
|
where: { id: user, teamId: admin.teamId },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!demotedUser) throw httpErrors.NotFound();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await team.removeAdmin(demotedUser);
|
||||||
|
ctx.body = presentUser(ctx, user, { includeDetails: true });
|
||||||
|
} catch (e) {
|
||||||
|
throw httpErrors.BadRequest(e.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
108
server/api/team.test.js
Normal file
108
server/api/team.test.js
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
/* eslint-disable flowtype/require-valid-file-annotation */
|
||||||
|
import TestServer from 'fetch-test-server';
|
||||||
|
|
||||||
|
import app from '..';
|
||||||
|
|
||||||
|
import { flushdb, seed } from '../test/support';
|
||||||
|
|
||||||
|
const server = new TestServer(app.callback());
|
||||||
|
|
||||||
|
beforeEach(flushdb);
|
||||||
|
afterAll(server.close);
|
||||||
|
|
||||||
|
describe('#team.users', async () => {
|
||||||
|
it('should return teams paginated user list', async () => {
|
||||||
|
const { admin } = await seed();
|
||||||
|
|
||||||
|
const res = await server.post('/api/team.users', {
|
||||||
|
body: { token: admin.getJwtToken() },
|
||||||
|
});
|
||||||
|
const body = await res.json();
|
||||||
|
|
||||||
|
expect(res.status).toEqual(200);
|
||||||
|
expect(body).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should require admin', async () => {
|
||||||
|
const { user } = await seed();
|
||||||
|
const res = await server.post('/api/team.users', {
|
||||||
|
body: { token: user.getJwtToken() },
|
||||||
|
});
|
||||||
|
const body = await res.json();
|
||||||
|
|
||||||
|
expect(res.status).toEqual(403);
|
||||||
|
expect(body).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#team.addAdmin', async () => {
|
||||||
|
it('should promote a new admin', async () => {
|
||||||
|
const { admin, user } = await seed();
|
||||||
|
|
||||||
|
const res = await server.post('/api/team.addAdmin', {
|
||||||
|
body: {
|
||||||
|
token: admin.getJwtToken(),
|
||||||
|
user: user.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const body = await res.json();
|
||||||
|
|
||||||
|
expect(res.status).toEqual(200);
|
||||||
|
expect(body).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should require admin', async () => {
|
||||||
|
const { user } = await seed();
|
||||||
|
const res = await server.post('/api/team.addAdmin', {
|
||||||
|
body: { token: user.getJwtToken() },
|
||||||
|
});
|
||||||
|
const body = await res.json();
|
||||||
|
|
||||||
|
expect(res.status).toEqual(403);
|
||||||
|
expect(body).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#team.removeAdmin', async () => {
|
||||||
|
it('should demote an admin', async () => {
|
||||||
|
const { admin, user } = await seed();
|
||||||
|
await user.update({ isAdmin: true }); // Make another admin
|
||||||
|
|
||||||
|
const res = await server.post('/api/team.removeAdmin', {
|
||||||
|
body: {
|
||||||
|
token: admin.getJwtToken(),
|
||||||
|
user: user.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const body = await res.json();
|
||||||
|
|
||||||
|
expect(res.status).toEqual(200);
|
||||||
|
expect(body).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shouldn't demote admins if only one available ", async () => {
|
||||||
|
const { admin } = await seed();
|
||||||
|
|
||||||
|
const res = await server.post('/api/team.removeAdmin', {
|
||||||
|
body: {
|
||||||
|
token: admin.getJwtToken(),
|
||||||
|
user: admin.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const body = await res.json();
|
||||||
|
|
||||||
|
expect(res.status).toEqual(400);
|
||||||
|
expect(body).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should require admin', async () => {
|
||||||
|
const { user } = await seed();
|
||||||
|
const res = await server.post('/api/team.addAdmin', {
|
||||||
|
body: { token: user.getJwtToken() },
|
||||||
|
});
|
||||||
|
const body = await res.json();
|
||||||
|
|
||||||
|
expect(res.status).toEqual(403);
|
||||||
|
expect(body).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
@ -1,6 +1,7 @@
|
|||||||
// @flow
|
// @flow
|
||||||
import { DataTypes, sequelize } from '../sequelize';
|
import { DataTypes, sequelize, Op } from '../sequelize';
|
||||||
import Collection from './Collection';
|
import Collection from './Collection';
|
||||||
|
import User from './User';
|
||||||
|
|
||||||
const Team = sequelize.define(
|
const Team = sequelize.define(
|
||||||
'team',
|
'team',
|
||||||
@ -41,4 +42,26 @@ Team.prototype.createFirstCollection = async function(userId) {
|
|||||||
return atlas;
|
return atlas;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Team.prototype.addAdmin = async function(user: User) {
|
||||||
|
return await user.update({ isAdmin: true });
|
||||||
|
};
|
||||||
|
|
||||||
|
Team.prototype.removeAdmin = async function(user: User) {
|
||||||
|
const res = await User.findAndCountAll({
|
||||||
|
where: {
|
||||||
|
teamId: user.teamId,
|
||||||
|
isAdmin: true,
|
||||||
|
id: {
|
||||||
|
[Op.ne]: user.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
limit: 1,
|
||||||
|
});
|
||||||
|
if (res.count >= 1) {
|
||||||
|
return await user.update({ isAdmin: false });
|
||||||
|
} else {
|
||||||
|
throw new Error('At least one admin is required');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export default Team;
|
export default Team;
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
exports[`presents a user 1`] = `
|
exports[`presents a user 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"avatarUrl": "http://example.com/avatar.png",
|
"avatarUrl": "http://example.com/avatar.png",
|
||||||
"email": undefined,
|
|
||||||
"id": "123",
|
"id": "123",
|
||||||
"name": "Test User",
|
"name": "Test User",
|
||||||
"username": "testuser",
|
"username": "testuser",
|
||||||
@ -13,7 +12,6 @@ Object {
|
|||||||
exports[`presents a user without slack data 1`] = `
|
exports[`presents a user without slack data 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"avatarUrl": null,
|
"avatarUrl": null,
|
||||||
"email": undefined,
|
|
||||||
"id": "123",
|
"id": "123",
|
||||||
"name": "Test User",
|
"name": "Test User",
|
||||||
"username": "testuser",
|
"username": "testuser",
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
// @flow
|
// @flow
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
import { Op } from 'sequelize';
|
||||||
import { User, Document } from '../models';
|
import { User, Document } from '../models';
|
||||||
import presentUser from './user';
|
import presentUser from './user';
|
||||||
import presentCollection from './collection';
|
import presentCollection from './collection';
|
||||||
@ -57,7 +58,7 @@ async function present(ctx: Object, document: Document, options: ?Options) {
|
|||||||
// This could be further optimized by using ctx.cache
|
// This could be further optimized by using ctx.cache
|
||||||
data.collaborators = await User.findAll({
|
data.collaborators = await User.findAll({
|
||||||
where: {
|
where: {
|
||||||
id: { $in: _.takeRight(document.collaboratorIds, 10) || [] },
|
id: { [Op.in]: _.takeRight(document.collaboratorIds, 10) || [] },
|
||||||
},
|
},
|
||||||
}).map(user => presentUser(ctx, user));
|
}).map(user => presentUser(ctx, user));
|
||||||
|
|
||||||
|
@ -1,17 +1,37 @@
|
|||||||
// @flow
|
// @flow
|
||||||
import User from '../models/User';
|
import User from '../models/User';
|
||||||
|
|
||||||
function present(ctx: Object, user: User) {
|
type Options = {
|
||||||
|
includeDetails?: boolean,
|
||||||
|
};
|
||||||
|
|
||||||
|
type UserPresentation = {
|
||||||
|
id: string,
|
||||||
|
username: string,
|
||||||
|
name: string,
|
||||||
|
avatarUrl: ?string,
|
||||||
|
email?: string,
|
||||||
|
isAdmin?: boolean,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default (
|
||||||
|
ctx: Object,
|
||||||
|
user: User,
|
||||||
|
options: Options = {}
|
||||||
|
): UserPresentation => {
|
||||||
ctx.cache.set(user.id, user);
|
ctx.cache.set(user.id, user);
|
||||||
|
|
||||||
return {
|
const userData = {};
|
||||||
id: user.id,
|
userData.id = user.id;
|
||||||
username: user.username,
|
userData.username = user.username;
|
||||||
name: user.name,
|
userData.name = user.name;
|
||||||
email: user.email,
|
userData.avatarUrl =
|
||||||
avatarUrl:
|
user.avatarUrl || (user.slackData ? user.slackData.image_192 : null);
|
||||||
user.avatarUrl || (user.slackData ? user.slackData.image_192 : null),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default present;
|
if (options.includeDetails) {
|
||||||
|
userData.isAdmin = user.isAdmin;
|
||||||
|
userData.email = user.email;
|
||||||
|
}
|
||||||
|
|
||||||
|
return userData;
|
||||||
|
};
|
||||||
|
@ -8,8 +8,10 @@ const secretKey = process.env.SECRET_KEY;
|
|||||||
export const encryptedFields = EncryptedField(Sequelize, secretKey);
|
export const encryptedFields = EncryptedField(Sequelize, secretKey);
|
||||||
|
|
||||||
export const DataTypes = Sequelize;
|
export const DataTypes = Sequelize;
|
||||||
|
export const Op = Sequelize.Op;
|
||||||
|
|
||||||
export const sequelize = new Sequelize(process.env.DATABASE_URL, {
|
export const sequelize = new Sequelize(process.env.DATABASE_URL, {
|
||||||
logging: debug('sql'),
|
logging: debug('sql'),
|
||||||
typeValidation: true,
|
typeValidation: true,
|
||||||
|
operatorsAliases: false,
|
||||||
});
|
});
|
||||||
|
@ -36,6 +36,21 @@ const seed = async () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const admin = await User.create({
|
||||||
|
id: 'fa952cff-fa64-4d42-a6ea-6955c9689046',
|
||||||
|
email: 'admin@example.com',
|
||||||
|
username: 'admin',
|
||||||
|
name: 'Admin User',
|
||||||
|
password: 'test123!',
|
||||||
|
teamId: team.id,
|
||||||
|
isAdmin: true,
|
||||||
|
slackId: 'U2399UF1P',
|
||||||
|
slackData: {
|
||||||
|
id: 'U2399UF1P',
|
||||||
|
image_192: 'http://example.com/avatar.png',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
let collection = await Collection.create({
|
let collection = await Collection.create({
|
||||||
id: '26fde1d4-0050-428f-9f0b-0bf77f8bdf62',
|
id: '26fde1d4-0050-428f-9f0b-0bf77f8bdf62',
|
||||||
name: 'Collection',
|
name: 'Collection',
|
||||||
@ -59,6 +74,7 @@ const seed = async () => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
user,
|
user,
|
||||||
|
admin,
|
||||||
collection,
|
collection,
|
||||||
document,
|
document,
|
||||||
team,
|
team,
|
||||||
|
32
yarn.lock
32
yarn.lock
@ -3611,9 +3611,9 @@ generic-pool@2.4.3:
|
|||||||
version "2.4.3"
|
version "2.4.3"
|
||||||
resolved "https://registry.yarnpkg.com/generic-pool/-/generic-pool-2.4.3.tgz#780c36f69dfad05a5a045dd37be7adca11a4f6ff"
|
resolved "https://registry.yarnpkg.com/generic-pool/-/generic-pool-2.4.3.tgz#780c36f69dfad05a5a045dd37be7adca11a4f6ff"
|
||||||
|
|
||||||
generic-pool@^3.1.6:
|
generic-pool@^3.1.8:
|
||||||
version "3.1.7"
|
version "3.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/generic-pool/-/generic-pool-3.1.7.tgz#dac22b2c7a7a04e41732f7d8d2d25a303c88f662"
|
resolved "https://registry.yarnpkg.com/generic-pool/-/generic-pool-3.2.0.tgz#c1d485ecbd6f18c0513d4741d098a6715eaeeca8"
|
||||||
|
|
||||||
get-caller-file@^1.0.1:
|
get-caller-file@^1.0.1:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
@ -8359,12 +8359,12 @@ restore-cursor@^1.0.1:
|
|||||||
exit-hook "^1.0.0"
|
exit-hook "^1.0.0"
|
||||||
onetime "^1.0.0"
|
onetime "^1.0.0"
|
||||||
|
|
||||||
retry-as-promised@^2.0.0:
|
retry-as-promised@^2.3.1:
|
||||||
version "2.3.0"
|
version "2.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/retry-as-promised/-/retry-as-promised-2.3.0.tgz#27bf5ccd999932b31665696825cf3630c27c562d"
|
resolved "https://registry.yarnpkg.com/retry-as-promised/-/retry-as-promised-2.3.2.tgz#cd974ee4fd9b5fe03cbf31871ee48221c07737b7"
|
||||||
dependencies:
|
dependencies:
|
||||||
bluebird "^3.4.6"
|
bluebird "^3.4.6"
|
||||||
debug "^2.2.0"
|
debug "^2.6.9"
|
||||||
|
|
||||||
right-align@^0.1.1:
|
right-align@^0.1.1:
|
||||||
version "0.1.3"
|
version "0.1.3"
|
||||||
@ -8527,26 +8527,26 @@ sequelize-encrypted@0.1.0:
|
|||||||
version "0.1.0"
|
version "0.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/sequelize-encrypted/-/sequelize-encrypted-0.1.0.tgz#f9c7a94dc1b4413e1347a49f06cd07b7f3bf9916"
|
resolved "https://registry.yarnpkg.com/sequelize-encrypted/-/sequelize-encrypted-0.1.0.tgz#f9c7a94dc1b4413e1347a49f06cd07b7f3bf9916"
|
||||||
|
|
||||||
sequelize@^4.3.1:
|
sequelize@4.28.6:
|
||||||
version "4.8.0"
|
version "4.28.6"
|
||||||
resolved "https://registry.yarnpkg.com/sequelize/-/sequelize-4.8.0.tgz#1987a97deceb749da7e25cd27059adb69dbf81c2"
|
resolved "https://registry.yarnpkg.com/sequelize/-/sequelize-4.28.6.tgz#44b4b69f550bc53f41135bf8db73c5d492cb7e64"
|
||||||
dependencies:
|
dependencies:
|
||||||
bluebird "^3.4.6"
|
bluebird "^3.4.6"
|
||||||
cls-bluebird "^2.0.1"
|
cls-bluebird "^2.0.1"
|
||||||
debug "^3.0.0"
|
debug "^3.0.0"
|
||||||
depd "^1.1.0"
|
depd "^1.1.0"
|
||||||
dottie "^2.0.0"
|
dottie "^2.0.0"
|
||||||
generic-pool "^3.1.6"
|
generic-pool "^3.1.8"
|
||||||
inflection "1.12.0"
|
inflection "1.12.0"
|
||||||
lodash "^4.17.1"
|
lodash "^4.17.1"
|
||||||
moment "^2.13.0"
|
moment "^2.13.0"
|
||||||
moment-timezone "^0.5.4"
|
moment-timezone "^0.5.4"
|
||||||
retry-as-promised "^2.0.0"
|
retry-as-promised "^2.3.1"
|
||||||
semver "^5.0.1"
|
semver "^5.0.1"
|
||||||
terraformer-wkt-parser "^1.1.2"
|
terraformer-wkt-parser "^1.1.2"
|
||||||
toposort-class "^1.0.1"
|
toposort-class "^1.0.1"
|
||||||
uuid "^3.0.0"
|
uuid "^3.0.0"
|
||||||
validator "^8.0.0"
|
validator "^9.1.0"
|
||||||
wkx "^0.4.1"
|
wkx "^0.4.1"
|
||||||
|
|
||||||
sequencify@~0.0.7:
|
sequencify@~0.0.7:
|
||||||
@ -9726,9 +9726,9 @@ validator@5.2.0:
|
|||||||
version "5.2.0"
|
version "5.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/validator/-/validator-5.2.0.tgz#e66fb3ec352348c1f7232512328738d8d66a9689"
|
resolved "https://registry.yarnpkg.com/validator/-/validator-5.2.0.tgz#e66fb3ec352348c1f7232512328738d8d66a9689"
|
||||||
|
|
||||||
validator@^8.0.0:
|
validator@^9.1.0:
|
||||||
version "8.1.0"
|
version "9.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/validator/-/validator-8.1.0.tgz#89cf6b512ff71eba886afd8d10d47f8dc800eac0"
|
resolved "https://registry.yarnpkg.com/validator/-/validator-9.2.0.tgz#ad216eed5f37cac31a6fe00ceab1f6b88bded03e"
|
||||||
|
|
||||||
value-equal@^0.4.0:
|
value-equal@^0.4.0:
|
||||||
version "0.4.0"
|
version "0.4.0"
|
||||||
|
Reference in New Issue
Block a user