diff --git a/app.rb b/app.rb new file mode 100644 index 0000000..9dd9d16 --- /dev/null +++ b/app.rb @@ -0,0 +1,173 @@ +$:.unshift File.join(File.dirname(__FILE__),'lib') + +require 'rubygems' +require 'net/http' +require 'sinatra' +require 'json' +require 'erb' +require 'uri' + +require 'User' +require 'Repo' + +DataMapper::Logger.new(STDOUT, :debug) + +disable :show_exceptions +set :environment, :production + +configure :development do + DataMapper.setup(:default, 'sqlite:////Users/lreilly/Projects/github-scores.com/db/dbdb') +end + +configure :production do + DataMapper.setup(:default, 'sqlite:////Users/lreilly/Projects/github-scores.com/db/db.db') +end + +error do + @title = "404" + @text = "Sorry, but this cat is in another castle!" + @display_small_search = false + erb :not_found +end + +get '/' do + begin + if params[:github_url] + @title = 'High Scores' + @github_url = sanitize_input params[:github_url] + @user = get_user_from_github_url(@github_url) + @repo = get_repo_from_github_url(@github_url) + @high_scores = get_high_scores(@user, @repo) + @display_small_search = true + redirect "/#{@user}/#{@repo}/high_scores/" + else + @title = 'High Scores' + @text = 'Please enter a Github repository URL' + @display_small_search = false + erb :index + end + rescue + @title = "404" + @text = "Sorry, but this cat is in another castle!" + @display_small_search = false + erb :not_found + end +end + +get '/recent/?' do + @repos = Repo.all(:limit => 5, :order => [ :updated_at.desc ]) + @display_small_search = true + erb :recent +end + + +get '/credits/?' do + erb :credits +end + +get '/help/?' do + @display_small_search = true + erb :help +end + +get '/about/?' do + @display_small_search = true + erb :about +end + +get '/:user/:repo/?' do + @user = User::create_from_username(params[:user]) + @repo = Repo::create_from_username_and_repo(params[:user], params[:repo]) + @display_small_search = true + erb :repo +end + +get '/:user/?' do + @user = User::create_from_username(params[:user]) + @display_small_search = true + erb :user +end + +not_found do + @title = "404" + @text = "Sorry, but this cat is in another castle!" + erb :not_found +end + +def sanitize_input(url) + url = url.downcase + + # Special rules for Github URLs starting with 'github.com' + if url[0..9] == 'github.com' + url = 'https://www.github.com' + url[9..url.size] + + # Special rules for Github URLs starting with 'www.github.com' + elsif url[0..13] == 'www.github.com' + url = 'https://www.github.com' + url[13..url.size] + end + + # Special rules for Github URLs ending in 'git' + if url[-4,4] == '.git' + url = url[0..-5] + end + + url = url.gsub("http://", "https://") + url = url.gsub("git@github.com:", "https://www.github.com/") + url = url.gsub("git://", "https://www.") + + # If someone just passes in user/repo e.g. leereilly/leereilly.net + tokens = url.split('/') + if tokens.size == 2 + url = "https://www.github.com/#{tokens[0]}/#{tokens[1]}" + end + + return url +end + +def get_user_from_github_url(sanitized_github_url) + return sanitized_github_url.split('/')[3] +end + +def get_repo_from_github_url(sanitized_github_url) + return sanitized_github_url.split('/')[4] +end + +def get_high_scores(user, repo) + begin + # Kludge - three API calls + stored_user = User::create_from_username(user) + puts "Storing user: #{stored_user}" + stored_repo = Repo::create_from_username_and_repo(user, repo) + puts "Storing repo: #{stored_repo}" + + contributors_url = "http://github.com/api/v2/json/repos/show/#{user}/#{repo}/contributors" + + contributors_feed = Net::HTTP.get_response(URI.parse(contributors_url)) + contributors = contributors_feed.body + contributors_result = JSON.parse(contributors) + repository_contributors = contributors_result['contributors'] + contributors_array = Array.new + repository_contributors.each do |repository_contributor| + user_hash = Hash.new + user_hash[:login] = repository_contributor['login'] + user_hash[:email] = repository_contributor['email'] + user_hash[:gravatar_id] = repository_contributor['gravatar_id'] + user_hash[:location] = repository_contributor['location'] + user_hash[:contributions] = repository_contributor['contributions'].to_i + contributors_array << user_hash + end + return contributors_array + rescue + raise "Sorry, this GitHub repository doesn't seem to exist or is private" + end +end + + +get '/:user/:repo/high_scores/?' do + @title = "New" + @user = params[:user] + @repo = params[:repo] + @high_scores = get_high_scores(@user, @repo) + @display_small_search = true + erb :high_scores +end diff --git a/config.ru b/config.ru new file mode 100644 index 0000000..88fffe3 --- /dev/null +++ b/config.ru @@ -0,0 +1,10 @@ +require 'rubygems' +require 'sinatra.rb' + +# Sinatra defines #set at the top level as a way to set application configuration +set :views, File.join(File.dirname(__FILE__), 'app','views') +set :run, false +set :env, (ENV['RACK_ENV'] ? ENV['RACK_ENV'].to_sym : :development) + +require 'app/main' +run Sinatra.application \ No newline at end of file diff --git a/db/db.db b/db/db.db new file mode 100644 index 0000000..cbe814f Binary files /dev/null and b/db/db.db differ diff --git a/lib/Contributor.rb b/lib/Contributor.rb new file mode 100644 index 0000000..d4c3792 --- /dev/null +++ b/lib/Contributor.rb @@ -0,0 +1,48 @@ +require 'rubygems' +require 'data_mapper' +require 'net/http' +require 'json' +require 'uri' + +DataMapper::Logger.new($stdout, :debug) +DataMapper.setup(:default, 'sqlite:////Users/lreilly/Projects/github-scores.com/db/db.db') + +class Contributor + include DataMapper::Resource + + API_VERSION = 'v2' + BASE_URL = 'http://github.com/api/' + API_VERSION + '/json/user/show/' + + property :id, Serial + property :login, String + property :gravatar_id, String + property :contributions, String + + def self.create_from_user_and_repo(user, repo) + stored_user = User::create_from_username(user) + stored_repo = Repo::create_from_username_and_repo(user, repo) + + contributors_url = "http://github.com/api/v2/json/repos/show/#{user}/#{repo}/contributors" + contributors_feed = Net::HTTP.get_response(URI.parse(contributors_url)) + contributors = contributors_feed.body + contributors_result = JSON.parse(contributors) + repository_contributors = contributors_result['contributors'] + contributors_array = Array.new + + repository_contributors.each do |repository_contributor| + contributor = Contributor.new + contributor.login = repository_contributor['login'] + contributor.gravatar_id = repository_contributor['gravatar_id'] + contributor.contributions = repository_contributor['contributions'] + contributor.save + end + end + + def self.get_json_response(url) + Net::HTTP.get_response(URI.parse(url)) + end +end + +DataMapper::auto_upgrade! +contributors = Contributor::create_from_user_and_repo('leereilly', 'leereilly.net') + diff --git a/lib/Repo.rb b/lib/Repo.rb new file mode 100644 index 0000000..0ca0f35 --- /dev/null +++ b/lib/Repo.rb @@ -0,0 +1,84 @@ +require 'rubygems' +require 'data_mapper' +require 'net/http' +require 'json' +require 'uri' + +DataMapper::Logger.new($stdout, :debug) +DataMapper.setup(:default, 'sqlite:////Users/lreilly/Projects/github-scores.com/db/db.db') + +class Repo + include DataMapper::Resource + + API_VERSION = 'v2' + BASE_URL = 'http://github.com/api/' + API_VERSION + '/json/repos/show/' + + property :id, Serial + property :owner, String + property :url, String + property :homepage, String + property :name, String + property :description, String + property :parent, String + property :has_issues, String + property :source, String + property :watchers, String + property :has_downloads, String + property :fork, String + property :forks, String + property :has_wiki, String + property :pushed_at, String + property :open_issues, String + property :updated_at, DateTime + + def self.create_from_username_and_repo(username, repo) + repo_data_url = Repo.get_repo_data_url(username, repo) + + if found_repo = Repo.first(:owner => username, :name => repo) + if Time.now - Time.parse(found_repo.updated_at.to_s) <= 60*60*24 + puts "Repo created less than 24 hours ago. Returning DB record" + return found_repo + else + puts "Updating current repo" + repo = found_repo + end + else + puts "User not found; using web services" + repo = Repo.new + end + + repo_data_response = get_json_response(repo_data_url) + repo_data = JSON.parse(repo_data_response.body) + repo_data = repo_data['repository'] + + repo.owner = repo_data['owner'] + repo.name = repo_data['name'] + repo.url = repo_data['url'] + repo.homepage = repo_data['homepage'] + repo.description = repo_data['description'] + repo.parent = repo_data['parent'] + repo.has_issues = repo_data['has_issues'] + repo.source = repo_data['source'] + repo.watchers = repo_data['watchers'] + repo.has_downloads = repo_data['has_downloads'] + repo.fork = repo_data['fork'] + repo.forks = repo_data['forks'] + repo.has_wiki = repo_data['has_wiki'] + repo.pushed_at = repo_data['pushed_at'] + repo.open_issues = repo_data['open_issues'] + repo.updated_at = Time.now + repo.save! + return repo + end + + def self.get_json_response(url) + Net::HTTP.get_response(URI.parse(url)) + end + + def self.get_repo_data_url(username, repo) + return BASE_URL + username + '/' + repo + end +end + +DataMapper.auto_upgrade! + diff --git a/lib/User.rb b/lib/User.rb new file mode 100644 index 0000000..ecabbae --- /dev/null +++ b/lib/User.rb @@ -0,0 +1,83 @@ +require 'rubygems' +require 'data_mapper' +require 'net/http' +require 'json' +require 'uri' + +DataMapper::Logger.new($stdout, :debug) +DataMapper.setup(:default, 'sqlite:////Users/lreilly/Projects/github-scores.com/db/db.db') + +class User + include DataMapper::Resource + + API_VERSION = 'v2' + BASE_URL = 'http://github.com/api/' + API_VERSION + '/json/user/show/' + + property :id, Serial + property :github_id, String + property :gravatar_id, String + property :login, String + property :email, String + property :name, String + property :blog, String + property :company, String + property :location, String + property :type, String + property :permission, String + property :created_at, String + property :public_repo_count, String + property :public_gist_count, String + property :following_count, String + property :followers_count, String + property :updated_at, DateTime + + + def self.create_from_username(username) + if found_user = User.first(:login => username) + if Time.now - Time.parse(found_user.updated_at.to_s) <= 60*60*24 + puts "User created less than 24 hours ago. Returning DB record" + return found_user + else + puts "Updating current user" + user = found_user + end + else + puts "User not found; using web services" + user = User.new + end + + user_data_url = User.get_user_data_url(username) + user_data_response = get_json_response(user_data_url) + user_data = JSON.parse(user_data_response.body) + user_data = user_data['user'] + + user.github_id = user_data['id'] + user.gravatar_id = user_data['gravatar_id'] + user.login = user_data['login'] + user.email = user_data['email'] + user.name = user_data['name'] + user.blog = user_data['blog'] + user.company = user_data['company'] + user.location = user_data['location'] + user.type = user_data['type'] + user.permission = user_data['permission'] + user.created_at = user_data['created_at'] + user.public_repo_count = user_data['public_repo_count'] + user.public_gist_count = user_data['public_gist_count'] + user.following_count = user_data['following_count'] + user.followers_count = user_data['followers_count'] + user.updated_at = Time.now + user.save! + return user + end + + def self.get_json_response(url) + Net::HTTP.get_response(URI.parse(url)) + end + + def self.get_user_data_url(username) + return BASE_URL + username + end +end + +DataMapper::auto_upgrade! diff --git a/lib/tasks/migrate.task b/lib/tasks/migrate.task new file mode 100644 index 0000000..0b43835 --- /dev/null +++ b/lib/tasks/migrate.task @@ -0,0 +1,3 @@ +task :migrate do + DataMapper.auto_migrate! +end diff --git a/public/css/style.css b/public/css/style.css new file mode 100644 index 0000000..a03b7d0 --- /dev/null +++ b/public/css/style.css @@ -0,0 +1,93 @@ +@font-face { + font-family: "ArcadeClassic"; + src: url("../font/ArcadeClassic.otf") format("opentype"); +} + +body { + font-family: "ArcadeClassic"; + letter-spacing: 2px; + word-spacing: 0.5em; + color: white; + background: #252525 url(/img/bg.png) top center no-repeat; +} + +#wrapper { + margin: 0 auto; + width: 700px; +} + +#content { + width: 100%; +} + +a { + text-decoration: none; + color: white; +} + +td { + font-size: 40px; +} + +th { + font-size: 40px; + font-weight: bold; +} + +td.small { + font-size: 20px; + padding:10px; +} + +th.small { + font-size: 20px; + font-weight: bold; + padding:10px; + color: yellow; +} + +table { + vertical-align: center; + margin-left: auto; + margin-right: auto; +} + +input { + color: #FFFFFF; + font-family: ArcadeClassic; + font-weight: bold; + font-size: 24px; + background-color: #72A4D2; +} + +input.small { + font-size: 12px; +} + +#header_search { + text-align: right; + float:right; +} + +.header_search { + text-align: right; + float: right; +} + +p.center { + text-align: center; +} + +h1 { + text-align: lightblue; +} + +h2 { + text-align: center; + color: lightblue; +} + +h3 { + /**text-align: center;*/ + color: lightblue; +} \ No newline at end of file diff --git a/public/font/ArcadeClassic.otf b/public/font/ArcadeClassic.otf new file mode 100644 index 0000000..e346fd6 Binary files /dev/null and b/public/font/ArcadeClassic.otf differ diff --git a/public/img/bg.png b/public/img/bg.png new file mode 100644 index 0000000..e08e47d Binary files /dev/null and b/public/img/bg.png differ diff --git a/public/img/favicon.png b/public/img/favicon.png new file mode 100644 index 0000000..ba3b495 Binary files /dev/null and b/public/img/favicon.png differ diff --git a/public/img/octocat.png b/public/img/octocat.png new file mode 100644 index 0000000..6c42ecb Binary files /dev/null and b/public/img/octocat.png differ diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..6a80308 --- /dev/null +++ b/public/index.html @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + +

!

+ + + + + + + + + + + + + + + + + + + + + + +
15600mojomboSan Francisco
5200schaconSan Francisco, CA
3400defunktSan Francisco, CA
+ + + + + \ No newline at end of file diff --git a/test/app_test.rb b/test/app_test.rb new file mode 100644 index 0000000..46fd2a6 --- /dev/null +++ b/test/app_test.rb @@ -0,0 +1,66 @@ +require 'rubygems' +require 'test/unit' +require 'rack/test' +require '../app' + +ENV['RACK_ENV'] = 'test' + +class AppTest < Test::Unit::TestCase + include Rack::Test::Methods + + def app + Sinatra::Application + end + + def test_a_variety_of_urls_work + valid_urls = [ + 'http://www.github.com/leereilly/leereilly.net', + 'https://www.github.com/leereilly/leereilly.net', + 'git@github.com:leereilly/leereilly.net.git', + 'git://github.com/leereilly/leereilly.net.git', + 'github.com/leereilly/leereilly.net.git', + 'www.github.com/leereilly/leereilly.net.git', + 'leereilly/leereilly.net' + ] + valid_urls.each do |valid_url| + get '/', params = {:github_url => valid_url} + assert_equal last_response.status, 302 + follow_redirect! + assert_equal last_response.status, 200 + end + end + + def test_landing_page_works_as_expected + get '/', params = {:github_url => 'http://www.github.com/leereilly/leereilly.net'} + assert_equal last_response.status, 302 + follow_redirect! + assert_equal last_response.status, 200 + end + + def test_search_redirected_to_seo_friendly_url + + end + + def test_cute_404_page + get '/lee/lee/lee' + assert_equal last_response.status, 404 + assert last_response.body.include? 'Sorry, but this cat is in another castle!' + end + + def test_that_peeps_get_credit_where_credit_is_deserved + get '/credits' + assert last_response.ok? + kudos_to = Array[ + 'Ruby', + 'Sinatra', + 'Passenger', + 'Dreamhost', + 'Github', + 'Lee Reilly', + 'Al Gore'] + kudos_to.pop + kudos_to.each do |kudo_to| + assert last_response.body.include?(kudo_to) + end + end +end \ No newline at end of file diff --git a/views/about.erb b/views/about.erb new file mode 100644 index 0000000..5176b43 --- /dev/null +++ b/views/about.erb @@ -0,0 +1,9 @@ +<%= erb :header %> +

About

+

Git is an extremely fast, efficient, distributed version control system ideal for the collaborative development of software.

+ +

git·hub is the best way to collaborate with others. Fork, send pull requests and manage all your public and private git repositories.

+ +

git·hub·scores is a fun way to rank software project contributors in a 8-bit, 80's-tastic viewing environment.

+ +<%= erb :footer %> \ No newline at end of file diff --git a/views/credits.erb b/views/credits.erb new file mode 100644 index 0000000..f265f9b --- /dev/null +++ b/views/credits.erb @@ -0,0 +1,7 @@ +<%= erb :header %> +

Credits

+

Running with Ruby, Sinatra, Passenger on a shared Dreamhost server

+

Powered by the Github API

+

Written by Lee Reilly

+

Octocat logo used with permission

+<%= erb :footer %> \ No newline at end of file diff --git a/views/footer.erb b/views/footer.erb new file mode 100644 index 0000000..0c9db45 --- /dev/null +++ b/views/footer.erb @@ -0,0 +1,11 @@ +

Credits 0

+

© Lee Reilly 2011 Fork me on Github! TWEET THIS!

+

+

[about] [contact] [help] [recent searches]

+ + + + + + + \ No newline at end of file diff --git a/views/header.erb b/views/header.erb new file mode 100644 index 0000000..9183d33 --- /dev/null +++ b/views/header.erb @@ -0,0 +1,12 @@ + + + + + + + + + <%= erb :small_search if @display_small_search == true %>

Github High Scores

+
+
+
\ No newline at end of file diff --git a/views/help.erb b/views/help.erb new file mode 100644 index 0000000..d6a3381 --- /dev/null +++ b/views/help.erb @@ -0,0 +1,29 @@ +<%= erb :header %> + +

Help

+ +

What on Earth is a Git? A Github? You did what with your what now?

+

Please check out the 'about' section for a general overview. + +

How do I view the high scores for my Github repository?

+

Simply enter the URL of your Github repository into the search box. Currently we support the following formats...

+ + +

It's Not Working!

+

Please create an issue in the Github repository

+ +

I'd Like To Contribute

+

You know what to do! #fork #pullRequest

+ +

This looks like a web site from the 80s! Running it through the Geocitiesizer is actually an improvement!!!

+

Cheers! :BEER:

+ +<%= erb :footer %> \ No newline at end of file diff --git a/views/high_scores.erb b/views/high_scores.erb new file mode 100644 index 0000000..e8faed1 --- /dev/null +++ b/views/high_scores.erb @@ -0,0 +1,24 @@ +<%= erb :header %> + +

<%= @user %> >> <%= @repo %> >> High Scores

+ + + + + + + + <% @high_scores.each do |high_score| %> + + + + + + <% end %> +
ScoreAvatarUser
<%= high_score[:contributions]*100 %><%= high_score[:login] %>
+

Permalink: https://github.com/<%= @user %>/<%= @repo %>

+ +

TWEET THIS!

+ +<%= erb :footer %> + diff --git a/views/index.erb b/views/index.erb new file mode 100644 index 0000000..bb71d5a --- /dev/null +++ b/views/index.erb @@ -0,0 +1,9 @@ +<%= erb :header %> +

<%= @title %>

+

<%= @text %>

+ +<%= erb :search_form %> + + +<%= erb :footer %> + diff --git a/views/not_found.erb b/views/not_found.erb new file mode 100644 index 0000000..6d02632 --- /dev/null +++ b/views/not_found.erb @@ -0,0 +1,11 @@ +<%= erb :header %> +
+

<%= @title %>

+

<%= @text %>

+ +
+
+<%= erb :search_form %> + +<%= erb :footer %> + diff --git a/views/random.erb b/views/random.erb new file mode 100644 index 0000000..e69de29 diff --git a/views/recent.erb b/views/recent.erb new file mode 100644 index 0000000..e7ce062 --- /dev/null +++ b/views/recent.erb @@ -0,0 +1,9 @@ +<%= erb :header %> + +

Recent searches

+ +<% @repos.each do |repo| %> +<%= "

#{repo.owner}/#{repo.name}

" %> +<% end %> + +<%= erb :footer %> \ No newline at end of file diff --git a/views/repo.erb b/views/repo.erb new file mode 100644 index 0000000..59303ab --- /dev/null +++ b/views/repo.erb @@ -0,0 +1,26 @@ +<%= erb :header %> + +

<%= @repo.owner %> >> <%= @repo.name %>

+ +
+
ID
+
<%= @repo.id %>
+ +
URL
+
<%= @repo.url %>
+ +
Homepage
+
<%= @repo.homepage %>
+ +
Name
+
<%= @repo.name %>
+ +
Description
+
<%= @repo.description %>
+ +
Created by
+
<%= @repo.owner %>
+ +
+ +<%= erb :footer %> diff --git a/views/search_form.erb b/views/search_form.erb new file mode 100644 index 0000000..0ff900a --- /dev/null +++ b/views/search_form.erb @@ -0,0 +1,6 @@ +
+
+ + +
+
\ No newline at end of file diff --git a/views/small_search.erb b/views/small_search.erb new file mode 100644 index 0000000..daa7c11 --- /dev/null +++ b/views/small_search.erb @@ -0,0 +1,5 @@ +
+
+ +
+
\ No newline at end of file diff --git a/views/user.erb b/views/user.erb new file mode 100644 index 0000000..fc17472 --- /dev/null +++ b/views/user.erb @@ -0,0 +1,43 @@ +<%= erb :header %> + +

<%= @user.login %>

+ +<%= @user.login %>< + +
+
ID
+
<%= @user.id %>
+ +
Login
+
<%= @user.login %>
+ +
Email
+
<%= @user.email %>
+ +
Blog
+
<%= @user.blog %>
+ +
Company
+
<%= @user.company %>
+ +
Location
+
<%= @user.location %>
+ +
Github'n since
+
<%= @user.created_at %>
+ +
Followers
+
<%= @user.followers_count %>
+ +
Following
+
<%= @user.following_count %>
+ +
Public repos
+
<%= @user.public_repo_count %>
+ +
Public gists
+
<%= @user.public_gist_count %>
+ +
+ +<%= erb :footer %>