├── .gitignore ├── README.md ├── config.js ├── index.js ├── package.json └── views └── index.html /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | resume.json 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [DEPRECATED] JSON resume LinkedIn builder 2 | 3 | **Deprecated due to changes in LinkedIn API** 4 | 5 | [![Dependencies](http://img.shields.io/david/mblarsen/resume-linkedin.svg 6 | )](https://david-dm.org/mblarsen/resume-linkedin) ![NPM version](http://img.shields.io/npm/v/resume-linkedin.svg) 7 | 8 | [![NPM](https://nodei.co/npm/resume-linkedin.png?downloads=true)](https://nodei.co/npm/resume-linkedin/) 9 | 10 | Builds a valid `resume.json` file based on http://jsonresume.org/ schema by drawing data from your LinkedIn profile ([example](http://registry.jsonresume.org/mblarsen)). 11 | 12 | 13 | After building you can publish your resume using: 14 | 15 | resume publish 16 | 17 | (given you have `resume-cli` installed globally) 18 | 19 | ## Install 20 | 21 | ``` 22 | git clone https://github.com/mblarsen/resume-linkedin.git 23 | ``` 24 | 25 | or 26 | 27 | ``` 28 | npm install resume-linkedin ; cd resume-linkedin 29 | ``` 30 | 31 | ## Usage 32 | 33 | Setup an application on [LinkedIn](https://www.linkedin.com/secure/developer). 34 | 35 | 1. __OAuth 2.0 Redirect URLs__ enter a callback URL, the host can be anything (we'll add the host to your hosts file), but the path must be `/oauth/linkedin/callback`. E.g. `http://resume.example.com/oauth/linkedin/callback`. 36 | 37 | 2. Edit the config.js file and enter you __API Key__ and __Secret Key__. In host enter the same value you used above. E.g. `http://resume.example.com/`. 38 | 39 | 3. Add a line with host to to `/etc/hosts/`. E.g. `127.0.0.1 resume.example.com` 40 | 41 | 4. `npm start` and follow instructions. 42 | 43 | 5. Adjust your resume. It is likely that the _skills_ section needs work. 44 | 45 | 6. Profit! 46 | 47 | ## TODO 48 | 49 | * Error and validation handling. 50 | * Make it run headless. 51 | -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | apiKey: 'Your API Key', 3 | secretKey: 'Your Secret Key', 4 | host: 'http://resume.example.com' 5 | } 6 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var config = require('./config'), 2 | LinkedIn = require('node-linkedin')(config.apiKey, config.secretKey, config.host + '/oauth/linkedin/callback'), 3 | express = require('express'), 4 | fs = require('fs'), 5 | resumeSchema = require('resume-schema'), 6 | log = require('debug')('resume'), 7 | app = express(), 8 | server; 9 | 10 | // Middleware 11 | var cookieParser = require('cookie-parser'), 12 | bodyParser = require('body-parser'), 13 | methodOverride = require('method-override'), 14 | session = require('express-session'), 15 | consolidate = require('consolidate'); 16 | 17 | app.engine('html', consolidate.jade); 18 | app.set('view engine', 'html'); 19 | 20 | app.use(bodyParser.urlencoded({ extended: true })); 21 | app.use(bodyParser.json()); 22 | app.use(methodOverride()); 23 | app.use(session({ secret: 'whistleblown', saveUninitialized: true, resave: true })); 24 | 25 | var resume = { 26 | "bio": { 27 | "email": { }, 28 | "phone": { }, 29 | "location": { }, 30 | "websites": { }, 31 | "profiles": { } 32 | }, 33 | "work": [ ], 34 | "education": [ ], 35 | "awards": [ ], 36 | "publications": [ ], 37 | "skills": [ ], 38 | "references": [ ] 39 | }; 40 | 41 | function makeDate(dateObj, isCurrent) { 42 | if (isCurrent !== undefined && isCurrent.valueOf() === true) { 43 | return ''; 44 | } 45 | if (dateObj.day) { 46 | return [dateObj.year, dateObj.month, dateObj.day].join('-'); 47 | } 48 | if (dateObj.month) { 49 | return [dateObj.year, dateObj.month].join('-'); 50 | } 51 | return String(dateObj.year); 52 | } 53 | 54 | function makeResume($in) { 55 | log('making resume'); 56 | resume.bio.firstName = $in.firstName; 57 | resume.bio.lastName = $in.lastName; 58 | resume.bio.email.personal = $in.emailAddress; 59 | resume.bio.profiles.twitter = $in.primaryTwitterAccount.providerAccountName; 60 | resume.bio.websites.linkedIn = $in.publicProfileUrl; 61 | resume.bio.summary = $in.summary; 62 | 63 | var collection = $in.phoneNumbers.values; 64 | for (i = 0, max = collection.length; i < max; i += 1) { 65 | log('adding phone ' + collection[i].phoneType + ' ' + collection[i].phoneNumber); 66 | resume.bio['phone'][collection[i].phoneType] = collection[i].phoneNumber; 67 | } 68 | 69 | resume['references'] = $in.recommendationsReceived.values.map(function (obj) { 70 | log('adding reference from ' + obj.recommender.firstName + ' ' + obj.recommender.lastName); 71 | return { 72 | name: obj.recommender.firstName + ' ' + obj.recommender.lastName, 73 | reference: obj.recommendationText 74 | }; 75 | }); 76 | 77 | resume['publications'] = $in.publications.values.map(function (obj) { 78 | log('adding publication ' + obj.title); 79 | return { 80 | name: obj.title, 81 | releaseDate: makeDate(obj.date), 82 | publisher: '', 83 | website: '' 84 | }; 85 | }); 86 | 87 | resume['work'] = $in.positions.values.map(function (obj) { 88 | log('adding work ' + obj.company.name); 89 | return { 90 | company: obj.company.name, 91 | position: obj.title, 92 | website: '', 93 | startDate: makeDate(obj.startDate), 94 | endDate: makeDate(obj.endDate, Boolean(obj.isCurrent)), 95 | summary: obj.summary, 96 | highlights: [] 97 | } 98 | }); 99 | 100 | resume['education'] = $in.educations.values.map(function (obj) { 101 | log('adding education ' + obj.schoolName); 102 | return { 103 | institution: obj.schoolName, 104 | startDate: makeDate(obj.startDate), 105 | endDate: makeDate(obj.endDate, obj.isCurrent), 106 | area: obj.fieldOfStudy, 107 | summary: obj.notes, 108 | studyType: obj.degree, 109 | courses: [] 110 | }; 111 | }); 112 | 113 | resume['skills'] = $in.skills.values.map(function (obj) { 114 | log('adding skill ' + obj.skill.name); 115 | return { 116 | name: obj.skill.name, 117 | level: 'beginner|intermediate|master', 118 | keywords: [] 119 | }; 120 | }); 121 | 122 | return resume; 123 | }; 124 | 125 | app.get('/', function(req, res) { 126 | if (req.session.accessToken) { 127 | var linkedIn = LinkedIn.init(req.session.accessToken); 128 | linkedIn.people.me(function(err, $in) { 129 | req.session.$in = $in; 130 | resume = makeResume($in); 131 | resumeSchema.validate(resume, function (result, validationErr) { 132 | var exitCode = 0; 133 | if (validationErr || result.valid !== true) { 134 | console.error(validationErr); 135 | console.error(result); 136 | exitCode = 1; 137 | } else { 138 | var resumeString = JSON.stringify(resume, undefined, 2); 139 | log('saving file resume.json:' + resumeString.length); 140 | fs.writeFileSync(__dirname + '/resume.json', resumeString); 141 | console.log('Done!'); 142 | } 143 | res.render('index', { accessToken: req.session.accessToken, resume: resume, result: result }); 144 | process.exit(exitCode); 145 | }); 146 | }); 147 | } else { 148 | console.log('Click Authorize with LinkedIn to continue'); 149 | res.render('index', { accessToken: req.session.accessToken, resume: resume, error: req.session.error, errorMsg: req.session.errorMsg }); 150 | } 151 | }); 152 | 153 | app.get('/oauth/linkedin', function(req, res) { 154 | console.log('Good, authorizing'); 155 | LinkedIn.auth.authorize(res, ['r_basicprofile', 'r_fullprofile', 'r_emailaddress', 'r_contactinfo']); 156 | }); 157 | 158 | app.get('/oauth/linkedin/callback', function(req, res) { 159 | console.log('Requesting access token'); 160 | LinkedIn.auth.getAccessToken(res, req.query.code, function(err, data) { 161 | if (err) return console.error(err); 162 | var result = JSON.parse(data); 163 | if (typeof result.error !== 'undefined') { 164 | req.session.error = result.error; 165 | req.session.errorMsg = result.error_description; 166 | } else { 167 | req.session.accessToken = result.access_token; 168 | console.log('Access token acquired, getting profile'); 169 | log(req.session.accessToken); 170 | } 171 | return res.redirect('/'); 172 | }); 173 | }); 174 | 175 | app.use(function(err, req, res, next) { 176 | if (err) console.error(err); 177 | next(); 178 | }); 179 | 180 | server = app.listen(80); 181 | console.log('Go visit ' + config.host + ' in your browser'); 182 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "resume-linkedin", 3 | "version": "0.2.1", 4 | "description": "Builds http://jsonresume.org/ JSON schema from your LinkedIn profile.", 5 | "main": "index.js", 6 | "dependencies": { 7 | "express": "~4.5.1", 8 | "node-linkedin": "^0.2.0", 9 | "resume-schema": "^0.0.11", 10 | "body-parser": "^1.4.3", 11 | "cookie-parser": "^1.3.2", 12 | "express-session": "^1.6.3", 13 | "method-override": "^2.1.0", 14 | "consolidate": "^0.10.0", 15 | "jade": "^1.3.1", 16 | "debug": "^1.0.2" 17 | }, 18 | "devDependencies": {}, 19 | "scripts": { 20 | "start": "export DEBUG=resume ; node index.js", 21 | "test": "echo \"Error: no test specified\" && exit 1" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "https://github.com/mblarsen/resume-linkedin.git" 26 | }, 27 | "keywords": [ 28 | "json", 29 | "resume", 30 | "linkedin" 31 | ], 32 | "author": "Michael Bøcker-Larsen", 33 | "license": "MIT" 34 | } 35 | -------------------------------------------------------------------------------- /views/index.html: -------------------------------------------------------------------------------- 1 | doctype html 2 | html(lang="en") 3 | body 4 | h1 JSON resume builder 5 | if error 6 | p= error + ': ' + errorMsg 7 | if accessToken == null 8 | p= accessToken 9 | a(href="/oauth/linkedin/") Authorize with LinkedIn 10 | else 11 | p Hi 12 | = ' ' + resume.bio.firstName 13 | |, return to your console. 14 | --------------------------------------------------------------------------------