├── .gitignore ├── LICENSE ├── README.md ├── app ├── github.js ├── router.js └── templates │ └── index.html ├── index.js ├── package.json └── processes.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/node 2 | 3 | ### Node ### 4 | # Logs 5 | logs 6 | *.log 7 | npm-debug.log* 8 | 9 | # Runtime data 10 | pids 11 | *.pid 12 | *.seed 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directory 30 | # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git 31 | node_modules 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Wang Dàpéng 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # first-commit 2 | a service to find the first commit of a repo, built with koa.js 3 | 4 | http://first-commit.com 5 | -------------------------------------------------------------------------------- /app/github.js: -------------------------------------------------------------------------------- 1 | var rp = require('request-promise'); 2 | var cheerio = require('cheerio'); 3 | var Promise = require("bluebird"); 4 | 5 | const BASE_URL = 'https://github.com'; 6 | 7 | 8 | function fetch(url) { 9 | var options = { 10 | uri: url, 11 | headers: { 12 | 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1) Gecko/20100101 Firefox/40.1' 13 | }, 14 | transform: function(body) { 15 | return cheerio.load(body); 16 | } 17 | }; 18 | return rp(options); 19 | } 20 | 21 | 22 | function getRepoCommitsCount(repo) { 23 | return new Promise((resolve, reject) => { 24 | var url = `${BASE_URL}/${repo}`; 25 | console.log('Fetching ', url); 26 | fetch(url) 27 | .then($ => { 28 | var num_str = $('.numbers-summary .commits .num').text().trim(); 29 | var num = parseInt(num_str.replace(/,/g, ''), 10); 30 | resolve(num); 31 | console.log('Got commit count', num); 32 | }) 33 | .catch(reject); 34 | }); 35 | } 36 | 37 | function getFirstPageUrl(repo, num_of_commits) { 38 | const COMMITS_PER_PAGE = 35; 39 | var first_page = Math.ceil(num_of_commits / COMMITS_PER_PAGE); 40 | return `${BASE_URL}/${repo}/commits?page=${first_page}`; 41 | } 42 | 43 | function extractFirstCommitInfo($) { 44 | var li = $('.commit.commits-list-item').last(); 45 | 46 | var repo = $('meta[property="og:title"]').attr('content'); 47 | var sha = li.find('.commit-links-cell .zeroclipboard-button').data('clipboard-text'); 48 | 49 | var author_id = li.find('.commit-author-section a').text().trim(); 50 | var commit_title = li.find('.commit-title a').text().trim(); 51 | var time_str = li.find('relative-time').attr('datetime'); 52 | 53 | return { 54 | repo: repo, 55 | sha: sha, 56 | title: commit_title, 57 | time: new Date(time_str), 58 | url: `${BASE_URL}/${repo}/commit/${sha}`, 59 | browse_url: `${BASE_URL}/${repo}/tree/${sha}`, 60 | author: author_id 61 | }; 62 | } 63 | 64 | function getFirstCommit(repo) { 65 | return new Promise((resolve, reject) => { 66 | getRepoCommitsCount(repo) 67 | .then(count => getFirstPageUrl(repo, count)) 68 | .then(fetch) 69 | .then(extractFirstCommitInfo) 70 | .then(resolve) 71 | .catch(reject); 72 | }); 73 | } 74 | 75 | 76 | exports.getFirstCommit = getFirstCommit; 77 | -------------------------------------------------------------------------------- /app/router.js: -------------------------------------------------------------------------------- 1 | var router = require('koa-router')(); 2 | var send = require('koa-send'); 3 | var github = require('./github') 4 | 5 | 6 | var serveIndex = function *(next) { 7 | yield send(this, 'index.html', { 8 | root: __dirname + '/templates' 9 | }); 10 | }; 11 | 12 | router.get('/', serveIndex); 13 | router.get('/:user/:repo', serveIndex); 14 | 15 | 16 | router.get('/api/:user/:repo', function *(next) { 17 | var params = this.params; 18 | var repo = params.user + '/' + params.repo; 19 | 20 | console.log('Got request for', repo); 21 | 22 | if (yield* this.cashed()) { 23 | console.log(repo, ' is cached'); 24 | return; 25 | } 26 | 27 | this.body = yield github.getFirstCommit(repo); 28 | }); 29 | 30 | 31 | 32 | module.exports = router; 33 | -------------------------------------------------------------------------------- /app/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | First Commit - wong2's lab 10 | 11 | 50 | 51 | 52 | 53 |
54 | 55 |
56 | 62 |
63 | 64 |
65 |

FIRST COMMIT

66 |

find out the first commit of Github repos

67 |
68 |
69 | 70 |
71 | Go 72 |
73 |
74 |
75 |
76 | try with:   77 | git 78 | bootstrap 79 | express 80 | redis 81 |
82 |
83 | 84 |
85 | 86 |
87 | 88 | 97 | 98 | 99 | 100 | 184 | 185 | 193 | 194 | 195 | 196 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var app = require('koa')(); 2 | var json = require('koa-json'); 3 | var cached = require('cached'); 4 | var raven = require('raven'); 5 | var router = require('./app/router'); 6 | 7 | var cache = cached('first-commit', { 8 | backend: { 9 | type: 'memcached', 10 | hosts: '127.0.0.1:11211', 11 | } 12 | }); 13 | 14 | app.use(require('koa-cash')({ 15 | get: function* (key, maxAge) { 16 | return cache.get(key); 17 | }, 18 | set: function* (key, value) { 19 | return cache.set(key, value); 20 | } 21 | })); 22 | 23 | app 24 | .use(json({pretty: false, param: 'pretty'})) 25 | .use(router.routes()) 26 | .use(router.allowedMethods()); 27 | 28 | 29 | app.on('error', function(err) { 30 | raven.captureException(err); 31 | }); 32 | 33 | app.listen(3000); 34 | console.log('Listening on port 3000 ...'); 35 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "first-commit", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "wong2", 10 | "license": "MIT", 11 | "dependencies": { 12 | "bluebird": "^3.0.5", 13 | "cached": "^4.0.2", 14 | "cheerio": "^0.19.0", 15 | "koa": "^1.1.2", 16 | "koa-cash": "^1.1.0", 17 | "koa-json": "^1.1.1", 18 | "koa-router": "^5.2.3", 19 | "koa-send": "^3.1.0", 20 | "raven": "^0.8.1", 21 | "request-promise": "^1.0.2" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /processes.json: -------------------------------------------------------------------------------- 1 | { 2 | "apps" : [ 3 | { 4 | "name" : "first-commit", 5 | "script" : "index.js", 6 | "exec_mode" : "cluster", 7 | "instances" : 2, 8 | "env": { 9 | "NODE_ENV": "production" 10 | } 11 | } 12 | ] 13 | } 14 | --------------------------------------------------------------------------------