From 24e02bfdc49c7d44b569d1fdef22853154c571ad Mon Sep 17 00:00:00 2001 From: Jori Lallo Date: Mon, 20 Jun 2016 00:18:03 -0700 Subject: [PATCH] New database with migrations --- .sequelizerc | 10 + package.json | 5 +- server/api/atlases.js | 8 +- server/api/auth.js | 37 ++- server/api/documents.js | 15 +- server/config/database.json | 14 ++ server/migrations/20160619080644-initial.js | 258 ++++++++++++++++++++ server/models/Atlas.js | 29 ++- server/models/Document.js | 4 - server/models/Team.js | 18 ++ server/models/User.js | 4 - server/presenters.js | 8 +- 12 files changed, 361 insertions(+), 49 deletions(-) create mode 100644 .sequelizerc create mode 100644 server/config/database.json create mode 100644 server/migrations/20160619080644-initial.js diff --git a/.sequelizerc b/.sequelizerc new file mode 100644 index 00000000..0f26a4e1 --- /dev/null +++ b/.sequelizerc @@ -0,0 +1,10 @@ +require('localenv'); + +var path = require('path'); + +module.exports = { + 'config': path.resolve('server/config', 'database.json'), + 'migrations-path': path.resolve('server', 'migrations'), + 'models-path': path.resolve('server', 'models'), + 'seeders-path': path.resolve('server/models', 'fixtures'), +} diff --git a/package.json b/package.json index ae29becb..d43dfba2 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,8 @@ "start": "cross-env NODE_ENV=development DEBUG=1 ./node_modules/.bin/nodemon --watch server index.js", "lint": "eslint src", "deploy": "git push heroku master", - "heroku-postbuild": "npm run build" + "heroku-postbuild": "npm run build && npm run sequelize db:migrate", + "sequelize": "./node_modules/.bin/sequelize" }, "repository": { "type": "git", @@ -97,7 +98,7 @@ "safestart": "^0.8.0", "sass-loader": "^3.2.0", "sequelize": "^3.21.0", - "sequelize-cli": "^2.3.1", + "sequelize-cli": "^2.4.0", "sequelize-encrypted": "^0.1.0", "slug": "^0.9.1", "style-loader": "^0.13.0", diff --git a/server/api/atlases.js b/server/api/atlases.js index 44a6921e..c1fca939 100644 --- a/server/api/atlases.js +++ b/server/api/atlases.js @@ -13,11 +13,11 @@ router.post('atlases.info', auth(), async (ctx) => { let { id } = ctx.request.body; ctx.assertPresent(id, 'id is required'); - const team = await ctx.state.user.getTeam(); + const user = ctx.state.user; const atlas = await Atlas.findOne({ where: { id: id, - teamId: team.id, + teamId: user.teamId, }, }); @@ -30,10 +30,10 @@ router.post('atlases.info', auth(), async (ctx) => { router.post('atlases.list', auth(), pagination(), async (ctx) => { - const team = await ctx.state.user.getTeam(); + const user = ctx.state.user; const atlases = await Atlas.findAll({ where: { - teamId: team.id, + teamId: user.teamId, }, order: [ ['updatedAt', 'DESC'], diff --git a/server/api/auth.js b/server/api/auth.js index 882637a6..1f00e066 100644 --- a/server/api/auth.js +++ b/server/api/auth.js @@ -40,13 +40,27 @@ router.post('auth.slack', async (ctx) => { const authResponse = await fetch(`https://slack.com/api/auth.test?token=${data.access_token}`); const authData = await authResponse.json(); + // Team + let team = await Team.findOne({ where: { slackId: data.team.id } }); + if (!team) { + team = await Team.create({ + name: data.team.name, + slackId: data.team.id, + slackData: data.team, + }); + const atlas = await team.createFirstAtlas(); + } else { + team.name = data.team.name; + team.slackData = data.team; + team = await team.save(); + } + if (user) { user.slackAccessToken = data.access_token; user.slackData = data.user; user = await user.save(); } else { - // Existing user - user = await User.create({ + user = await team.createUser({ slackId: data.user.id, username: authData.user, name: data.user.name, @@ -56,30 +70,11 @@ router.post('auth.slack', async (ctx) => { }); } - // Team - let team = await Team.findOne({ where: { slackId: data.team.id } }); - if (!team) { - team = await Team.create({ - name: data.team.name, - slackId: data.team.id, - slackData: data.team, - }); - } else { - // Update data - team.name = data.team.name; - team.slackData = data.team; - team = await team.save(); - } - - // Add to correct team - user.setTeam(team); - ctx.body = { data: { user: await presentUser(user), team: await presentTeam(team), accessToken: user.getJwtToken(), }}; - console.log("enf") }); export default router; diff --git a/server/api/documents.js b/server/api/documents.js index 9a02389b..4aef31d1 100644 --- a/server/api/documents.js +++ b/server/api/documents.js @@ -23,8 +23,8 @@ router.post('documents.info', auth({ require: false }), async (ctx) => { if (document.private) { if (!ctx.state.user) throw httpErrors.NotFound(); - const team = await ctx.state.user.getTeam(); - if (document.teamId !== team.id) { + const user = await ctx.state.user; + if (document.teamId !== user.teamId) { throw httpErrors.NotFound(); } @@ -52,11 +52,10 @@ router.post('documents.create', auth(), async (ctx) => { ctx.assertPresent(text, 'text is required'); const user = ctx.state.user; - const team = await user.getTeam(); const ownerAtlas = await Atlas.findOne({ where: { id: atlas, - teamId: team.id, + teamId: user.teamId, }, }); @@ -64,7 +63,7 @@ router.post('documents.create', auth(), async (ctx) => { const document = await Document.create({ atlasId: ownerAtlas.id, - teamId: team.id, + teamId: user.teamId, userId: user.id, title: title, text: text, @@ -86,11 +85,10 @@ router.post('documents.update', auth(), async (ctx) => { ctx.assertPresent(text, 'text is required'); const user = ctx.state.user; - const team = await user.getTeam(); let document = await Document.findOne({ where: { id: id, - teamId: team.id, + teamId: user.teamId, }, }); @@ -112,11 +110,10 @@ router.post('documents.delete', auth(), async (ctx) => { ctx.assertPresent(id, 'id is required'); const user = ctx.state.user; - const team = await user.getTeam(); let document = await Document.findOne({ where: { id: id, - teamId: team.id, + teamId: user.teamId, }, }); diff --git a/server/config/database.json b/server/config/database.json new file mode 100644 index 00000000..7d739b97 --- /dev/null +++ b/server/config/database.json @@ -0,0 +1,14 @@ +{ + "development": { + "use_env_variable": "DATABASE_URL", + "dialect": "postgres" + }, + "test": { + "use_env_variable": "DATABASE_URL", + "dialect": "postgres" + }, + "production": { + "use_env_variable": "DATABASE_URL", + "dialect": "postgres" + } +} diff --git a/server/migrations/20160619080644-initial.js b/server/migrations/20160619080644-initial.js new file mode 100644 index 00000000..2407ae40 --- /dev/null +++ b/server/migrations/20160619080644-initial.js @@ -0,0 +1,258 @@ +'use strict'; + +module.exports = { + up: function (queryInterface, Sequelize) { + queryInterface.createTable('atlases', { + id: + { type: 'UUID', + allowNull: false, + defaultValue: null, + special: [], + primaryKey: true }, + name: + { type: 'CHARACTER VARYING', + allowNull: true, + defaultValue: null, + special: [], + primaryKey: false }, + description: + { type: 'CHARACTER VARYING', + allowNull: true, + defaultValue: null, + special: [], + primaryKey: false }, + type: + { type: 'CHARACTER VARYING', + allowNull: true, + defaultValue: null, + special: [], + primaryKey: false }, + atlasStructure: + { type: 'JSONB', + allowNull: true, + defaultValue: null, + special: [], + primaryKey: false }, + createdAt: + { type: 'TIMESTAMP WITH TIME ZONE', + allowNull: false, + defaultValue: null, + special: [], + primaryKey: false }, + updatedAt: + { type: 'TIMESTAMP WITH TIME ZONE', + allowNull: false, + defaultValue: null, + special: [], + primaryKey: false }, + teamId: + { type: 'UUID', + allowNull: true, + defaultValue: null, + special: [], + primaryKey: false } } + ); + + // documents + queryInterface.createTable('documents', { + id: + { type: 'UUID', + allowNull: false, + defaultValue: null, + special: [], + primaryKey: true }, + urlId: + { type: 'CHARACTER VARYING', + allowNull: false, + unique: true, + defaultValue: null, + special: [], + primaryKey: false }, + private: + { type: 'BOOLEAN', + allowNull: false, + defaultValue: true, + special: [], + primaryKey: false }, + title: + { type: 'CHARACTER VARYING', + allowNull: false, + defaultValue: null, + special: [], + primaryKey: false }, + text: + { type: 'TEXT', + allowNull: true, + defaultValue: null, + special: [], + primaryKey: false }, + html: + { type: 'TEXT', + allowNull: true, + defaultValue: null, + special: [], + primaryKey: false }, + preview: + { type: 'TEXT', + allowNull: true, + defaultValue: null, + special: [], + primaryKey: false }, + createdAt: + { type: 'TIMESTAMP WITH TIME ZONE', + allowNull: false, + defaultValue: null, + special: [], + primaryKey: false }, + updatedAt: + { type: 'TIMESTAMP WITH TIME ZONE', + allowNull: false, + defaultValue: null, + special: [], + primaryKey: false }, + userId: + { type: 'UUID', + allowNull: true, + defaultValue: null, + special: [], + primaryKey: false }, + atlasId: + { type: 'UUID', + allowNull: true, + defaultValue: null, + special: [], + primaryKey: false }, + rootDocumentForId: + { type: 'UUID', + allowNull: true, + defaultValue: null, + special: [], + primaryKey: false }, + teamId: + { type: 'UUID', + allowNull: true, + defaultValue: null, + special: [], + primaryKey: false } + }); + + queryInterface.createTable('teams', { + id: + { type: 'UUID', + allowNull: false, + defaultValue: null, + special: [], + primaryKey: true }, + name: + { type: 'CHARACTER VARYING', + allowNull: true, + defaultValue: null, + special: [], + primaryKey: false }, + slackId: + { type: 'CHARACTER VARYING', + allowNull: true, + defaultValue: null, + special: [], + primaryKey: true }, + slackData: + { type: 'JSONB', + allowNull: true, + defaultValue: null, + special: [], + primaryKey: false }, + createdAt: + { type: 'TIMESTAMP WITH TIME ZONE', + allowNull: false, + defaultValue: null, + special: [], + primaryKey: false }, + updatedAt: + { type: 'TIMESTAMP WITH TIME ZONE', + allowNull: false, + defaultValue: null, + special: [], + primaryKey: false } } + ); + + queryInterface.createTable('users', { + id: + { type: 'UUID', + allowNull: false, + defaultValue: null, + special: [], + primaryKey: true }, + email: + { type: 'CHARACTER VARYING', + allowNull: false, + defaultValue: null, + special: [], + primaryKey: false }, + username: + { type: 'CHARACTER VARYING', + allowNull: false, + defaultValue: null, + special: [], + primaryKey: false }, + name: + { type: 'CHARACTER VARYING', + allowNull: false, + defaultValue: null, + special: [], + primaryKey: false }, + isAdmin: + { type: 'BOOLEAN', + allowNull: true, + defaultValue: false, + special: [], + primaryKey: false }, + slackAccessToken: + { type: 'bytea', + allowNull: true, + defaultValue: null, + special: [], + primaryKey: false }, + slackId: + { type: 'CHARACTER VARYING', + allowNull: false, + defaultValue: null, + unique: true, + special: [], + primaryKey: false }, + slackData: + { type: 'JSONB', + allowNull: true, + defaultValue: null, + special: [], + primaryKey: false }, + jwtSecret: + { type: 'bytea', + allowNull: true, + defaultValue: null, + special: [], + primaryKey: false }, + createdAt: + { type: 'TIMESTAMP WITH TIME ZONE', + allowNull: false, + defaultValue: null, + special: [], + primaryKey: false }, + updatedAt: + { type: 'TIMESTAMP WITH TIME ZONE', + allowNull: false, + defaultValue: null, + special: [], + primaryKey: false }, + teamId: + { type: 'UUID', + allowNull: true, + defaultValue: null, + special: [], + primaryKey: false } + }); + }, + + down: function (queryInterface, Sequelize) { + queryInterface.dropAllTables(); + } +}; diff --git a/server/models/Atlas.js b/server/models/Atlas.js index 175bd1ba..5c358618 100644 --- a/server/models/Atlas.js +++ b/server/models/Atlas.js @@ -2,7 +2,7 @@ import { DataTypes, sequelize, } from '../sequelize'; -import Team from './Team'; +import Document from './Document'; const allowedAtlasTypes = [['atlas', 'journal']]; @@ -11,8 +11,33 @@ const Atlas = sequelize.define('atlas', { name: DataTypes.STRING, description: DataTypes.STRING, type: { type: DataTypes.STRING, validate: { isIn: allowedAtlasTypes }}, + + /* type: atlas */ + atlasStructure: DataTypes.JSONB, +}, { + tableName: 'atlases', + hooks: { + // beforeValidate: (doc) => { + // doc.urlId = randomstring.generate(15); + // }, + // beforeCreate: (doc) => { + // doc.html = convertToMarkdown(doc.text); + // doc.preview = truncateMarkdown(doc.text, 160); + // }, + // beforeUpdate: (doc) => { + // doc.html = convertToMarkdown(doc.text); + // doc.preview = truncateMarkdown(doc.text, 160); + // }, + }, + instanceMethods: { + // buildUrl() { + // const slugifiedTitle = slug(this.title); + // return `${slugifiedTitle}-${this.urlId}`; + // } + } }); -Atlas.belongsTo(Team); +Atlas.hasMany(Document, { as: 'documents', foreignKey: 'atlasId' }); +Atlas.hasOne(Document, { as: 'rootDocument', foreignKey: 'rootDocumentForId', constraints: false }); export default Atlas; diff --git a/server/models/Document.js b/server/models/Document.js index d0413afc..904ba7ad 100644 --- a/server/models/Document.js +++ b/server/models/Document.js @@ -10,8 +10,6 @@ import { import { truncateMarkdown, } from '../utils/truncate'; -import Atlas from './Atlas'; -import Team from './Team'; import User from './User'; slug.defaults.mode ='rfc3986'; @@ -51,8 +49,6 @@ const Document = sequelize.define('document', { } }); -Document.belongsTo(Atlas, { as: 'atlas' }); -Document.belongsTo(Team); Document.belongsTo(User); export default Document; diff --git a/server/models/Team.js b/server/models/Team.js index 9c933d25..bd6e4e1e 100644 --- a/server/models/Team.js +++ b/server/models/Team.js @@ -2,6 +2,9 @@ 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 }, @@ -9,6 +12,17 @@ const Team = sequelize.define('team', { slackId: { type: DataTypes.STRING, unique: true }, slackData: DataTypes.JSONB, }, { + instanceMethods: { + async createFirstAtlas() { + const atlas = await Atlas.create({ + name: this.name, + description: 'Your first Atlas', + type: 'journal', + teamId: this.id, + }); + return atlas; + } + }, indexes: [ { unique: true, @@ -17,4 +31,8 @@ const Team = sequelize.define('team', { ], }); +Team.hasMany(Atlas, { as: 'atlases' }); +Team.hasMany(Document, { as: 'documents' }); +Team.hasMany(User, { as: 'users' }); + export default Team; diff --git a/server/models/User.js b/server/models/User.js index 64452ab8..6eab9086 100644 --- a/server/models/User.js +++ b/server/models/User.js @@ -4,7 +4,6 @@ import { sequelize, encryptedFields } from '../sequelize'; -import Team from './Team'; import JWT from 'jsonwebtoken'; @@ -39,8 +38,5 @@ const setRandomJwtSecret = (model) => { }; User.beforeCreate(setRandomJwtSecret); -User.belongsTo(Team); - -sequelize.sync(); export default User; diff --git a/server/presenters.js b/server/presenters.js index 7c3977ec..e00b4fcf 100644 --- a/server/presenters.js +++ b/server/presenters.js @@ -1,5 +1,5 @@ import _orderBy from 'lodash.orderby'; -import Document from './models/Document'; +import { Document, Atlas } from './models'; export function presentUser(user) { return new Promise(async (resolve, reject) => { @@ -65,12 +65,14 @@ export async function presentDocument(document, includeAtlas=false) { private: document.private, createdAt: document.createdAt, updatedAt: document.updatedAt, - atlas: document.atlaId, + atlas: document.atlasId, team: document.teamId, } if (includeAtlas) { - const atlas = await document.getAtlas(); + const atlas = await Atlas.findOne({ where: { + id: document.atlasId, + }}); data.atlas = await presentAtlas(atlas, false); }