commit 2bd9fd800f1578f168671260e95222e3854c5b6d
parent ae3fd247cbbc852417ce54c638bcc7b5ded101f2
Author: Konstantin Tarkus <hello@tarkus.me>
Date: Sun, 10 Apr 2016 12:27:00 +0300
Update PR #534 before merging to master
Diffstat:
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}/`);
+ });
});