diff --git a/README.md b/README.md
index 6e794ebdc5..7e98d323e7 100644
--- a/README.md
+++ b/README.md
@@ -177,6 +177,23 @@ app.get('/auth/facebook/callback', passport.authenticate('facebook', { successRe
+
+- Sign in at [LinkedIn Developer Network](http://developer.linkedin.com/)
+- From the account name dropdown menu select **API Keys**
+ - *It may ask you to sign in once again*
+- Click **+ Add New Application** button
+- Fill out all *required* fields
+- For **Default Scope** make sure *at least* the following is checked:
+ - `r_fullprofile`
+ - `r_emailaddress`
+ - `r_network`
+- Finish by clicking **Add Application** button
+- Copy and paste *API Key* and *Secret Key* keys into `config/secrets.js`
+ - *API Key* is your **clientID**
+ - *Secret Key* is your **clientSecret**
+
+
+
- Visit the **Account** section of your Venmo profile after logging in
- Click on the **Developers** tab
diff --git a/app.js b/app.js
index 4dc6f8716d..3179e4d77e 100755
--- a/app.js
+++ b/app.js
@@ -132,6 +132,7 @@ app.get('/api/github', passportConf.isAuthenticated, passportConf.isAuthorized,
app.get('/api/twitter', passportConf.isAuthenticated, passportConf.isAuthorized, apiController.getTwitter);
app.get('/api/venmo', passportConf.isAuthenticated, passportConf.isAuthorized, apiController.getVenmo);
app.post('/api/venmo', passportConf.isAuthenticated, passportConf.isAuthorized, apiController.postVenmo);
+app.get('/api/linkedin', apiController.getLinkedin);
/**
* OAuth routes for sign-in.
@@ -145,6 +146,8 @@ app.get('/auth/google', passport.authenticate('google', { scope: 'profile email'
app.get('/auth/google/callback', passport.authenticate('google', { successRedirect: '/', failureRedirect: '/login' }));
app.get('/auth/twitter', passport.authenticate('twitter'));
app.get('/auth/twitter/callback', passport.authenticate('twitter', { successRedirect: '/', failureRedirect: '/login' }));
+app.get('/auth/linkedin', passport.authenticate('linkedin', { state: 'SOME STATE' }));
+app.get('/auth/linkedin/callback', passport.authenticate('linkedin', { successRedirect: '/', failureRedirect: '/login' }));
/**
* OAuth routes for API examples that require authorization.
diff --git a/config/passport.js b/config/passport.js
index a1a3f5c902..3215e4e3bb 100755
--- a/config/passport.js
+++ b/config/passport.js
@@ -5,6 +5,7 @@ var FacebookStrategy = require('passport-facebook').Strategy;
var TwitterStrategy = require('passport-twitter').Strategy;
var GitHubStrategy = require('passport-github').Strategy;
var GoogleStrategy = require('passport-google-oauth').OAuth2Strategy;
+var LinkedInStrategy = require('passport-linkedin-oauth2').Strategy;
var OAuthStrategy = require('passport-oauth').OAuthStrategy; // Tumblr
var OAuth2Strategy = require('passport-oauth').OAuth2Strategy; // Venmo, Foursquare
var User = require('../models/User');
@@ -229,6 +230,59 @@ passport.use(new GoogleStrategy(secrets.google, function(req, accessToken, refre
}
}));
+/**
+ * Sign in with LinkedIn.
+ */
+
+passport.use(new LinkedInStrategy(secrets.linkedin, function(req, accessToken, refreshToken, profile, done) {
+ if (req.user) {
+ User.findOne({ $or: [
+ { linkedin: profile.id },
+ { email: profile._json.emailAddress }
+ ] }, function(err, existingUser) {
+ if (existingUser) {
+ req.flash('errors', { msg: 'There is already a LinkedIn account that belongs to you. Sign in with that account or delete it, then link it with your current account.' });
+ done(err);
+ } else {
+ User.findById(req.user.id, function(err, user) {
+ user.linkedin = profile.id;
+ user.tokens.push({ kind: 'linkedin', accessToken: accessToken });
+ user.profile.name = user.profile.name || profile.displayName;
+ user.profile.location = user.profile.location || profile._json.location.name;
+ user.profile.picture = user.profile.picture || profile._json.pictureUrl;
+ user.profile.website = user.profile.website || profile._json.publicProfileUrl;
+ user.save(function(err) {
+ req.flash('info', { msg: 'LinkedIn account has been linked.' });
+ done(err, user);
+ });
+ });
+ }
+ });
+ } else {
+ User.findOne({ linkedin: profile.id }, function(err, existingUser) {
+ if (existingUser) return done(null, existingUser);
+ User.findOne({ email: profile._json.emailAddress }, function(err, existingEmailUser) {
+ if (existingEmailUser) {
+ req.flash('errors', { msg: 'There is already an account using this email address. Sign in to that account and link it with Google manually from Account Settings.' });
+ done(err);
+ } else {
+ var user = new User();
+ user.linkedin = profile.id;
+ user.tokens.push({ kind: 'linkedin', accessToken: accessToken });
+ user.email = profile._json.emailAddress;
+ user.profile.name = profile.displayName;
+ user.profile.location = profile._json.location.name;
+ user.profile.picture = profile._json.pictureUrl;
+ user.profile.website = profile._json.publicProfileUrl;
+ user.save(function(err) {
+ done(err, user);
+ });
+ }
+ });
+ });
+ }
+}));
+
/**
* Tumblr API
* Uses OAuth 1.0a Strategy.
diff --git a/controllers/api.js b/controllers/api.js
index 99a63fdddb..32428df35a 100644
--- a/controllers/api.js
+++ b/controllers/api.js
@@ -14,6 +14,7 @@ var Github = require('github-api');
var Twit = require('twit');
var paypal = require('paypal-rest-sdk');
var twilio = require('twilio')(secrets.twilio.sid, secrets.twilio.token);
+var Linkedin = require('node-linkedin')(secrets.linkedin.clientID, secrets.linkedin.clientSecret, secrets.linkedin.callbackURL);
/**
* GET /api
@@ -34,31 +35,31 @@ exports.getApi = function(req, res) {
exports.getFoursquare = function(req, res, next) {
var token = _.findWhere(req.user.tokens, { kind: 'foursquare' });
async.parallel({
- trendingVenues: function(callback) {
- foursquare.Venues.getTrending('40.7222756', '-74.0022724', { limit: 50 }, token.accessToken, function(err, results) {
- callback(err, results);
- });
- },
- venueDetail: function(callback) {
- foursquare.Venues.getVenue('49da74aef964a5208b5e1fe3', token.accessToken, function(err, results) {
- callback(err, results);
- });
- },
- userCheckins: function(callback) {
- foursquare.Users.getCheckins('self', null, token.accessToken, function(err, results) {
- callback(err, results);
- });
- }
- },
- function(err, results) {
- if (err) return next(err);
- res.render('api/foursquare', {
- title: 'Foursquare API',
- trendingVenues: results.trendingVenues,
- venueDetail: results.venueDetail,
- userCheckins: results.userCheckins
+ trendingVenues: function(callback) {
+ foursquare.Venues.getTrending('40.7222756', '-74.0022724', { limit: 50 }, token.accessToken, function(err, results) {
+ callback(err, results);
});
+ },
+ venueDetail: function(callback) {
+ foursquare.Venues.getVenue('49da74aef964a5208b5e1fe3', token.accessToken, function(err, results) {
+ callback(err, results);
+ });
+ },
+ userCheckins: function(callback) {
+ foursquare.Users.getCheckins('self', null, token.accessToken, function(err, results) {
+ callback(err, results);
+ });
+ }
+ },
+ function(err, results) {
+ if (err) return next(err);
+ res.render('api/foursquare', {
+ title: 'Foursquare API',
+ trendingVenues: results.trendingVenues,
+ venueDetail: results.venueDetail,
+ userCheckins: results.userCheckins
});
+ });
};
/**
@@ -92,25 +93,25 @@ exports.getFacebook = function(req, res, next) {
var token = _.findWhere(req.user.tokens, { kind: 'facebook' });
graph.setAccessToken(token.accessToken);
async.parallel({
- getMe: function(done) {
- graph.get(req.user.facebook, function(err, me) {
- done(err, me);
- });
- },
- getMyFriends: function(done) {
- graph.get(req.user.facebook + '/friends', function(err, friends) {
- done(err, friends.data);
- });
- }
- },
- function(err, results) {
- if (err) return next(err);
- res.render('api/facebook', {
- title: 'Facebook API',
- me: results.getMe,
- friends: results.getMyFriends
+ getMe: function(done) {
+ graph.get(req.user.facebook, function(err, me) {
+ done(err, me);
});
+ },
+ getMyFriends: function(done) {
+ graph.get(req.user.facebook + '/friends', function(err, friends) {
+ done(err, friends.data);
+ });
+ }
+ },
+ function(err, results) {
+ if (err) return next(err);
+ res.render('api/facebook', {
+ title: 'Facebook API',
+ me: results.getMe,
+ friends: results.getMyFriends
});
+ });
};
/**
@@ -187,53 +188,53 @@ exports.getNewYorkTimes = function(req, res, next) {
exports.getLastfm = function(req, res, next) {
var lastfm = new LastFmNode(secrets.lastfm);
async.parallel({
- artistInfo: function(done) {
- lastfm.request("artist.getInfo", {
- artist: 'Epica',
- handlers: {
- success: function(data) {
- done(null, data);
- },
- error: function(err) {
- done(err);
- }
+ artistInfo: function(done) {
+ lastfm.request("artist.getInfo", {
+ artist: 'Epica',
+ handlers: {
+ success: function(data) {
+ done(null, data);
+ },
+ error: function(err) {
+ done(err);
}
- });
- },
- artistTopAlbums: function(done) {
- lastfm.request("artist.getTopAlbums", {
- artist: 'Epica',
- handlers: {
- success: function(data) {
- var albums = [];
- _.each(data.topalbums.album, function(album) {
- albums.push(album.image.slice(-1)[0]['#text']);
- });
- done(null, albums.slice(0, 4));
- },
- error: function(err) {
- done(err);
- }
- }
- });
- }
- },
- function(err, results) {
- if (err) return next(err.message);
- var artist = {
- name: results.artistInfo.artist.name,
- image: results.artistInfo.artist.image.slice(-1)[0]['#text'],
- tags: results.artistInfo.artist.tags.tag,
- bio: results.artistInfo.artist.bio.summary,
- stats: results.artistInfo.artist.stats,
- similar: results.artistInfo.artist.similar.artist,
- topAlbums: results.artistTopAlbums
- };
- res.render('api/lastfm', {
- title: 'Last.fm API',
- artist: artist
+ }
});
+ },
+ artistTopAlbums: function(done) {
+ lastfm.request("artist.getTopAlbums", {
+ artist: 'Epica',
+ handlers: {
+ success: function(data) {
+ var albums = [];
+ _.each(data.topalbums.album, function(album) {
+ albums.push(album.image.slice(-1)[0]['#text']);
+ });
+ done(null, albums.slice(0, 4));
+ },
+ error: function(err) {
+ done(err);
+ }
+ }
+ });
+ }
+ },
+ function(err, results) {
+ if (err) return next(err.message);
+ var artist = {
+ name: results.artistInfo.artist.name,
+ image: results.artistInfo.artist.image.slice(-1)[0]['#text'],
+ tags: results.artistInfo.artist.tags.tag,
+ bio: results.artistInfo.artist.bio.summary,
+ stats: results.artistInfo.artist.stats,
+ similar: results.artistInfo.artist.similar.artist,
+ topAlbums: results.artistTopAlbums
+ };
+ res.render('api/lastfm', {
+ title: 'Last.fm API',
+ artist: artist
});
+ });
};
/**
@@ -347,41 +348,41 @@ exports.getSteam = function(req, res, next) {
var query = { l: 'english', steamid: steamId, key: secrets.steam.apiKey };
async.parallel({
- playerAchievements: function(done) {
- query.appid = '49520';
- var qs = querystring.stringify(query);
- request.get({ url: 'http://api.steampowered.com/ISteamUserStats/GetPlayerAchievements/v0001/?' + qs, json: true }, function(error, request, body) {
- if (request.statusCode === 401) return done(new Error('Missing or Invalid Steam API Key'));
- done(error, body);
- });
- },
- playerSummaries: function(done) {
- query.steamids = steamId;
- var qs = querystring.stringify(query);
- request.get({ url: 'http://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?' + qs, json: true }, function(error, request, body) {
- if (request.statusCode === 401) return done(new Error('Missing or Invalid Steam API Key'));
- done(error, body);
- });
- },
- ownedGames: function(done) {
- query.include_appinfo = 1;
- query.include_played_free_games = 1;
- var qs = querystring.stringify(query);
- request.get({ url: 'http://api.steampowered.com/IPlayerService/GetOwnedGames/v0001/?' + qs, json: true }, function(error, request, body) {
- if (request.statusCode === 401) return done(new Error('Missing or Invalid Steam API Key'));
- done(error, body);
- });
- }
- },
- function(err, results) {
- if (err) return next(err);
- res.render('api/steam', {
- title: 'Steam Web API',
- ownedGames: results.ownedGames.response.games,
- playerAchievemments: results.playerAchievements.playerstats,
- playerSummary: results.playerSummaries.response.players[0]
+ playerAchievements: function(done) {
+ query.appid = '49520';
+ var qs = querystring.stringify(query);
+ request.get({ url: 'http://api.steampowered.com/ISteamUserStats/GetPlayerAchievements/v0001/?' + qs, json: true }, function(error, request, body) {
+ if (request.statusCode === 401) return done(new Error('Missing or Invalid Steam API Key'));
+ done(error, body);
});
+ },
+ playerSummaries: function(done) {
+ query.steamids = steamId;
+ var qs = querystring.stringify(query);
+ request.get({ url: 'http://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?' + qs, json: true }, function(error, request, body) {
+ if (request.statusCode === 401) return done(new Error('Missing or Invalid Steam API Key'));
+ done(error, body);
+ });
+ },
+ ownedGames: function(done) {
+ query.include_appinfo = 1;
+ query.include_played_free_games = 1;
+ var qs = querystring.stringify(query);
+ request.get({ url: 'http://api.steampowered.com/IPlayerService/GetOwnedGames/v0001/?' + qs, json: true }, function(error, request, body) {
+ if (request.statusCode === 401) return done(new Error('Missing or Invalid Steam API Key'));
+ done(error, body);
+ });
+ }
+ },
+ function(err, results) {
+ if (err) return next(err);
+ res.render('api/steam', {
+ title: 'Steam Web API',
+ ownedGames: results.ownedGames.response.games,
+ playerAchievemments: results.playerAchievements.playerstats,
+ playerSummary: results.playerSummaries.response.players[0]
});
+ });
};
/**
@@ -482,3 +483,17 @@ exports.postVenmo = function(req, res, next) {
res.redirect('/api/venmo');
});
};
+
+exports.getLinkedin = function(req, res, next) {
+ var token = _.findWhere(req.user.tokens, { kind: 'linkedin' });
+ var linkedin = Linkedin.init(token.accessToken);
+
+ linkedin.people.me(function(err, $in) {
+ if (err) return next(err);
+ console.log($in.positions.values);
+ res.render('api/linkedin', {
+ title: 'LinkedIn API',
+ profile: $in
+ });
+ });
+};
diff --git a/models/User.js b/models/User.js
index ef1a3d8683..4660e7fd98 100644
--- a/models/User.js
+++ b/models/User.js
@@ -10,6 +10,7 @@ var userSchema = new mongoose.Schema({
twitter: { type: String, unique: true, sparse: true },
google: { type: String, unique: true, sparse: true },
github: { type: String, unique: true, sparse: true },
+ linkedin: { type: String, unique: true, sparse: true },
tokens: Array,
profile: {
diff --git a/package.json b/package.json
index ab24dc2065..2416aa00f6 100755
--- a/package.json
+++ b/package.json
@@ -2,8 +2,8 @@
"name": "hackathon-starter",
"version": "0.0.0",
"repository": {
- "type" : "git",
- "url" : "https://github.com/sahat/hackathon-starter.git"
+ "type": "git",
+ "url": "https://github.com/sahat/hackathon-starter.git"
},
"scripts": {
"start": "node app.js",
@@ -14,6 +14,8 @@
"bcrypt-nodejs": "~0.0.3",
"cheerio": "~0.13.1",
"connect-assets": "~3.0.0-beta1",
+ "connect-mongo": "~0.4.0",
+ "csso": "~1.3.11",
"express": "~3.4.8",
"express-flash": "~0.0.2",
"express-validator": "~1.0.1",
@@ -24,24 +26,24 @@
"less": "~1.6.3",
"mongoose": "~3.8.7",
"node-foursquare": "~0.2.0",
+ "node-linkedin": "~0.1.4",
"nodemailer": "~0.6.0",
"passport": "~0.2.0",
"passport-facebook": "~1.0.2",
"passport-github": "~0.1.5",
"passport-google-oauth": "~0.1.5",
+ "passport-linkedin-oauth2": "~1.1.1",
"passport-local": "~0.1.6",
"passport-oauth": "~1.0.0",
"passport-twitter": "~1.0.2",
+ "paypal-rest-sdk": "~0.6.4",
"request": "~2.34.0",
"tumblr.js": "~0.0.4",
+ "twilio": "~1.5.0",
"twit": "~1.1.12",
"underscore": "~1.6.0",
- "paypal-rest-sdk": "~0.6.4",
- "connect-mongo": "~0.4.0",
- "twilio": "~1.5.0",
- "validator": "~3.3.0",
- "csso": "~1.3.11",
- "uglify-js": "~2.4.12"
+ "uglify-js": "~2.4.12",
+ "validator": "~3.3.0"
},
"devDependencies": {
"chai": "~1.9.0",
diff --git a/public/css/lib/bootstrap-social.less b/public/css/lib/bootstrap-social.less
new file mode 100755
index 0000000000..c46b1bab28
--- /dev/null
+++ b/public/css/lib/bootstrap-social.less
@@ -0,0 +1,140 @@
+/*
+ * Social Buttons for Bootstrap
+ *
+ * Copyright 2013-2014 Panayiotis Lipiridis
+ * Licensed under the MIT License
+ *
+ * https://github.com/lipis/bootstrap-social
+ */
+
+.btn-social {
+ @bs-height-base: (@line-height-computed + @padding-base-vertical * 2);
+ @bs-height-lg: (floor(@font-size-large * @line-height-base) + @padding-large-vertical * 2);
+ @bs-height-sm: (floor(@font-size-small * 1.5) + @padding-small-vertical * 2);
+ @bs-height-xs: (floor(@font-size-small * 1.2) + @padding-small-vertical + 1);
+
+ position: relative;
+ padding-left: @bs-height-base + @padding-base-horizontal;
+ text-align: left;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ :first-child {
+ position: absolute;
+ left: 0;
+ top: 0;
+ bottom: 0;
+ width: @bs-height-base;
+ line-height: (@bs-height-base + 2);
+ font-size: 1.6em;
+ text-align: center;
+ border-right: 1px solid rgba(0, 0, 0, 0.2);
+ }
+ &.btn-lg {
+ padding-left: @bs-height-lg + @padding-large-horizontal;
+ :first-child {
+ line-height: @bs-height-lg;
+ width: @bs-height-lg;
+ font-size: 1.8em;
+ }
+ }
+ &.btn-sm {
+ padding-left: @bs-height-sm + @padding-small-horizontal;
+ :first-child {
+ line-height: @bs-height-sm;
+ width: @bs-height-sm;
+ font-size: 1.4em;
+ }
+ }
+ &.btn-xs {
+ padding-left: @bs-height-xs + @padding-small-horizontal;
+ :first-child {
+ line-height: @bs-height-xs;
+ width: @bs-height-xs;
+ font-size: 1.2em;
+ }
+ }
+}
+
+.btn-social-icon {
+ .btn-social;
+ height: (@bs-height-base + 2);
+ width: (@bs-height-base + 2);
+ padding: 0;
+ :first-child {
+ border: none;
+ text-align: center;
+ width: 100% !important;
+ }
+ &.btn-lg {
+ height: @bs-height-lg;
+ width: @bs-height-lg;
+ padding-left: 0;
+ padding-right: 0;
+ }
+ &.btn-sm {
+ height: (@bs-height-sm + 2);
+ width: (@bs-height-sm + 2);
+ padding-left: 0;
+ padding-right: 0;
+ }
+ &.btn-xs {
+ height: (@bs-height-xs + 2);
+ width: (@bs-height-xs + 2);
+ padding-left: 0;
+ padding-right: 0;
+ }
+}
+
+.btn-social(@color-bg, @color: white) {
+ background-color: @color-bg;
+ .button-variant(@color, @color-bg, rgba(0, 0, 0, 0.2));
+}
+
+.btn-bitbucket {
+ .btn-social(#205081);
+}
+
+.btn-dropbox {
+ .btn-social(#1087dd);
+}
+
+.btn-facebook {
+ .btn-social(#3b5998);
+}
+
+.btn-flickr {
+ .btn-social(#ff0084);
+}
+
+.btn-foursquare {
+ .btn-social(#0072b1);
+}
+
+.btn-github {
+ .btn-social(#444444);
+}
+
+.btn-google-plus {
+ .btn-social(#dd4b39);
+}
+
+.btn-instagram {
+ .btn-social(#3f729b);
+}
+
+.btn-linkedin {
+ .btn-social(#007bb6);
+}
+
+.btn-tumblr {
+ .btn-social(#2c4762);
+}
+
+.btn-twitter {
+ .btn-social(#55acee);
+}
+
+.btn-vk {
+ .btn-social(#587ea3);
+}
diff --git a/public/css/styles.less b/public/css/styles.less
index f5c0b93106..e816435690 100644
--- a/public/css/styles.less
+++ b/public/css/styles.less
@@ -1,6 +1,7 @@
@import (less) "lib/animate.css";
@import (less) "lib/font-awesome.min.css";
@import "lib/bootstrap/bootstrap";
+@import "lib/bootstrap-social";
@import "themes/default";
// Scaffolding
@@ -50,49 +51,6 @@ body {
height: 45px;
}
-// Social Buttons
-// -------------------------
-
-.btn-facebook {
- color: #fff;
- background: #3b5998;
- border: 1px solid rgba(0, 0, 0, 0.07);
-
- &:hover {
- color: #fff;
- }
-}
-
-.btn-twitter {
- color: #fff;
- background: #00aced;
- border: 1px solid rgba(0, 0, 0, 0.07);
-
- &:hover {
- color: #fff;
- }
-}
-
-.btn-google-plus {
- color: #fff;
- background: #dd4b39;
- border: 1px solid rgba(0, 0, 0, 0.07);
-
- &:hover {
- color: #fff;
- }
-}
-
-.btn-github {
- color: #fff;
- background: #333;
- border: 1px solid rgba(0, 0, 0, 0.07);
-
- &:hover {
- color: #fff;
- }
-}
-
// Extra space between font-awesome icons and text
[class^="fa-"],
[class*="fa-"] {
@@ -119,7 +77,7 @@ body {
.facebook-caption {
position: absolute;
- background-color: rgba(0,0,0,0.5);
+ background-color: rgba(0, 0, 0, 0.5);
font-size: 12px;
color: #fff;
padding: 3px;
diff --git a/views/account/login.jade b/views/account/login.jade
index 2eeee1ceb8..068bed1c74 100644
--- a/views/account/login.jade
+++ b/views/account/login.jade
@@ -1,37 +1,41 @@
extends ../layout
block content
- .col-sm-8.col-sm-offset-2
- form(method='POST')
- legend Sign In
- input(type='hidden', name='_csrf', value=token)
- .form-group
- .btn-group.btn-group-justified
- if secrets.facebookAuth
- a.btn.btn-facebook(href='/auth/facebook')
- i.fa.fa-facebook
- | Facebook
- if secrets.twitterAuth
- a.btn.btn-twitter(href='/auth/twitter')
- i.fa.fa-twitter
- | Twitter
- if secrets.githubAuth
- a.btn.btn-github(href='/auth/github')
- i.fa.fa-github
- | GitHub
- if secrets.googleAuth
- a.btn.btn-google-plus(href='/auth/google')
- i.fa.fa-google-plus
- | Google
+ form(method='POST')
+ legend Sign In
+ input(type='hidden', name='_csrf', value=token)
+ .row
+ .col-sm-4
+ if secrets.facebookAuth
+ a.btn.btn-block.btn-facebook.btn-social(href='/auth/facebook')
+ i.fa.fa-facebook
+ | Sign in with Facebook
+ if secrets.twitterAuth
+ a.btn.btn-block.btn-twitter.btn-social(href='/auth/twitter')
+ i.fa.fa-twitter
+ | Sign in with Twitter
+ if secrets.googleAuth
+ a.btn.btn-block.btn-google-plus.btn-social(href='/auth/google')
+ i.fa.fa-google-plus
+ | Sign in with Google
+ if secrets.githubAuth
+ a.btn.btn-block.btn-github.btn-social(href='/auth/github')
+ i.fa.fa-github
+ | Sign in with GitHub
+ if secrets.linkedinAuth
+ a.btn.btn-block.btn-linkedin.btn-social(href='/auth/linkedin')
+ i.fa.fa-linkedin
+ | Sign in with LinkedIn
if secrets.localAuth
- .form-group
- label.control-label(for='email') Email
- input.form-control(type='text', name='email', id='email', placeholder='Email', autofocus=true)
- .form-group
- label.control-label(for='password') Password
- input.form-control(type='password', name='password', id='password', placeholder='Password')
- .form-group
- button.btn.btn-primary(type='submit')
- i.fa.fa-unlock-alt
- | Login
- a.btn.btn-link(href='/forgot') Forgot your password?
+ .col-sm-8
+ .form-group
+ label.control-label(for='email') Email
+ input.form-control(type='text', name='email', id='email', placeholder='Email', autofocus=true)
+ .form-group
+ label.control-label(for='password') Password
+ input.form-control(type='password', name='password', id='password', placeholder='Password')
+ .form-group
+ button.btn.btn-primary(type='submit')
+ i.fa.fa-unlock-alt
+ | Login
+ a.btn.btn-link(href='/forgot') Forgot your password?
diff --git a/views/account/profile.jade b/views/account/profile.jade
index e45f8b433a..047d077389 100644
--- a/views/account/profile.jade
+++ b/views/account/profile.jade
@@ -98,3 +98,9 @@ block content
p: a.text-danger(href='/account/unlink/github') Unlink your GitHub account
else
p: a(href='/auth/github') Link your GitHub account
+
+ if secrets.linkedinAuth
+ if user.linkedin
+ p: a.text-danger(href='/account/unlink/linkedin') Unlink your LinkedIn account
+ else
+ p: a(href='/auth/linkedin') Link your LinkedIn account
diff --git a/views/api/index.jade b/views/api/index.jade
index 9798ac46fb..0ef57f499e 100644
--- a/views/api/index.jade
+++ b/views/api/index.jade
@@ -17,6 +17,9 @@ block content
small ⇢ Login Required
li
a(href='/api/lastfm') Last.fm
+ li
+ a(href='/api/linkedin') LinkedIn
+ small ⇢ Login Required
li
a(href='/api/nyt') New York Times
li
diff --git a/views/api/linkedin.jade b/views/api/linkedin.jade
new file mode 100644
index 0000000000..f7d79906a4
--- /dev/null
+++ b/views/api/linkedin.jade
@@ -0,0 +1,71 @@
+extends ../layout
+
+block content
+ .page-header
+ h2
+ i.fa.fa-linkedin-square
+ | LinkedIn API
+
+ .btn-group.btn-group-justified
+ a.btn.btn-primary(href='https://github.com/Kuew/node-linkedin', target='_blank')
+ i.fa.fa-book
+ | Node LinkedIn Docs
+ a.btn.btn-primary(href='http://developer.linkedin.com/documents/authentication', target='_blank')
+ i.fa.fa-check-square-o
+ | Getting Started
+ a.btn.btn-primary(href='http://developer.linkedin.com/apis', target='_blank')
+ i.fa.fa-code-fork
+ | API Endpoints
+
+ h3.text-primary My LinkedIn Profile
+ .well.well-sm
+ .row
+ .col-sm-12
+ .col-sm-2
+ br
+ img.thumbnail(src='#{profile.pictureUrl}')
+ .col-sm-10
+ h3= profile.formattedName
+ h4= profile.headline
+ span.text-muted #{profile.location.name} | #{profile.industry}
+ br
+ .row
+ .col-sm-12
+ dl.dl-horizontal
+ dt.text-muted Current
+ for company in profile.positions.values
+ if company.isCurrent
+ dd
+ strong= company.title
+ | at
+ strong #{company.company.name}
+ dt.text-muted Previous
+ for company in profile.positions.values
+ if !company.isCurrent
+ dd
+ | #{company.title}
+ | at
+ | #{company.company.name}
+ dt.text-muted Education
+ for education in profile.educations.values
+ dd= education.schoolName
+ dt.text-muted Recommendations
+ dd
+ strong #{profile.numRecommenders}
+ | recommendation(s) received
+ dt.text-muted Connections
+ dd
+ strong #{profile.numConnections}
+ | connections
+ .text-center
+ small.text-muted= profile.publicProfileUrl
+
+ h3.text-primary LinkedIn Connections
+ table.table.table-hover.table-striped.table-bordered
+ tbody
+ for connection in profile.connections.values
+ if connection.id != 'private'
+ tr
+ td
+ strong #{connection.firstName} #{connection.lastName}
+ .text-muted #{connection.headline}