From a452f30d85babd14a5838fd943abdf55d1047f7e Mon Sep 17 00:00:00 2001 From: Kirk Chen Date: Sat, 30 May 2015 10:18:46 +0800 Subject: [PATCH 1/4] Implement navbar basic layout --- app/components/nav/nav.js | 61 ++++++++++++ app/main.js | 8 +- public/css/base.css | 200 +++++++++++++++++++++++++++++--------- 3 files changed, 222 insertions(+), 47 deletions(-) create mode 100644 app/components/nav/nav.js diff --git a/app/components/nav/nav.js b/app/components/nav/nav.js new file mode 100644 index 0000000..02c9e3b --- /dev/null +++ b/app/components/nav/nav.js @@ -0,0 +1,61 @@ +var React = require('react/addons'); +var $ = require('jquery'); + +var Nav = React.createClass({ + getInitialState: function () { + return { + hideMenu: true + } + }, + handleToggleMenu: function () { + this.setState({ + hideMenu: !this.state.hideMenu + }) + }, + render: function () { + var cx = React.addons.classSet; + var logoClass = cx({ + 'nav-logo': true, + 'hide': !this.state.hideMenu + }); + var menuClass = cx({ + 'nav-menu' : true, + 'hide': this.state.hideMenu + }); + + return ( + + ); + } + }) + ; + +module.exports = Nav; \ No newline at end of file diff --git a/app/main.js b/app/main.js index f67e95f..e39a2cd 100644 --- a/app/main.js +++ b/app/main.js @@ -3,8 +3,12 @@ var React = require('react'); // Related Control var PostBox = require('./components/post/PostBox'); +var Nav = require('./components/nav/nav'); React.render( - , - document.getElementById('content') +
+
+ , document.getElementById('content') ); \ No newline at end of file diff --git a/public/css/base.css b/public/css/base.css index e1b4a97..59b8899 100644 --- a/public/css/base.css +++ b/public/css/base.css @@ -1,5 +1,5 @@ body { - font-family: "Microsoft Jhenghei", "ff-tisa-web-pro",Georgia,Cambria,"Times New Roman",Times,serif; + font-family: "Microsoft Jhenghei", "ff-tisa-web-pro", Georgia, Cambria, "Times New Roman", Times, serif; } a { @@ -8,84 +8,194 @@ a { } h1 { - font-family: Georgia,Cambria,"Times New Roman",Times,serif; - font-size: 50px; - text-shadow: 0 1px 3px rgba(0,0,0,0.3); - margin-bottom: 30px; - margin-top: 12px; - text-align: center; + font-family: Georgia, Cambria, "Times New Roman", Times, serif; + font-size: 50px; + text-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); + margin-bottom: 30px; + margin-top: 12px; + text-align: center; } h2 { - padding: 0; - margin-top: 16px; - margin-bottom: 4px; + padding: 0; + margin-top: 16px; + margin-bottom: 4px; } h3 { - padding: 0; - margin: 0; - color: rgba(0,0,0,0.3); - font-weight: normal; + padding: 0; + margin: 0; + color: rgba(0, 0, 0, 0.3); + font-weight: normal; +} + +p, ul { + margin: 0; } #content { - width: 100%; - max-width: 700px; - overflow: hidden; - margin: 0 auto; + width: 100%; + max-width: 700px; + overflow: hidden; + margin: 0 auto; } img { - max-width: 100%; + max-width: 100%; +} + +.show{ + display: block; } +.hide { + display: none; +} + +/* Post */ .post { - border-bottom: 1px solid #ccc; - margin-bottom: 50px; - padding-bottom: 50px; + border-bottom: 1px solid #ccc; + margin-bottom: 50px; + padding-bottom: 50px; } .postAuthor { - margin-top: 4px; + margin-top: 4px; } .postTitle { - font-size: 42px; + font-size: 42px; } .postContent { - /*display: none;*/ - font-size: 18px; - line-height: 30px; - margin-top: 30px; - overflow: hidden; - height: 90px; + /*display: none;*/ + font-size: 18px; + line-height: 30px; + margin-top: 30px; + overflow: hidden; + height: 90px; } .postMetaInline-avatar { - width: 36px; - height: 36px; - display: table-cell; + width: 36px; + height: 36px; + display: table-cell; } .avatar-image { - width: 100%; - height: 100%; - border-radius: 100%; + width: 100%; + height: 100%; + border-radius: 100%; } .postMetaInline-feedSummary { - display: table-cell; - vertical-align: middle; - font-size: 14px; - line-height: 1.4; - padding-left: 10px; + display: table-cell; + vertical-align: middle; + font-size: 14px; + line-height: 1.4; + padding-left: 10px; } .postMetaInline--supplemental { - display: block; - color: rgba(0,0,0,0.3); - font-size: 12px; - line-height: 1.1; + display: block; + color: rgba(0, 0, 0, 0.3); + font-size: 12px; + line-height: 1.1; +} + +/* Nav */ +.nav { + position: absolute; + top: 0; + left: 0; + bottom: 0; + outline: 0; + background-color: #232322; + z-index: 300; +} + +.nav-logo { + box-sizing: initial; + cursor: pointer; + position: absolute; + top: 10px; + left: 10px; + padding: 8px; + height: 26px; + width: 26px; + z-index: 700; + background-color: #333332; + text-align: center; + border: 0; + -webkit-border-radius: 100%; + -moz-border-radius: 100%; + border-radius: 100%; +} + +.nav-logo h1 { + font-family: Georgia, Cambria, "Times New Roman", Times, serif; + margin: 0; + padding: 0; + line-height: 26px; + font-size: 22px; + color: #FFF; +} + +.nav-menu { + text-align: left; + background: #ccc; + width: 280px; + height: 100%; +} + +.nav-menu-list { + list-style-type: none; + padding-top: 10px; + padding-left: 20px; +} + +.nav-menu-item { + padding: 5px; + line-height: 24px; + color: #555; + font-size: 14px; + font-weight: bold; +} + +.nav-menu-title { + line-height: 24px; + vertical-align: text-bottom; +} + +/* Icon */ +.icon { + font-style: normal; + display: inline-block; + vertical-align: baseline; + width: 24px; + height: 24px; + margin-right: 15px; } + +.icons-search { + background-image: url(../img/search.png); + background-size: 18px 18px !important; + background-repeat: no-repeat; +} + +.icons-keanux { + color: #000; + font-family: Georgia, Cambria, "Times New Roman", Times, serif; + font-size: 22px; + font-weight: bold; +} + +.icons-avatar { + color: #FFF; +} + +.icons-avatar img { + -webkit-border-radius: 100%; + -moz-border-radius: 100%; + border-radius: 100%; +} \ No newline at end of file From 072f923cd309a4d4f0cf871119baaefe86c7b768 Mon Sep 17 00:00:00 2001 From: Kirk Chen Date: Sun, 7 Jun 2015 09:58:20 +0800 Subject: [PATCH 2/4] Integrate passport facebook --- app/components/nav/nav.js | 7 ++++ package.json | 3 ++ routes/api.js | 12 +++---- server.js | 72 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 88 insertions(+), 6 deletions(-) diff --git a/app/components/nav/nav.js b/app/components/nav/nav.js index 02c9e3b..262b2e7 100644 --- a/app/components/nav/nav.js +++ b/app/components/nav/nav.js @@ -50,6 +50,13 @@ var Nav = React.createClass({ +
  • + + + Facebook Login + + +
  • diff --git a/package.json b/package.json index aaeec05..5212b3f 100644 --- a/package.json +++ b/package.json @@ -21,9 +21,12 @@ "body-parser": "~1.0.1", "connect-browserify": "^4.0.0", "express": "~4.0.0", + "express-session": "^1.11.2", "jquery": "^2.1.4", "mysql": "git://github.com/felixge/node-mysql.git", "node-jsx": "^0.13.3", + "passport": "^0.2.2", + "passport-facebook": "^2.0.0", "react": "^0.13.3", "react-ago-component": "^0.6.1", "reactify": "^1.1.1", diff --git a/routes/api.js b/routes/api.js index 933e39c..e2d59b1 100644 --- a/routes/api.js +++ b/routes/api.js @@ -15,19 +15,19 @@ router.get('/', function (req, res) { // To get all user data router.get('/users', function (req, res) { Models.User.findAll() - .then(function(users){ + .then(function (users) { res.json(users); - }, function(err){ + }, function (err) { throw err; }) }); // To get all posts router.get('/posts', function (req, res) { - Models.Post.findAll({ include: [{ model: Models.User, required: true}]}) - .then(function(posts){ - res.json({ data: posts}); - }, function(err){ + Models.Post.findAll({include: [{model: Models.User, required: true}]}) + .then(function (posts) { + res.json({data: posts}); + }, function (err) { throw err; }); }); diff --git a/server.js b/server.js index 23c9acf..4aeec66 100644 --- a/server.js +++ b/server.js @@ -1,8 +1,52 @@ // Express Related Library var express = require('express'); +var session = require('express-session'); var bodyParser = require('body-parser'); var browserify = require('connect-browserify'); +// Passport Library +var passport = require('passport'); +var FacebookStrategy = require('passport-facebook').Strategy; + +var FACEBOOK_APP_ID = "FACEBOOK_APP_ID"; +var FACEBOOK_APP_SECRET = "FACEBOOK_APP_SECRET"; + +// Passport session setup. +// To support persistent login sessions, Passport needs to be able to +// serialize users into and deserialize users out of the session. Typically, +// this will be as simple as storing the user ID when serializing, and finding +// the user by ID when deserializing. However, since this example does not +// have a database of user records, the complete Facebook profile is serialized +// and deserialized. +passport.serializeUser(function(user, done) { + done(null, user); +}); + +passport.deserializeUser(function(obj, done) { + done(null, obj); +}); + +// Use the FacebookStrategy within Passport. +// Strategies in Passport require a `verify` function, which accept +// credentials (in this case, an accessToken, refreshToken, and Facebook +// profile), and invoke a callback with a user object. +passport.use(new FacebookStrategy({ + clientID: FACEBOOK_APP_ID, + clientSecret: FACEBOOK_APP_SECRET, + callbackURL: "http://localhost:8080/auth/facebook/callback" + }, + function(accessToken, refreshToken, profile, done) { + // asynchronous verification, for effect... + process.nextTick(function () { + // To keep the example simple, the user's Facebook profile is returned to + // represent the logged-in user. In a typical application, you would want + // to associate the Facebook account with a user record in your database, + // and return that user instead. + return done(null, profile); + }); + } +)); + // React Related Library var reactify = require('reactify'); var React = require('react'); @@ -14,6 +58,11 @@ nodeJsx.install({extension: '.jsx'}); var app = express(); app.use(bodyParser.urlencoded({extended: true})); app.use(bodyParser.json()); +app.use(session({ secret: 'keyboard cat' })); +// Initialize Passport! Also use passport.session() middleware, to support +// persistent login sessions (recommended). +app.use(passport.initialize()); +app.use(passport.session()); app.use(express.static('public')); // Register Route and Bundle.js @@ -26,6 +75,29 @@ app.use('/api', apiRoute) transforms: [reactify] })); +// GET /auth/facebook +// Use passport.authenticate() as route middleware to authenticate the +// request. The first step in Facebook authentication will involve +// redirecting the user to facebook.com. After authorization, Facebook will +// redirect the user back to this application at /auth/facebook/callback +app.get('/auth/facebook', + passport.authenticate('facebook'), + function(req, res){ + // The request will be redirected to Facebook for authentication, so this + // function will not be called. + }); + +// GET /auth/facebook/callback +// Use passport.authenticate() as route middleware to authenticate the +// request. If authentication fails, the user will be redirected back to the +// login page. Otherwise, the primary route function function will be called, +// which, in this example, will redirect the user to the home page. +app.get('/auth/facebook/callback', + passport.authenticate('facebook', { failureRedirect: '/login' }), + function(req, res) { + res.redirect('/'); + }); + // Start application var port = process.env.PORT || 8080; app.listen(port); From 9bb40bf6fcab822b802b61ce099de911b45823b6 Mon Sep 17 00:00:00 2001 From: Kirk Chen Date: Sun, 7 Jun 2015 10:13:16 +0800 Subject: [PATCH 3/4] Add Facebook login auto register user --- data/seed.js | 4 +++- models/user.js | 4 +++- server.js | 50 ++++++++++++++++++++++++++++++++------------------ 3 files changed, 38 insertions(+), 20 deletions(-) diff --git a/data/seed.js b/data/seed.js index 039208e..fb41085 100644 --- a/data/seed.js +++ b/data/seed.js @@ -7,7 +7,9 @@ Models.sequelize.sync({force: true}).then(function () { // Add new User Models.User.create({ name: 'keanyc', - nickname: 'KeaNy' + nickname: 'KeaNy', + provider: 'Facebook', + loginId: '10204525184038018' }) .then(function (user) { diff --git a/models/user.js b/models/user.js index c588f12..b7254ad 100644 --- a/models/user.js +++ b/models/user.js @@ -3,7 +3,9 @@ module.exports = function (sequelize, DataTypes) { "User", { name: DataTypes.STRING, - nickname: DataTypes.STRING + nickname: DataTypes.STRING, + provider: DataTypes.ENUM('Local', 'Facebook'), + loginId: DataTypes.STRING }, { classMethods: { diff --git a/server.js b/server.js index 4aeec66..4f33bb1 100644 --- a/server.js +++ b/server.js @@ -7,6 +7,7 @@ var browserify = require('connect-browserify'); // Passport Library var passport = require('passport'); var FacebookStrategy = require('passport-facebook').Strategy; +var Models = require('./models'); var FACEBOOK_APP_ID = "FACEBOOK_APP_ID"; var FACEBOOK_APP_SECRET = "FACEBOOK_APP_SECRET"; @@ -18,11 +19,11 @@ var FACEBOOK_APP_SECRET = "FACEBOOK_APP_SECRET"; // the user by ID when deserializing. However, since this example does not // have a database of user records, the complete Facebook profile is serialized // and deserialized. -passport.serializeUser(function(user, done) { +passport.serializeUser(function (user, done) { done(null, user); }); -passport.deserializeUser(function(obj, done) { +passport.deserializeUser(function (obj, done) { done(null, obj); }); @@ -35,14 +36,27 @@ passport.use(new FacebookStrategy({ clientSecret: FACEBOOK_APP_SECRET, callbackURL: "http://localhost:8080/auth/facebook/callback" }, - function(accessToken, refreshToken, profile, done) { + function (accessToken, refreshToken, profile, done) { // asynchronous verification, for effect... process.nextTick(function () { - // To keep the example simple, the user's Facebook profile is returned to - // represent the logged-in user. In a typical application, you would want - // to associate the Facebook account with a user record in your database, - // and return that user instead. - return done(null, profile); + Models.User + .findOrCreate({ + where: { + provider: 'Facebook', + loginId: profile.id + }, + defaults: { + name: profile.name.familyName + profile.name.givenName, + nickname: profile.displayName, + provider: 'Facebook', + loginId: profile.id + } + }) + .spread(function (user, created) { + console.log(user, created); + + return done(null, user); + }); }); } )); @@ -58,7 +72,7 @@ nodeJsx.install({extension: '.jsx'}); var app = express(); app.use(bodyParser.urlencoded({extended: true})); app.use(bodyParser.json()); -app.use(session({ secret: 'keyboard cat' })); +app.use(session({secret: 'keyboard cat'})); // Initialize Passport! Also use passport.session() middleware, to support // persistent login sessions (recommended). app.use(passport.initialize()); @@ -68,12 +82,12 @@ app.use(express.static('public')); // Register Route and Bundle.js var apiRoute = require('./routes/api'); app.use('/api', apiRoute) - .use('/bundle.js', browserify.serve({ - entry: __dirname + '/app/main', - debug: true, - watch: true, - transforms: [reactify] - })); + .use('/bundle.js', browserify.serve({ + entry: __dirname + '/app/main', + debug: true, + watch: true, + transforms: [reactify] + })); // GET /auth/facebook // Use passport.authenticate() as route middleware to authenticate the @@ -82,7 +96,7 @@ app.use('/api', apiRoute) // redirect the user back to this application at /auth/facebook/callback app.get('/auth/facebook', passport.authenticate('facebook'), - function(req, res){ + function (req, res) { // The request will be redirected to Facebook for authentication, so this // function will not be called. }); @@ -93,8 +107,8 @@ app.get('/auth/facebook', // login page. Otherwise, the primary route function function will be called, // which, in this example, will redirect the user to the home page. app.get('/auth/facebook/callback', - passport.authenticate('facebook', { failureRedirect: '/login' }), - function(req, res) { + passport.authenticate('facebook', {failureRedirect: '/login'}), + function (req, res) { res.redirect('/'); }); From 8baf32575a71a0778b7b81d9182870527e9837b8 Mon Sep 17 00:00:00 2001 From: Kirk Chen Date: Sun, 7 Jun 2015 10:44:05 +0800 Subject: [PATCH 4/4] Refactor and integrate Facebook login into side menu --- app/components/nav/nav.js | 46 ++++++++++++++------- data/seed.js | 3 +- models/user.js | 3 +- routes/api.js | 87 +++++++++++++++++++++++++++++++++++++++ server.js | 77 ---------------------------------- 5 files changed, 121 insertions(+), 95 deletions(-) diff --git a/app/components/nav/nav.js b/app/components/nav/nav.js index 262b2e7..b273da5 100644 --- a/app/components/nav/nav.js +++ b/app/components/nav/nav.js @@ -4,9 +4,21 @@ var $ = require('jquery'); var Nav = React.createClass({ getInitialState: function () { return { - hideMenu: true + hideMenu: true, + isLogin: false, + user: null } }, + componentDidMount: function () { + var self = this; + $.get('/api/login/getStatus') + .then(function (result) { + self.setState({ + isLogin: result.isLogin, + user: result.user + }); + }); + }, handleToggleMenu: function () { this.setState({ hideMenu: !this.state.hideMenu @@ -19,18 +31,18 @@ var Nav = React.createClass({ 'hide': !this.state.hideMenu }); var menuClass = cx({ - 'nav-menu' : true, + 'nav-menu': true, 'hide': this.state.hideMenu }); return ( diff --git a/data/seed.js b/data/seed.js index fb41085..fdf70f6 100644 --- a/data/seed.js +++ b/data/seed.js @@ -9,7 +9,8 @@ Models.sequelize.sync({force: true}).then(function () { name: 'keanyc', nickname: 'KeaNy', provider: 'Facebook', - loginId: '10204525184038018' + loginId: '10204525184038018', + photo: 'https://graph.facebook.com/keanyc/picture?width=120&height=120' }) .then(function (user) { diff --git a/models/user.js b/models/user.js index b7254ad..9e455be 100644 --- a/models/user.js +++ b/models/user.js @@ -5,7 +5,8 @@ module.exports = function (sequelize, DataTypes) { name: DataTypes.STRING, nickname: DataTypes.STRING, provider: DataTypes.ENUM('Local', 'Facebook'), - loginId: DataTypes.STRING + loginId: DataTypes.STRING, + photo: DataTypes.STRING }, { classMethods: { diff --git a/routes/api.js b/routes/api.js index e2d59b1..81a549d 100644 --- a/routes/api.js +++ b/routes/api.js @@ -1,6 +1,62 @@ // Express Related Library var express = require('express'); +// Passport Library +var passport = require('passport'); +var FacebookStrategy = require('passport-facebook').Strategy; + +var FACEBOOK_APP_ID = "FACEBOOK_APP_ID"; +var FACEBOOK_APP_SECRET = "FACEBOOK_APP_SECRET"; + +// Passport session setup. +// To support persistent login sessions, Passport needs to be able to +// serialize users into and deserialize users out of the session. Typically, +// this will be as simple as storing the user ID when serializing, and finding +// the user by ID when deserializing. However, since this example does not +// have a database of user records, the complete Facebook profile is serialized +// and deserialized. +passport.serializeUser(function (user, done) { + done(null, user); +}); + +passport.deserializeUser(function (obj, done) { + done(null, obj); +}); + +// Use the FacebookStrategy within Passport. +// Strategies in Passport require a `verify` function, which accept +// credentials (in this case, an accessToken, refreshToken, and Facebook +// profile), and invoke a callback with a user object. +passport.use(new FacebookStrategy({ + clientID: FACEBOOK_APP_ID, + clientSecret: FACEBOOK_APP_SECRET, + callbackURL: "http://localhost:8080/api/login/facebook/callback", + profileFields: ['id', 'name', 'displayName', 'photos'] + }, + function (accessToken, refreshToken, profile, done) { + // asynchronous verification, for effect... + process.nextTick(function () { + Models.User + .findOrCreate({ + where: { + provider: 'Facebook', + loginId: profile.id + }, + defaults: { + name: profile.name.familyName + profile.name.givenName, + nickname: profile.displayName, + provider: 'Facebook', + loginId: profile.id, + photo: profile.photos[0].value + } + }) + .spread(function (user, created) { + return done(null, user); + }); + }); + } +)); + // Models var Models = require('../models'); @@ -32,4 +88,35 @@ router.get('/posts', function (req, res) { }); }); +// GET /login/facebook +// Use passport.authenticate() as route middleware to authenticate the +// request. The first step in Facebook authentication will involve +// redirecting the user to facebook.com. After authorization, Facebook will +// redirect the user back to this application at /login/facebook/callback +router.get('/login/facebook', + passport.authenticate('facebook'), + function (req, res) { + // The request will be redirected to Facebook for authentication, so this + // function will not be called. + }); + +// GET /login/facebook/callback +// Use passport.authenticate() as route middleware to authenticate the +// request. If authentication fails, the user will be redirected back to the +// login page. Otherwise, the primary route function function will be called, +// which, in this example, will redirect the user to the home page. +router.get('/login/facebook/callback', + passport.authenticate('facebook', {failureRedirect: '/login'}), + function (req, res) { + res.redirect('/'); + }); + +// GET /login/getStatus +router.get('/login/getStatus', function (req, res) { + res.json({ + isLogin: req.isAuthenticated(), + user: req.user + }) +}); + module.exports = router; \ No newline at end of file diff --git a/server.js b/server.js index 4f33bb1..8261b37 100644 --- a/server.js +++ b/server.js @@ -6,60 +6,6 @@ var browserify = require('connect-browserify'); // Passport Library var passport = require('passport'); -var FacebookStrategy = require('passport-facebook').Strategy; -var Models = require('./models'); - -var FACEBOOK_APP_ID = "FACEBOOK_APP_ID"; -var FACEBOOK_APP_SECRET = "FACEBOOK_APP_SECRET"; - -// Passport session setup. -// To support persistent login sessions, Passport needs to be able to -// serialize users into and deserialize users out of the session. Typically, -// this will be as simple as storing the user ID when serializing, and finding -// the user by ID when deserializing. However, since this example does not -// have a database of user records, the complete Facebook profile is serialized -// and deserialized. -passport.serializeUser(function (user, done) { - done(null, user); -}); - -passport.deserializeUser(function (obj, done) { - done(null, obj); -}); - -// Use the FacebookStrategy within Passport. -// Strategies in Passport require a `verify` function, which accept -// credentials (in this case, an accessToken, refreshToken, and Facebook -// profile), and invoke a callback with a user object. -passport.use(new FacebookStrategy({ - clientID: FACEBOOK_APP_ID, - clientSecret: FACEBOOK_APP_SECRET, - callbackURL: "http://localhost:8080/auth/facebook/callback" - }, - function (accessToken, refreshToken, profile, done) { - // asynchronous verification, for effect... - process.nextTick(function () { - Models.User - .findOrCreate({ - where: { - provider: 'Facebook', - loginId: profile.id - }, - defaults: { - name: profile.name.familyName + profile.name.givenName, - nickname: profile.displayName, - provider: 'Facebook', - loginId: profile.id - } - }) - .spread(function (user, created) { - console.log(user, created); - - return done(null, user); - }); - }); - } -)); // React Related Library var reactify = require('reactify'); @@ -89,29 +35,6 @@ app.use('/api', apiRoute) transforms: [reactify] })); -// GET /auth/facebook -// Use passport.authenticate() as route middleware to authenticate the -// request. The first step in Facebook authentication will involve -// redirecting the user to facebook.com. After authorization, Facebook will -// redirect the user back to this application at /auth/facebook/callback -app.get('/auth/facebook', - passport.authenticate('facebook'), - function (req, res) { - // The request will be redirected to Facebook for authentication, so this - // function will not be called. - }); - -// GET /auth/facebook/callback -// Use passport.authenticate() as route middleware to authenticate the -// request. If authentication fails, the user will be redirected back to the -// login page. Otherwise, the primary route function function will be called, -// which, in this example, will redirect the user to the home page. -app.get('/auth/facebook/callback', - passport.authenticate('facebook', {failureRedirect: '/login'}), - function (req, res) { - res.redirect('/'); - }); - // Start application var port = process.env.PORT || 8080; app.listen(port);