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}