ff-stream-web

git clone git://archive.git.mtrnord.blog/MTRNord/ff-stream-web.git
Log | Files | Refs | README | LICENSE

commit 78611c92ea8642d09675c13ec3ac77d8826f7591
parent ae3fd247cbbc852417ce54c638bcc7b5ded101f2
Author: Konstantin Tarkus <hello@tarkus.me>
Date:   Sun, 10 Apr 2016 12:30:41 +0300

Merge branch 'arkeros-feature/orm-sequelize'

Diffstat:
M.gitignore | 1+
Mpackage.json | 5+++--
Msrc/config.js | 2+-
Dsrc/core/db.js | 81-------------------------------------------------------------------------------
Msrc/core/passport.js | 140+++++++++++++++++++++++++++++++++++++++++++------------------------------------
Asrc/data/models/.eslintrc | 5+++++
Asrc/data/models/User.js | 39+++++++++++++++++++++++++++++++++++++++
Asrc/data/models/UserClaim.js | 25+++++++++++++++++++++++++
Asrc/data/models/UserLogin.js | 27+++++++++++++++++++++++++++
Asrc/data/models/UserProfile.js | 33+++++++++++++++++++++++++++++++++
Asrc/data/models/index.js | 42++++++++++++++++++++++++++++++++++++++++++
Asrc/data/sequelize.js | 19+++++++++++++++++++
Msrc/server.js | 9++++++---
13 files changed, 278 insertions(+), 150 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -2,6 +2,7 @@ # Read about how to use .gitignore: https://help.github.com/articles/ignoring-files build +database.sqlite node_modules ncp-debug.log npm-debug.log diff --git a/package.json b/package.json @@ -30,11 +30,12 @@ "normalize.css": "4.0.0", "passport": "0.3.2", "passport-facebook": "2.1.0", - "pg": "4.5.3", "pretty-error": "2.0.0", "react": "15.0.1", "react-dom": "15.0.1", + "sequelize": "^3.21.0", "source-map-support": "0.4.0", + "sqlite3": "^3.1.3", "universal-router": "1.1.0-beta.3", "whatwg-fetch": "0.11.0" }, @@ -48,7 +49,7 @@ "babel-plugin-react-transform": "^2.0.2", "babel-plugin-transform-react-constant-elements": "^6.5.0", "babel-plugin-transform-react-inline-elements": "^6.6.5", - "babel-plugin-transform-react-remove-prop-types": "^0.2.4", + "babel-plugin-transform-react-remove-prop-types": "^0.2.5", "babel-plugin-transform-runtime": "^6.7.5", "babel-preset-es2015": "^6.6.0", "babel-preset-node5": "^11.0.1", diff --git a/src/config.js b/src/config.js @@ -13,7 +13,7 @@ export const port = process.env.PORT || 3000; export const host = process.env.WEBSITE_HOSTNAME || `localhost:${port}`; -export const databaseUrl = process.env.DATABASE_URL || 'postgresql://demo:Lqk62xg6TBm5UhfR@demo.ctbl5itzitm4.us-east-1.rds.amazonaws.com:5432/membership01'; +export const databaseUrl = process.env.DATABASE_URL || 'sqlite:database.sqlite'; export const analytics = { diff --git a/src/core/db.js b/src/core/db.js @@ -1,81 +0,0 @@ -/** - * React Starter Kit (https://www.reactstarterkit.com/) - * - * Copyright © 2014-2016 Kriasoft, LLC. All rights reserved. - * - * This source code is licensed under the MIT license found in the - * LICENSE.txt file in the root directory of this source tree. - */ - -import db from 'pg'; -import Promise from 'bluebird'; -import { databaseUrl } from '../config'; - -// TODO: Customize database connection settings -/* jscs:disable requireCamelCaseOrUpperCaseIdentifiers */ -db.defaults.ssl = true; -db.defaults.poolSize = 2; -db.defaults.application_name = 'RSK'; -/* jscs:enable requireCamelCaseOrUpperCaseIdentifiers */ - -/** - * Promise-based wrapper for pg.Client - * https://github.com/brianc/node-postgres/wiki/Client - */ -function AsyncClient(client) { - this.client = client; - this.query = this.query.bind(this); - this.end = this.end.bind(this); -} - -AsyncClient.prototype.query = function query(sql, ...args) { - return new Promise((resolve, reject) => { - if (args.length) { - this.client.query(sql, args, (err, result) => { - if (err) { - reject(err); - } else { - resolve(result); - } - }); - } else { - this.client.query(sql, (err, result) => { - if (err) { - reject(err); - } else { - resolve(result); - } - }); - } - }); -}; - -AsyncClient.prototype.end = function end() { - this.client.end(); -}; - -/** - * Promise-based wrapper for pg.connect() - * https://github.com/brianc/node-postgres/wiki/pg - */ -db.connect = (connect => callback => new Promise((resolve, reject) => { - connect.call(db, databaseUrl, (err, client, done) => { - if (err) { - if (client) { - done(client); - } - - reject(err); - } else { - callback(new AsyncClient(client)).then(() => { - done(); - resolve(); - }).catch(error => { - done(client); - reject(error); - }); - } - }); -}))(db.connect); - -export default db; diff --git a/src/core/passport.js b/src/core/passport.js @@ -15,7 +15,7 @@ import passport from 'passport'; import { Strategy as FacebookStrategy } from 'passport-facebook'; -import db from './db'; +import { User, UserLogin, UserClaim, UserProfile } from '../data/models'; import { auth as config } from '../config'; /** @@ -29,83 +29,97 @@ passport.use(new FacebookStrategy({ passReqToCallback: true, }, (req, accessToken, refreshToken, profile, done) => { const loginName = 'facebook'; - db.connect(async ({ query }) => { + const claimType = 'urn:facebook:access_token'; + const fooBar = async () => { if (req.user) { - let result = await query( - 'SELECT 1 FROM user_login WHERE name = $1 AND key = $2', - loginName, profile.id - ); - if (result.rowCount) { + const userLogin = await UserLogin.findOne({ + attributes: ['name', 'key'], + where: { name: loginName, key: profile.id }, + }); + if (userLogin) { // There is already a Facebook account that belongs to you. // Sign in with that account or delete it, then link it with your current account. done(); } else { - await query(` - INSERT INTO user_account (id, email) SELECT $1, $2::character - WHERE NOT EXISTS (SELECT 1 FROM user_account WHERE id = $1);`, - req.user.id, profile._json.email); - await query(` - INSERT INTO user_login (user_id, name, key) VALUES ($1, 'facebook', $2);`, - req.user.id, profile.id); - await query(` - INSERT INTO user_claim (user_id, type, value) VALUES - ($1, 'urn:facebook:access_token', $3);`, - req.user.id, profile.id); - await query(` - INSERT INTO user_profile (user_id) SELECT $1 - WHERE NOT EXISTS (SELECT 1 FROM user_profile WHERE user_id = $1);`, - req.user.id); - await query(` - UPDATE user_profile SET - display_name = COALESCE(NULLIF(display_name, ''), $2), - gender = COALESCE(NULLIF(gender, ''), $3), - picture = COALESCE(NULLIF(picture, ''), $4) - WHERE user_id = $1;`, - req.user.id, profile.displayName, profile._json.gender, - `https://graph.facebook.com/${profile.id}/picture?type=large`); - result = await query(` - SELECT id, email FROM user_account WHERE id = $1;`, - req.user.id); - done(null, result.rows[0]); + const user = await User.create({ + id: req.user.id, + email: profile._json.email, + logins: [ + { name: loginName, key: profile.id }, + ], + claims: [ + { type: claimType, value: profile.id }, + ], + profile: { + displayName: profile.displayName, + gender: profile._json.gender, + picture: `https://graph.facebook.com/${profile.id}/picture?type=large`, + }, + }, { + include: [ + { model: UserLogin, as: 'logins' }, + { model: UserClaim, as: 'claims' }, + { model: UserProfile, as: 'profile' }, + ], + }); + done(null, { + id: user.id, + email: user.email, + }); } } else { - let result = await query(` - SELECT u.id, u.email FROM user_account AS u - LEFT JOIN user_login AS l ON l.user_id = u.id - WHERE l.name = $1 AND l.key = $2`, loginName, profile.id); - if (result.rowCount) { - done(null, result.rows[0]); + const users = await User.findAll({ + attributes: ['id', 'email'], + where: { '$logins.name$': loginName, '$logins.key$': profile.id }, + include: [ + { + attributes: ['name', 'key'], + model: UserLogin, + as: 'logins', + required: true, + }, + ], + }); + if (users.length) { + done(null, users[0]); } else { - result = await query('SELECT 1 FROM user_account WHERE email = $1', profile._json.email); - if (result.rowCount) { + let user = await User.findOne({ where: { email: profile._json.email } }); + if (user) { // There is already an account using this email address. Sign in to // that account and link it with Facebook manually from Account Settings. done(null); } else { - result = await query(` - INSERT INTO user_account (email) VALUES ($1) RETURNING (id)`, - profile._json.email - ); - const userId = result.rows[0].id; - await query(` - INSERT INTO user_login (user_id, name, key) VALUES ($1, 'facebook', $2)`, - userId, profile.id); - await query(` - INSERT INTO user_claim (user_id, type, value) VALUES - ($1, 'urn:facebook:access_token', $2);`, - userId, accessToken); - await query(` - INSERT INTO user_profile (user_id, display_name, gender, picture) - VALUES ($1, $2, $3, $4);`, - userId, profile.displayName, profile._json.gender, - `https://graph.facebook.com/${profile.id}/picture?type=large` - ); - result = await query('SELECT id, email FROM user_account WHERE id = $1;', userId); - done(null, result.rows[0]); + user = await User.create({ + email: profile._json.email, + emailVerified: true, + logins: [ + { name: loginName, key: profile.id }, + ], + claims: [ + { type: claimType, value: accessToken }, + ], + profile: { + displaynName: profile.displayName, + gender: profile._json.gender, + picture: `https://graph.facebook.com/${profile.id}/picture?type=large`, + }, + }, { + include: [ + { model: UserLogin, as: 'logins' }, + { model: UserClaim, as: 'claims' }, + { model: UserProfile, as: 'profile' }, + ], + }); + done(null, { + id: user.id, + email: user.email, + }); } } } - }).catch(done); + }; + + fooBar().catch(done); })); export default passport; diff --git a/src/data/models/.eslintrc b/src/data/models/.eslintrc @@ -0,0 +1,5 @@ +{ + "rules": { + "new-cap": 0 + } +} diff --git a/src/data/models/User.js b/src/data/models/User.js @@ -0,0 +1,39 @@ +/** + * React Starter Kit (https://www.reactstarterkit.com/) + * + * Copyright © 2014-2016 Kriasoft, LLC. All rights reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE.txt file in the root directory of this source tree. + */ + +import DataType from 'sequelize'; +import Model from '../sequelize'; + +const User = Model.define('User', { + + id: { + type: DataType.UUID, + defaultValue: DataType.UUIDV1, + primaryKey: true, + }, + + email: { + type: DataType.STRING(256), + validate: { isEmail: true }, + }, + + emailConfirmed: { + type: DataType.BOOLEAN, + defaultValue: false, + }, + +}, { + + indexes: [ + { fields: ['email'] }, + ], + +}); + +export default User; diff --git a/src/data/models/UserClaim.js b/src/data/models/UserClaim.js @@ -0,0 +1,25 @@ +/** + * React Starter Kit (https://www.reactstarterkit.com/) + * + * Copyright © 2014-2016 Kriasoft, LLC. All rights reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE.txt file in the root directory of this source tree. + */ + +import DataType from 'sequelize'; +import Model from '../sequelize'; + +const UserClaim = Model.define('UserClaim', { + + type: { + type: DataType.STRING, + }, + + value: { + type: DataType.INTEGER, + }, + +}); + +export default UserClaim; diff --git a/src/data/models/UserLogin.js b/src/data/models/UserLogin.js @@ -0,0 +1,27 @@ +/** + * React Starter Kit (https://www.reactstarterkit.com/) + * + * Copyright © 2014-2016 Kriasoft, LLC. All rights reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE.txt file in the root directory of this source tree. + */ + +import DataType from 'sequelize'; +import Model from '../sequelize'; + +const UserLogin = Model.define('UserLogin', { + + name: { + type: DataType.STRING(50), + primaryKey: true, + }, + + key: { + type: DataType.STRING(100), + primaryKey: true, + }, + +}); + +export default UserLogin; diff --git a/src/data/models/UserProfile.js b/src/data/models/UserProfile.js @@ -0,0 +1,33 @@ +import DataType from 'sequelize'; +import Model from '../sequelize'; + +const UserProfile = Model.define('UserProfile', { + + userId: { + type: DataType.UUID, + primaryKey: true, + }, + + displayName: { + type: DataType.STRING(100), + }, + + picture: { + type: DataType.STRING(256), + }, + + gender: { + type: DataType.STRING(50), + }, + + location: { + type: DataType.STRING(100), + }, + + website: { + type: DataType.STRING(256), + }, + +}); + +export default UserProfile; diff --git a/src/data/models/index.js b/src/data/models/index.js @@ -0,0 +1,42 @@ +/** + * React Starter Kit (https://www.reactstarterkit.com/) + * + * Copyright © 2014-2016 Kriasoft, LLC. All rights reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE.txt file in the root directory of this source tree. + */ + +import sequelize from '../sequelize'; +import User from './User'; +import UserLogin from './UserLogin'; +import UserClaim from './UserClaim'; +import UserProfile from './UserProfile'; + +User.hasMany(UserLogin, { + foreignKey: 'userId', + as: 'logins', + onUpdate: 'cascade', + onDelete: 'cascade', +}); + +User.hasMany(UserClaim, { + foreignKey: 'userId', + as: 'claims', + onUpdate: 'cascade', + onDelete: 'cascade', +}); + +User.hasOne(UserProfile, { + foreignKey: 'userId', + as: 'profile', + onUpdate: 'cascade', + onDelete: 'cascade', +}); + +function sync(...args) { + return sequelize.sync(...args); +} + +export default { sync }; +export { User, UserLogin, UserClaim, UserProfile }; diff --git a/src/data/sequelize.js b/src/data/sequelize.js @@ -0,0 +1,19 @@ +/** + * React Starter Kit (https://www.reactstarterkit.com/) + * + * Copyright © 2014-2016 Kriasoft, LLC. All rights reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE.txt file in the root directory of this source tree. + */ + +import Sequelize from 'sequelize'; +import { databaseUrl } from '../config'; + +const sequelize = new Sequelize(databaseUrl, { + define: { + freezeTableName: true, + }, +}); + +export default sequelize; diff --git a/src/server.js b/src/server.js @@ -19,6 +19,7 @@ import ReactDOM from 'react-dom/server'; import { match } from 'universal-router'; import PrettyError from 'pretty-error'; import passport from './core/passport'; +import models from './data/models'; import schema from './data/schema'; import routes from './routes'; import assets from './assets'; @@ -135,7 +136,9 @@ server.use((err, req, res, next) => { // eslint-disable-line no-unused-vars // // Launch the server // ----------------------------------------------------------------------------- -server.listen(port, () => { - /* eslint-disable no-console */ - console.log(`The server is running at http://localhost:${port}/`); +models.sync().then(() => { + server.listen(port, () => { + /* eslint-disable no-console */ + console.log(`The server is running at http://localhost:${port}/`); + }); });