diff --git a/.gitignore b/.gitignore
index b2dae98..b836692 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
/config/database.yml
+/config/api_keys.yml
/log/*.log
/tmp
diff --git a/Gemfile b/Gemfile
index 15f132e..3d3b661 100644
--- a/Gemfile
+++ b/Gemfile
@@ -8,6 +8,7 @@ group :assets do
gem "coffee-rails", "~> 3.2.1"
gem "therubyracer", platforms: :ruby
+ gem "hogan_assets"
gem "uglifier", ">= 1.0.3"
end
@@ -21,3 +22,9 @@ gem "thin"
# Deploy with Capistrano
gem "capistrano"
+
+gem "robbie", path: "../robbie"
+gem "beatparser", path: "../beatparser"
+gem "rails-backbone"
+gem "eco"
+gem 'bootstrap-sass', '~> 2.0.4.0'
diff --git a/Gemfile.lock b/Gemfile.lock
index af41caa..ccf9f06 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,5 +1,20 @@
+PATH
+ remote: ../beatparser
+ specs:
+ beatparser (0.0.1)
+ lastfm-client
+ musicbrainz
+ robbie
+
+PATH
+ remote: ../robbie
+ specs:
+ robbie (0.0.1)
+ httparty
+ oj
+
GEM
- remote: https://rubygems.org/
+ remote: http://rubygems.org/
specs:
actionmailer (3.2.8)
actionpack (= 3.2.8)
@@ -29,7 +44,14 @@ GEM
i18n (~> 0.6)
multi_json (~> 1.0)
arel (3.0.2)
+ bootstrap-sass (2.0.4.0)
builder (3.0.0)
+ capistrano (2.12.0)
+ highline
+ net-scp (>= 1.0.0)
+ net-sftp (>= 2.0.0)
+ net-ssh (>= 2.0.14)
+ net-ssh-gateway (>= 1.1.0)
coffee-rails (3.2.2)
coffee-script (>= 2.2.0)
railties (~> 3.2.0)
@@ -37,22 +59,53 @@ GEM
coffee-script-source
execjs
coffee-script-source (1.3.3)
+ daemons (1.1.9)
+ eco (1.0.0)
+ coffee-script
+ eco-source
+ execjs
+ eco-source (1.1.0.rc.1)
+ ejs (1.0.0)
erubis (2.7.0)
+ eventmachine (0.12.10)
execjs (1.4.0)
multi_json (~> 1.0)
+ highline (1.6.13)
hike (1.2.1)
+ hogan_assets (1.3.2)
+ execjs (>= 1.2.9)
+ sprockets (>= 2.0.3)
+ tilt (>= 1.3.3)
+ httparty (0.8.3)
+ multi_json (~> 1.0)
+ multi_xml
i18n (0.6.0)
journey (1.0.4)
jquery-rails (2.1.1)
railties (>= 3.1.0, < 5.0)
thor (~> 0.14)
json (1.7.5)
+ lastfm-client (0.0.3)
+ json (>= 1.4.6)
+ libv8 (3.3.10.4)
mail (2.4.4)
i18n (>= 0.4.0)
mime-types (~> 1.16)
treetop (~> 1.4.8)
mime-types (1.19)
multi_json (1.3.6)
+ multi_xml (0.5.1)
+ musicbrainz (0.7.0)
+ nokogiri
+ net-scp (1.0.4)
+ net-ssh (>= 1.99.1)
+ net-sftp (2.0.5)
+ net-ssh (>= 2.0.9)
+ net-ssh (2.5.2)
+ net-ssh-gateway (1.1.0)
+ net-ssh (>= 1.99.1)
+ nokogiri (1.5.5)
+ oj (1.3.4)
pg (0.14.0)
polyglot (0.3.3)
rack (1.4.1)
@@ -70,6 +123,10 @@ GEM
activesupport (= 3.2.8)
bundler (~> 1.0)
railties (= 3.2.8)
+ rails-backbone (0.7.2)
+ coffee-script (~> 2.2.0)
+ ejs (~> 1.0.0)
+ railties (>= 3.1.0)
railties (3.2.8)
actionpack (= 3.2.8)
activesupport (= 3.2.8)
@@ -89,6 +146,12 @@ GEM
hike (~> 1.2)
rack (~> 1.0)
tilt (~> 1.1, != 1.3.0)
+ therubyracer (0.10.2)
+ libv8 (~> 3.3.10)
+ thin (1.4.1)
+ daemons (>= 1.0.9)
+ eventmachine (>= 0.12.6)
+ rack (>= 1.0.0)
thor (0.16.0)
tilt (1.3.3)
treetop (1.4.10)
@@ -103,9 +166,18 @@ PLATFORMS
ruby
DEPENDENCIES
+ beatparser!
+ bootstrap-sass (~> 2.0.4.0)
+ capistrano
coffee-rails (~> 3.2.1)
+ eco
+ hogan_assets
jquery-rails
pg
rails (= 3.2.8)
+ rails-backbone
+ robbie!
sass-rails (~> 3.2.3)
+ therubyracer
+ thin
uglifier (>= 1.0.3)
diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js
index 9097d83..8428731 100644
--- a/app/assets/javascripts/application.js
+++ b/app/assets/javascripts/application.js
@@ -12,4 +12,11 @@
//
//= require jquery
//= require jquery_ujs
+//= require mustache
+//= require hogan
+//= require underscore
+//= require backbone
+//= require backbone_rails_sync
+//= require backbone_datalink
+//= require backbone/beat_haven
//= require_tree .
diff --git a/app/assets/javascripts/backbone/beat_haven.js.coffee b/app/assets/javascripts/backbone/beat_haven.js.coffee
new file mode 100644
index 0000000..c88d4f2
--- /dev/null
+++ b/app/assets/javascripts/backbone/beat_haven.js.coffee
@@ -0,0 +1,18 @@
+#= require_self
+#= require_tree ./templates
+#= require_tree ./models
+#= require_tree ./views
+#= require_tree ./routers
+
+window.BeatHaven =
+ Models: {}
+ Collections: {}
+ Routers: {}
+ Views: {}
+
+ init: ->
+ new BeatHaven.Routers.Artist()
+ Backbone.history.start();
+
+$ ->
+ BeatHaven.init()
diff --git a/app/assets/javascripts/backbone/models/artist.js.coffee b/app/assets/javascripts/backbone/models/artist.js.coffee
new file mode 100644
index 0000000..ecdf6f8
--- /dev/null
+++ b/app/assets/javascripts/backbone/models/artist.js.coffee
@@ -0,0 +1,2 @@
+class BeatHaven.Models.Artist extends Backbone.Model
+ urlRoot: "/api/artists"
diff --git a/app/assets/javascripts/backbone/routers/artist_router.js.coffee b/app/assets/javascripts/backbone/routers/artist_router.js.coffee
new file mode 100644
index 0000000..d7f2f8d
--- /dev/null
+++ b/app/assets/javascripts/backbone/routers/artist_router.js.coffee
@@ -0,0 +1,9 @@
+class BeatHaven.Routers.Artist extends Backbone.Router
+ routes:
+ "artist/:name": "show"
+
+ show: (name) ->
+ artist = new BeatHaven.Models.Artist(id: name)
+ artist.fetch()
+ view = new BeatHaven.Views.ArtistShow(model: artist)
+ $("#main").html(view.render().el)
diff --git a/app/assets/javascripts/backbone/templates/artists/show.mustache b/app/assets/javascripts/backbone/templates/artists/show.mustache
new file mode 100644
index 0000000..cc2da89
--- /dev/null
+++ b/app/assets/javascripts/backbone/templates/artists/show.mustache
@@ -0,0 +1,39 @@
+
+
{{name}}
+
+
+
+
+
{{bio}}
+
+
+ {{#albums}}
+
+
+
{{title}} ({{year}})
+
+
+
+ {{#tracks}}
+
+
+
+ {{title}}
+ |
+
+ {{length}}
+
+ |
+
+ {{/tracks}}
+
+
+
+
+ {{/albums}}
+
+
diff --git a/app/assets/javascripts/backbone/views/artist/artist_show.js.coffee b/app/assets/javascripts/backbone/views/artist/artist_show.js.coffee
new file mode 100644
index 0000000..f895978
--- /dev/null
+++ b/app/assets/javascripts/backbone/views/artist/artist_show.js.coffee
@@ -0,0 +1,10 @@
+class BeatHaven.Views.ArtistShow extends Backbone.View
+ template: HoganTemplates["backbone/templates/artists/show"]
+
+ initialize: ->
+ @model.on("change", @render, this)
+
+ render: ->
+ return this if typeof @model.attributes.id is "string"
+ $(@el).html(@template.render(@model.toJSON()))
+ this
diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css
deleted file mode 100644
index 3192ec8..0000000
--- a/app/assets/stylesheets/application.css
+++ /dev/null
@@ -1,13 +0,0 @@
-/*
- * This is a manifest file that'll be compiled into application.css, which will include all the files
- * listed below.
- *
- * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
- * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
- *
- * You're free to add application-wide styles to this file and they'll appear at the top of the
- * compiled file, but it's generally better to create a new file per style scope.
- *
- *= require_self
- *= require_tree .
- */
diff --git a/app/assets/stylesheets/application.css.scss b/app/assets/stylesheets/application.css.scss
new file mode 100644
index 0000000..c1f6c3f
--- /dev/null
+++ b/app/assets/stylesheets/application.css.scss
@@ -0,0 +1,20 @@
+@import "bootstrap";
+@import "artist";
+@import url(http://fonts.googleapis.com/css?family=Lobster);
+@import url(http://fonts.googleapis.com/css?family=Source+Sans+Pro);
+
+body {
+ background: url(data:image/png;base64,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)
+}
+
+#main {
+ margin-top: 50px;
+}
+
+.navbar a.brand {
+ font-family: Lobster, Georgia, Serif;
+ font-size: 24px;
+ color: #fafafa;
+ letter-spacing: 1px;
+ text-shadow: 1px 1px 0 rgba(255, 255, 255, .2);
+}
diff --git a/app/assets/stylesheets/artist.css.scss b/app/assets/stylesheets/artist.css.scss
new file mode 100644
index 0000000..82d41c1
--- /dev/null
+++ b/app/assets/stylesheets/artist.css.scss
@@ -0,0 +1,132 @@
+.artist-page {
+ h1 {
+ font-family: Lobster, Georgia, serif;
+ font-size: 42px;
+ line-height: 64px;
+ letter-spacing: 1px;
+ text-shadow: 1px 1px 0 rgba(0, 0, 0, .2);
+ }
+
+ .artist-info {
+ min-height: 250px;
+ margin-bottom: 50px;
+
+ .pic {
+ float: left;
+
+ img {
+ width: 250px;
+ height: 250px;
+ border-size: 1px;
+ border-style: solid;
+ border-color: rgba(255, 255, 255, .2);
+ border-radius: 5px;
+ box-shadow: 1px 1px 5px rgba(0, 0, 0, .2);
+ }
+ }
+ .bio {
+ margin-left: 270px;
+ font-family: "Source Sans Pro", Helvetica, sans-serif;
+ font-size: 22px;
+ line-height: 26px;
+ text-shadow: 1px 1px 0 rgba(0, 0, 0, .1);
+ }
+ }
+
+ .album {
+ min-height: 250px;
+ margin-bottom: 30px;
+
+ h2 {
+ font-family: Lobster, Georgia, Serif;
+ font-size: 26px;
+ text-shadow: 1px 1px 0 rgba(0, 0, 0, .2);
+ }
+ .pic {
+ float: left;
+ height: 300px;
+ text-align: center;
+
+ img {
+ background-color: #a0a0a0;
+ width: 250px;
+ height: 250px;
+ border-size: 1px;
+ border-style: solid;
+ border-color: rgba(255, 255, 255, .2);
+ border-radius: 5px;
+ box-shadow: 1px 1px 5px rgba(0, 0, 0, .2);
+ margin-bottom: 10px;
+ }
+ }
+ .title {
+ margin-left: 270px;
+ margin-bottom: 10px;
+ }
+ .tracks {
+ margin-left: 270px;
+
+ tr:hover {
+ td {
+ background-color: rgba(200, 200, 200, .3);
+
+ .track-play i {
+ opacity: 1;
+ }
+ &.length {
+ .track-add {
+ display: block;
+ }
+ .length {
+ display: none;
+ }
+ }
+ }
+ }
+
+ td {
+ position: relative;
+ font-family: "Source Sans Pro", Helvetica, sans-serif;
+ font-size: 20px;
+ line-height: 28px;
+ text-shadow: 1px 1px 0 rgba(0, 0, 0, .1);
+
+ .btn-round {
+ width: 10px;
+ height: 22px;
+ border-radius: 50px;
+
+ i {
+ margin: 3px 0 0 -1px;
+ }
+ }
+ }
+
+ td.title {
+ padding-left: 40px;
+ .track-play {
+ position: absolute;
+ margin: -2px 0 0 -40px;
+
+ i {
+ opacity: .3;
+ }
+ }
+ .track-link {
+ color: #303030;
+ }
+ }
+
+ td.length {
+ text-align: right;
+
+ .track-add {
+ position: absolute;
+ right: 8px;
+ margin: -2px 0 0 0;
+ display: none;
+ }
+ }
+ }
+ }
+}
diff --git a/app/controllers/api/albums_controller.rb b/app/controllers/api/albums_controller.rb
new file mode 100644
index 0000000..dbab9a9
--- /dev/null
+++ b/app/controllers/api/albums_controller.rb
@@ -0,0 +1,8 @@
+module Api
+ class AlbumsController < ::ApplicationController
+ def picture
+ album = Album.find(params[:id])
+ redirect_to album.load_pic
+ end
+ end
+end
diff --git a/app/controllers/api/artists_controller.rb b/app/controllers/api/artists_controller.rb
new file mode 100644
index 0000000..463406c
--- /dev/null
+++ b/app/controllers/api/artists_controller.rb
@@ -0,0 +1,10 @@
+module Api
+ class ArtistsController < ::ApplicationController
+ def show
+ artist = Artist.with_name(params[:id].gsub("+", " "))
+ return render json: { fail: true } if artist.nil?
+
+ render json: artist.dump_json
+ end
+ end
+end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index e8065d9..676ede8 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -1,3 +1,7 @@
class ApplicationController < ActionController::Base
protect_from_forgery
+
+ def main
+
+ end
end
diff --git a/app/models/album.rb b/app/models/album.rb
new file mode 100644
index 0000000..a7a8947
--- /dev/null
+++ b/app/models/album.rb
@@ -0,0 +1,24 @@
+class Album < ActiveRecord::Base
+ belongs_to :artist
+ has_many :tracks
+
+ attr_accessible :artist_id, :pic, :rovi_id, :title, :year
+
+ def pic_safe
+ unless pic.nil?
+ pic
+ else
+ "/api/albums/#{id}/picture"
+ end
+ end
+
+ def load_pic
+ info = BeatParser::Sources::Lastfm.album_info(artist.name, title)
+ unless info[:pic].nil?
+ update_attributes(pic: info[:pic])
+ info[:pic]
+ else
+ "/assets/images/album-dummy.png"
+ end
+ end
+end
diff --git a/app/models/artist.rb b/app/models/artist.rb
new file mode 100644
index 0000000..447a50b
--- /dev/null
+++ b/app/models/artist.rb
@@ -0,0 +1,88 @@
+class Artist < ActiveRecord::Base
+ has_many :albums
+ has_many :performers
+ has_many :tracks, through: :performers
+ has_many :genres, through: :artist_genres
+
+ attr_accessible :bio, :is_group, :name, :pic, :rovi_id
+
+ def loaded?
+ pic? && bio?
+ end
+
+ def dump_json
+ serialized = to_json(
+ include: {
+ albums: {
+ include: {
+ tracks: {
+ methods: [:length],
+ except: [:created_at, :updated_at, :rovi_id, :album_id]
+ }
+ },
+ except: [:created_at, :updated_at, :rovi_id, :pic],
+ methods: [:pic_safe]
+ }
+ },
+ except: [:created_at, :updated_at, :rovi_id],
+ )
+ end
+
+ class << self
+ def with_name(name)
+ # DB lookup
+ artist = find_by_name(name)
+ return artist unless artist.nil?
+
+ # Rovi correction
+ rovi_artist = Robbie::Artist.find_by_name(name)
+ return artist if rovi_artist && artist = find_by_rovi_id(rovi_artist.id)
+
+ # Parsing artist if ok
+ import(rovi_artist) if rovi_artist
+ end
+
+ def import(rovi_artist)
+ data = BeatParser::Aggregator.new.combine(rovi_artist.id)
+ artist = Artist.find_or_create_by_rovi_id(data[:id])
+ artist.update_attributes(
+ name: data[:name],
+ is_group: data[:is_group],
+ pic: data[:pic],
+ bio: data[:bio]
+ )
+ data[:albums].each do |album_meta|
+ album = Album.find_or_create_by_rovi_id(album_meta[:id])
+ album.update_attributes(
+ artist_id: artist.id,
+ title: album_meta[:title],
+ year: album_meta[:year].to_i
+ )
+ album_meta[:tracks].each do |track_meta|
+ track = Track.find_or_create_by_rovi_id(track_meta[:id])
+ track.update_attributes(
+ album_id: album.id,
+ disc_id: track_meta[:disc_id],
+ position: track_meta[:position],
+ title: track_meta[:title],
+ duration: track_meta[:duration]
+ )
+ track_meta[:artists].each do |performer|
+ performer_artist = Artist.find_or_create_by_rovi_id(performer[:id])
+ performer_artist.update_attributes(
+ name: performer[:name]
+ )
+ Performer.find_or_create_by_artist_id_and_track_id(performer_artist.id, track.id)
+ end
+ end
+ end
+ data[:genres].each do |genre_meta|
+ genre = Genre.find_or_create_by_rovi_id(genre_meta[:id])
+ genre.update_attributes(
+ name: genre_meta[:name]
+ )
+ ArtistGenre.find_or_create_by_artist_id_and_genre_id(artist.id, genre.id)
+ end
+ end
+ end
+end
diff --git a/app/models/artist_genre.rb b/app/models/artist_genre.rb
new file mode 100644
index 0000000..e7a4271
--- /dev/null
+++ b/app/models/artist_genre.rb
@@ -0,0 +1,3 @@
+class ArtistGenre < ActiveRecord::Base
+ attr_accessible :artist_id, :genre_id
+end
diff --git a/app/models/genre.rb b/app/models/genre.rb
new file mode 100644
index 0000000..25b9626
--- /dev/null
+++ b/app/models/genre.rb
@@ -0,0 +1,3 @@
+class Genre < ActiveRecord::Base
+ attr_accessible :name, :rovi_id
+end
diff --git a/app/models/performer.rb b/app/models/performer.rb
new file mode 100644
index 0000000..6958b10
--- /dev/null
+++ b/app/models/performer.rb
@@ -0,0 +1,6 @@
+class Performer < ActiveRecord::Base
+ belongs_to :artist
+ belongs_to :track
+
+ attr_accessible :artist_id, :track_id
+end
diff --git a/app/models/track.rb b/app/models/track.rb
new file mode 100644
index 0000000..17515e0
--- /dev/null
+++ b/app/models/track.rb
@@ -0,0 +1,14 @@
+class Track < ActiveRecord::Base
+ belongs_to :album
+ has_many :performers
+ has_many :artists, through: :performers
+
+ attr_accessible :album_id, :disc_id, :duration, :position, :rovi_id, :title
+
+ def length
+ return if duration.nil?
+ length = duration.divmod(60).map(&:to_s)
+ length[1] = "0" << length[1] if length[1].length == 1
+ length.join(":")
+ end
+end
diff --git a/app/views/application/main.html b/app/views/application/main.html
new file mode 100644
index 0000000..e69de29
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb
index 2bdac2b..a6bb5af 100644
--- a/app/views/layouts/application.html.erb
+++ b/app/views/layouts/application.html.erb
@@ -2,13 +2,20 @@
BeatHaven
- <%= stylesheet_link_tag "application", :media => "all" %>
+ <%= stylesheet_link_tag "application", media: "all" %>
<%= javascript_include_tag "application" %>
<%= csrf_meta_tags %>
-<%= yield %>
+
+ Loading...
diff --git a/config/api_keys.example.yml b/config/api_keys.example.yml
new file mode 100644
index 0000000..784b272
--- /dev/null
+++ b/config/api_keys.example.yml
@@ -0,0 +1,7 @@
+lastfm:
+ api_key: secret
+ api_secret: secret
+ client_name: BeatHaven
+rovi:
+ api_key: secret
+ api_secret: secret
diff --git a/config/initializers/beatparser.rb b/config/initializers/beatparser.rb
new file mode 100644
index 0000000..f15911a
--- /dev/null
+++ b/config/initializers/beatparser.rb
@@ -0,0 +1,13 @@
+File.open("#{Rails.root}/config/api_keys.yml") do |file|
+ config = YAML.load(file.read)
+
+ LastFM.api_key = config["lastfm"]["api_key"]
+ LastFM.secret = config["lastfm"]["api_secret"]
+ LastFM.client_name = config["lastfm"]["client_name"]
+
+ Robbie.setup(
+ api_key: config["rovi"]["api_key"],
+ api_secret: config["rovi"]["api_secret"]
+ )
+ Robbie.enable_cache
+end
diff --git a/config/initializers/hogan_templates.rb b/config/initializers/hogan_templates.rb
new file mode 100644
index 0000000..122204f
--- /dev/null
+++ b/config/initializers/hogan_templates.rb
@@ -0,0 +1,3 @@
+HoganAssets::Config.configure do |config|
+ config.path_prefix = "backbone/templates/"
+end
diff --git a/config/routes.rb b/config/routes.rb
index d06904e..5bb6d03 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -1,58 +1,10 @@
BeatHaven::Application.routes.draw do
- # The priority is based upon order of creation:
- # first created -> highest priority.
+ namespace :api do
+ resources :artists, only: [:show], constraints: { id: /.+/ }
+ resources :albums, only: [:picture] do
+ member { get :picture }
+ end
+ end
- # Sample of regular route:
- # match 'products/:id' => 'catalog#view'
- # Keep in mind you can assign values other than :controller and :action
-
- # Sample of named route:
- # match 'products/:id/purchase' => 'catalog#purchase', :as => :purchase
- # This route can be invoked with purchase_url(:id => product.id)
-
- # Sample resource route (maps HTTP verbs to controller actions automatically):
- # resources :products
-
- # Sample resource route with options:
- # resources :products do
- # member do
- # get 'short'
- # post 'toggle'
- # end
- #
- # collection do
- # get 'sold'
- # end
- # end
-
- # Sample resource route with sub-resources:
- # resources :products do
- # resources :comments, :sales
- # resource :seller
- # end
-
- # Sample resource route with more complex sub-resources
- # resources :products do
- # resources :comments
- # resources :sales do
- # get 'recent', :on => :collection
- # end
- # end
-
- # Sample resource route within a namespace:
- # namespace :admin do
- # # Directs /admin/products/* to Admin::ProductsController
- # # (app/controllers/admin/products_controller.rb)
- # resources :products
- # end
-
- # You can have the root of your site routed with "root"
- # just remember to delete public/index.html.
- # root :to => 'welcome#index'
-
- # See how all your routes lay out with "rake routes"
-
- # This is a legacy wild controller route that's not recommended for RESTful applications.
- # Note: This route will make all actions in every controller accessible via GET requests.
- # match ':controller(/:action(/:id))(.:format)'
+ root to: "application#main"
end
diff --git a/db/migrate/20120826170848_create_genres.rb b/db/migrate/20120826170848_create_genres.rb
new file mode 100644
index 0000000..c380cdd
--- /dev/null
+++ b/db/migrate/20120826170848_create_genres.rb
@@ -0,0 +1,10 @@
+class CreateGenres < ActiveRecord::Migration
+ def change
+ create_table :genres do |t|
+ t.string :rovi_id
+ t.string :name
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20120826171119_create_artist_genres.rb b/db/migrate/20120826171119_create_artist_genres.rb
new file mode 100644
index 0000000..f1b0cde
--- /dev/null
+++ b/db/migrate/20120826171119_create_artist_genres.rb
@@ -0,0 +1,10 @@
+class CreateArtistGenres < ActiveRecord::Migration
+ def change
+ create_table :artist_genres do |t|
+ t.integer :artist_id
+ t.integer :genre_id
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20120826171313_create_artists.rb b/db/migrate/20120826171313_create_artists.rb
new file mode 100644
index 0000000..c8652f5
--- /dev/null
+++ b/db/migrate/20120826171313_create_artists.rb
@@ -0,0 +1,13 @@
+class CreateArtists < ActiveRecord::Migration
+ def change
+ create_table :artists do |t|
+ t.string :rovi_id
+ t.string :name
+ t.boolean :is_group
+ t.text :bio
+ t.string :pic
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20120826171911_create_albums.rb b/db/migrate/20120826171911_create_albums.rb
new file mode 100644
index 0000000..1afb99a
--- /dev/null
+++ b/db/migrate/20120826171911_create_albums.rb
@@ -0,0 +1,13 @@
+class CreateAlbums < ActiveRecord::Migration
+ def change
+ create_table :albums do |t|
+ t.integer :artist_id
+ t.string :rovi_id
+ t.string :title
+ t.integer :year
+ t.string :pic
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20120826172032_create_tracks.rb b/db/migrate/20120826172032_create_tracks.rb
new file mode 100644
index 0000000..d32d390
--- /dev/null
+++ b/db/migrate/20120826172032_create_tracks.rb
@@ -0,0 +1,14 @@
+class CreateTracks < ActiveRecord::Migration
+ def change
+ create_table :tracks do |t|
+ t.integer :album_id
+ t.string :rovi_id
+ t.integer :disc_id
+ t.integer :position
+ t.string :title
+ t.integer :duration
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20120826172120_create_performers.rb b/db/migrate/20120826172120_create_performers.rb
new file mode 100644
index 0000000..2d72405
--- /dev/null
+++ b/db/migrate/20120826172120_create_performers.rb
@@ -0,0 +1,10 @@
+class CreatePerformers < ActiveRecord::Migration
+ def change
+ create_table :performers do |t|
+ t.integer :track_id
+ t.integer :artist_id
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
new file mode 100644
index 0000000..3ac82e1
--- /dev/null
+++ b/db/schema.rb
@@ -0,0 +1,68 @@
+# encoding: UTF-8
+# This file is auto-generated from the current state of the database. Instead
+# of editing this file, please use the migrations feature of Active Record to
+# incrementally modify your database, and then regenerate this schema definition.
+#
+# Note that this schema.rb definition is the authoritative source for your
+# database schema. If you need to create the application database on another
+# system, you should be using db:schema:load, not running all the migrations
+# from scratch. The latter is a flawed and unsustainable approach (the more migrations
+# you'll amass, the slower it'll run and the greater likelihood for issues).
+#
+# It's strongly recommended to check this file into your version control system.
+
+ActiveRecord::Schema.define(:version => 20120826172120) do
+
+ create_table "albums", :force => true do |t|
+ t.integer "artist_id"
+ t.string "rovi_id"
+ t.string "title"
+ t.integer "year"
+ t.string "pic"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+ create_table "artist_genres", :force => true do |t|
+ t.integer "artist_id"
+ t.integer "genre_id"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+ create_table "artists", :force => true do |t|
+ t.string "rovi_id"
+ t.string "name"
+ t.boolean "is_group"
+ t.text "bio"
+ t.string "pic"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+ create_table "genres", :force => true do |t|
+ t.string "rovi_id"
+ t.string "name"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+ create_table "performers", :force => true do |t|
+ t.integer "track_id"
+ t.integer "artist_id"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+ create_table "tracks", :force => true do |t|
+ t.integer "album_id"
+ t.string "rovi_id"
+ t.integer "disc_id"
+ t.integer "position"
+ t.string "title"
+ t.integer "duration"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+end
diff --git a/public/index.html b/public/index.html
deleted file mode 100644
index a1d5099..0000000
--- a/public/index.html
+++ /dev/null
@@ -1,241 +0,0 @@
-
-
-
- Ruby on Rails: Welcome aboard
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Getting started
-
Here’s how to get rolling:
-
-
- -
-
Use rails generate
to create your models and controllers
- To see all available options, run it without parameters.
-
-
- -
-
Set up a default route and remove public/index.html
- Routes are set up in config/routes.rb.
-
-
- -
-
Create your database
- Run rake db:create
to create your database. If you're not using SQLite (the default), edit config/database.yml with your username and password.
-
-
-
-
-
-
-
-
-
diff --git a/vendor/assets/javascripts/mustache.js b/vendor/assets/javascripts/mustache.js
new file mode 100644
index 0000000..9e20b87
--- /dev/null
+++ b/vendor/assets/javascripts/mustache.js
@@ -0,0 +1,613 @@
+/*!
+ * mustache.js - Logic-less {{mustache}} templates with JavaScript
+ * http://github.com/janl/mustache.js
+ */
+
+/*global define: false*/
+
+var Mustache;
+
+(function (exports) {
+ if (typeof module !== "undefined" && typeof module.exports !== "undefined") {
+ module.exports = exports; // CommonJS
+ } else if (typeof define === "function") {
+ define(exports); // AMD
+ } else {
+ Mustache = exports; //