[chore] added prettier

This commit is contained in:
Jori Lallo
2017-04-26 21:47:03 -07:00
parent fcdeb67bc5
commit 08b1609440
53 changed files with 1983 additions and 928 deletions

View File

@@ -8,10 +8,8 @@ import { ApiKey } from '../models';
const router = new Router();
router.post('apiKeys.create', auth(), async (ctx) => {
const {
name,
} = ctx.body;
router.post('apiKeys.create', auth(), async ctx => {
const { name } = ctx.body;
ctx.assertPresent(name, 'name is required');
const user = ctx.state.user;
@@ -26,15 +24,13 @@ router.post('apiKeys.create', auth(), async (ctx) => {
};
});
router.post('apiKeys.list', auth(), pagination(), async (ctx) => {
router.post('apiKeys.list', auth(), pagination(), async ctx => {
const user = ctx.state.user;
const keys = await ApiKey.findAll({
where: {
userId: user.id,
},
order: [
['createdAt', 'DESC'],
],
order: [['createdAt', 'DESC']],
offset: ctx.state.pagination.offset,
limit: ctx.state.pagination.limit,
});
@@ -49,10 +45,8 @@ router.post('apiKeys.list', auth(), pagination(), async (ctx) => {
};
});
router.post('apiKeys.delete', auth(), async (ctx) => {
const {
id,
} = ctx.body;
router.post('apiKeys.delete', auth(), async ctx => {
const { id } = ctx.body;
ctx.assertPresent(id, 'id is required');
const user = ctx.state.user;

View File

@@ -9,7 +9,7 @@ import { User, Team } from '../models';
const router = new Router();
router.post('auth.signup', async (ctx) => {
router.post('auth.signup', async ctx => {
const { username, name, email, password } = ctx.request.body;
ctx.assertPresent(username, 'name is required');
@@ -19,11 +19,19 @@ router.post('auth.signup', async (ctx) => {
ctx.assertPresent(password, 'password is required');
if (await User.findOne({ where: { email } })) {
throw apiError(400, 'user_exists_with_email', 'User already exists with this email');
throw apiError(
400,
'user_exists_with_email',
'User already exists with this email'
);
}
if (await User.findOne({ where: { username } })) {
throw apiError(400, 'user_exists_with_username', 'User already exists with this username');
throw apiError(
400,
'user_exists_with_username',
'User already exists with this username'
);
}
const user = await User.create({
@@ -33,13 +41,15 @@ router.post('auth.signup', async (ctx) => {
password,
});
ctx.body = { data: {
user: await presentUser(ctx, user),
accessToken: user.getJwtToken(),
} };
ctx.body = {
data: {
user: await presentUser(ctx, user),
accessToken: user.getJwtToken(),
},
};
});
router.post('auth.login', async (ctx) => {
router.post('auth.login', async ctx => {
const { username, password } = ctx.request.body;
ctx.assertPresent(username, 'username/email is required');
@@ -47,10 +57,9 @@ router.post('auth.login', async (ctx) => {
let user;
if (username) {
user = await User.findOne({ where: Sequelize.or(
{ email: username },
{ username },
) });
user = await User.findOne({
where: Sequelize.or({ email: username }, { username }),
});
} else {
throw apiError(400, 'invalid_credentials', 'username or email is invalid');
}
@@ -67,13 +76,15 @@ router.post('auth.login', async (ctx) => {
throw apiError(400, 'invalid_password', 'Invalid password');
}
ctx.body = { data: {
user: await presentUser(ctx, user),
accessToken: user.getJwtToken(),
} };
ctx.body = {
data: {
user: await presentUser(ctx, user),
accessToken: user.getJwtToken(),
},
};
});
router.post('auth.slack', async (ctx) => {
router.post('auth.slack', async ctx => {
const { code } = ctx.body;
ctx.assertPresent(code, 'code is required');
@@ -86,7 +97,9 @@ router.post('auth.slack', async (ctx) => {
let data;
try {
const response = await fetch(`https://slack.com/api/oauth.access?${querystring.stringify(body)}`);
const response = await fetch(
`https://slack.com/api/oauth.access?${querystring.stringify(body)}`
);
data = await response.json();
} catch (e) {
throw httpErrors.BadRequest();
@@ -97,7 +110,11 @@ router.post('auth.slack', async (ctx) => {
// Temp to block
const allowedSlackDomains = process.env.ALLOWED_SLACK_DOMAINS.split(',');
if (!allowedSlackDomains.includes(data.team.domain)) {
throw apiError(400, 'invalid_slack_team', 'Atlas is currently in private beta');
throw apiError(
400,
'invalid_slack_team',
'Atlas is currently in private beta'
);
}
// User
@@ -138,14 +155,16 @@ router.post('auth.slack', async (ctx) => {
await team.createFirstAtlas(user.id);
}
ctx.body = { data: {
user: await presentUser(ctx, user),
team: await presentTeam(ctx, team),
accessToken: user.getJwtToken(),
} };
ctx.body = {
data: {
user: await presentUser(ctx, user),
team: await presentTeam(ctx, team),
accessToken: user.getJwtToken(),
},
};
});
router.post('auth.slackCommands', async (ctx) => {
router.post('auth.slackCommands', async ctx => {
const { code } = ctx.body;
ctx.assertPresent(code, 'code is required');
@@ -158,7 +177,9 @@ router.post('auth.slackCommands', async (ctx) => {
let data;
try {
const response = await fetch(`https://slack.com/api/oauth.access?${querystring.stringify(body)}`);
const response = await fetch(
`https://slack.com/api/oauth.access?${querystring.stringify(body)}`
);
data = await response.json();
} catch (e) {
throw httpErrors.BadRequest();
@@ -167,5 +188,4 @@ router.post('auth.slackCommands', async (ctx) => {
if (!data.ok) throw httpErrors.BadRequest(data.error);
});
export default router;

View File

@@ -37,7 +37,6 @@ describe('#auth.signup', async () => {
expect(body).toMatchSnapshot();
});
it('should require valid email', async () => {
const res = await server.post('/api/auth.signup', {
body: {

View File

@@ -9,12 +9,8 @@ import { Atlas } from '../models';
const router = new Router();
router.post('collections.create', auth(), async (ctx) => {
const {
name,
description,
type,
} = ctx.body;
router.post('collections.create', auth(), async ctx => {
const { name, description, type } = ctx.body;
ctx.assertPresent(name, 'name is required');
const user = ctx.state.user;
@@ -32,7 +28,7 @@ router.post('collections.create', auth(), async (ctx) => {
};
});
router.post('collections.info', auth(), async (ctx) => {
router.post('collections.info', auth(), async ctx => {
const { id } = ctx.body;
ctx.assertPresent(id, 'id is required');
@@ -51,25 +47,24 @@ router.post('collections.info', auth(), async (ctx) => {
};
});
router.post('collections.list', auth(), pagination(), async (ctx) => {
router.post('collections.list', auth(), pagination(), async ctx => {
const user = ctx.state.user;
const collections = await Atlas.findAll({
where: {
teamId: user.teamId,
},
order: [
['updatedAt', 'DESC'],
],
order: [['updatedAt', 'DESC']],
offset: ctx.state.pagination.offset,
limit: ctx.state.pagination.limit,
});
// Atlases
let data = [];
await Promise.all(collections.map(async (atlas) => {
return data.push(await presentCollection(ctx, atlas, true));
}));
await Promise.all(
collections.map(async atlas => {
return data.push(await presentCollection(ctx, atlas, true));
})
);
data = _.orderBy(data, ['updatedAt'], ['desc']);
@@ -79,7 +74,7 @@ router.post('collections.list', auth(), pagination(), async (ctx) => {
};
});
router.post('collections.updateNavigationTree', auth(), async (ctx) => {
router.post('collections.updateNavigationTree', auth(), async ctx => {
const { id, tree } = ctx.body;
ctx.assertPresent(id, 'id is required');

View File

@@ -12,7 +12,7 @@ import { Document, Atlas } from '../models';
const router = new Router();
const getDocumentForId = async (id) => {
const getDocumentForId = async id => {
try {
let document;
if (isUUID(id)) {
@@ -38,7 +38,7 @@ const getDocumentForId = async (id) => {
};
// FIXME: This really needs specs :/
router.post('documents.info', auth(), async (ctx) => {
router.post('documents.info', auth(), async ctx => {
const { id } = ctx.body;
ctx.assertPresent(id, 'id is required');
const document = await getDocumentForId(id);
@@ -69,7 +69,7 @@ router.post('documents.info', auth(), async (ctx) => {
}
});
router.post('documents.search', auth(), async (ctx) => {
router.post('documents.search', auth(), async ctx => {
const { query } = ctx.body;
ctx.assertPresent(query, 'query is required');
@@ -78,12 +78,16 @@ router.post('documents.search', auth(), async (ctx) => {
const documents = await Document.searchForUser(user, query);
const data = [];
await Promise.all(documents.map(async (document) => {
data.push(await presentDocument(ctx, document, {
includeCollection: true,
includeCollaborators: true,
}));
}));
await Promise.all(
documents.map(async document => {
data.push(
await presentDocument(ctx, document, {
includeCollection: true,
includeCollaborators: true,
})
);
})
);
ctx.body = {
pagination: ctx.state.pagination,
@@ -91,14 +95,8 @@ router.post('documents.search', auth(), async (ctx) => {
};
});
router.post('documents.create', auth(), async (ctx) => {
const {
collection,
title,
text,
parentDocument,
} = ctx.body;
router.post('documents.create', auth(), async ctx => {
const { collection, title, text, parentDocument } = ctx.body;
ctx.assertPresent(collection, 'collection is required');
ctx.assertPresent(title, 'title is required');
ctx.assertPresent(text, 'text is required');
@@ -115,7 +113,7 @@ router.post('documents.create', auth(), async (ctx) => {
const document = await (() => {
return new Promise(resolve => {
lock(ownerCollection.id, 10000, async (done) => {
lock(ownerCollection.id, 10000, async done => {
// FIXME: should we validate the existance of parentDocument?
let parentDocumentObj = {};
if (parentDocument && ownerCollection.type === 'atlas') {
@@ -158,12 +156,8 @@ router.post('documents.create', auth(), async (ctx) => {
};
});
router.post('documents.update', auth(), async (ctx) => {
const {
id,
title,
text,
} = ctx.body;
router.post('documents.update', auth(), async ctx => {
const { id, title, text } = ctx.body;
ctx.assertPresent(id, 'id is required');
ctx.assertPresent(title, 'title is required');
ctx.assertPresent(text, 'text is required');
@@ -171,7 +165,8 @@ router.post('documents.update', auth(), async (ctx) => {
const user = ctx.state.user;
const document = await getDocumentForId(id);
if (!document || document.teamId !== user.teamId) throw httpErrors.BadRequest();
if (!document || document.teamId !== user.teamId)
throw httpErrors.BadRequest();
// Update document
document.title = title;
@@ -194,23 +189,22 @@ router.post('documents.update', auth(), async (ctx) => {
};
});
router.post('documents.delete', auth(), async (ctx) => {
const {
id,
} = ctx.body;
router.post('documents.delete', auth(), async ctx => {
const { id } = ctx.body;
ctx.assertPresent(id, 'id is required');
const user = ctx.state.user;
const document = await getDocumentForId(id);
const collection = await Atlas.findById(document.atlasId);
if (!document || document.teamId !== user.teamId) throw httpErrors.BadRequest();
if (!document || document.teamId !== user.teamId)
throw httpErrors.BadRequest();
// TODO: Add locking
if (collection.type === 'atlas') {
// Don't allow deletion of root docs
if (!document.parentDocumentId) {
throw httpErrors.BadRequest('Unable to delete atlas\'s root document');
throw httpErrors.BadRequest("Unable to delete atlas's root document");
}
// Delete all chilren

View File

@@ -4,17 +4,14 @@ import { Document, User } from '../models';
const router = new Router();
router.post('hooks.slack', async (ctx) => {
const {
token,
user_id,
text,
} = ctx.body;
router.post('hooks.slack', async ctx => {
const { token, user_id, text } = ctx.body;
ctx.assertPresent(token, 'token is required');
ctx.assertPresent(user_id, 'user_id is required');
ctx.assertPresent(text, 'text is required');
if (token !== process.env.SLACK_VERIFICATION_TOKEN) throw httpErrors.BadRequest('Invalid token');
if (token !== process.env.SLACK_VERIFICATION_TOKEN)
throw httpErrors.BadRequest('Invalid token');
const user = await User.find({
where: {
@@ -31,7 +28,9 @@ router.post('hooks.slack', async (ctx) => {
const results = [];
let number = 1;
for (const document of documents) {
results.push(`${number}. <${process.env.URL}${document.getUrl()}|${document.title}>`);
results.push(
`${number}. <${process.env.URL}${document.getUrl()}|${document.title}>`
);
number += 1;
}

View File

@@ -1,20 +1,17 @@
import uuid from 'uuid';
import Router from 'koa-router';
import {
makePolicy,
signPolicy,
} from '../utils/s3';
import { makePolicy, signPolicy } from '../utils/s3';
import auth from './middlewares/authentication';
import { presentUser } from '../presenters';
const router = new Router();
router.post('user.info', auth(), async (ctx) => {
router.post('user.info', auth(), async ctx => {
ctx.body = { data: await presentUser(ctx, ctx.state.user) };
});
router.post('user.s3Upload', auth(), async (ctx) => {
router.post('user.s3Upload', auth(), async ctx => {
const { filename, kind, size } = ctx.body;
ctx.assertPresent(filename, 'filename is required');
ctx.assertPresent(kind, 'kind is required');
@@ -24,25 +21,27 @@ router.post('user.s3Upload', auth(), async (ctx) => {
const key = `${s3Key}/${filename}`;
const policy = makePolicy();
ctx.body = { data: {
maxUploadSize: process.env.AWS_S3_UPLOAD_MAX_SIZE,
uploadUrl: process.env.AWS_S3_UPLOAD_BUCKET_URL,
form: {
AWSAccessKeyId: process.env.AWS_ACCESS_KEY_ID,
'Cache-Control': 'max-age=31557600',
'Content-Type': kind,
key,
acl: 'public-read',
signature: signPolicy(policy),
policy,
ctx.body = {
data: {
maxUploadSize: process.env.AWS_S3_UPLOAD_MAX_SIZE,
uploadUrl: process.env.AWS_S3_UPLOAD_BUCKET_URL,
form: {
AWSAccessKeyId: process.env.AWS_ACCESS_KEY_ID,
'Cache-Control': 'max-age=31557600',
'Content-Type': kind,
key,
acl: 'public-read',
signature: signPolicy(policy),
policy,
},
asset: {
contentType: kind,
url: `${process.env.AWS_S3_UPLOAD_BUCKET_URL}${s3Key}/${filename}`,
name: filename,
size,
},
},
asset: {
contentType: kind,
url: `${process.env.AWS_S3_UPLOAD_BUCKET_URL}${s3Key}/${filename}`,
name: filename,
size,
},
} };
};
});
export default router;

View File

@@ -8,7 +8,7 @@ export default function cache() {
ctx.cache.set = async (id, value) => {
ctx.cache[id] = value;
}
};
ctx.cache.get = async (id, def) => {
if (ctx.cache[id]) {

View File

@@ -5,7 +5,7 @@ export default function methodOverride(_options) {
if (ctx.method === 'POST') {
ctx.body = ctx.request.body;
} else if (ctx.method === 'GET') {
ctx.method= 'POST'; // eslint-disable-line
ctx.method = 'POST'; // eslint-disable-line
ctx.body = queryString.parse(ctx.querystring);
}
return next();

View File

@@ -2,9 +2,8 @@ export default function subdomainRedirect(options) {
return async function subdomainRedirectMiddleware(ctx, next) {
if (ctx.headers.host === 'beautifulatlas.com') {
ctx.redirect('https://www.' + ctx.headers.host + ctx.path);
}
else {
} else {
return next();
}
}
};
};
}

View File

@@ -1,12 +1,12 @@
'use strict';
module.exports = {
up: function (queryInterface, Sequelize) {
up: function(queryInterface, Sequelize) {
queryInterface.createTable('teams', {
id: {
type: 'UUID',
allowNull: false,
primaryKey: true
primaryKey: true,
},
name: {
type: 'CHARACTER VARYING',
@@ -15,7 +15,7 @@ module.exports = {
slackId: {
type: 'CHARACTER VARYING',
allowNull: true,
unique: true
unique: true,
},
slackData: {
type: 'JSONB',
@@ -28,14 +28,14 @@ module.exports = {
updatedAt: {
type: 'TIMESTAMP WITH TIME ZONE',
allowNull: false,
}
},
});
queryInterface.createTable('atlases', {
id: {
type: 'UUID',
allowNull: false,
primaryKey: true
primaryKey: true,
},
name: {
type: 'CHARACTER VARYING',
@@ -68,14 +68,14 @@ module.exports = {
// model: "teams",
// key: "id",
// }
}
},
});
queryInterface.createTable('users', {
id: {
type: 'UUID',
allowNull: false,
primaryKey: true
primaryKey: true,
},
email: {
type: 'CHARACTER VARYING',
@@ -96,7 +96,8 @@ module.exports = {
},
slackAccessToken: {
type: 'bytea',
allowNull: true, },
allowNull: true,
},
slackId: {
type: 'CHARACTER VARYING',
unique: true,
@@ -125,46 +126,48 @@ module.exports = {
// model: "teams",
// key: "id",
// }
}
},
});
queryInterface.createTable('documents', {
id:
{ type: 'UUID',
allowNull: false,
primaryKey: true },
urlId:
{ type: 'CHARACTER VARYING',
allowNull: false,
unique: true, },
private:
{ type: 'BOOLEAN',
allowNull: false,
defaultValue: true,
},
title:
{ type: 'CHARACTER VARYING',
allowNull: false,
id: {
type: 'UUID',
allowNull: false,
primaryKey: true,
},
text:
{ type: 'TEXT',
allowNull: true,
urlId: {
type: 'CHARACTER VARYING',
allowNull: false,
unique: true,
},
html:
{ type: 'TEXT',
allowNull: true,
private: {
type: 'BOOLEAN',
allowNull: false,
defaultValue: true,
},
preview:
{ type: 'TEXT',
allowNull: true,
title: {
type: 'CHARACTER VARYING',
allowNull: false,
},
createdAt:
{ type: 'TIMESTAMP WITH TIME ZONE',
allowNull: false,
text: {
type: 'TEXT',
allowNull: true,
},
updatedAt:
{ type: 'TIMESTAMP WITH TIME ZONE',
allowNull: false,
html: {
type: 'TEXT',
allowNull: true,
},
preview: {
type: 'TEXT',
allowNull: true,
},
createdAt: {
type: 'TIMESTAMP WITH TIME ZONE',
allowNull: false,
},
updatedAt: {
type: 'TIMESTAMP WITH TIME ZONE',
allowNull: false,
},
userId: {
type: 'UUID',
@@ -189,11 +192,11 @@ module.exports = {
// model: "teams",
// key: "id",
// }
}
},
});
},
down: function (queryInterface, Sequelize) {
down: function(queryInterface, Sequelize) {
queryInterface.dropAllTables();
}
},
};

View File

@@ -1,18 +1,14 @@
'use strict';
module.exports = {
up: function (queryInterface, Sequelize) {
queryInterface.addColumn(
'documents',
'parentDocumentId',
{
type: Sequelize.UUID,
allowNull: true,
}
);
up: function(queryInterface, Sequelize) {
queryInterface.addColumn('documents', 'parentDocumentId', {
type: Sequelize.UUID,
allowNull: true,
});
},
down: function (queryInterface, Sequelize) {
down: function(queryInterface, Sequelize) {
queryInterface.removeColumn('documents', 'parentDocumentId');
}
},
};

View File

@@ -1,7 +1,7 @@
'use strict';
module.exports = {
up: function (queryInterface, Sequelize) {
up: function(queryInterface, Sequelize) {
queryInterface.addIndex('documents', ['urlId']);
queryInterface.addIndex('documents', ['id', 'atlasId']);
queryInterface.addIndex('documents', ['id', 'teamId']);
@@ -14,7 +14,7 @@ module.exports = {
queryInterface.addIndex('users', ['slackId']);
},
down: function (queryInterface, Sequelize) {
down: function(queryInterface, Sequelize) {
queryInterface.removeIndex('documents', ['urlId']);
queryInterface.removeIndex('documents', ['id', 'atlasId']);
queryInterface.removeIndex('documents', ['id', 'teamId']);
@@ -25,5 +25,5 @@ module.exports = {
queryInterface.removeIndex('teams', ['slackId']);
queryInterface.removeIndex('users', ['slackId']);
}
},
};

View File

@@ -1,43 +1,43 @@
'use strict';
module.exports = {
up: function (queryInterface, Sequelize) {
up: function(queryInterface, Sequelize) {
queryInterface.createTable('revisions', {
id: {
type: 'UUID',
allowNull: false,
primaryKey: true
primaryKey: true,
},
title:
{ type: 'CHARACTER VARYING',
allowNull: false,
title: {
type: 'CHARACTER VARYING',
allowNull: false,
},
text:
{ type: 'TEXT',
allowNull: true,
text: {
type: 'TEXT',
allowNull: true,
},
html:
{ type: 'TEXT',
allowNull: true,
html: {
type: 'TEXT',
allowNull: true,
},
preview:
{ type: 'TEXT',
allowNull: true,
preview: {
type: 'TEXT',
allowNull: true,
},
createdAt:
{ type: 'TIMESTAMP WITH TIME ZONE',
allowNull: false,
createdAt: {
type: 'TIMESTAMP WITH TIME ZONE',
allowNull: false,
},
updatedAt:
{ type: 'TIMESTAMP WITH TIME ZONE',
allowNull: false,
updatedAt: {
type: 'TIMESTAMP WITH TIME ZONE',
allowNull: false,
},
userId: {
type: 'UUID',
allowNull: false,
references: {
model: 'users',
}
},
},
documentId: {
type: 'UUID',
@@ -45,36 +45,28 @@ module.exports = {
references: {
model: 'documents',
onDelete: 'CASCADE',
}
},
},
});
queryInterface.addColumn(
'documents',
'lastModifiedById',
{
type: 'UUID',
allowNull: false,
references: {
model: 'users',
}
}
);
queryInterface.addColumn('documents', 'lastModifiedById', {
type: 'UUID',
allowNull: false,
references: {
model: 'users',
},
});
queryInterface.addColumn(
'documents',
'revisionCount',
{
type: 'INTEGER',
defaultValue: 0
}
);
queryInterface.addColumn('documents', 'revisionCount', {
type: 'INTEGER',
defaultValue: 0,
});
},
down: function (queryInterface, Sequelize) {
down: function(queryInterface, Sequelize) {
queryInterface.dropTable('revisions');
queryInterface.removeColumn('documents', 'lastModifiedById');
queryInterface.removeColumn('documents', 'revisionCount');
}
},
};

View File

@@ -1,7 +1,7 @@
'use strict';
module.exports = {
up: function (queryInterface, Sequelize) {
up: function(queryInterface, Sequelize) {
const searchDocument = `
ALTER TABLE documents ADD COLUMN "searchVector" tsvector;
CREATE INDEX documents_tsv_idx ON documents USING gin("searchVector");
@@ -40,7 +40,7 @@ ON atlases FOR EACH ROW EXECUTE PROCEDURE atlases_search_trigger();
queryInterface.sequelize.query(searchAtlas);
},
down: function (queryInterface, Sequelize) {
down: function(queryInterface, Sequelize) {
// TODO?
}
},
};

View File

@@ -1,18 +1,14 @@
'use strict';
module.exports = {
up: function (queryInterface, Sequelize) {
queryInterface.addColumn(
'atlases',
'creatorId',
{
type: Sequelize.UUID,
allowNull: true,
}
);
up: function(queryInterface, Sequelize) {
queryInterface.addColumn('atlases', 'creatorId', {
type: Sequelize.UUID,
allowNull: true,
});
},
down: function (queryInterface, Sequelize) {
down: function(queryInterface, Sequelize) {
queryInterface.removeColumn('atlases', 'creatorId');
}
},
};

View File

@@ -1,28 +1,20 @@
'use strict';
module.exports = {
up: function (queryInterface, Sequelize) {
queryInterface.addColumn(
'atlases',
'deletedAt',
{
type: Sequelize.DATE,
allowNull: true,
}
);
up: function(queryInterface, Sequelize) {
queryInterface.addColumn('atlases', 'deletedAt', {
type: Sequelize.DATE,
allowNull: true,
});
queryInterface.addColumn(
'documents',
'deletedAt',
{
type: Sequelize.DATE,
allowNull: true,
}
);
queryInterface.addColumn('documents', 'deletedAt', {
type: Sequelize.DATE,
allowNull: true,
});
},
down: function (queryInterface, Sequelize) {
down: function(queryInterface, Sequelize) {
queryInterface.removeColumn('atlases', 'deletedAt');
queryInterface.removeColumn('documents', 'deletedAt');
}
},
};

View File

@@ -1,7 +1,7 @@
'use strict';
module.exports = {
up: function (queryInterface, Sequelize) {
up: function(queryInterface, Sequelize) {
// Remove old indeces
queryInterface.removeIndex('documents', ['urlId']);
queryInterface.removeIndex('documents', ['id', 'atlasId']);
@@ -15,13 +15,17 @@ module.exports = {
queryInterface.addIndex('documents', ['urlId', 'deletedAt']);
queryInterface.addIndex('documents', ['id', 'atlasId', 'deletedAt']);
queryInterface.addIndex('documents', ['id', 'teamId', 'deletedAt']);
queryInterface.addIndex('documents', ['parentDocumentId', 'atlasId', 'deletedAt']);
queryInterface.addIndex('documents', [
'parentDocumentId',
'atlasId',
'deletedAt',
]);
queryInterface.addIndex('atlases', ['id', 'deletedAt']);
queryInterface.addIndex('atlases', ['id', 'teamId', 'deletedAt']);
},
down: function (queryInterface, Sequelize) {
down: function(queryInterface, Sequelize) {
queryInterface.addIndex('documents', ['urlId']);
queryInterface.addIndex('documents', ['id', 'atlasId']);
queryInterface.addIndex('documents', ['id', 'teamId']);
@@ -33,9 +37,13 @@ module.exports = {
queryInterface.removeIndex('documents', ['urlId', 'deletedAt']);
queryInterface.removeIndex('documents', ['id', 'atlasId', 'deletedAt']);
queryInterface.removeIndex('documents', ['id', 'teamId', 'deletedAt']);
queryInterface.removeIndex('documents', ['parentDocumentId', 'atlasId', 'deletedAt']);
queryInterface.removeIndex('documents', [
'parentDocumentId',
'atlasId',
'deletedAt',
]);
queryInterface.removeIndex('atlases', ['id', 'deletedAt']);
queryInterface.removeIndex('atlases', ['id', 'teamId', 'deletedAt']);
}
},
};

View File

@@ -1,21 +1,17 @@
'use strict';
module.exports = {
up: function (queryInterface, Sequelize) {
queryInterface.addColumn(
'documents',
'createdById',
{
type: 'UUID',
allowNull: true,
references: {
model: 'users',
},
}
);
up: function(queryInterface, Sequelize) {
queryInterface.addColumn('documents', 'createdById', {
type: 'UUID',
allowNull: true,
references: {
model: 'users',
},
});
},
down: function (queryInterface, Sequelize) {
down: function(queryInterface, Sequelize) {
queryInterface.removeColumn('documents', 'createdById');
},
};

View File

@@ -1,14 +1,12 @@
'use strict';
module.exports = {
up: function (queryInterface, Sequelize) {
queryInterface.addColumn(
'documents',
'collaboratorIds',
{ type: Sequelize.ARRAY(Sequelize.UUID) }
)
up: function(queryInterface, Sequelize) {
queryInterface.addColumn('documents', 'collaboratorIds', {
type: Sequelize.ARRAY(Sequelize.UUID),
});
},
down: function (queryInterface, Sequelize) {
down: function(queryInterface, Sequelize) {
queryInterface.removeColumn('documents', 'collaboratorIds');
},
};

View File

@@ -1,18 +1,14 @@
'use strict';
module.exports = {
up: function (queryInterface, Sequelize) {
queryInterface.addColumn(
'atlases',
'urlId',
{
type: Sequelize.STRING,
unique: true,
}
);
up: function(queryInterface, Sequelize) {
queryInterface.addColumn('atlases', 'urlId', {
type: Sequelize.STRING,
unique: true,
});
},
down: function (queryInterface, Sequelize) {
down: function(queryInterface, Sequelize) {
queryInterface.removeColumn('atlases', 'urlId');
}
},
};

View File

@@ -1,11 +1,11 @@
'use strict';
module.exports = {
up: function (queryInterface, Sequelize) {
up: function(queryInterface, Sequelize) {
queryInterface.addIndex('revisions', ['documentId']);
},
down: function (queryInterface, Sequelize) {
down: function(queryInterface, Sequelize) {
queryInterface.removeIndex('revisions', ['documentId']);
},
};

View File

@@ -1,7 +1,7 @@
'use strict';
module.exports = {
up: function (queryInterface, Sequelize) {
up: function(queryInterface, Sequelize) {
queryInterface.createTable('apiKeys', {
id: {
type: 'UUID',
@@ -40,7 +40,7 @@ module.exports = {
});
},
down: function (queryInterface, Sequelize) {
down: function(queryInterface, Sequelize) {
queryInterface.createTable('apiKeys');
},
};

View File

@@ -1,12 +1,12 @@
'use strict';
module.exports = {
up: function (queryInterface, Sequelize) {
up: function(queryInterface, Sequelize) {
queryInterface.addIndex('apiKeys', ['secret', 'deletedAt']);
queryInterface.addIndex('apiKeys', ['userId', 'deletedAt']);
},
down: function (queryInterface, Sequelize) {
down: function(queryInterface, Sequelize) {
queryInterface.removeIndex('apiKeys', ['secret', 'deletedAt']);
queryInterface.removeIndex('apiKeys', ['userId', 'deletedAt']);
},

View File

@@ -2,45 +2,29 @@
'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,
}
);
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,
}
);
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

@@ -1,44 +1,28 @@
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,
}
);
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,
}
);
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,
}
);
queryInterface.changeColumn('users', 'username', {
type: Sequelize.STRING,
unique: false,
allowNull: true,
});
},
};

View File

@@ -1,28 +1,33 @@
import {
DataTypes,
sequelize,
} from '../sequelize';
import { DataTypes, sequelize } from '../sequelize';
import randomstring from 'randomstring';
const Team = sequelize.define('team', {
id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, primaryKey: true },
name: DataTypes.STRING,
secret: { type: DataTypes.STRING, unique: true },
userId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'users',
const Team = sequelize.define(
'team',
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
name: DataTypes.STRING,
secret: { type: DataTypes.STRING, unique: true },
userId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'users',
},
},
},
}, {
tableName: 'apiKeys',
paranoid: true,
hooks: {
beforeValidate: (key) => {
key.secret = randomstring.generate(38);
{
tableName: 'apiKeys',
paranoid: true,
hooks: {
beforeValidate: key => {
key.secret = randomstring.generate(38);
},
},
},
});
}
);
export default Team;

View File

@@ -1,9 +1,6 @@
import slug from 'slug';
import randomstring from 'randomstring';
import {
DataTypes,
sequelize,
} from '../sequelize';
import { DataTypes, sequelize } from '../sequelize';
import _ from 'lodash';
import Document from './Document';
@@ -11,184 +8,207 @@ slug.defaults.mode = 'rfc3986';
const allowedAtlasTypes = [['atlas', 'journal']];
const Atlas = sequelize.define('atlas', {
id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, primaryKey: true },
urlId: { type: DataTypes.STRING, unique: true },
name: DataTypes.STRING,
description: DataTypes.STRING,
type: { type: DataTypes.STRING, validate: { isIn: allowedAtlasTypes } },
creatorId: DataTypes.UUID,
/* type: atlas */
navigationTree: DataTypes.JSONB,
}, {
tableName: 'atlases',
paranoid: true,
hooks: {
beforeValidate: (collection) => {
collection.urlId = collection.urlId || randomstring.generate(10);
const Atlas = sequelize.define(
'atlas',
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
afterCreate: async (collection) => {
if (collection.type !== 'atlas') return;
urlId: { type: DataTypes.STRING, unique: true },
name: DataTypes.STRING,
description: DataTypes.STRING,
type: { type: DataTypes.STRING, validate: { isIn: allowedAtlasTypes } },
creatorId: DataTypes.UUID,
await Document.create({
parentDocumentId: null,
atlasId: collection.id,
teamId: collection.teamId,
userId: collection.creatorId,
lastModifiedById: collection.creatorId,
createdById: collection.creatorId,
title: 'Introduction',
text: '# Introduction\n\nLets get started...',
});
await collection.buildStructure();
await collection.save();
},
/* type: atlas */
navigationTree: DataTypes.JSONB,
},
instanceMethods: {
getUrl() {
// const slugifiedName = slug(this.name);
// return `/${slugifiedName}-c${this.urlId}`;
return `/collections/${this.id}`;
},
async buildStructure() {
if (this.navigationTree) return this.navigationTree;
{
tableName: 'atlases',
paranoid: true,
hooks: {
beforeValidate: collection => {
collection.urlId = collection.urlId || randomstring.generate(10);
},
afterCreate: async collection => {
if (collection.type !== 'atlas') return;
const getNodeForDocument = async (document) => {
const children = await Document.findAll({ where: {
parentDocumentId: document.id,
atlasId: this.id,
} });
const childNodes = [];
await Promise.all(children.map(async (child) => {
return childNodes.push(await getNodeForDocument(child));
}));
return {
title: document.title,
id: document.id,
url: document.getUrl(),
children: childNodes,
};
};
const rootDocument = await Document.findOne({
where: {
await Document.create({
parentDocumentId: null,
atlasId: this.id,
},
});
this.navigationTree = await getNodeForDocument(rootDocument);
return this.navigationTree;
atlasId: collection.id,
teamId: collection.teamId,
userId: collection.creatorId,
lastModifiedById: collection.creatorId,
createdById: collection.creatorId,
title: 'Introduction',
text: '# Introduction\n\nLets get started...',
});
await collection.buildStructure();
await collection.save();
},
},
async updateNavigationTree(tree = this.navigationTree) {
const nodeIds = [];
nodeIds.push(tree.id);
instanceMethods: {
getUrl() {
// const slugifiedName = slug(this.name);
// return `/${slugifiedName}-c${this.urlId}`;
return `/collections/${this.id}`;
},
async buildStructure() {
if (this.navigationTree) return this.navigationTree;
const rootDocument = await Document.findOne({
where: {
id: tree.id,
atlasId: this.id,
},
});
if (!rootDocument) throw new Error;
const newTree = {
id: tree.id,
title: rootDocument.title,
url: rootDocument.getUrl(),
children: [],
};
const getIdsForChildren = async (children) => {
const childNodes = [];
for (const child of children) {
const childDocument = await Document.findOne({
const getNodeForDocument = async document => {
const children = await Document.findAll({
where: {
id: child.id,
parentDocumentId: document.id,
atlasId: this.id,
},
});
if (childDocument) {
childNodes.push({
id: childDocument.id,
title: childDocument.title,
url: childDocument.getUrl(),
children: await getIdsForChildren(child.children),
});
nodeIds.push(child.id);
}
}
return childNodes;
};
newTree.children = await getIdsForChildren(tree.children);
const documents = await Document.findAll({
attributes: ['id'],
where: {
atlasId: this.id,
},
});
const documentIds = documents.map(doc => doc.id);
const childNodes = [];
await Promise.all(
children.map(async child => {
return childNodes.push(await getNodeForDocument(child));
})
);
if (!_.isEqual(nodeIds.sort(), documentIds.sort())) {
throw new Error('Invalid navigation tree');
}
return {
title: document.title,
id: document.id,
url: document.getUrl(),
children: childNodes,
};
};
this.navigationTree = newTree;
await this.save();
return newTree;
},
async addNodeToNavigationTree(document) {
const newNode = {
id: document.id,
title: document.title,
url: document.getUrl(),
children: [],
};
const insertNode = (node) => {
if (document.parentDocumentId === node.id) {
node.children.push(newNode);
} else {
node.children = node.children.map(childNode => {
return insertNode(childNode);
});
}
return node;
};
this.navigationTree = insertNode(this.navigationTree);
return this.navigationTree;
},
async deleteDocument(document) {
const deleteNodeAndDocument = async (node, documentId, shouldDelete = false) => {
// Delete node if id matches
if (document.id === node.id) shouldDelete = true;
const newChildren = [];
node.children.forEach(async childNode => {
const child = await deleteNodeAndDocument(childNode, documentId, shouldDelete);
if (child) newChildren.push(child);
const rootDocument = await Document.findOne({
where: {
parentDocumentId: null,
atlasId: this.id,
},
});
node.children = newChildren;
if (shouldDelete) {
const doc = await Document.findById(node.id);
await doc.destroy();
this.navigationTree = await getNodeForDocument(rootDocument);
return this.navigationTree;
},
async updateNavigationTree(tree = this.navigationTree) {
const nodeIds = [];
nodeIds.push(tree.id);
const rootDocument = await Document.findOne({
where: {
id: tree.id,
atlasId: this.id,
},
});
if (!rootDocument) throw new Error();
const newTree = {
id: tree.id,
title: rootDocument.title,
url: rootDocument.getUrl(),
children: [],
};
const getIdsForChildren = async children => {
const childNodes = [];
for (const child of children) {
const childDocument = await Document.findOne({
where: {
id: child.id,
atlasId: this.id,
},
});
if (childDocument) {
childNodes.push({
id: childDocument.id,
title: childDocument.title,
url: childDocument.getUrl(),
children: await getIdsForChildren(child.children),
});
nodeIds.push(child.id);
}
}
return childNodes;
};
newTree.children = await getIdsForChildren(tree.children);
const documents = await Document.findAll({
attributes: ['id'],
where: {
atlasId: this.id,
},
});
const documentIds = documents.map(doc => doc.id);
if (!_.isEqual(nodeIds.sort(), documentIds.sort())) {
throw new Error('Invalid navigation tree');
}
return shouldDelete ? null : node;
};
this.navigationTree = newTree;
await this.save();
this.navigationTree = await deleteNodeAndDocument(this.navigationTree, document.id);
return newTree;
},
async addNodeToNavigationTree(document) {
const newNode = {
id: document.id,
title: document.title,
url: document.getUrl(),
children: [],
};
const insertNode = node => {
if (document.parentDocumentId === node.id) {
node.children.push(newNode);
} else {
node.children = node.children.map(childNode => {
return insertNode(childNode);
});
}
return node;
};
this.navigationTree = insertNode(this.navigationTree);
return this.navigationTree;
},
async deleteDocument(document) {
const deleteNodeAndDocument = async (
node,
documentId,
shouldDelete = false
) => {
// Delete node if id matches
if (document.id === node.id) shouldDelete = true;
const newChildren = [];
node.children.forEach(async childNode => {
const child = await deleteNodeAndDocument(
childNode,
documentId,
shouldDelete
);
if (child) newChildren.push(child);
});
node.children = newChildren;
if (shouldDelete) {
const doc = await Document.findById(node.id);
await doc.destroy();
}
return shouldDelete ? null : node;
};
this.navigationTree = await deleteNodeAndDocument(
this.navigationTree,
document.id
);
},
},
},
});
}
);
Atlas.hasMany(Document, { as: 'documents', foreignKey: 'atlasId' });

View File

@@ -1,22 +1,15 @@
import slug from 'slug';
import _ from 'lodash';
import randomstring from 'randomstring';
import {
DataTypes,
sequelize,
} from '../sequelize';
import {
convertToMarkdown,
} from '../../frontend/utils/markdown';
import {
truncateMarkdown,
} from '../utils/truncate';
import { DataTypes, sequelize } from '../sequelize';
import { convertToMarkdown } from '../../frontend/utils/markdown';
import { truncateMarkdown } from '../utils/truncate';
import User from './User';
import Revision from './Revision';
slug.defaults.mode = 'rfc3986';
const createRevision = async (doc) => {
const createRevision = async doc => {
// Create revision of the current (latest)
await Revision.create({
title: doc.title,
@@ -28,7 +21,7 @@ const createRevision = async (doc) => {
});
};
const documentBeforeSave = async (doc) => {
const documentBeforeSave = async doc => {
doc.html = convertToMarkdown(doc.text);
doc.preview = truncateMarkdown(doc.text, 160);
@@ -52,50 +45,58 @@ const documentBeforeSave = async (doc) => {
return doc;
};
const Document = sequelize.define('document', {
id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, primaryKey: true },
urlId: { type: DataTypes.STRING, primaryKey: true },
private: { type: DataTypes.BOOLEAN, defaultValue: true },
title: DataTypes.STRING,
text: DataTypes.TEXT,
html: DataTypes.TEXT,
preview: DataTypes.TEXT,
revisionCount: { type: DataTypes.INTEGER, defaultValue: 0 },
const Document = sequelize.define(
'document',
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
urlId: { type: DataTypes.STRING, primaryKey: true },
private: { type: DataTypes.BOOLEAN, defaultValue: true },
title: DataTypes.STRING,
text: DataTypes.TEXT,
html: DataTypes.TEXT,
preview: DataTypes.TEXT,
revisionCount: { type: DataTypes.INTEGER, defaultValue: 0 },
parentDocumentId: DataTypes.UUID,
createdById: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'users',
parentDocumentId: DataTypes.UUID,
createdById: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'users',
},
},
},
lastModifiedById: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'users',
lastModifiedById: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'users',
},
},
collaboratorIds: DataTypes.ARRAY(DataTypes.UUID),
},
collaboratorIds: DataTypes.ARRAY(DataTypes.UUID),
}, {
paranoid: true,
hooks: {
beforeValidate: (doc) => {
doc.urlId = doc.urlId || randomstring.generate(10);
{
paranoid: true,
hooks: {
beforeValidate: doc => {
doc.urlId = doc.urlId || randomstring.generate(10);
},
beforeCreate: documentBeforeSave,
beforeUpdate: documentBeforeSave,
afterCreate: async doc => await createRevision(doc),
afterUpdate: async doc => await createRevision(doc),
},
beforeCreate: documentBeforeSave,
beforeUpdate: documentBeforeSave,
afterCreate: async (doc) => await createRevision(doc),
afterUpdate: async (doc) => await createRevision(doc),
},
instanceMethods: {
getUrl() {
const slugifiedTitle = slug(this.title);
return `/d/${slugifiedTitle}-${this.urlId}`;
instanceMethods: {
getUrl() {
const slugifiedTitle = slug(this.title);
return `/d/${slugifiedTitle}-${this.urlId}`;
},
},
},
});
}
);
Document.belongsTo(User);
@@ -112,18 +113,14 @@ Document.searchForUser = async (user, query, options = {}) => {
LIMIT :limit OFFSET :offset;
`;
const documents = await sequelize
.query(
sql,
{
replacements: {
query,
limit,
offset,
},
model: Document,
}
);
const documents = await sequelize.query(sql, {
replacements: {
query,
limit,
offset,
},
model: Document,
});
return documents;
};

View File

@@ -1,10 +1,11 @@
import {
DataTypes,
sequelize,
} from '../sequelize';
import { DataTypes, sequelize } from '../sequelize';
const Revision = sequelize.define('revision', {
id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, primaryKey: true },
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
title: DataTypes.STRING,
text: DataTypes.TEXT,
html: DataTypes.TEXT,
@@ -15,7 +16,7 @@ const Revision = sequelize.define('revision', {
allowNull: false,
references: {
model: 'users',
}
},
},
documentId: {
@@ -24,7 +25,7 @@ const Revision = sequelize.define('revision', {
references: {
model: 'documents',
onDelete: 'CASCADE',
}
},
},
});

View File

@@ -1,36 +1,41 @@
import {
DataTypes,
sequelize,
} from '../sequelize';
import { DataTypes, sequelize } from '../sequelize';
import Atlas from './Atlas';
import Document from './Document';
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, allowNull: true },
slackData: DataTypes.JSONB,
}, {
instanceMethods: {
async createFirstAtlas(userId) {
const atlas = await Atlas.create({
name: this.name,
description: 'Your first Atlas',
type: 'atlas',
teamId: this.id,
creatorId: userId,
});
return atlas;
const Team = sequelize.define(
'team',
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
name: DataTypes.STRING,
slackId: { type: DataTypes.STRING, allowNull: true },
slackData: DataTypes.JSONB,
},
indexes: [
{
unique: true,
fields: ['slackId'],
{
instanceMethods: {
async createFirstAtlas(userId) {
const atlas = await Atlas.create({
name: this.name,
description: 'Your first Atlas',
type: 'atlas',
teamId: this.id,
creatorId: userId,
});
return atlas;
},
},
],
});
indexes: [
{
unique: true,
fields: ['slackId'],
},
],
}
);
Team.hasMany(Atlas, { as: 'atlases' });
Team.hasMany(Document, { as: 'documents' });

View File

@@ -1,61 +1,65 @@
import crypto from 'crypto';
import bcrypt from 'bcrypt';
import {
DataTypes,
sequelize,
encryptedFields,
} from '../sequelize';
import { DataTypes, sequelize, encryptedFields } from '../sequelize';
import JWT from 'jsonwebtoken';
const BCRYPT_COST = process.env.NODE_ENV !== 'production' ? 4 : 12;
const User = sequelize.define('user', {
id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, primaryKey: true },
email: { type: DataTypes.STRING, unique: true },
username: { type: DataTypes.STRING, unique: true },
name: DataTypes.STRING,
password: DataTypes.VIRTUAL,
passwordDigest: DataTypes.STRING,
isAdmin: DataTypes.BOOLEAN,
slackAccessToken: encryptedFields.vault('slackAccessToken'),
slackId: { type: DataTypes.STRING, allowNull: true },
slackData: DataTypes.JSONB,
jwtSecret: encryptedFields.vault('jwtSecret'),
}, {
instanceMethods: {
getJwtToken() {
return JWT.sign({ id: this.id }, this.jwtSecret);
const User = sequelize.define(
'user',
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
async getTeam() {
return this.team;
},
verifyPassword(password) {
return new Promise((resolve, reject) => {
if (!this.passwordDigest) {
resolve(false);
return;
}
bcrypt.compare(password, this.passwordDigest, (err, ok) => {
if (err) {
reject(err);
email: { type: DataTypes.STRING, unique: true },
username: { type: DataTypes.STRING, unique: true },
name: DataTypes.STRING,
password: DataTypes.VIRTUAL,
passwordDigest: DataTypes.STRING,
isAdmin: DataTypes.BOOLEAN,
slackAccessToken: encryptedFields.vault('slackAccessToken'),
slackId: { type: DataTypes.STRING, allowNull: true },
slackData: DataTypes.JSONB,
jwtSecret: encryptedFields.vault('jwtSecret'),
},
{
instanceMethods: {
getJwtToken() {
return JWT.sign({ id: this.id }, this.jwtSecret);
},
async getTeam() {
return this.team;
},
verifyPassword(password) {
return new Promise((resolve, reject) => {
if (!this.passwordDigest) {
resolve(false);
return;
}
resolve(ok);
});
});
},
},
indexes: [
{
fields: ['email'],
},
],
});
bcrypt.compare(password, this.passwordDigest, (err, ok) => {
if (err) {
reject(err);
return;
}
const setRandomJwtSecret = (model) => {
resolve(ok);
});
});
},
},
indexes: [
{
fields: ['email'],
},
],
}
);
const setRandomJwtSecret = model => {
model.jwtSecret = crypto.randomBytes(64).toString('hex');
};
const hashPassword = function hashPassword(model) {

View File

@@ -5,11 +5,4 @@ import Document from './Document';
import Revision from './Revision';
import ApiKey from './ApiKey';
export {
User,
Team,
Atlas,
Document,
Revision,
ApiKey,
};
export { User, Team, Atlas, Document, Revision, ApiKey };

View File

@@ -3,31 +3,25 @@ import presentUser from './user';
import ctx from '../../__mocks__/ctx';
it('presents a user', async () => {
const user = await presentUser(
ctx,
{
id: '123',
name: 'Test User',
username: 'testuser',
slackData: {
image_192: 'http://example.com/avatar.png',
},
const user = await presentUser(ctx, {
id: '123',
name: 'Test User',
username: 'testuser',
slackData: {
image_192: 'http://example.com/avatar.png',
},
);
});
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,
},
);
const user = await presentUser(ctx, {
id: '123',
name: 'Test User',
username: 'testuser',
slackData: null,
});
expect(user).toMatchSnapshot();
});

View File

@@ -16,52 +16,856 @@
limitations under the License.
*/
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.toolbox = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
"use strict";function debug(e,n){n=n||{};var t=n.debug||globalOptions.debug;t&&console.log("[sw-toolbox] "+e)}function openCache(e){var n;return e&&e.cache&&(n=e.cache.name),n=n||globalOptions.cache.name,caches.open(n)}function fetchAndCache(e,n){n=n||{};var t=n.successResponses||globalOptions.successResponses;return fetch(e.clone()).then(function(c){return"GET"===e.method&&t.test(c.status)&&openCache(n).then(function(t){t.put(e,c).then(function(){var c=n.cache||globalOptions.cache;(c.maxEntries||c.maxAgeSeconds)&&c.name&&queueCacheExpiration(e,t,c)})}),c.clone()})}function queueCacheExpiration(e,n,t){var c=cleanupCache.bind(null,e,n,t);cleanupQueue=cleanupQueue?cleanupQueue.then(c):c()}function cleanupCache(e,n,t){var c=e.url,a=t.maxAgeSeconds,u=t.maxEntries,o=t.name,r=Date.now();return debug("Updating LRU order for "+c+". Max entries is "+u+", max age is "+a),idbCacheExpiration.getDb(o).then(function(e){return idbCacheExpiration.setTimestampForUrl(e,c,r)}).then(function(e){return idbCacheExpiration.expireEntries(e,u,a,r)}).then(function(e){debug("Successfully updated IDB.");var t=e.map(function(e){return n["delete"](e)});return Promise.all(t).then(function(){debug("Done with cache cleanup.")})})["catch"](function(e){debug(e)})}function renameCache(e,n,t){return debug("Renaming cache: ["+e+"] to ["+n+"]",t),caches["delete"](n).then(function(){return Promise.all([caches.open(e),caches.open(n)]).then(function(n){var t=n[0],c=n[1];return t.keys().then(function(e){return Promise.all(e.map(function(e){return t.match(e).then(function(n){return c.put(e,n)})}))}).then(function(){return caches["delete"](e)})})})}var globalOptions=require("./options"),idbCacheExpiration=require("./idb-cache-expiration"),cleanupQueue;module.exports={debug:debug,fetchAndCache:fetchAndCache,openCache:openCache,renameCache:renameCache};
},{"./idb-cache-expiration":2,"./options":3}],2:[function(require,module,exports){
"use strict";function openDb(e){return new Promise(function(r,n){var t=indexedDB.open(DB_PREFIX+e,DB_VERSION);t.onupgradeneeded=function(){var e=t.result.createObjectStore(STORE_NAME,{keyPath:URL_PROPERTY});e.createIndex(TIMESTAMP_PROPERTY,TIMESTAMP_PROPERTY,{unique:!1})},t.onsuccess=function(){r(t.result)},t.onerror=function(){n(t.error)}})}function getDb(e){return e in cacheNameToDbPromise||(cacheNameToDbPromise[e]=openDb(e)),cacheNameToDbPromise[e]}function setTimestampForUrl(e,r,n){return new Promise(function(t,o){var i=e.transaction(STORE_NAME,"readwrite"),u=i.objectStore(STORE_NAME);u.put({url:r,timestamp:n}),i.oncomplete=function(){t(e)},i.onabort=function(){o(i.error)}})}function expireOldEntries(e,r,n){return r?new Promise(function(t,o){var i=1e3*r,u=[],c=e.transaction(STORE_NAME,"readwrite"),s=c.objectStore(STORE_NAME),a=s.index(TIMESTAMP_PROPERTY);a.openCursor().onsuccess=function(e){var r=e.target.result;if(r&&n-i>r.value[TIMESTAMP_PROPERTY]){var t=r.value[URL_PROPERTY];u.push(t),s["delete"](t),r["continue"]()}},c.oncomplete=function(){t(u)},c.onabort=o}):Promise.resolve([])}function expireExtraEntries(e,r){return r?new Promise(function(n,t){var o=[],i=e.transaction(STORE_NAME,"readwrite"),u=i.objectStore(STORE_NAME),c=u.index(TIMESTAMP_PROPERTY),s=c.count();c.count().onsuccess=function(){var e=s.result;e>r&&(c.openCursor().onsuccess=function(n){var t=n.target.result;if(t){var i=t.value[URL_PROPERTY];o.push(i),u["delete"](i),e-o.length>r&&t["continue"]()}})},i.oncomplete=function(){n(o)},i.onabort=t}):Promise.resolve([])}function expireEntries(e,r,n,t){return expireOldEntries(e,n,t).then(function(n){return expireExtraEntries(e,r).then(function(e){return n.concat(e)})})}var DB_PREFIX="sw-toolbox-",DB_VERSION=1,STORE_NAME="store",URL_PROPERTY="url",TIMESTAMP_PROPERTY="timestamp",cacheNameToDbPromise={};module.exports={getDb:getDb,setTimestampForUrl:setTimestampForUrl,expireEntries:expireEntries};
},{}],3:[function(require,module,exports){
"use strict";var scope;scope=self.registration?self.registration.scope:self.scope||new URL("./",self.location).href,module.exports={cache:{name:"$$$toolbox-cache$$$"+scope+"$$$",maxAgeSeconds:null,maxEntries:null},debug:!1,networkTimeoutSeconds:null,preCacheItems:[],successResponses:/^0|([123]\d\d)|(40[14567])|410$/};
},{}],4:[function(require,module,exports){
"use strict";var url=new URL("./",self.location),basePath=url.pathname,pathRegexp=require("path-to-regexp"),Route=function(e,t,i,s){t instanceof RegExp?this.fullUrlRegExp=t:(0!==t.indexOf("/")&&(t=basePath+t),this.keys=[],this.regexp=pathRegexp(t,this.keys)),this.method=e,this.options=s,this.handler=i};Route.prototype.makeHandler=function(e){var t;if(this.regexp){var i=this.regexp.exec(e);t={},this.keys.forEach(function(e,s){t[e.name]=i[s+1]})}return function(e){return this.handler(e,t,this.options)}.bind(this)},module.exports=Route;
},{"path-to-regexp":13}],5:[function(require,module,exports){
"use strict";function regexEscape(e){return e.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}var Route=require("./route"),keyMatch=function(e,t){for(var r=e.entries(),o=r.next();!o.done;){var n=new RegExp(o.value[0]);if(n.test(t))return o.value[1];o=r.next()}return null},Router=function(){this.routes=new Map,this["default"]=null};["get","post","put","delete","head","any"].forEach(function(e){Router.prototype[e]=function(t,r,o){return this.add(e,t,r,o)}}),Router.prototype.add=function(e,t,r,o){o=o||{};var n;t instanceof RegExp?n=RegExp:(n=o.origin||self.location.origin,n=n instanceof RegExp?n.source:regexEscape(n)),e=e.toLowerCase();var u=new Route(e,t,r,o);this.routes.has(n)||this.routes.set(n,new Map);var a=this.routes.get(n);a.has(e)||a.set(e,new Map);var s=a.get(e),i=u.regexp||u.fullUrlRegExp;s.set(i.source,u)},Router.prototype.matchMethod=function(e,t){var r=new URL(t),o=r.origin,n=r.pathname;return this._match(e,keyMatch(this.routes,o),n)||this._match(e,this.routes.get(RegExp),t)},Router.prototype._match=function(e,t,r){if(t){var o=t.get(e.toLowerCase());if(o){var n=keyMatch(o,r);if(n)return n.makeHandler(r)}}return null},Router.prototype.match=function(e){return this.matchMethod(e.method,e.url)||this.matchMethod("any",e.url)},module.exports=new Router;
},{"./route":4}],6:[function(require,module,exports){
"use strict";function cacheFirst(e,r,t){return helpers.debug("Strategy: cache first ["+e.url+"]",t),helpers.openCache(t).then(function(r){return r.match(e).then(function(r){return r?r:helpers.fetchAndCache(e,t)})})}var helpers=require("../helpers");module.exports=cacheFirst;
},{"../helpers":1}],7:[function(require,module,exports){
"use strict";function cacheOnly(e,r,c){return helpers.debug("Strategy: cache only ["+e.url+"]",c),helpers.openCache(c).then(function(r){return r.match(e)})}var helpers=require("../helpers");module.exports=cacheOnly;
},{"../helpers":1}],8:[function(require,module,exports){
"use strict";function fastest(e,n,t){return helpers.debug("Strategy: fastest ["+e.url+"]",t),new Promise(function(r,s){var c=!1,o=[],a=function(e){o.push(e.toString()),c?s(new Error('Both cache and network failed: "'+o.join('", "')+'"')):c=!0},h=function(e){e instanceof Response?r(e):a("No result returned")};helpers.fetchAndCache(e.clone(),t).then(h,a),cacheOnly(e,n,t).then(h,a)})}var helpers=require("../helpers"),cacheOnly=require("./cacheOnly");module.exports=fastest;
},{"../helpers":1,"./cacheOnly":7}],9:[function(require,module,exports){
module.exports={networkOnly:require("./networkOnly"),networkFirst:require("./networkFirst"),cacheOnly:require("./cacheOnly"),cacheFirst:require("./cacheFirst"),fastest:require("./fastest")};
},{"./cacheFirst":6,"./cacheOnly":7,"./fastest":8,"./networkFirst":10,"./networkOnly":11}],10:[function(require,module,exports){
"use strict";function networkFirst(e,r,t){t=t||{};var s=t.successResponses||globalOptions.successResponses,n=t.networkTimeoutSeconds||globalOptions.networkTimeoutSeconds;return helpers.debug("Strategy: network first ["+e.url+"]",t),helpers.openCache(t).then(function(r){var o,u,c=[];if(n){var i=new Promise(function(t){o=setTimeout(function(){r.match(e).then(function(e){e&&t(e)})},1e3*n)});c.push(i)}var a=helpers.fetchAndCache(e,t).then(function(e){if(o&&clearTimeout(o),s.test(e.status))return e;throw helpers.debug("Response was an HTTP error: "+e.statusText,t),u=e,new Error("Bad response")})["catch"](function(){return helpers.debug("Network or response error, fallback to cache ["+e.url+"]",t),r.match(e).then(function(e){return e||u})});return c.push(a),Promise.race(c)})}var globalOptions=require("../options"),helpers=require("../helpers");module.exports=networkFirst;
},{"../helpers":1,"../options":3}],11:[function(require,module,exports){
"use strict";function networkOnly(e,r,t){return helpers.debug("Strategy: network only ["+e.url+"]",t),fetch(e)}var helpers=require("../helpers");module.exports=networkOnly;
},{"../helpers":1}],12:[function(require,module,exports){
"use strict";function cache(e,t){return helpers.openCache(t).then(function(t){return t.add(e)})}function uncache(e,t){return helpers.openCache(t).then(function(t){return t["delete"](e)})}function precache(e){Array.isArray(e)||(e=[e]),options.preCacheItems=options.preCacheItems.concat(e)}require("serviceworker-cache-polyfill");var options=require("./options"),router=require("./router"),helpers=require("./helpers"),strategies=require("./strategies");helpers.debug("Service Worker Toolbox is loading");var flatten=function(e){return e.reduce(function(e,t){return e.concat(t)},[])};self.addEventListener("install",function(e){var t=options.cache.name+"$$$inactive$$$";helpers.debug("install event fired"),helpers.debug("creating cache ["+t+"]"),e.waitUntil(helpers.openCache({cache:{name:t}}).then(function(e){return Promise.all(options.preCacheItems).then(flatten).then(function(t){return helpers.debug("preCache list: "+(t.join(", ")||"(none)")),e.addAll(t)})}))}),self.addEventListener("activate",function(e){helpers.debug("activate event fired");var t=options.cache.name+"$$$inactive$$$";e.waitUntil(helpers.renameCache(t,options.cache.name))}),self.addEventListener("fetch",function(e){var t=router.match(e.request);t?e.respondWith(t(e.request)):router["default"]&&"GET"===e.request.method&&e.respondWith(router["default"](e.request))}),module.exports={networkOnly:strategies.networkOnly,networkFirst:strategies.networkFirst,cacheOnly:strategies.cacheOnly,cacheFirst:strategies.cacheFirst,fastest:strategies.fastest,router:router,options:options,cache:cache,uncache:uncache,precache:precache};
},{"./helpers":1,"./options":3,"./router":5,"./strategies":9,"serviceworker-cache-polyfill":15}],13:[function(require,module,exports){
function parse(e){for(var t,r=[],n=0,o=0,p="";null!=(t=PATH_REGEXP.exec(e));){var a=t[0],i=t[1],s=t.index;if(p+=e.slice(o,s),o=s+a.length,i)p+=i[1];else{p&&(r.push(p),p="");var u=t[2],c=t[3],l=t[4],f=t[5],g=t[6],x=t[7],h="+"===g||"*"===g,m="?"===g||"*"===g,y=u||"/",T=l||f||(x?".*":"[^"+y+"]+?");r.push({name:c||n++,prefix:u||"",delimiter:y,optional:m,repeat:h,pattern:escapeGroup(T)})}}return o<e.length&&(p+=e.substr(o)),p&&r.push(p),r}function compile(e){return tokensToFunction(parse(e))}function tokensToFunction(e){for(var t=new Array(e.length),r=0;r<e.length;r++)"object"==typeof e[r]&&(t[r]=new RegExp("^"+e[r].pattern+"$"));return function(r){for(var n="",o=r||{},p=0;p<e.length;p++){var a=e[p];if("string"!=typeof a){var i,s=o[a.name];if(null==s){if(a.optional)continue;throw new TypeError('Expected "'+a.name+'" to be defined')}if(isarray(s)){if(!a.repeat)throw new TypeError('Expected "'+a.name+'" to not repeat, but received "'+s+'"');if(0===s.length){if(a.optional)continue;throw new TypeError('Expected "'+a.name+'" to not be empty')}for(var u=0;u<s.length;u++){if(i=encodeURIComponent(s[u]),!t[p].test(i))throw new TypeError('Expected all "'+a.name+'" to match "'+a.pattern+'", but received "'+i+'"');n+=(0===u?a.prefix:a.delimiter)+i}}else{if(i=encodeURIComponent(s),!t[p].test(i))throw new TypeError('Expected "'+a.name+'" to match "'+a.pattern+'", but received "'+i+'"');n+=a.prefix+i}}else n+=a}return n}}function escapeString(e){return e.replace(/([.+*?=^!:${}()[\]|\/])/g,"\\$1")}function escapeGroup(e){return e.replace(/([=!:$\/()])/g,"\\$1")}function attachKeys(e,t){return e.keys=t,e}function flags(e){return e.sensitive?"":"i"}function regexpToRegexp(e,t){var r=e.source.match(/\((?!\?)/g);if(r)for(var n=0;n<r.length;n++)t.push({name:n,prefix:null,delimiter:null,optional:!1,repeat:!1,pattern:null});return attachKeys(e,t)}function arrayToRegexp(e,t,r){for(var n=[],o=0;o<e.length;o++)n.push(pathToRegexp(e[o],t,r).source);var p=new RegExp("(?:"+n.join("|")+")",flags(r));return attachKeys(p,t)}function stringToRegexp(e,t,r){for(var n=parse(e),o=tokensToRegExp(n,r),p=0;p<n.length;p++)"string"!=typeof n[p]&&t.push(n[p]);return attachKeys(o,t)}function tokensToRegExp(e,t){t=t||{};for(var r=t.strict,n=t.end!==!1,o="",p=e[e.length-1],a="string"==typeof p&&/\/$/.test(p),i=0;i<e.length;i++){var s=e[i];if("string"==typeof s)o+=escapeString(s);else{var u=escapeString(s.prefix),c=s.pattern;s.repeat&&(c+="(?:"+u+c+")*"),c=s.optional?u?"(?:"+u+"("+c+"))?":"("+c+")?":u+"("+c+")",o+=c}}return r||(o=(a?o.slice(0,-2):o)+"(?:\\/(?=$))?"),o+=n?"$":r&&a?"":"(?=\\/|$)",new RegExp("^"+o,flags(t))}function pathToRegexp(e,t,r){return t=t||[],isarray(t)?r||(r={}):(r=t,t=[]),e instanceof RegExp?regexpToRegexp(e,t,r):isarray(e)?arrayToRegexp(e,t,r):stringToRegexp(e,t,r)}var isarray=require("isarray");module.exports=pathToRegexp,module.exports.parse=parse,module.exports.compile=compile,module.exports.tokensToFunction=tokensToFunction,module.exports.tokensToRegExp=tokensToRegExp;var PATH_REGEXP=new RegExp(["(\\\\.)","([\\/.])?(?:(?:\\:(\\w+)(?:\\(((?:\\\\.|[^()])+)\\))?|\\(((?:\\\\.|[^()])+)\\))([+*?])?|(\\*))"].join("|"),"g");
},{"isarray":14}],14:[function(require,module,exports){
module.exports=Array.isArray||function(r){return"[object Array]"==Object.prototype.toString.call(r)};
},{}],15:[function(require,module,exports){
Cache.prototype.addAll||(Cache.prototype.addAll=function(t){function e(t){this.name="NetworkError",this.code=19,this.message=t}var r=this;return e.prototype=Object.create(Error.prototype),Promise.resolve().then(function(){if(arguments.length<1)throw new TypeError;return t=t.map(function(t){return t instanceof Request?t:String(t)}),Promise.all(t.map(function(t){"string"==typeof t&&(t=new Request(t));var r=new URL(t.url).protocol;if("http:"!==r&&"https:"!==r)throw new e("Invalid scheme");return fetch(t.clone())}))}).then(function(e){return Promise.all(e.map(function(e,n){return r.put(t[n],e)}))}).then(function(){})});
},{}]},{},[12])(12)
(function(f) {
if (typeof exports === 'object' && typeof module !== 'undefined') {
module.exports = f();
} else if (typeof define === 'function' && define.amd) {
define([], f);
} else {
var g;
if (typeof window !== 'undefined') {
g = window;
} else if (typeof global !== 'undefined') {
g = global;
} else if (typeof self !== 'undefined') {
g = self;
} else {
g = this;
}
g.toolbox = f();
}
})(function() {
var define, module, exports;
return (function e(t, n, r) {
function s(o, u) {
if (!n[o]) {
if (!t[o]) {
var a = typeof require == 'function' && require;
if (!u && a) return a(o, !0);
if (i) return i(o, !0);
var f = new Error("Cannot find module '" + o + "'");
throw ((f.code = 'MODULE_NOT_FOUND'), f);
}
var l = (n[o] = { exports: {} });
t[o][0].call(
l.exports,
function(e) {
var n = t[o][1][e];
return s(n ? n : e);
},
l,
l.exports,
e,
t,
n,
r
);
}
return n[o].exports;
}
var i = typeof require == 'function' && require;
for (var o = 0; o < r.length; o++) s(r[o]);
return s;
})(
{
1: [
function(require, module, exports) {
'use strict';
function debug(e, n) {
n = n || {};
var t = n.debug || globalOptions.debug;
t && console.log('[sw-toolbox] ' + e);
}
function openCache(e) {
var n;
return e && e.cache && (n = e.cache.name), (n =
n || globalOptions.cache.name), caches.open(n);
}
function fetchAndCache(e, n) {
n = n || {};
var t = n.successResponses || globalOptions.successResponses;
return fetch(e.clone()).then(function(c) {
return 'GET' === e.method &&
t.test(c.status) &&
openCache(n).then(function(t) {
t.put(e, c).then(function() {
var c = n.cache || globalOptions.cache;
(c.maxEntries || c.maxAgeSeconds) &&
c.name &&
queueCacheExpiration(e, t, c);
});
}), c.clone();
});
}
function queueCacheExpiration(e, n, t) {
var c = cleanupCache.bind(null, e, n, t);
cleanupQueue = cleanupQueue ? cleanupQueue.then(c) : c();
}
function cleanupCache(e, n, t) {
var c = e.url,
a = t.maxAgeSeconds,
u = t.maxEntries,
o = t.name,
r = Date.now();
return debug(
'Updating LRU order for ' +
c +
'. Max entries is ' +
u +
', max age is ' +
a
), idbCacheExpiration
.getDb(o)
.then(function(e) {
return idbCacheExpiration.setTimestampForUrl(e, c, r);
})
.then(function(e) {
return idbCacheExpiration.expireEntries(e, u, a, r);
})
.then(function(e) {
debug('Successfully updated IDB.');
var t = e.map(function(e) {
return n['delete'](e);
});
return Promise.all(t).then(function() {
debug('Done with cache cleanup.');
});
})['catch'](function(e) {
debug(e);
});
}
function renameCache(e, n, t) {
return debug(
'Renaming cache: [' + e + '] to [' + n + ']',
t
), caches['delete'](n).then(function() {
return Promise.all([
caches.open(e),
caches.open(n),
]).then(function(n) {
var t = n[0], c = n[1];
return t
.keys()
.then(function(e) {
return Promise.all(
e.map(function(e) {
return t.match(e).then(function(n) {
return c.put(e, n);
});
})
);
})
.then(function() {
return caches['delete'](e);
});
});
});
}
var globalOptions = require('./options'),
idbCacheExpiration = require('./idb-cache-expiration'),
cleanupQueue;
module.exports = {
debug: debug,
fetchAndCache: fetchAndCache,
openCache: openCache,
renameCache: renameCache,
};
},
{ './idb-cache-expiration': 2, './options': 3 },
],
2: [
function(require, module, exports) {
'use strict';
function openDb(e) {
return new Promise(function(r, n) {
var t = indexedDB.open(DB_PREFIX + e, DB_VERSION);
(t.onupgradeneeded = function() {
var e = t.result.createObjectStore(STORE_NAME, {
keyPath: URL_PROPERTY,
});
e.createIndex(TIMESTAMP_PROPERTY, TIMESTAMP_PROPERTY, {
unique: !1,
});
}), (t.onsuccess = function() {
r(t.result);
}), (t.onerror = function() {
n(t.error);
});
});
}
function getDb(e) {
return e in cacheNameToDbPromise ||
(cacheNameToDbPromise[e] = openDb(e)), cacheNameToDbPromise[e];
}
function setTimestampForUrl(e, r, n) {
return new Promise(function(t, o) {
var i = e.transaction(STORE_NAME, 'readwrite'),
u = i.objectStore(STORE_NAME);
u.put({ url: r, timestamp: n }), (i.oncomplete = function() {
t(e);
}), (i.onabort = function() {
o(i.error);
});
});
}
function expireOldEntries(e, r, n) {
return r
? new Promise(function(t, o) {
var i = 1e3 * r,
u = [],
c = e.transaction(STORE_NAME, 'readwrite'),
s = c.objectStore(STORE_NAME),
a = s.index(TIMESTAMP_PROPERTY);
(a.openCursor().onsuccess = function(e) {
var r = e.target.result;
if (r && n - i > r.value[TIMESTAMP_PROPERTY]) {
var t = r.value[URL_PROPERTY];
u.push(t), s['delete'](t), r['continue']();
}
}), (c.oncomplete = function() {
t(u);
}), (c.onabort = o);
})
: Promise.resolve([]);
}
function expireExtraEntries(e, r) {
return r
? new Promise(function(n, t) {
var o = [],
i = e.transaction(STORE_NAME, 'readwrite'),
u = i.objectStore(STORE_NAME),
c = u.index(TIMESTAMP_PROPERTY),
s = c.count();
(c.count().onsuccess = function() {
var e = s.result;
e > r &&
(c.openCursor().onsuccess = function(n) {
var t = n.target.result;
if (t) {
var i = t.value[URL_PROPERTY];
o.push(i), u['delete'](i), e - o.length > r &&
t['continue']();
}
});
}), (i.oncomplete = function() {
n(o);
}), (i.onabort = t);
})
: Promise.resolve([]);
}
function expireEntries(e, r, n, t) {
return expireOldEntries(e, n, t).then(function(n) {
return expireExtraEntries(e, r).then(function(e) {
return n.concat(e);
});
});
}
var DB_PREFIX = 'sw-toolbox-',
DB_VERSION = 1,
STORE_NAME = 'store',
URL_PROPERTY = 'url',
TIMESTAMP_PROPERTY = 'timestamp',
cacheNameToDbPromise = {};
module.exports = {
getDb: getDb,
setTimestampForUrl: setTimestampForUrl,
expireEntries: expireEntries,
};
},
{},
],
3: [
function(require, module, exports) {
'use strict';
var scope;
(scope = self.registration
? self.registration.scope
: self.scope ||
new URL('./', self.location).href), (module.exports = {
cache: {
name: '$$$toolbox-cache$$$' + scope + '$$$',
maxAgeSeconds: null,
maxEntries: null,
},
debug: !1,
networkTimeoutSeconds: null,
preCacheItems: [],
successResponses: /^0|([123]\d\d)|(40[14567])|410$/,
});
},
{},
],
4: [
function(require, module, exports) {
'use strict';
var url = new URL('./', self.location),
basePath = url.pathname,
pathRegexp = require('path-to-regexp'),
Route = function(e, t, i, s) {
t instanceof RegExp
? (this.fullUrlRegExp = t)
: (0 !== t.indexOf('/') && (t = basePath + t), (this.keys = [
]), (this.regexp = pathRegexp(
t,
this.keys
))), (this.method = e), (this.options = s), (this.handler = i);
};
(Route.prototype.makeHandler = function(e) {
var t;
if (this.regexp) {
var i = this.regexp.exec(e);
(t = {}), this.keys.forEach(function(e, s) {
t[e.name] = i[s + 1];
});
}
return function(e) {
return this.handler(e, t, this.options);
}.bind(this);
}), (module.exports = Route);
},
{ 'path-to-regexp': 13 },
],
5: [
function(require, module, exports) {
'use strict';
function regexEscape(e) {
return e.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
}
var Route = require('./route'),
keyMatch = function(e, t) {
for (var r = e.entries(), o = r.next(); !o.done; ) {
var n = new RegExp(o.value[0]);
if (n.test(t)) return o.value[1];
o = r.next();
}
return null;
},
Router = function() {
(this.routes = new Map()), (this['default'] = null);
};
['get', 'post', 'put', 'delete', 'head', 'any'].forEach(function(e) {
Router.prototype[e] = function(t, r, o) {
return this.add(e, t, r, o);
};
}), (Router.prototype.add = function(e, t, r, o) {
o = o || {};
var n;
t instanceof RegExp
? (n = RegExp)
: ((n = o.origin || self.location.origin), (n = n instanceof
RegExp
? n.source
: regexEscape(n))), (e = e.toLowerCase());
var u = new Route(e, t, r, o);
this.routes.has(n) || this.routes.set(n, new Map());
var a = this.routes.get(n);
a.has(e) || a.set(e, new Map());
var s = a.get(e), i = u.regexp || u.fullUrlRegExp;
s.set(i.source, u);
}), (Router.prototype.matchMethod = function(e, t) {
var r = new URL(t), o = r.origin, n = r.pathname;
return (
this._match(e, keyMatch(this.routes, o), n) ||
this._match(e, this.routes.get(RegExp), t)
);
}), (Router.prototype._match = function(e, t, r) {
if (t) {
var o = t.get(e.toLowerCase());
if (o) {
var n = keyMatch(o, r);
if (n) return n.makeHandler(r);
}
}
return null;
}), (Router.prototype.match = function(e) {
return (
this.matchMethod(e.method, e.url) ||
this.matchMethod('any', e.url)
);
}), (module.exports = new Router());
},
{ './route': 4 },
],
6: [
function(require, module, exports) {
'use strict';
function cacheFirst(e, r, t) {
return helpers.debug(
'Strategy: cache first [' + e.url + ']',
t
), helpers.openCache(t).then(function(r) {
return r.match(e).then(function(r) {
return r ? r : helpers.fetchAndCache(e, t);
});
});
}
var helpers = require('../helpers');
module.exports = cacheFirst;
},
{ '../helpers': 1 },
],
7: [
function(require, module, exports) {
'use strict';
function cacheOnly(e, r, c) {
return helpers.debug(
'Strategy: cache only [' + e.url + ']',
c
), helpers.openCache(c).then(function(r) {
return r.match(e);
});
}
var helpers = require('../helpers');
module.exports = cacheOnly;
},
{ '../helpers': 1 },
],
8: [
function(require, module, exports) {
'use strict';
function fastest(e, n, t) {
return helpers.debug(
'Strategy: fastest [' + e.url + ']',
t
), new Promise(function(r, s) {
var c = !1,
o = [],
a = function(e) {
o.push(e.toString()), c
? s(
new Error(
'Both cache and network failed: "' +
o.join('", "') +
'"'
)
)
: (c = !0);
},
h = function(e) {
e instanceof Response ? r(e) : a('No result returned');
};
helpers
.fetchAndCache(e.clone(), t)
.then(h, a), cacheOnly(e, n, t).then(h, a);
});
}
var helpers = require('../helpers'),
cacheOnly = require('./cacheOnly');
module.exports = fastest;
},
{ '../helpers': 1, './cacheOnly': 7 },
],
9: [
function(require, module, exports) {
module.exports = {
networkOnly: require('./networkOnly'),
networkFirst: require('./networkFirst'),
cacheOnly: require('./cacheOnly'),
cacheFirst: require('./cacheFirst'),
fastest: require('./fastest'),
};
},
{
'./cacheFirst': 6,
'./cacheOnly': 7,
'./fastest': 8,
'./networkFirst': 10,
'./networkOnly': 11,
},
],
10: [
function(require, module, exports) {
'use strict';
function networkFirst(e, r, t) {
t = t || {};
var s = t.successResponses || globalOptions.successResponses,
n =
t.networkTimeoutSeconds || globalOptions.networkTimeoutSeconds;
return helpers.debug(
'Strategy: network first [' + e.url + ']',
t
), helpers.openCache(t).then(function(r) {
var o, u, c = [];
if (n) {
var i = new Promise(function(t) {
o = setTimeout(function() {
r.match(e).then(function(e) {
e && t(e);
});
}, 1e3 * n);
});
c.push(i);
}
var a = helpers.fetchAndCache(e, t).then(function(e) {
if ((o && clearTimeout(o), s.test(e.status))) return e;
throw (helpers.debug(
'Response was an HTTP error: ' + e.statusText,
t
), (u = e), new Error('Bad response'));
})['catch'](function() {
return helpers.debug(
'Network or response error, fallback to cache [' +
e.url +
']',
t
), r.match(e).then(function(e) {
return e || u;
});
});
return c.push(a), Promise.race(c);
});
}
var globalOptions = require('../options'),
helpers = require('../helpers');
module.exports = networkFirst;
},
{ '../helpers': 1, '../options': 3 },
],
11: [
function(require, module, exports) {
'use strict';
function networkOnly(e, r, t) {
return helpers.debug(
'Strategy: network only [' + e.url + ']',
t
), fetch(e);
}
var helpers = require('../helpers');
module.exports = networkOnly;
},
{ '../helpers': 1 },
],
12: [
function(require, module, exports) {
'use strict';
function cache(e, t) {
return helpers.openCache(t).then(function(t) {
return t.add(e);
});
}
function uncache(e, t) {
return helpers.openCache(t).then(function(t) {
return t['delete'](e);
});
}
function precache(e) {
Array.isArray(e) ||
(e = [e]), (options.preCacheItems = options.preCacheItems.concat(
e
));
}
require('serviceworker-cache-polyfill');
var options = require('./options'),
router = require('./router'),
helpers = require('./helpers'),
strategies = require('./strategies');
helpers.debug('Service Worker Toolbox is loading');
var flatten = function(e) {
return e.reduce(function(e, t) {
return e.concat(t);
}, []);
};
self.addEventListener('install', function(e) {
var t = options.cache.name + '$$$inactive$$$';
helpers.debug(
'install event fired'
), helpers.debug('creating cache [' + t + ']'), e.waitUntil(
helpers.openCache({ cache: { name: t } }).then(function(e) {
return Promise.all(options.preCacheItems)
.then(flatten)
.then(function(t) {
return helpers.debug(
'preCache list: ' + (t.join(', ') || '(none)')
), e.addAll(t);
});
})
);
}), self.addEventListener('activate', function(e) {
helpers.debug('activate event fired');
var t = options.cache.name + '$$$inactive$$$';
e.waitUntil(helpers.renameCache(t, options.cache.name));
}), self.addEventListener('fetch', function(e) {
var t = router.match(e.request);
t
? e.respondWith(t(e.request))
: router['default'] &&
'GET' === e.request.method &&
e.respondWith(router['default'](e.request));
}), (module.exports = {
networkOnly: strategies.networkOnly,
networkFirst: strategies.networkFirst,
cacheOnly: strategies.cacheOnly,
cacheFirst: strategies.cacheFirst,
fastest: strategies.fastest,
router: router,
options: options,
cache: cache,
uncache: uncache,
precache: precache,
});
},
{
'./helpers': 1,
'./options': 3,
'./router': 5,
'./strategies': 9,
'serviceworker-cache-polyfill': 15,
},
],
13: [
function(require, module, exports) {
function parse(e) {
for (
var t, r = [], n = 0, o = 0, p = '';
null != (t = PATH_REGEXP.exec(e));
) {
var a = t[0], i = t[1], s = t.index;
if (((p += e.slice(o, s)), (o = s + a.length), i)) p += i[1];
else {
p && (r.push(p), (p = ''));
var u = t[2],
c = t[3],
l = t[4],
f = t[5],
g = t[6],
x = t[7],
h = '+' === g || '*' === g,
m = '?' === g || '*' === g,
y = u || '/',
T = l || f || (x ? '.*' : '[^' + y + ']+?');
r.push({
name: c || n++,
prefix: u || '',
delimiter: y,
optional: m,
repeat: h,
pattern: escapeGroup(T),
});
}
}
return o < e.length && (p += e.substr(o)), p && r.push(p), r;
}
function compile(e) {
return tokensToFunction(parse(e));
}
function tokensToFunction(e) {
for (var t = new Array(e.length), r = 0; r < e.length; r++)
'object' == typeof e[r] &&
(t[r] = new RegExp('^' + e[r].pattern + '$'));
return function(r) {
for (var n = '', o = r || {}, p = 0; p < e.length; p++) {
var a = e[p];
if ('string' != typeof a) {
var i, s = o[a.name];
if (null == s) {
if (a.optional) continue;
throw new TypeError(
'Expected "' + a.name + '" to be defined'
);
}
if (isarray(s)) {
if (!a.repeat)
throw new TypeError(
'Expected "' +
a.name +
'" to not repeat, but received "' +
s +
'"'
);
if (0 === s.length) {
if (a.optional) continue;
throw new TypeError(
'Expected "' + a.name + '" to not be empty'
);
}
for (var u = 0; u < s.length; u++) {
if (((i = encodeURIComponent(s[u])), !t[p].test(i)))
throw new TypeError(
'Expected all "' +
a.name +
'" to match "' +
a.pattern +
'", but received "' +
i +
'"'
);
n += (0 === u ? a.prefix : a.delimiter) + i;
}
} else {
if (((i = encodeURIComponent(s)), !t[p].test(i)))
throw new TypeError(
'Expected "' +
a.name +
'" to match "' +
a.pattern +
'", but received "' +
i +
'"'
);
n += a.prefix + i;
}
} else n += a;
}
return n;
};
}
function escapeString(e) {
return e.replace(/([.+*?=^!:${}()[\]|\/])/g, '\\$1');
}
function escapeGroup(e) {
return e.replace(/([=!:$\/()])/g, '\\$1');
}
function attachKeys(e, t) {
return (e.keys = t), e;
}
function flags(e) {
return e.sensitive ? '' : 'i';
}
function regexpToRegexp(e, t) {
var r = e.source.match(/\((?!\?)/g);
if (r)
for (var n = 0; n < r.length; n++)
t.push({
name: n,
prefix: null,
delimiter: null,
optional: !1,
repeat: !1,
pattern: null,
});
return attachKeys(e, t);
}
function arrayToRegexp(e, t, r) {
for (var n = [], o = 0; o < e.length; o++)
n.push(pathToRegexp(e[o], t, r).source);
var p = new RegExp('(?:' + n.join('|') + ')', flags(r));
return attachKeys(p, t);
}
function stringToRegexp(e, t, r) {
for (
var n = parse(e), o = tokensToRegExp(n, r), p = 0;
p < n.length;
p++
)
'string' != typeof n[p] && t.push(n[p]);
return attachKeys(o, t);
}
function tokensToRegExp(e, t) {
t = t || {};
for (
var r = t.strict,
n = t.end !== !1,
o = '',
p = e[e.length - 1],
a = 'string' == typeof p && /\/$/.test(p),
i = 0;
i < e.length;
i++
) {
var s = e[i];
if ('string' == typeof s) o += escapeString(s);
else {
var u = escapeString(s.prefix), c = s.pattern;
s.repeat && (c += '(?:' + u + c + ')*'), (c = s.optional
? u ? '(?:' + u + '(' + c + '))?' : '(' + c + ')?'
: u + '(' + c + ')'), (o += c);
}
}
return r ||
(o = (a ? o.slice(0, -2) : o) + '(?:\\/(?=$))?'), (o += n
? '$'
: r && a ? '' : '(?=\\/|$)'), new RegExp('^' + o, flags(t));
}
function pathToRegexp(e, t, r) {
return (t = t || []), isarray(t)
? r || (r = {})
: ((r = t), (t = [])), e instanceof RegExp
? regexpToRegexp(e, t, r)
: isarray(e) ? arrayToRegexp(e, t, r) : stringToRegexp(e, t, r);
}
var isarray = require('isarray');
(module.exports = pathToRegexp), (module.exports.parse = parse), (module.exports.compile = compile), (module.exports.tokensToFunction = tokensToFunction), (module.exports.tokensToRegExp = tokensToRegExp);
var PATH_REGEXP = new RegExp(
[
'(\\\\.)',
'([\\/.])?(?:(?:\\:(\\w+)(?:\\(((?:\\\\.|[^()])+)\\))?|\\(((?:\\\\.|[^()])+)\\))([+*?])?|(\\*))',
].join('|'),
'g'
);
},
{ isarray: 14 },
],
14: [
function(require, module, exports) {
module.exports =
Array.isArray ||
function(r) {
return '[object Array]' == Object.prototype.toString.call(r);
};
},
{},
],
15: [
function(require, module, exports) {
Cache.prototype.addAll ||
(Cache.prototype.addAll = function(t) {
function e(t) {
(this.name =
'NetworkError'), (this.code = 19), (this.message = t);
}
var r = this;
return (e.prototype = Object.create(
Error.prototype
)), Promise.resolve()
.then(function() {
if (arguments.length < 1) throw new TypeError();
return (t = t.map(function(t) {
return t instanceof Request ? t : String(t);
})), Promise.all(
t.map(function(t) {
'string' == typeof t && (t = new Request(t));
var r = new URL(t.url).protocol;
if ('http:' !== r && 'https:' !== r)
throw new e('Invalid scheme');
return fetch(t.clone());
})
);
})
.then(function(e) {
return Promise.all(
e.map(function(e, n) {
return r.put(t[n], e);
})
);
})
.then(function() {});
});
},
{},
],
},
{},
[12]
)(12);
});
(global => {
'use strict';
// Assets
global.toolbox.router.get(/\/static\//, global.toolbox.cacheFirst, { cache: { name: 'static' } });
global.toolbox.router.get('/(.*)', global.toolbox.fastest, {origin: 'https://secure.gravatar.com'});
global.toolbox.router.get(/\/static\//, global.toolbox.cacheFirst, {
cache: { name: 'static' },
});
global.toolbox.router.get('/(.*)', global.toolbox.fastest, {
origin: 'https://secure.gravatar.com',
});
// API
global.toolbox.router.get(/\/api\//, global.toolbox.networkFirst, {
cache: {
name: 'api',
maxEntries: 100
}
cache: {
name: 'api',
maxEntries: 100,
},
});
// API GET calls
@@ -69,8 +873,10 @@ Cache.prototype.addAll||(Cache.prototype.addAll=function(t){function e(t){this.n
// Boilerplate to ensure our service worker takes control of the page as soon
// as possible.
global.addEventListener('install',
event => event.waitUntil(global.skipWaiting()));
global.addEventListener('activate',
event => event.waitUntil(global.clients.claim()));
global.addEventListener('install', event =>
event.waitUntil(global.skipWaiting())
);
global.addEventListener('activate', event =>
event.waitUntil(global.clients.claim())
);
})(self);

View File

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

View File

@@ -3,8 +3,9 @@ import { sequelize } from '../sequelize';
export function flushdb() {
const sql = sequelize.getQueryInterface();
const tables = Object.keys(sequelize.models).map((model) =>
sql.quoteTable(sequelize.models[model].getTableName()));
const tables = Object.keys(sequelize.models).map(model =>
sql.quoteTable(sequelize.models[model].getTableName())
);
const query = `TRUNCATE ${tables.join(', ')} CASCADE`;
return sequelize.query(query);
@@ -24,7 +25,4 @@ const seed = async () => {
});
};
export {
seed,
sequelize,
};
export { seed, sequelize };

View File

@@ -4,29 +4,28 @@ import moment from 'moment';
const makePolicy = () => {
const policy = {
conditions: [
{'bucket': process.env.AWS_S3_UPLOAD_BUCKET_NAME},
{ bucket: process.env.AWS_S3_UPLOAD_BUCKET_NAME },
['starts-with', '$key', ''],
{'acl': 'public-read'},
{ acl: 'public-read' },
['content-length-range', 0, process.env.AWS_S3_UPLOAD_MAX_SIZE],
['starts-with', '$Content-Type', 'image'],
['starts-with', '$Cache-Control', ''],
],
expiration: moment().add(24*60, 'minutes').format('YYYY-MM-DDTHH:mm:ss\\Z'),
expiration: moment()
.add(24 * 60, 'minutes')
.format('YYYY-MM-DDTHH:mm:ss\\Z'),
};
return new Buffer(JSON.stringify(policy)).toString('base64')
return new Buffer(JSON.stringify(policy)).toString('base64');
};
const signPolicy = (policy) => {
const signature = crypto.createHmac(
'sha1',
process.env.AWS_SECRET_ACCESS_KEY
).update(policy).digest('base64');
const signPolicy = policy => {
const signature = crypto
.createHmac('sha1', process.env.AWS_SECRET_ACCESS_KEY)
.update(policy)
.digest('base64');
return signature;
};
export {
makePolicy,
signPolicy,
};
export { makePolicy, signPolicy };

View File

@@ -5,7 +5,7 @@ truncate.defaultOptions = {
stripTags: false,
ellipsis: '...',
decodeEntities: false,
excludes: ['h1', 'pre', ],
excludes: ['h1', 'pre'],
};
const truncateMarkdown = (text, length) => {
@@ -13,6 +13,4 @@ const truncateMarkdown = (text, length) => {
return truncate(html, length);
};
export {
truncateMarkdown,
};
export { truncateMarkdown };