Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

first commit

  • Loading branch information...
commit 39fcde3a03b354a32441c56b1795219cef378c8b 0 parents
@sudara authored
Showing with 6,425 additions and 0 deletions.
  1. +15 −0 .gitignore
  2. +3 −0  Capfile
  3. +38 −0 README
  4. +10 −0 Rakefile
  5. +141 −0 app/controllers/application.rb
  6. +170 −0 app/controllers/assets_controller.rb
  7. +14 −0 app/controllers/browse_controller.rb
  8. +35 −0 app/controllers/comments_controller.rb
  9. +64 −0 app/controllers/facebook_accounts_controller.rb
  10. +35 −0 app/controllers/pages_controller.rb
  11. +104 −0 app/controllers/pics_controller.rb
  12. +161 −0 app/controllers/playlists_controller.rb
  13. +75 −0 app/controllers/sessions_controller.rb
  14. +92 −0 app/controllers/updates_controller.rb
  15. +95 −0 app/controllers/user_reports_controller.rb
  16. +164 −0 app/controllers/users_controller.rb
  17. +2 −0  app/helpers/account_helper.rb
  18. +70 −0 app/helpers/application_helper.rb
  19. +3 −0  app/helpers/assets_helper.rb
  20. +2 −0  app/helpers/comments_helper.rb
  21. +2 −0  app/helpers/facebook_accounts_helper.rb
  22. +16 −0 app/helpers/pages_helper.rb
  23. +2 −0  app/helpers/pics_helper.rb
  24. +2 −0  app/helpers/playlists_helper.rb
  25. +2 −0  app/helpers/updates_helper.rb
  26. +2 −0  app/helpers/user_reports_helper.rb
  27. +3 −0  app/helpers/users_helper.rb
  28. +181 −0 app/models/asset.rb
  29. +29 −0 app/models/comment.rb
  30. +5 −0 app/models/facebook_account.rb
  31. +15 −0 app/models/facebook_addable.rb
  32. +27 −0 app/models/listen.rb
  33. +17 −0 app/models/pic.rb
  34. +102 −0 app/models/playlist.rb
  35. +27 −0 app/models/track.rb
  36. +9 −0 app/models/update.rb
  37. +237 −0 app/models/user.rb
  38. +31 −0 app/models/user_mailer.rb
  39. +5 −0 app/models/user_observer.rb
  40. +12 −0 app/models/user_report.rb
  41. +35 −0 app/views/assets/_asset.html.erb
  42. +5 −0 app/views/assets/_asset_wrapper.html.erb
  43. +5 −0 app/views/assets/_assets.html.erb
  44. +15 −0 app/views/assets/_comment.html.erb
  45. +55 −0 app/views/assets/_form.rhtml
  46. +21 −0 app/views/assets/_latest.rhtml
  47. +10 −0 app/views/assets/_playlist.html.erb
  48. +7 −0 app/views/assets/_results.html.erb
  49. +12 −0 app/views/assets/_search.html.erb
  50. +51 −0 app/views/assets/_share.html.erb
  51. +27 −0 app/views/assets/edit.html.erb
  52. +18 −0 app/views/assets/index.html.erb
  53. +32 −0 app/views/assets/latest.html.erb
  54. +35 −0 app/views/assets/latest.rss.builder
  55. +40 −0 app/views/assets/new.html.erb
  56. +31 −0 app/views/assets/show.html.erb
  57. 0  app/views/assets/show.xml.builder
  58. +5 −0 app/views/facebook_accounts/_alonetoner.fbml.erb
  59. +15 −0 app/views/facebook_accounts/_asset.fbml.erb
  60. +3 −0  app/views/facebook_accounts/_error.fbml.erb
  61. 0  app/views/facebook_accounts/_mp3.fbml.erb
  62. +16 −0 app/views/facebook_accounts/_profile.fbml.erb
  63. +27 −0 app/views/facebook_accounts/_search.fbml.erb
  64. 0  app/views/facebook_accounts/add_to_profile.fbml.erb
  65. +6 −0 app/views/facebook_accounts/index.fbml.erb
  66. +3 −0  app/views/facebook_accounts/show.html.erb
  67. +16 −0 app/views/layouts/application.fbml.erb
  68. +66 −0 app/views/layouts/application.html.erb
  69. +22 −0 app/views/listens/edit.html.erb
  70. +22 −0 app/views/listens/index.html.erb
  71. +21 −0 app/views/listens/new.html.erb
  72. +13 −0 app/views/listens/show.html.erb
  73. +184 −0 app/views/pages/_facebooker.html.haml
  74. +7 −0 app/views/pages/_todo_item.html.erb
  75. +10 −0 app/views/pages/_todo_list.html.erb
  76. +2 −0  app/views/pages/about.html.erb
  77. +2 −0  app/views/pages/answers.html.erb
  78. +1 −0  app/views/pages/home.html.erb
  79. +155 −0 app/views/pages/index.html.haml
  80. 0  app/views/pages/not_yet.html.erb
  81. +49 −0 app/views/pages/sitemap.xml.builder
  82. +20 −0 app/views/pages/todo.html.erb
  83. +47 −0 app/views/pics/edit.html.erb
  84. +32 −0 app/views/pics/index.html.erb
  85. +46 −0 app/views/pics/new.html.erb
  86. +38 −0 app/views/pics/show.html.erb
  87. +6 −0 app/views/playlists/_asset.html.erb
  88. 0  app/views/playlists/_comment.html.erb
  89. +8 −0 app/views/playlists/_inline_help.html.erb
  90. +1 −0  app/views/playlists/_listen.html.erb
  91. +6 −0 app/views/playlists/_options.html.erb
  92. +1 −0  app/views/playlists/_play_time.html.erb
  93. +50 −0 app/views/playlists/_playlist.html.erb
  94. +11 −0 app/views/playlists/_share.html.erb
  95. +34 −0 app/views/playlists/_track.html.erb
  96. +4 −0 app/views/playlists/_tracks.html.erb
  97. +7 −0 app/views/playlists/_your_stuff.html.erb
  98. +1 −0  app/views/playlists/add_track.html.erb
  99. +4 −0 app/views/playlists/add_track.js.rjs
  100. +90 −0 app/views/playlists/edit.html.erb
  101. +7 −0 app/views/playlists/index.html.erb
  102. +20 −0 app/views/playlists/index.rss.builder
  103. +24 −0 app/views/playlists/new.html.erb
  104. +4 −0 app/views/playlists/remove_track.js.rjs
  105. +5 −0 app/views/playlists/show.html.erb
  106. +20 −0 app/views/playlists/show.rss.builder
  107. +18 −0 app/views/playlists/show.xml.builder
  108. +31 −0 app/views/sessions/_login.rhtml
  109. +3 −0  app/views/sessions/new.html.erb
  110. +12 −0 app/views/shared/_asset.rss.builder
  111. +28 −0 app/views/shared/_comment.html.erb
  112. +10 −0 app/views/shared/_comments.html.erb
  113. +70 −0 app/views/shared/_crop.rhtml
  114. +15 −0 app/views/shared/_flash.html.erb
  115. +10 −0 app/views/shared/_footer.rhtml
  116. +7 −0 app/views/shared/_ie6_warning.html.erb
  117. 0  app/views/shared/_javascript_warning.html.erb
  118. +23 −0 app/views/shared/_nav.rhtml
  119. +11 −0 app/views/shared/_player.rhtml
  120. +8 −0 app/views/shared/_self_confidence.html.erb
  121. +4 −0 app/views/shared/_user_assets.rhtml
  122. +9 −0 app/views/shared/_user_bar.rhtml
  123. +7 −0 app/views/updates/_update.html.erb
  124. +22 −0 app/views/updates/edit.html.erb
  125. +9 −0 app/views/updates/index.html.erb
  126. +22 −0 app/views/updates/new.html.erb
  127. +13 −0 app/views/updates/show.html.erb
  128. +9 −0 app/views/user_mailer/activation.html.erb
  129. +3 −0  app/views/user_mailer/forgot_password.html.erb
  130. +17 −0 app/views/user_mailer/signup.html.erb
  131. +9 −0 app/views/user_reports/_form.html.erb
  132. +30 −0 app/views/user_reports/_user_report.html.erb
  133. +25 −0 app/views/user_reports/edit.html.erb
  134. +8 −0 app/views/user_reports/index.html.erb
  135. +16 −0 app/views/user_reports/new.html.erb
  136. +28 −0 app/views/user_reports/show.html.erb
  137. +25 −0 app/views/users/_contact_info.rhtml
  138. +20 −0 app/views/users/_form.rhtml
  139. +9 −0 app/views/users/_latest.html.erb
  140. +1 −0  app/views/users/_listen.html.erb
  141. +9 −0 app/views/users/_listens.html.erb
  142. +10 −0 app/views/users/_playlist.html.erb
  143. +20 −0 app/views/users/_profile.fbml.erb
  144. +30 −0 app/views/users/_settings.rhtml
  145. +10 −0 app/views/users/_track_play.html.erb
  146. +12 −0 app/views/users/_track_plays.html.erb
  147. +17 −0 app/views/users/_user.html.erb
  148. +29 −0 app/views/users/_users.html.erb
  149. +6 −0 app/views/users/_wtf.fbml.erb
  150. +49 −0 app/views/users/bio.html.erb
  151. +53 −0 app/views/users/edit.html.erb
  152. +8 −0 app/views/users/index.html.erb
  153. +37 −0 app/views/users/new.html.erb
  154. +30 −0 app/views/users/show.html.erb
  155. +35 −0 app/views/users/show.rss.builder
  156. +17 −0 config/amazon_s3.example.yml
  157. +7 −0 config/basecamp.example.yml
  158. +108 −0 config/boot.rb
  159. +35 −0 config/database.example.yml
  160. +11 −0 config/defensio.example.yml
  161. +60 −0 config/environment.rb
  162. +18 −0 config/environments/development.rb
  163. +61 −0 config/environments/production.rb
  164. +22 −0 config/environments/test.rb
  165. +14 −0 config/facebooker.example.yml
  166. +490 −0 config/initializers/basecamp.rb
  167. +6 −0 config/initializers/mime_types.rb
  168. +30 −0 config/initializers/pretty_text.rb
  169. +33 −0 config/initializers/setup.rb
  170. +53 −0 config/lighttpd.conf
  171. +7 −0 config/mongrel_cluster.yml
  172. +64 −0 config/routes.rb
  173. +30 −0 config/smf_template.erb
  174. +7 −0 config/tunnel.yml
  175. +23 −0 db/migrate/001_create_users.rb
  176. +22 −0 db/migrate/002_create_assets.rb
  177. +10 −0 db/migrate/003_modify_users.rb
  178. +10 −0 db/migrate/004_adjust_users.rb
  179. +10 −0 db/migrate/005_change_to_password_hash.rb
  180. +11 −0 db/migrate/006_change_hash_to_crypted_password.rb
  181. +9 −0 db/migrate/007_add_counter_cache.rb
  182. +9 −0 db/migrate/008_add_display_name_to_user.rb
  183. +9 −0 db/migrate/009_add_identity_url.rb
  184. +18 −0 db/migrate/010_add_exception_table.rb
  185. +16 −0 db/migrate/011_add_sessions.rb
  186. +12 −0 db/migrate/012_add_fields_to_asset.rb
  187. +32 −0 db/migrate/013_create_playlists.rb
  188. +17 −0 db/migrate/014_create_listens.rb
  189. +22 −0 db/migrate/015_create_pics.rb
  190. +14 −0 db/migrate/016_add_permalinks.rb
  191. +13 −0 db/migrate/017_pimp_up_users.rb
  192. +18 −0 db/migrate/018_add_mp3info_to_assets.rb
  193. +14 −0 db/migrate/019_tweak_listens.rb
  194. +9 −0 db/migrate/020_add_counts_for_listens.rb
  195. +16 −0 db/migrate/021_set_listens_count_to_zero.rb
  196. +9 −0 db/migrate/022_make_pics_polymorphic.rb
  197. +13 −0 db/migrate/023_add_fb_user_ids.rb
  198. +17 −0 db/migrate/024_create_user_reports.rb
  199. +12 −0 db/migrate/025_kill_listens_without_asset.rb
  200. +9 −0 db/migrate/026_tell_playlists_to_wake_up.rb
  201. +14 −0 db/migrate/027_create_updates.rb
  202. +8 −0 db/migrate/028_add_itunes_to_user.rb
  203. +15 −0 db/migrate/029_create_comments.rb
  204. +14 −0 db/migrate/030_improve_comments.rb
  205. +8 −0 db/migrate/031_add_comments_count_to_users.rb
  206. +9 −0 db/migrate/032_make_playlists_smarter.rb
  207. +11 −0 db/migrate/033_set_playlist_to_album_or_mix.rb
  208. +8 −0 db/migrate/034_add_i_pto_users.rb
  209. +23 −0 db/migrate/035_add_facebook_user_and_relations.rb
  210. +15 −0 db/migrate/036_create_facebook_addables.rb
  211. +8 −0 db/migrate/037_add_revision_to_updates.rb
  212. +11 −0 db/migrate/038_add_spam_control_to_comments.rb
  213. +12 −0 db/migrate/039_flesh_out_user.rb
  214. +11 −0 db/migrate/040_flesh_out_track.rb
  215. +16 −0 db/migrate/041_add_defensio_columns_to_comments.rb
  216. +16 −0 db/migrate/042_add_defensio_columns_to_user_reports.rb
  217. +2 −0  doc/README_FOR_APP
  218. BIN  fonts/RonniaBold.otf
  219. BIN  fonts/RonniaBoldItalic.otf
  220. BIN  fonts/RonniaExtrabold.otf
  221. BIN  fonts/RonniaExtraboldItalic.otf
  222. BIN  fonts/RonniaHeavy.otf
  223. BIN  fonts/RonniaHeavyItalic.otf
  224. BIN  fonts/RonniaLight.otf
  225. BIN  fonts/RonniaLightItalic.otf
  226. BIN  fonts/RonniaReg.otf
  227. BIN  fonts/RonniaRegItalic.otf
  228. BIN  fonts/SebastianLight.otf
  229. BIN  fonts/SebastianMediumPro.otf
  230. +104 −0 lib/authenticated_system.rb
  231. +6 −0 lib/format.rb
  232. +39 −0 lib/randomness.rb
  233. +86 −0 lib/tasks/facebook.rake
  234. +40 −0 public/.htaccess
  235. +8 −0 public/404.html
  236. +8 −0 public/500.html
  237. BIN  public/assets/AreYouLonely.mp3
  238. BIN  public/assets/bycdwithbass.mp3
  239. BIN  public/assets/you_bought_it_mix_1.mp3
  240. +6 −0 public/crossdomain.xml
  241. +10 −0 public/dispatch.cgi
  242. +24 −0 public/dispatch.fcgi
  243. +10 −0 public/dispatch.rb
  244. BIN  public/favicon.ico
  245. BIN  public/flash/alonetone_player.swf
  246. BIN  public/flash/mediaplayer.swf
  247. BIN  public/flash/soundmanager2.swf
  248. BIN  public/images/alonetone.png
  249. BIN  public/images/alonetone16.jpg
  250. BIN  public/images/alonetone300.png
  251. BIN  public/images/alonetone75.jpg
  252. BIN  public/images/alonetone75.png
  253. BIN  public/images/buttons/add.png
  254. BIN  public/images/buttons/remove.png
  255. BIN  public/images/dummy_album.jpg
  256. BIN  public/images/favicon.png
  257. BIN  public/images/flash/flash-background.png
  258. BIN  public/images/flash/flash-bottom-right.png
  259. BIN  public/images/flash/flash-bottom.png
  260. BIN  public/images/flash/flash-error.png
  261. BIN  public/images/flash/flash-info.png
  262. BIN  public/images/flash/flash-ok.png
  263. BIN  public/images/flash/flash-top-right.png
  264. BIN  public/images/flash/flash-top.png
  265. BIN  public/images/header-background.jpg
  266. BIN  public/images/icons/bullet.png
  267. BIN  public/images/jewel-case-125.png
  268. BIN  public/images/jewel-case-200.png
  269. BIN  public/images/jewel-case-400.png
  270. BIN  public/images/jewel-case-50.png
  271. BIN  public/images/large-comment.png
  272. BIN  public/images/nav/browse-all-artists-active.png
  273. BIN  public/images/nav/browse-all-artists-over.png
  274. BIN  public/images/nav/browse-all-artists.png
  275. BIN  public/images/nav/browse-buttons.png
  276. BIN  public/images/nav/upload-active.png
  277. BIN  public/images/nav/upload-buttons.png
  278. BIN  public/images/nav/upload-over.png
  279. BIN  public/images/nav/upload.png
  280. BIN  public/images/nav/your-stuff-active.png
  281. BIN  public/images/nav/your-stuff-buttons.png
  282. BIN  public/images/nav/your-stuff-over.png
  283. BIN  public/images/nav/your-stuff.png
  284. BIN  public/images/no-cover-125.jpg
  285. BIN  public/images/no-cover-200.jpg
  286. BIN  public/images/no-cover-400.jpg
  287. BIN  public/images/no-cover-50.jpg
  288. BIN  public/images/no-pic-125.png
  289. BIN  public/images/no-pic-200.png
  290. BIN  public/images/no-pic-50.png
  291. BIN  public/images/no-pic-thumb100.jpg
  292. BIN  public/images/no-pic-thumb50.jpg
  293. BIN  public/images/no-pic.jpg
  294. BIN  public/images/player/pause-hover.png
  295. BIN  public/images/player/pause-on.png
  296. BIN  public/images/player/pause.png
  297. BIN  public/images/player/play-hover.png
  298. BIN  public/images/player/play-on.png
  299. BIN  public/images/player/play.png
  300. BIN  public/images/player/play_buttons.png
Sorry, we could not display the entire diff because too many files (1,503) changed.
15 .gitignore
@@ -0,0 +1,15 @@
+*.log
+db/schema.rb
+db/schema.sql
+config/deploy.rb
+config/database.yml
+config/amazon_s3.yml
+config/defensio.yml
+config/facebooker.yml
+config/basecamp.yml
+*.sql
+public/images/pretty_text/*.*
+log/*.log
+.DS_Store
+*.tmp
+public/stylesheets/*.css
3  Capfile
@@ -0,0 +1,3 @@
+load 'deploy' if respond_to?(:namespace) # cap2 differentiator
+Dir['vendor/plugins/*/recipes/*.rb'].each { |plugin| load(plugin) }
+load 'config/deploy'
38 README
@@ -0,0 +1,38 @@
+= The future is bright
+
+The future of music distribution online, brought to you by Sudara
+
+Amazing: We now live in a world where 1 person, in his spare time can provide limitless distribution for hundreds of artists, thousands of songs, millions of copies. Truly, we are at the beginning of a revolution in music making.
+
+I invite you to join me in moving good music away from companies looking (still) to profit from musicians and help me to create the best damn online home for DIY / independent / online-savy musicians.
+
+== Want to join forces?
+
+First of all, talk to me. You can send me electronic mail. Sudara...at...nameofthisproject....com
+
+The nameofthisproject and the production site is:
+
+http://alonetone.com
+
+Here is the codebase, which is open source and open development.
+
+== Set it up
+
+You'll need to setup 5 config files for it to run flawlessly:
+
+ database.yml
+ amazon_s3.yml (you can always ignore this and set Asset and Pic to use the filesysem)
+ basecamp.yml (for todo list, you don't need it unless you want pages/todo)
+ defensio.yml (spam protection)
+ facebooker.yml (for facebook app)
+
+You'll need some gems, at least:
+ rmagick
+ haml
+ facebooker
+ ruby-mp3info
+ mocha (for rspec)
+ googlecharts
+ aws-s3
+
+If I were you, I would email me.
10 Rakefile
@@ -0,0 +1,10 @@
+# Add your own tasks in files placed in lib/tasks ending in .rake,
+# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
+
+require(File.join(File.dirname(__FILE__), 'config', 'boot'))
+
+require 'rake'
+require 'rake/testtask'
+require 'rake/rdoctask'
+
+require 'tasks/rails'
141 app/controllers/application.rb
@@ -0,0 +1,141 @@
+class ApplicationController < ActionController::Base
+ helper :all # include all helpers, all the time
+
+ protect_from_forgery :secret => 'f90bac24ac12962052d00bfc2bd34ef2'
+
+ include AuthenticatedSystem
+ include ExceptionLoggable
+ before_filter :set_tab, :ie6, :is_sudo
+ before_filter :ie6
+ before_filter :login_by_token, :display_news
+ before_filter :set_page_title
+ before_filter :currently_online, :prep_bugaboo
+ before_filter :update_last_seen_at, :only => [:index, :show]
+ before_filter :set_latest_update_title
+
+ # let ActionView have a taste of our authentication
+ helper_method :current_user, :logged_in?, :admin?, :last_active
+
+
+ rescue_from ActiveRecord::RecordNotFound, :with => :show_error
+ rescue_from NoMethodError, :with => :show_error
+
+ # all errors end up here
+ def show_error(exception)
+ if RAILS_ENV == 'production' && admin?
+ flash[:error] = "#{exception.message}"
+ redirect_to_default
+ elsif RAILS_ENV == 'production'
+ if facebook?
+ flash[:error] = "Alonetone made a boo boo: <br/> #{exception.message}"
+ render :partial => 'facebook_accounts/error', :layout => true
+ else
+ # show something decent for visitors
+ flash[:error] = "Whups! That didn't work out. We've logged it, but feel free to let us know (bottom right) if something is giving you trouble"
+ redirect_to_default
+ end
+ else
+ # let me see what's wrong in dev mode.
+ raise exception
+ end
+ end
+
+
+
+ protected
+ def facebook?
+ !(params[:fb_sig] == nil)
+ end
+
+
+ def sudo_to(destination_user)
+ return false unless session[:sudo] || current_user.admin?
+ if session[:sudo] && destination_user.admin?
+ logger.warn('coming out of sudo to admin account')
+ session[:sudo] = nil
+ @sudo = nil
+ else
+ session[:sudo] = true
+ logger.warn("SUDO: #{current_user.name} is sudoing to #{destination_user.name}")
+ @sudo = true
+ end
+ self.current_user = destination_user
+ logger.warn("SUDO: #{current_user.name}")
+ true
+ end
+
+ def ie6
+ @ie6 = true if request.env['HTTP_USER_AGENT'] and request.env['HTTP_USER_AGENT'].include? "MSIE 6.0"
+ end
+
+ def currently_online
+ @online = User.currently_online
+ end
+
+ def find_user
+ @user = (params[:login] || params[:id])? User.find_by_login(params[:login] || params[:id]) : current_user
+ end
+
+ def find_asset
+ @asset = @user.assets.find_by_permalink(params[:permalink] || params[:id])
+ @asset = @user.assets.find(params[:id]) if !@asset && params[:id]
+ end
+
+ def find_playlists
+ @playlist = @user.playlists.find_by_permalink(params[:permalink] || params[:id], :include =>[:tracks => :asset])
+ @playlist = @user.playlists.find(params[:id], :include =>[:tracks => :asset]) if !@playlist && params[:id]
+ end
+
+ def authorized?
+ # by default, users can hit every action if it involves their user, and it's not about deleting things.
+ admin_or_owner
+ end
+
+ # authorization tricks
+
+ def admin_or_owner(record=current_user)
+ admin? || (!%w(destroy admin edit update).include?(action_name) && (params[:login].nil? || params[:login] == record.login))
+ end
+
+ def admin_or_owner_with_delete(record=current_user)
+ admin? || (params[:login].nil? || params[:login] == record.login)
+ end
+
+ def set_page_title
+ @page_title = "alonetone - a damn fine home for musicians. Upload mp3s, host and share your music."
+ end
+
+
+ def render_text(text)
+ render :text => text
+ end
+
+ def set_tab
+ @tab = ''
+ end
+
+ def prep_bugaboo
+ @user_report = UserReport.new(:user => @current_user || nil, :params => params)
+ end
+
+ def display_news
+ return unless logged_in?
+ @display_news = true if session[:last_active] && (session[:last_active] < Update.find(:first, :order => 'created_at DESC').created_at)
+ end
+
+ def is_sudo
+ @sudo = session[:sudo]
+ end
+
+ # override default behavior to ensure that 'log in to app' returns user somewhere useful
+ def application_is_not_installed_by_facebook_user
+ redirect_to session[:facebook_session].install_url(:next => "#{request.request_uri}")
+ end
+
+ def set_latest_update_title
+ @latest_update = Update.find(:all, :order => 'created_at DESC', :limit => 1 ).first
+ end
+
+ private
+
+end
170 app/controllers/assets_controller.rb
@@ -0,0 +1,170 @@
+class AssetsController < ApplicationController
+ before_filter :find_user
+ before_filter :find_asset, :only => [:show, :edit, :update, :destroy]
+
+ # we check to see if the current_user is authorized based on the asset.user
+ before_filter :login_required, :except => [:index, :show, :latest]
+ before_filter :find_referer, :only => :show
+
+ rescue_from NoMethodError, :with => :latest
+
+ # GET /assets
+ # GET /assets.xml
+ def index
+ @page_title = @user.name + "'s uploaded music on alonetone"
+ @assets = @user.assets.paginate(:all, :order => 'created_at DESC', :per_page => 60, :page => params[:page])
+ respond_to do |format|
+ format.html # index.rhtml
+ format.xml { render :xml => @assets.to_xml }
+ format.rss { render :xml => @assets.to_xml }
+ format.js do render :update do |page|
+ page.replace 'stash', :partial => "assets"
+ end
+ end
+ end
+ end
+
+ # GET /assets/1
+ # GET /assets/1.xml
+ def show
+ respond_to do |format|
+ format.rss
+ format.html do
+ @page_title = "#{@asset.title} by #{@user.name} on alonetone"
+ @assets = [@asset]
+ @listens = @asset.listens.find(:all)
+ @comments = @asset.comments.find_all_by_spam(false)
+ end
+ format.mp3 do
+ register_listen
+ redirect_to @asset.public_mp3
+ end
+ end
+ end
+
+ # aka home page
+ def latest
+ limit = (params[:latest] && params[:latest].to_i < 50) ? params[:latest] : 5
+ @page_title = "Latest #{limit} uploaded mp3s on alonetone" if params[:latest]
+ @assets = Asset.latest(limit)
+ @popular = Asset.most_popular(limit)
+ @playlists = Playlist.latest(6)
+ respond_to do |wants|
+ wants.html
+ wants.rss
+ end
+ end
+
+ def top
+ top = (params[:top] && params[:top].to_i < 50) ? params[:top] : 20
+ @page_title = "Top #{top} tracks on alonetone"
+ @popular = Asset.most_popular(top)
+ respond_to do |wants|
+ wants.html { render :action => 'latest'}
+ wants.rss
+ end
+ end
+
+ def search
+ @assets = Asset.find(:all, :conditions => [ "assets.filename LIKE ? OR assets.title LIKE ?", "%#{params[:search]}%","%#{params[:search]}%"], :limit => 10)
+ render :partial => 'results', :layout => false
+ end
+
+ # GET /assets/new
+ def new
+ @tab = 'upload' if current_user == @user
+ @asset = Asset.new
+ end
+
+ # GET /assets/1;edit
+ def edit
+ end
+
+ # POST /assets
+ # POST /assets.xml
+ def create
+ #collect and prepare
+ @assets = []
+ params[:asset] ||= {}
+ params[:asset_data] ||= []
+ params[:asset].delete(:title) if params[:asset_data].size > 1
+
+ params[:asset_data].each do |file|
+ unless file.is_a?(String)
+ Asset.extract_mp3s(file) do |valid_mp3|
+ @assets << current_user.assets.create(params[:asset].merge(:uploaded_data => valid_mp3))
+ end
+ end
+ end
+ flashes = ''
+ good = false
+ @assets.each do |asset|
+ # TODO: find a non-hackish way to ensure content_types are only mp3s at this point
+ # The problem is a zip can contain a zip, which passes validation
+ # Furthermore, if there is an issue with the zip, the rescue in the Asset model will hand the file back
+ # Butt ugly, my friends.
+ if !asset.new_record?
+ flashes += "#{CGI.escapeHTML asset.filename} uploaded!<br/>"
+ good = true
+ else
+ errors = asset.errors.collect{|attr, msg| msg }
+ flashes += "'#{CGI.escapeHTML asset.filename}' failed to upload: <br/>#{errors}<br/>"
+ end
+ end
+ if good
+ flash[:ok] = flashes
+ redirect_to user_tracks_path(current_user)
+ else
+ flash[:error] = flashes
+ redirect_to new_user_track_path(current_user)
+ end
+ end
+
+ # PUT /assets/1
+ # PUT /assets/1.xml
+ def update
+ respond_to do |format|
+ if @asset.update_attributes(params[:asset])
+ flash[:ok] = 'Track updated!'
+ format.html { redirect_to edit_user_track_url(current_user, @asset) }
+ format.xml { head :ok }
+ else
+ format.html { render :action => "edit" }
+ format.xml { render :xml => @asset.errors.to_xml }
+ end
+ end
+ end
+
+ # DELETE /assets/1
+ # DELETE /assets/1.xml
+ def destroy
+ @asset.destroy
+ flash[:ok] = 'We threw the puppy away. No one can listen to it again (unless you reupload it, of course ;)'
+ respond_to do |format|
+ format.html { redirect_to user_tracks_url(current_user) }
+ format.xml { head :ok }
+ end
+ end
+
+ protected
+
+ def find_referer
+ case params[:referer]
+ when 'itunes' then @referer = 'itunes'
+ when 'download' then @referer = 'download'
+ when 'home' then @referer = 'home'
+ when 'facebook' then @referer = 'facebook'
+ when nil
+ @referer = (request.env['HTTP_REFERER'] && !request.env['HTTP_REFERER'].empty?) ? request.env['HTTP_REFERER'] : 'alonetone'
+ end
+ end
+
+ def authorized?
+ # admin or the owner of the asset can edit/update/delete
+ admin? || (params[:permalink].nil? || (current_user != :false && @asset.user_id.to_s == current_user.id.to_s))
+ end
+
+ def register_listen
+ @asset.listens.create(:listener => (current_user || nil), :track_owner=> @asset.user, :source => @referer)
+ end
+end
14 app/controllers/browse_controller.rb
@@ -0,0 +1,14 @@
+class BrowseController < ApplicationController
+
+ def index
+
+ end
+
+ def artists
+
+ end
+
+ def songs
+
+ end
+end
35 app/controllers/comments_controller.rb
@@ -0,0 +1,35 @@
+class CommentsController < ApplicationController
+
+ before_filter :find_user
+ before_filter :find_commenter
+
+ def create
+ respond_to do |wants|
+ wants.js do
+ case params[:comment][:commentable_type]
+ when 'asset'
+ find_asset
+ @comment = @asset.comments.build(:commenter => @commenter,
+ :body => params[:comment][:body],
+ :user => @asset.user,
+ :remote_ip => request.remote_ip,
+ :user_agent => request.env['HTTP_USER_AGENT'],
+ :referer => request.env['HTTP_REFERER'])
+ @comment.env = request.env
+
+ end
+ return head(:bad_request) unless @comment.save && User.increment_counter(:comments_count, @asset.user)
+ render :nothing => true
+ end
+ end
+ end
+
+ protected
+
+
+
+ def find_commenter
+ @commenter = current_user if logged_in?
+ end
+
+end
64 app/controllers/facebook_accounts_controller.rb
@@ -0,0 +1,64 @@
+class FacebookAccountsController < ApplicationController
+ ensure_application_is_installed_by_facebook_user
+
+ before_filter :find_facebook_user
+ before_filter :find_alonetone_user
+ before_filter :check_for_correct_params, :only => [:add_to_profile, :remove_from_profile]
+
+ def index
+ @assets = Asset.paginate(:all, :include => :user, :per_page => 10, :order => 'assets.created_at DESC', :page => params[:page])
+ @search_assets = Asset.find(:all, :select => 'id, title, filename', :limit => 200)
+ end
+
+ def create
+
+ end
+
+ def add_to_profile
+ # one word: Fugly
+ @addable = FacebookAddable.find_or_create_by_profile_chunk_type_and_profile_chunk_id_and_facebook_account_id(:profile_chunk_type => params[:addable_type].capitalize,
+ :profile_chunk_id => (params[:addable_id_val] || params[:addable_id]), :facebook_account_id => @facebook_account.id)
+ if @addable
+ flash[:notice] = "Profile updated! We added that killer track"
+ @facebook_user.profile_fbml = (render_to_string :partial => 'profile')
+ else
+ flash[:error] = "Hm, that failed to add to your profile"
+ end
+ redirect_to facebook_home_path
+ end
+
+ def remove_from_profile
+ @addable = FacebookAddable.find_by_facebook_account_id(@facebook_account.id, :conditions => {:profile_chunk_type => params[:addable_type], :profile_chunk_id => params[:addable_id]})
+ if @addable && @addable.destroy
+ flash[:notice] = "Profile updated! We removed that track."
+ if @facebook_account.facebook_addables.find(:all).size > 0
+ @facebook_user.profile_fbml = (render_to_string :partial => 'profile')
+ else
+ @facebook_user.profile_fbml = (render_to_string :partial => 'default_profile')
+ end
+ else
+ flash[:error] = "Whups. huh. That didn't do what you wanted it to do."
+ end
+ redirect_to facebook_home_path
+ end
+
+ protected
+
+ def check_for_correct_params
+ unless params[:addable_type] && params[:addable_id]
+ flash[:error] = "Whups, that wasn't possible"
+ redirect_to facebook_path and return false
+ end
+ end
+
+ def find_alonetone_user
+ @user = current_user if logged_in?
+ end
+
+ def find_facebook_user
+ @facebook_user = session[:facebook_session].user
+ @facebook_account = FacebookAccount.find_or_create_by_fb_user_id(@facebook_user.id)
+ end
+
+
+end
35 app/controllers/pages_controller.rb
@@ -0,0 +1,35 @@
+require 'bluecloth'
+
+class PagesController < ApplicationController
+
+ class Fuck < StandardError; end
+
+ def home
+ end
+
+ def about
+ end
+
+ def actually_going_somewhere_with_facebooker_and_rails
+ render :partial => 'facebooker', :layout => true
+ end
+
+ def answers
+ raise Fuck
+ end
+
+ def todo
+ expire_fragment('todos') if params[:expire]
+ end
+
+ def not_yet
+ render :layout => false
+ end
+
+ def sitemap
+ @users = User.find(:all)
+ respond_to do |wants|
+ wants.xml
+ end
+ end
+end
104 app/controllers/pics_controller.rb
@@ -0,0 +1,104 @@
+class PicsController < ApplicationController
+ # GET /pics
+ # GET /pics.xml
+ def index
+ @pics = Pic.find(:all)
+
+ respond_to do |format|
+ format.html # index.html.erb
+ format.xml { render :xml => @pics }
+ end
+ end
+
+ # GET /pics/1
+ # GET /pics/1.xml
+ def show
+ @pic = Pic.find(params[:id])
+
+ respond_to do |format|
+ format.html # show.html.erb
+ format.xml { render :xml => @pic }
+ end
+ end
+
+ # GET /pics/new
+ # GET /pics/new.xml
+ def new
+ @pic = Pic.new
+
+ respond_to do |format|
+ format.html # new.html.erb
+ format.xml { render :xml => @pic }
+ end
+ end
+
+ # GET /pics/1/edit
+ def edit
+ @pic = Pic.find(params[:id])
+ end
+
+ # POST /pics
+ # POST /pics.xml
+ def create
+ @pic = Pic.new(params[:Pic])
+ @pic.save!
+ redirect_to crop_Pic_path(@pic)
+ rescue ActiveRecord::RecordInvalid
+ render :action => 'new'
+ end
+
+ def crop
+ @pic = Pic.find(params[:id])
+ if request.post?
+ # we got a post request, so first see if the cancel button was clicked
+ if params[:crop_cancel] && params[:crop_cancel] == "true"
+ # this means the cancel button was clicked. you might
+ # want to implement a more-sophisticated cancel behavior
+ # in your app -- for instance, if you store the previous
+ # request in the session, you could redirect there instead
+ # of to the app's root, as i'm doing here.
+ flash[:notice] = "Cropping cancelled."
+ redirect_to pic_path(@pic)
+ return
+ end
+ # cancel was not clicked, so crop the image
+ @pic.crop! params
+ if @pic.save
+ flash[:notice] = "Pic cropped and saved successfully."
+ redirect_to pic_path(@pic)
+ return
+ end
+ end
+ rescue Pic::InvalidCropRect
+ flash[:error] = "Sorry, could not crop the image."
+ end
+
+ # PUT /pics/1
+ # PUT /pics/1.xml
+ def update
+ @pic = Pic.find(params[:id])
+
+ respond_to do |format|
+ if @pic.update_attributes(params[:pic])
+ flash[:notice] = 'Pic was successfully updated.'
+ format.html { redirect_to(@pic) }
+ format.xml { head :ok }
+ else
+ format.html { render :action => "edit" }
+ format.xml { render :xml => @pic.errors, :status => :unprocessable_entity }
+ end
+ end
+ end
+
+ # DELETE /pics/1
+ # DELETE /pics/1.xml
+ def destroy
+ @pic = Pic.find(params[:id])
+ @pic.destroy
+
+ respond_to do |format|
+ format.html { redirect_to(pics_url) }
+ format.xml { head :ok }
+ end
+ end
+end
161 app/controllers/playlists_controller.rb
@@ -0,0 +1,161 @@
+class PlaylistsController < ApplicationController
+
+ before_filter :find_user
+ before_filter :find_playlists, :except => [:index, :new, :create]
+ before_filter :login_required, :except => [:index, :show]
+
+ before_filter :find_tracks, :only => [:show, :edit]
+ in_place_edit_for :playlist, :description
+ in_place_edit_for :playlist, :title
+
+
+ # GET /playlists
+ # GET /playlists.xml
+ def index
+ @playlists = @user.playlists.find(:all)
+
+ respond_to do |format|
+ format.html # index.html.erb
+ format.xml { render :xml => @playlists }
+ end
+ end
+
+ # GET /playlists/1
+ # GET /playlists/1.xml
+ def show
+ @page_title = "\"#{@playlist.title}\" by #{@user.name} on alonetone"
+ respond_to do |format|
+ format.html # show.html.erb
+ format.xml
+ format.rss
+ end
+ end
+
+ # GET /playlists/new
+ # GET /playlists/new.xml
+ def new
+ @playlist = @user.playlists.build
+ respond_to do |format|
+ format.html # new.html.erb
+ format.xml { render :xml => @playlist }
+ end
+ end
+
+ # GET /playlists/1/edit
+ def edit
+ # allow them to add their own assets
+ @assets = @user.assets.paginate(:all, :limit => 10, :per_page => 10, :order => 'created_at', :page => params[:page])
+ @listens = @user.listens.find(:all, :limit => 10, :order => 'listens.created_at DESC')
+ respond_to do |format|
+ format.html
+ format.js do
+ render :update do |page|
+ page.replace 'your_stuff_box', :partial => "your_stuff"
+ end
+ end
+ end
+ end
+
+
+ def add_track
+ @track = @playlist.tracks.create(:asset_id => params[:asset_id].split("_")[1]) if params[:asset_id]
+ respond_to do |format|
+ format.html { render :layout => false }
+ format.js
+ end
+ end
+
+ def attach_pic
+ @pic = @playlist.build_pic(params[:pic])
+ if @pic.save
+ flash[:notice] = 'Cover art updated!'
+ else
+ flash[:error] = 'Whups, make sure you choose a valid jpg, gif, or png file!'
+ end
+ redirect_to edit_user_playlist_path(@user, @playlist)
+ end
+
+
+
+ def remove_track
+ @track = @playlist.tracks.find(params[:track_id])
+ if @track.destroy
+ respond_to do |format|
+ format.js
+ end
+ else
+ render :nothing => true
+ end
+ end
+
+ def sort_tracks
+ # get the params for this playlist
+ params["tracks"].each_with_index do |id, position|
+ Track.update(id, :position => position)
+ end
+ render :nothing => true
+ end
+
+ # POST /playlists
+ # POST /playlists.xml
+ def create
+ @playlist = @user.playlists.build(params[:playlist])
+
+ respond_to do |format|
+ if @playlist.save
+ flash[:notice] = 'Playlist was successfully created.'
+ format.html { redirect_to edit_user_playlist_path(@user, @playlist) }
+ format.xml { render :xml => @playlist, :status => :created, :location => @playlist }
+ else
+ format.html { render :action => "new" }
+ format.xml { render :xml => @playlist.errors, :status => :unprocessable_entity }
+ end
+ end
+ end
+
+
+
+ # PUT /playlists/1
+ # PUT /playlists/1.xml
+ def update
+
+ respond_to do |format|
+ if @playlist.update_attributes(params[:playlist])
+ flash[:notice] = 'Playlist was successfully updated.'
+ format.html { redirect_to(@playlist) }
+ format.xml { head :ok }
+ else
+ format.html { render :action => "edit" }
+ format.xml { render :xml => @playlist.errors, :status => :unprocessable_entity }
+ end
+ end
+ end
+
+ # DELETE /playlists/1
+ # DELETE /playlists/1.xml
+ def destroy
+ @playlist.destroy
+
+ respond_to do |format|
+ format.html { redirect_to(user_playlists_url(@user)) }
+ format.xml { head :ok }
+ end
+ end
+
+
+ protected
+
+ def authorized?
+ (!%w(destroy admin edit update remove_track attach_pic sort_tracks add_track set_playlist_description set_playlist_title).include?(action_name)) || (@playlist.user_id.to_s == current_user.id.to_s) || admin?
+ end
+
+ def find_playlists
+ @playlist = @user.playlists.find_by_permalink(params[:permalink] || params[:id], :include =>[:tracks => :asset])
+ @playlist = @user.playlists.find(params[:id], :include =>[:tracks => :asset]) if !@playlist && params[:id]
+ end
+
+ def find_tracks
+ @tracks = @playlist.tracks.find(:all, :order => :position, :include => :asset)
+ end
+
+end
75 app/controllers/sessions_controller.rb
@@ -0,0 +1,75 @@
+class SessionsController < ApplicationController
+
+ def new
+ if logged_in?
+ flash[:error] = "Confused?<br/> Either you aren't allowed to do that, or uhm, you aren't allowed to do that"
+ redirect_to default_url
+ end
+ end
+
+ def create
+ # if open_id?(params[:login])
+ # open_id_authentication params[:login]
+ #else
+ if params[:password]
+ password_authentication params[:login], params[:password]
+ else
+ render :action => 'new'
+ end
+ #end
+ end
+
+ def destroy
+ reset_session
+ cookies.delete :auth_token
+ flash[:ok] = "Goodbye!"
+ redirect_to_default
+ end
+
+ protected
+ def open_id_authentication(identity_url)
+ authenticate_with_open_id(identity_url, :required => [:nickname, :email], :optional => :fullname) do |status, identity_url, registration|
+ case status
+ when :missing
+ failed_login "Sorry, the OpenID server couldn't be found"
+ when :canceled
+ failed_login "OpenID verification was canceled"
+ when :failed
+ failed_login "Sorry, the OpenID verification failed"
+ when :successful
+ if self.current_user = User.find_or_initialize_by_identity_url(identity_url)
+ {'login=' => 'nickname', 'email=' => 'email', 'display_name=' => 'fullname'}.each do |attr, reg|
+ current_user.send(attr, registration[reg]) unless registration[reg].blank?
+ end
+ unless current_user.save
+ flash[:error] = "Error saving the fields from your OpenID profile at #{identity_url.inspect}: #{current_user.errors.full_messages.to_sentence}"
+ end
+ successful_login
+ else
+ failed_login "Sorry, no user by the identity URL #{identity_url.inspect} exists"
+ end
+ end
+ end
+ end
+
+ def password_authentication(name, password)
+ if self.current_user = User.authenticate(name, password)
+ successful_login
+ else
+ failed_login "Invalid login or password, try again please."
+ end
+ end
+
+ def successful_login
+ cookies[:auth_token] = { :value => self.current_user.token , :expires => 2.weeks.from_now }
+ flash[:ok] = "Welcome back to alonetone!"
+ update_last_seen_at
+ redirect_to_default
+ end
+
+ def failed_login(message)
+ flash[:error] = message
+ render :action => 'new'
+ end
+
+end
92 app/controllers/updates_controller.rb
@@ -0,0 +1,92 @@
+class UpdatesController < ApplicationController
+ before_filter :login_required, :except => :index
+ # GET /updates
+ # GET /updates.xml
+ def index
+ @updates = Update.find(:all, :order => 'created_at DESC')
+
+ respond_to do |format|
+ format.html # index.html.erb
+ format.xml { render :xml => @updates }
+ end
+ end
+
+ # GET /updates/1
+ # GET /updates/1.xml
+ def show
+ @update = Update.find(params[:id])
+
+ respond_to do |format|
+ format.html # show.html.erb
+ format.xml { render :xml => @update }
+ end
+ end
+
+ # GET /updates/new
+ # GET /updates/new.xml
+ def new
+ @update = Update.new
+
+ respond_to do |format|
+ format.html # new.html.erb
+ format.xml { render :xml => @update }
+ end
+ end
+
+ # GET /updates/1/edit
+ def edit
+ @update = Update.find(params[:id])
+ end
+
+ # POST /updates
+ # POST /updates.xml
+ def create
+ @update = Update.new(params[:update])
+
+ respond_to do |format|
+ if @update.save
+ flash[:notice] = 'Update was successfully created.'
+ format.html { redirect_to updates_path }
+ format.xml { render :xml => @update, :status => :created, :location => @update }
+ else
+ format.html { render :action => "new" }
+ format.xml { render :xml => @update.errors, :status => :unprocessable_entity }
+ end
+ end
+ end
+
+ # PUT /updates/1
+ # PUT /updates/1.xml
+ def update
+ @update = Update.find(params[:id])
+
+ respond_to do |format|
+ if @update.update_attributes(params[:update])
+ flash[:notice] = 'Update was successfully updated.'
+ format.html { redirect_to updates_path }
+ format.xml { head :ok }
+ else
+ format.html { render :action => "edit" }
+ format.xml { render :xml => @update.errors, :status => :unprocessable_entity }
+ end
+ end
+ end
+
+ # DELETE /updates/1
+ # DELETE /updates/1.xml
+ def destroy
+ @update = Update.find(params[:id])
+ @update.destroy
+
+ respond_to do |format|
+ format.html { redirect_to(updates_url) }
+ format.xml { head :ok }
+ end
+ end
+
+ protected
+
+ def authorized?
+ logged_in? && admin?
+ end
+end
95 app/controllers/user_reports_controller.rb
@@ -0,0 +1,95 @@
+class UserReportsController < ApplicationController
+
+ before_filter :login_required, :except => [:index, :show, :new, :create]
+
+ # GET /user_reports
+ # GET /user_reports.xml
+ def index
+ @user_reports = UserReport.find_all_by_spam(false, :include => :user, :order => 'user_reports.created_at DESC')
+
+ respond_to do |format|
+ format.html # index.html.erb
+ format.xml { render :xml => @user_reports }
+ end
+ end
+
+ # GET /user_reports/1
+ # GET /user_reports/1.xml
+ def show
+ @user_reports = UserReport.find(params[:id])
+
+ respond_to do |format|
+ format.html # show.html.erb
+ format.xml { render :xml => @user_reports }
+ end
+ end
+
+ # GET /user_reports/new
+ # GET /user_reports/new.xml
+ def new
+ @user_reports = UserReport.new(params[:user_report])
+
+ respond_to do |format|
+ format.html # new.html.erb
+ format.xml { render :xml => @user_reports }
+ end
+ end
+
+ # GET /user_reports/1/edit
+ def edit
+ @user_reports = UserReport.find(params[:id])
+ end
+
+ # POST /user_reports
+ # POST /user_reports.xml
+ def create
+ @user_report = UserReport.new(params[:user_report])
+ @user_report.env = request.env
+ respond_to do |format|
+ if @user_report.save
+ flash[:ok] = 'Got it, thanks for taking the time!'
+ format.html { redirect_to_default }
+ format.xml { render :xml => @user_report, :status => :created, :location => @user_reports }
+ format.js { render :nothing => true}
+ else
+ format.html { render :action => "new" }
+ format.xml { render :xml => @user_report.errors, :status => :unprocessable_entity }
+ end
+ end
+ end
+
+ # PUT /user_reports/1
+ # PUT /user_reports/1.xml
+ def update
+ @user_reports = UserReport.find(params[:id])
+
+ respond_to do |format|
+ if @user_reports.update_attributes(params[:user_report])
+ flash[:notice] = 'UserReport was successfully updated.'
+ format.html { redirect_to(@user_reports) }
+ format.xml { head :ok }
+ else
+ format.html { render :action => "edit" }
+ format.xml { render :xml => @user_reports.errors, :status => :unprocessable_entity }
+ end
+ end
+ end
+
+ # DELETE /user_reports/1
+ # DELETE /user_reports/1.xml
+ def destroy
+ @user_reports = UserReport.find(params[:id])
+ @user_reports.destroy
+
+ respond_to do |format|
+ format.html { redirect_to(user_reports_url) }
+ format.xml { head :ok }
+ end
+ end
+
+ protected
+
+ def authorized?
+ admin?
+ end
+end
164 app/controllers/users_controller.rb
@@ -0,0 +1,164 @@
+class UsersController < ApplicationController
+
+
+ skip_before_filter :update_last_seen_at, :only => [:create, :new, :activate, :sudo]
+ before_filter :find_user, :except => [:new, :create, :sudo]
+
+ before_filter :login_required, :except => [:index, :show, :new, :create, :activate, :bio]
+ skip_before_filter :login_by_token, :only => :sudo
+
+ rescue_from NoMethodError, :with => :display_user_home_or_index
+
+
+ def index
+ @page_title = 'the music makers and music lovers of alonetone'
+ @tab = 'browse'
+ @users = User.paginate_by_params(params)
+ flash[:info] = "Want to see your pretty face show up here?<br/> Edit <a href='#{edit_user_path(current_user)}'>your profile</a>" unless current_user = :false || current_user.has_pic?
+ respond_to do |format|
+ format.html do
+ @user_count = User.count
+ @active = User.count(:all, :conditions => "assets_count > 0")
+ end
+ format.xml do
+ @users = User.search(params[:q], :limit => 25)
+ render :xml => @users.to_xml
+ end
+ format.js do render :update do |page|
+ page.replace 'user-index', :partial => 'users'
+ end
+ end
+ # format.fbml do
+ # @users = User.paginate(:all, :per_page => 10, :order => 'listens_count DESC', :page => params[:page])
+ # end
+ end
+ end
+
+ def show
+ respond_to do |format|
+ format.html do
+ @page_title = @user.name + "'s music, albums, and playlists on alonetone"
+ @tab = 'your_stuff' if current_user == @user
+ @assets = @user.assets.paginate(:all, :per_page => 5, :page=>params[:page])
+ @playlists = @user.playlists.find(:all,:conditions => [ "tracks_count > 0"])
+ @listens = @user.listens.find(:all, :limit =>5)
+ @track_plays = @user.track_plays.find(:all, :limit =>10)
+ @comments = @user.comments.find_all_by_spam(false, :limit => 10)
+ end
+ format.xml { @assets = @user.assets.find(:all, :order => 'created_at DESC')}
+ format.rss { @assets = @user.assets.find(:all, :order => 'created_at DESC')}
+ format.js do render :update do |page|
+ page.replace 'user_latest', :partial => "latest"
+ end
+ end
+ end
+ end
+
+ def new
+ @user = User.new
+ @page_title = "Sign up with alonetone to upload your mp3s or discover new music"
+ end
+
+
+ def create
+ respond_to do |format|
+ format.html do
+ @user = params[:user].blank? ? User.find_by_email(params[:email]) : User.new(params[:user])
+ flash[:error] = "I could not find an account with the email address '#{CGI.escapeHTML params[:email]}'. Did you type it correctly?" if params[:email] and not @user
+ redirect_to login_path and return unless @user
+ @user.login = params[:user][:login] unless params[:user].blank?
+ @user.reset_token!
+ begin
+ UserMailer.deliver_signup(@user)
+ rescue Net::SMTPFatalError => e
+ flash[:error] = "A permanent error occured while sending the signup message to '#{CGI.escapeHTML @user.email}'. Please check the e-mail address."
+ redirect_to :action => "new"
+ rescue Net::SMTPServerBusy, Net::SMTPUnknownError, \
+ Net::SMTPSyntaxError, TimeoutError => e
+ flash[:error] = "The signup message cannot be sent to '#{CGI.escapeHTML @user.email}' at this moment. Please, try again later."
+ redirect_to :action => "new"
+ end
+ flash[:ok] = "An email was sent to '#{CGI.escapeHTML @user.email}'. <br/>You just have to click the link in the email, and the hard work is over! <br/> Note: check your junk/spam inbox if you don't see a new email right away."
+ redirect_to login_path
+ end
+ end
+ rescue ActiveRecord::RecordInvalid
+ render :action => 'new'
+ end
+
+
+ def activate
+ self.current_user = User.find_by_activation_code(params[:activation_code])
+ if current_user != false && !current_user.activated?
+ current_user.activate
+ flash[:ok] = "Whew! All done, your account is activated. Go ahead and upload your first track."
+ redirect_to new_user_track_path(current_user)
+ else
+ flash[:error] = "Hm. Activation didn't work. Maybe your account is already activated?"
+ redirect_to default_url
+ end
+ end
+
+ def edit
+
+ end
+
+ def bio
+ @page_title = "#{@user.name}'s detailed biography on alonetone"
+ end
+
+ def attach_pic
+ @pic = @user.build_pic(params[:pic])
+ if @pic.save
+ flash[:ok] = 'Pic updated!'
+ else
+ flash[:error] = 'Pic not updated!'
+ end
+ redirect_to edit_user_path(@user)
+ end
+
+
+ def update
+ @user.attributes = params[:user]
+ # temp fix to let people with dumb usernames change them
+ # @user.login = params[:user][:login] if not @user.valid? and @user.errors.on(:login)
+ respond_to do |format|
+ format.html do
+ if @user.save
+ flash[:ok] = "Sweet, your profile is updated"
+ end
+ render :action => 'edit'
+ end
+ end
+ end
+
+ def destroy
+ return unless admin?
+ @user.destroy
+ respond_to do |format|
+ format.html { redirect_to users_path }
+ format.xml { head 200 }
+ end
+ end
+
+ def sudo
+ @to_user = User.find_by_login(params[:login] || params[:id])
+ redirect_to :back and return false unless (current_user.admin? || session[:sudo]) && @to_user
+ flash[:ok] = "Sudo to #{@to_user.name}" if sudo_to(@to_user)
+ redirect_to :back
+ end
+
+ protected
+ def authorized?
+ admin? || (!%w(destroy admin sudo).include?(action_name) && logged_in? && (current_user.id.to_s == @user.id.to_s))
+ end
+
+ def display_user_home_or_index
+ if params[:login] && User.find_by_login(params[:login])
+ redirect_to user_home_url(params[:user])
+ else
+ redirect_to users_url
+ end
+ end
+
+end
2  app/helpers/account_helper.rb
@@ -0,0 +1,2 @@
+module AccountHelper
+end
70 app/helpers/application_helper.rb
@@ -0,0 +1,70 @@
+# Methods added to this helper will be available to all templates in the application.
+module ApplicationHelper
+
+ def authorized_for(user_related_record)
+ logged_in? && (current_user.admin? || (user_related_record.user == current_user))
+ end
+
+ def authorized_for_comment(commment)
+ (logged_in? && admin?) || (logged_in? && (comment.commenter == current_user || comment.user == current_user))
+ end
+
+ def edit_or_show(user, playlist)
+ if authorized_for(playlist)
+ edit_user_playlist_path(@user.login, playlist)
+ else
+ user_playlist_path(@user.login, playlist.permalink)
+ end
+ end
+
+ def mp3_info_for(asset)
+ mp3 = Mp3Info::Mp3Info.new(asset.authenticated_s3_url)
+ "Filename: #{asset.permalink}<br/>Length: #{mp3.length}<br/>Name: #{mp3.tag.title}<br/>Artist: #{mp3.tag.artist} <br/>Album: #{mp3.tag.album}<br/>#{mp3.to_s}"
+ rescue
+ "File #{asset.permalink} was unopenable or did not exist."
+ end
+
+ def offer(html)
+ result = '<div id="offer">'
+ result += html
+ result += '</div>'
+ result
+ end
+
+ def rss_date(date)
+ CGI.rfc1123_date(date)
+ end
+
+ def website_for(user)
+ link_to "#{user.name}'s website", ('http://'+h(user.website))
+ end
+
+ def itunes_link_for(user)
+ link_to "Open #{user.name}'s music in iTunes", 'http://'+h(user.itunes)
+ end
+
+ def avatar(user, size=nil)
+ case size
+ when 100 then image_tag(user.has_pic? ? user.pic.public_filename(:large) : 'no-pic-thumb100.jpg')
+ when 50 then image_tag(user.has_pic? ? user.pic.thumb.public_filename(:small) : 'no-pic-thumb50.jpg')
+ when nil then image_tag(user.has_pic? ? user.pic.public_filename(:tiny) : 'no-pic.jpg' )
+ end
+ end
+
+ def link_to_play(asset, referer=nil)
+ link_to truncate(h(asset.name),25), formatted_user_track_path(asset.user.login, asset.permalink, :mp3), :id=>"play-#{asset.id}", :referer => referer
+ end
+
+ def user_bar_for(user)
+ if user then return "#{link_to_unless_current "Your Profile", profile_path(user)} #{link_to_unless_current "Logout", logout_path}<br /> Hi, #{user.login}!<br/>" end
+ (link_to "Sign up for free account", signup_path) << " or " << (link_to "Login", login_path)
+ end
+
+ def recently_online
+ @online.each {|person| link_to person.login, user_home_path(person) }
+ end
+
+ def authorized?
+ admin? || @user === current_user
+ end
+end
3  app/helpers/assets_helper.rb
@@ -0,0 +1,3 @@
+module AssetsHelper
+
+end
2  app/helpers/comments_helper.rb
@@ -0,0 +1,2 @@
+module CommentsHelper
+end
2  app/helpers/facebook_accounts_helper.rb
@@ -0,0 +1,2 @@
+module FacebookAccountsHelper
+end
16 app/helpers/pages_helper.rb
@@ -0,0 +1,16 @@
+module PagesHelper
+
+ def basecamp_session
+ config = YAML.load_file(File.join(RAILS_ROOT,'config','basecamp.yml'))['basecamp']
+ @session = Basecamp.new(config['url'],config['user'],config['pass'])
+ @completed = @session.lists(config['project_id'], true).collect{|list| @session.get_list(list.id)}
+ @uncompleted = @session.lists(config['project_id'], false).collect{|list| @session.get_list(list.id)}
+ end
+
+ # proper date format for google sitemaps
+ def w3c_date(date)
+ date.utc.strftime("%Y-%m-%dT%H:%M:%S+00:00")
+ end
+
+
+end
2  app/helpers/pics_helper.rb
@@ -0,0 +1,2 @@
+module PicsHelper
+end
2  app/helpers/playlists_helper.rb
@@ -0,0 +1,2 @@
+module PlaylistsHelper
+end
2  app/helpers/updates_helper.rb
@@ -0,0 +1,2 @@
+module UpdatesHelper
+end
2  app/helpers/user_reports_helper.rb
@@ -0,0 +1,2 @@
+module UserReportsHelper
+end
3  app/helpers/users_helper.rb
@@ -0,0 +1,3 @@
+module UsersHelper
+
+end
181 app/models/asset.rb
@@ -0,0 +1,181 @@
+# create_table "assets", :force => true do |t|
+# t.string "content_type"
+# t.string "filename"
+# t.integer "size"
+# t.integer "parent_id"
+# t.string "thumbnail"
+# t.integer "width"
+# t.integer "height"
+# t.integer "site_id"
+# t.datetime "created_at"
+# t.string "title"
+# t.integer "thumbnails_count", :default => 0
+# t.integer "user_id"
+# t.integer "length"
+# t.string "album"
+# t.string "permalink"
+# end
+
+class Asset < ActiveRecord::Base
+
+ # used for extra mime types that dont follow the convention
+ @@extra_content_types = { :audio => ['application/ogg'], :movie => ['application/x-shockwave-flash'], :pdf => ['application/pdf'] }.freeze
+ @@allowed_extensions = %w(.mp3)
+ cattr_reader :extra_content_types, :allowed_extensions
+
+
+ # use #send due to a ruby 1.8.2 issue
+ @@movie_condition = send(:sanitize_sql, ['content_type LIKE ? OR content_type IN (?)', 'video%', extra_content_types[:movie]]).freeze
+ @@audio_condition = send(:sanitize_sql, ['content_type LIKE ? OR content_type IN (?)', 'audio%', extra_content_types[:audio]]).freeze
+ @@image_condition = send(:sanitize_sql, ['content_type IN (?)', Technoweenie::AttachmentFu.content_types]).freeze
+ @@other_condition = send(:sanitize_sql, [
+ 'content_type NOT LIKE ? AND content_type NOT LIKE ? AND content_type NOT IN (?)',
+ 'audio%', 'video%', (extra_content_types[:movie] + extra_content_types[:audio] + Technoweenie::AttachmentFu.content_types)]).freeze
+ cattr_reader *%w(movie audio image other).collect! { |t| "#{t}_condition".to_sym }
+
+ has_many :tracks, :dependent => :destroy
+ belongs_to :user, :counter_cache => true
+
+ has_many :listens, :dependent => :destroy
+ has_many :listeners, :through => :listens
+
+ has_many :comments, :as => :commentable, :dependent => :destroy, :order => 'created_at DESC'
+
+ acts_as_defensio_article
+
+ has_many :facebook_addables, :as => :profile_chunks
+
+ has_permalink :name
+ # make sure we update permalink when user changes title
+ before_save :create_unique_permalink
+
+ has_attachment :storage => :s3,
+ :processor => :mp3info,
+ :content_type => ['audio/mpeg','application/zip'],
+ :max_size => 40.megabytes,
+ :path_prefix => "mp3"
+
+ validates_as_attachment
+ validates_presence_of :user_id
+
+ # after_resize do |record, mp3|
+ # mp3.tag.author = "#{record.user.name} (#{record.user.site})" unless mp3.tag.author
+ # end
+ # the attachment_fu callback is actually named after_resize
+
+ def self.extract_mp3s(zip_file, &block)
+ # try to open the zip file
+ Zip::ZipFile.open(zip_file.path) do |z|
+ z.each do |entry|
+ # so, if we've got a file with an mp3 in there with a decent size
+ if entry.to_s =~ /(\.\w+)$/ && allowed_extensions.include?($1) && entry.size > 2000
+ # throw together a new tempfile of the rails flavor
+ # spoof the necessary attributes to get Attachment_fu to accept our zipped friends
+ #temp.content_type = 'audio/mpeg'
+ # pass back each mp3 within the zip
+ tempfile_name = File.basename entry.name
+ temp = ActionController::UploadedTempfile.new(tempfile_name, Technoweenie::AttachmentFu.tempfile_path)
+ temp.open
+ temp.binmode
+ temp << z.read(entry)
+ temp.content_type= 'audio/mpeg'
+ # if there are some directories, remove them
+ temp.original_path = tempfile_name
+ yield temp
+ #debugger
+ # deletes the temp files
+ temp.close
+
+ logger.warn("ZIP: #{entry.to_s} was extracted from zip file: #{zip_file.path}")
+ end
+ end
+ end
+ # pass back the file unprocessed if the file is not a zip
+ rescue Zip::ZipError => e
+ logger.warn("An error occured with attempted extraction from #{zip_file.path}:"+e)
+ yield zip_file
+ end
+
+ def self.latest(limit=10)
+ find(:all, :include => :user, :limit => limit, :order => 'assets.created_at DESC')
+ end
+
+ def self.most_popular(limit=10, time_period=5.days.ago)
+ popular = Listen.count(:all,:include => :asset, :group => 'listens.asset_id', :limit => limit, :conditions => ["listens.created_at > ? AND (listens.listener_id IS NULL OR listens.listener_id != listens.track_owner_id)", time_period], :order => 'count_all DESC')
+ find(popular.collect{|pop| pop.first}, :include => :user)
+ # In the last week, people have been listening to the following
+ #find(:all, :include => :user, :limit => limit, :order => 'assets.listens_count DESC')
+ end
+
+ class << self
+ def movie?(content_type)
+ content_type.to_s =~ /^video/ || extra_content_types[:movie].include?(content_type)
+ end
+
+ def audio?(content_type)
+ content_type.to_s =~ /^audio/ || extra_content_types[:audio].include?(content_type)
+ end
+
+ def other?(content_type)
+ ![:image, :movie, :audio].any? { |a| send("#{a}?", content_type) }
+ end
+
+ def find_all_by_content_types(types, *args)
+ with_content_types(types) { find *args }
+ end
+
+ def with_content_types(types, &block)
+ with_scope(:find => { :conditions => types_to_conditions(types).join(' OR ') }, &block)
+ end
+
+ def types_to_conditions(types)
+ types.collect! { |t| '(' + send("#{t}_condition") + ')' }
+ end
+ end
+
+
+ [:movie, :audio, :other, :pdf].each do |content|
+ define_method("#{content}?") { self.class.send("#{content}?", content_type) }
+ end
+
+ def name
+ # make sure the title is there, and if not, the filename is used...
+ title && !title.empty? ? title : clean_filename
+ end
+
+ def public_mp3
+ self.s3_url
+ end
+
+ def clean_filename
+ self.filename[0..-5].gsub(/-|_/,' ').titleize
+ end
+
+ def clean_permalink
+ self.permalink = nil
+ end
+
+ # allows classes outside Asset to use the same format
+ def self.formatted_time(time)
+ if time
+ min_and_sec = time.divmod(60)
+ minutes = min_and_sec[0].to_s
+ seconds = min_and_sec[1].to_s
+ seconds = "0"+seconds if seconds.length == 1
+ minutes + ':' + seconds
+ else
+ "?:??"
+ end
+ end
+
+ def length
+ self.class.formatted_time(self[:length])
+ end
+
+ protected
+
+ def set_title_to_filename
+ title = filename.split('.').first unless title
+ end
+
+end
29 app/models/comment.rb
@@ -0,0 +1,29 @@
+class Comment < ActiveRecord::Base
+ belongs_to :commentable, :polymorphic => true
+
+ # optional user who made the comment
+ belongs_to :commenter, :class_name => 'User'
+
+ # optional user who is recieving the comment
+ # this helps simplify a user lookup of all comments across tracks/playlists/whatever
+ belongs_to :user
+
+ validates_length_of :body, :within => 1..700
+
+ acts_as_defensio_comment :fields => { :content => :body, :article => :commentable, :author => :commenter }
+ attr_accessor :current_user
+
+
+ def duplicate?
+ Comment.find_by_remote_ip_and_body(self.remote_ip, self.body)
+ end
+
+ def user_logged_in
+ commenter_id
+ end
+
+ def trusted_user
+ commenter_id && commenter.admin?
+ end
+
+end
5 app/models/facebook_account.rb
@@ -0,0 +1,5 @@
+class FacebookAccount < ActiveRecord::Base
+ has_many :facebook_addables
+ has_many :assets, :through => :facebook_addables, :source => :asset,
+ :conditions => "facebook_addables.profile_chunk_type = 'Asset'"
+end
15 app/models/facebook_addable.rb
@@ -0,0 +1,15 @@
+class FacebookAddable < ActiveRecord::Base
+ # The owner
+ belongs_to :facebook_account
+
+ # The things we want the owner to be able to add
+ belongs_to :profile_chunk, :polymorphic => true
+
+ # Each addable item gets it's own entry here so the owner can reference it directly
+ belongs_to :asset, :class_name => "Asset",
+ :foreign_key => "profile_chunk_id"
+ belongs_to :playlist, :class_name => "Playlist",
+ :foreign_key => "profile_chunk_id"
+
+ validates_presence_of :profile_chunk_id, :profile_chunk_type, :facebook_account_id
+end
27 app/models/listen.rb
@@ -0,0 +1,27 @@
+# == Schema Information
+# Schema version: 16
+#
+# Table name: listens
+#
+# id :integer(11) not null, primary key
+# asset_id :integer(11)
+# user_id :integer(11)
+# created_at :datetime
+# updated_at :datetime
+#
+
+class Listen < ActiveRecord::Base
+
+ # A "Listen" occurs when a user listens to another users track
+ belongs_to :asset, :counter_cache => true
+
+ belongs_to :listener, :class_name => 'User'
+ belongs_to :track_owner, :class_name => 'User', :counter_cache => true
+
+ validates_presence_of :asset_id, :track_owner_id
+
+ def source
+ self[:source] || 'alonetone'
+ end
+
+end
17 app/models/pic.rb
@@ -0,0 +1,17 @@
+class Pic < ActiveRecord::Base
+ belongs_to :picable, :polymorphic => true
+
+ # Pic
+ has_attachment :min_size => 100.bytes,
+ :max_size => 2048.kilobytes,
+ :resize_to => 'x400',
+ :content_type => :image,
+ :thumbnails => { :tiny => [25,25], :small => [50,50], :large => [125,125], :album=>[200,200] },
+ :storage => :s3,
+ :path_prefix => 'pics'
+
+ # not yet it can ;)
+ #can_be_cropped
+ validates_as_attachment
+
+end
102 app/models/playlist.rb
@@ -0,0 +1,102 @@
+# == Schema Information
+# Schema version: 16
+#
+# Table name: playlists
+#
+# id :integer(11) not null, primary key
+# title :string(255)
+# description :string(255)
+# image :string(255)
+# user_id :integer(11)
+# tracks_count :integer(11)
+# created_at :datetime
+# updated_at :datetime
+# pic_id :integer(11)
+# permalink :string(255)
+#
+require 'zip/zip'
+require 'zip/zipfilesystem'
+class Playlist < ActiveRecord::Base
+ belongs_to :user, :counter_cache => true
+
+ has_one :pic, :as => :picable, :dependent => :destroy
+ has_many :tracks, :dependent => :destroy, :order => :position
+ has_many :assets, :through => :tracks #, :after_add =>
+
+ validates_presence_of :title
+ validates_length_of :title, :within => 4..100
+ validates_presence_of :description
+
+ has_permalink :title
+ # make sure we update permalink when user changes title
+ before_save :create_unique_permalink
+ before_update :set_mix_or_album
+
+ def to_param
+ "#{self.permalink}"
+ end
+
+ def dummy_pic(size)
+ case size
+ when :small then 'no-cover-50.jpg'
+ when :large then 'no-cover-125.jpg'
+ when :album then 'no-cover-200.jpg'
+ when nil then 'no-cover-400.jpg'
+ end
+ end
+
+ def cover(size)
+ return dummy_pic(size) unless self.pic && !self.pic.new_record?
+ self.pic.public_filename(size)
+ end
+
+ def has_tracks?
+ (self.tracks_count || 0) > 0
+ end
+
+ def empty?
+ !has_tracks?
+ end
+
+ def play_time
+ Asset.formatted_time(self.tracks.inject(0){|total,track| total += track.asset[:length] if track.asset && track.asset[:length]})
+ end
+
+ def zip(name = self.permalink, set = self.artist.permalink)
+ bundle_filename = "#{RAILS_ROOT}/public/uploads/#{set}-#{name}.zip"
+
+
+
+ # set the bundle_filename attribute of this object
+ self.bundle_filename = "/uploads/#{set}-#{name}.zip"
+
+ # open or create the zip file
+ Zip::ZipFile.open(bundle_filename, Zip::ZipFile::CREATE) {
+ |zipfile|
+ # collect the album's tracks
+ self.tracks.collect {
+ |track|
+ # add each track to the archive, names using the track's attributes
+ zipfile.add( "#{set}/#{track.num}-#{track.filename}", "#{RAILS_ROOT}/public#{track.public_filename}")
+ }
+ }
+
+ # set read permissions on the file
+ File.chmod(0644, bundle_filename)
+
+ # save the object
+ self.save
+ end
+
+ def self.latest(limit=5)
+ self.find(:all, :conditions => 'playlists.tracks_count > 0', :include => :user, :limit => limit, :order => 'playlists.created_at DESC')
+ end
+
+ protected
+
+ def set_mix_or_album
+ # playlist is a mix if there is at least one track with a track from another user
+ self.is_mix = self.tracks.any?{ |track| track.asset.user.id.to_s != self.user.id.to_s}
+ true
+ end
+end
27 app/models/track.rb
@@ -0,0 +1,27 @@
+# == Schema Information
+# Schema version: 16
+#
+# Table name: tracks
+#
+# id :integer(11) not null, primary key
+# playlist_id :integer(11)
+# asset_id :integer(11)
+# position :integer(11)
+# created_at :datetime
+# updated_at :datetime
+#
+
+class Track < ActiveRecord::Base
+ belongs_to :playlist, :counter_cache => true
+ belongs_to :asset
+ belongs_to :user
+
+ acts_as_list :scope => :playlist_id, :order => :position
+
+ validates_presence_of :asset_id, :playlist_id
+
+ # allow us to pretend that the track has info by forwarding to the asset
+ [:length, :name].each do |attribute|
+ define_method("#{attribute}?") { self.track.send("#{attribute}") }
+ end
+end
9 app/models/update.rb
@@ -0,0 +1,9 @@
+require 'bluecloth'
+class Update < ActiveRecord::Base
+
+
+ def print
+ BlueCloth::new(self.content).to_html
+ end
+
+end
237 app/models/user.rb
@@ -0,0 +1,237 @@
+# == Schema Information
+# Schema version: 16
+#
+# Table name: users
+#
+# id :integer(11) not null, primary key
+# login :string(40)
+# email :string(100)
+# salt :string(40)
+# activation_code :string(40)
+# activated_at :datetime
+# created_at :datetime
+# updated_at :datetime
+# deleted_at :datetime
+# token :string(255)
+# token_expires_at :datetime
+# admin :boolean(1)
+# last_seen_at :datetime
+# crypted_password :string(40)
+# assets_count :integer(11)
+# display_name :string(255)
+# identity_url :string(255)
+# pic_id :integer(11)
+#
+
+require 'digest/sha1'
+class User < ActiveRecord::Base
+
+ # Can create music
+ has_many :assets, :dependent => :destroy, :order => 'created_at DESC'
+ has_many :playlists, :dependent => :destroy, :order => 'playlists.created_at DESC'
+ has_one :pic, :as => :picable
+ has_many :comments, :dependent => :destroy, :order => 'created_at DESC'
+
+ # Can listen to music, and have that tracked
+ has_many :listens, :foreign_key => 'listener_id', :include => :asset, :order => 'listens.created_at DESC'
+
+ # Can have their music listened to
+ has_many :track_plays, :foreign_key => 'track_owner_id', :class_name => 'Listen', :include => :asset, :order => 'listens.created_at DESC'
+
+ # And therefore have listeners
+ has_many :listeners, :through => :track_plays, :uniq => true
+
+ # top tracks
+ has_many :top_tracks, :class_name => 'Asset', :limit => 10, :order => 'listens_count DESC'
+
+ # Virtual attribute for the unencrypted password
+ attr_accessor :password
+
+ # These attributes can be changed by a user
+ attr_accessible :login, :email, :password, :password_confirmation, :website, :bio, :display_name, :itunes
+
+ # Validations
+ validates_presence_of :email
+ validates_format_of :email, :with => Format::EMAIL
+ validates_presence_of :password, :if =>