diff --git a/Gemfile b/Gemfile index 0210b64..20ff1b3 100755 --- a/Gemfile +++ b/Gemfile @@ -24,7 +24,7 @@ group :development, :test, :assets do gem 'jquery-rails' # Use jquery as the JavaScript library gem 'turbolinks' # Turbolinks makes following links in your web application faster gem 'selenium-webdriver' - # gem 'selenium-phantomjs' + gem 'database_cleaner' end gem 'font-awesome-sass' # View helper for font-awesome, not really neccesary, but handy diff --git a/Gemfile.lock b/Gemfile.lock index f35ae66..a9bc825 100755 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -136,6 +136,7 @@ GEM coffee-script-source (1.7.0) crack (0.4.2) safe_yaml (~> 1.0.0) + database_cleaner (1.3.0) devise (3.2.4) bcrypt (~> 3.0) orm_adapter (~> 0.1) @@ -344,6 +345,7 @@ DEPENDENCIES coffee-rails (~> 4.0.0) compass! compass-rails! + database_cleaner devise factory_girl_rails font-awesome-sass diff --git a/app/assets/stylesheets/mods/show.css.sass b/app/assets/stylesheets/mods/show.css.sass index 789fc99..4528de9 100755 --- a/app/assets/stylesheets/mods/show.css.sass +++ b/app/assets/stylesheets/mods/show.css.sass @@ -28,7 +28,7 @@ overflow: hidden &.active display: block - &-link + &-container //@extend %thumbnail-hover display: block width: 100% @@ -53,7 +53,7 @@ width: rhythm(3) height: rhythm(3) margin: 0 space()/2 space() space()/2 - &-link + &-container display: block width: 100% height: 100% diff --git a/app/controllers/mods_controller.rb b/app/controllers/mods_controller.rb index 113a1e8..41d2c56 100755 --- a/app/controllers/mods_controller.rb +++ b/app/controllers/mods_controller.rb @@ -75,8 +75,7 @@ class ModsController < ApplicationController end def create - @mod = Mod.new mod_params - @mod.author = current_user + @mod = Mod.new(current_user.is_admin? ? mod_params_admin : mod_params) if @mod.save redirect_to category_mod_url(@mod.category, @mod) else @@ -95,8 +94,28 @@ class ModsController < ApplicationController private - def mod_params - params.require(:mod).permit(:name, :category_id, :github, :forum_url, :description, :summary, :media_links_string) + def mod_params + params.require(:mod).permit(:name, + :category_id, + :github, + :forum_url, + :description, + :summary, + :media_links_string, + versions_attributes: [ + :number, + :released_at, + game_version_ids: [], + files_attributes: [ + :attachment, + :name + ] + ]) + .merge(author_id: current_user.id) + end + + def mod_params_admin + mod_params.merge params.require(:mod).permit(:author_name) end # def category diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 86e3d60..3fe4d16 100755 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -19,9 +19,9 @@ module ApplicationHelper @empty_asset.image.url(size) end - def if_na(condition, result = nil) + def if_na(condition, result = nil, &block) if !result and block_given? - condition ? yield : 'N/A' + condition ? capture(&block) : 'N/A' else condition ? result : 'N/A' end @@ -67,6 +67,10 @@ module ApplicationHelper polymorphic_path([@sort, @category, :mods], filter_params.merge(q: query)) end + def edit_mod_url(mod) + edit_category_mod_url(mod.category, mod) + end + ### Links helpers #################### diff --git a/app/models/mod.rb b/app/models/mod.rb index 53ab7a2..9e0ff8f 100755 --- a/app/models/mod.rb +++ b/app/models/mod.rb @@ -23,14 +23,14 @@ class Mod < ActiveRecord::Base has_many :downloads has_many :visits - has_many :files, class_name: 'ModFile' - has_many :versions, class_name: 'ModVersion' - has_many :assets, ->{ order 'sort_order asc' }, class_name: 'ModAsset' + has_many :files, class_name: 'ModFile', dependent: :destroy + has_many :versions, class_name: 'ModVersion', dependent: :destroy + has_many :assets, ->{ order 'sort_order asc' }, class_name: 'ModAsset', dependent: :destroy has_many :tags has_many :favorites has_many :forum_posts - has_many :mod_game_versions, -> { uniq } + has_many :mod_game_versions, -> { uniq }, dependent: :destroy has_many :game_versions, -> { uniq.sort_by_older_to_newer }, through: :mod_game_versions # has_one :latest_version, -> { sort_by_newer_to_older.limit(1) }, class_name: 'ModVersion' @@ -130,11 +130,7 @@ class Mod < ActiveRecord::Base end def author_name - if author - author.name - else - read_attribute :author_name - end + super || (author ? author.name : nil) end def github_path @@ -160,6 +156,10 @@ class Mod < ActiveRecord::Base versions.size > 0 end + def has_files? + files.size > 0 + end + private def set_game_versions_string diff --git a/app/models/mod_file.rb b/app/models/mod_file.rb index 8555c4f..80f2294 100755 --- a/app/models/mod_file.rb +++ b/app/models/mod_file.rb @@ -7,9 +7,11 @@ class ModFile < ActiveRecord::Base :content_type => ['application/zip', 'application/x-zip-compressed', 'application/octet-stream/'] - validates :mod_version, presence: true + before_save do + self.mod = mod_version.mod if mod_version + end def delegated_name - name || mod_version.number + name || (mod_version ? mod_version.number : nil) end end diff --git a/app/models/mod_version.rb b/app/models/mod_version.rb index 2bc797e..c3e925b 100755 --- a/app/models/mod_version.rb +++ b/app/models/mod_version.rb @@ -14,6 +14,7 @@ class ModVersion < ActiveRecord::Base ################# validates :number, presence: true + # validates :game_versions, presence: true validate :validate_non_consecutive_game_versions def validate_non_consecutive_game_versions diff --git a/app/views/mods/_download_button.html.haml b/app/views/mods/_download_button.html.haml index 183de11..1b78f04 100755 --- a/app/views/mods/_download_button.html.haml +++ b/app/views/mods/_download_button.html.haml @@ -1,4 +1,4 @@ -- if mod.has_versions? +- if mod.has_versions? and mod.has_files? .mod-link.btn.btn-download %a{href: mod.latest_version.files.first.attachment.url} = icon 'download' diff --git a/app/views/mods/_mod_data_table.html.haml b/app/views/mods/_mod_data_table.html.haml index 147a62d..802b3eb 100755 --- a/app/views/mods/_mod_data_table.html.haml +++ b/app/views/mods/_mod_data_table.html.haml @@ -9,7 +9,7 @@ %td.mod-data-row-name Source code %td.mod-data-row-value= if_na(@mod.github_url) { link_to(@mod.github_path, @mod.github_url) } %td.mod-data-row-name Tags - %td.mod-data-row-value + %td.mod-data-row-value N/A %tr.mod-data-row %td.mod-data-row-name First version %td.mod-data-row-value= if_na(@mod.has_versions?) { @mod.versions.first.number } @@ -21,14 +21,16 @@ %td.mod-data-row-name Last release %td.mod-data-row-value= if_na(@mod.has_versions? && @mod.versions.last.released_at) { @mod.versions.last.released_at.to_s(:rfc822) } %tr.mod-data-row - %td.mod-data-row-name License - %td.mod-data-row-value= if_na(@mod.license) { link_to(@mod.license, @mod.license_url) } + / %td.mod-data-row-name License + / %td.mod-data-row-value= if_na(@mod.license) { link_to(@mod.license, @mod.license_url) } %td.mod-data-row-name Official forum post %td.mod-data-row-value - = if_na(!@mod.forum_post_url.blank?) do + = if_na(@mod.forum_post_url.present?) do %a{href: @mod.forum_post_url} - = @mod.forum_comments_count unless @mod.forum_comments_count.blank? - comments + On the Factorio forums + / = @mod.forum_comments_count unless @mod.forum_comments_count.blank? + / comments %tr.mod-data-row %td.mod-data-row-name Official site - %td.mod-data-row-value{colspan: 3}= @mod.official_url if @mod.official_url \ No newline at end of file + %td.mod-data-row-value{colspan: 3} + = link_to @mod.official_url, @mod.official_url \ No newline at end of file diff --git a/app/views/mods/new.html.haml b/app/views/mods/new.html.haml index 831ed3e..209bdf2 100755 --- a/app/views/mods/new.html.haml +++ b/app/views/mods/new.html.haml @@ -4,6 +4,7 @@ = f.inputs do = f.input :name = f.input :category + = f.input :author_name if current_user.is_admin? = f.input :github = f.input :official_url = f.input :forum_url diff --git a/app/views/mods/show.html.haml b/app/views/mods/show.html.haml index dfbac23..991d220 100755 --- a/app/views/mods/show.html.haml +++ b/app/views/mods/show.html.haml @@ -4,19 +4,20 @@ .mod .mod-images< %ul.mod-medium-images - - if @mod.assets.empty? + - if @mod.media_links.empty? .mod-medium-image.active - .mod-medium-image-link + .mod-medium-image-container %img.mod-medium-image-img{src: mod_asset_missing_image(:medium)} - - @mod.assets.each_with_index do |asset, i| - %li.mod-medium-image{'data-thumb' => asset.id, class: ('active' if i == 0)} - %a.mod-medium-image-link{href: asset.image.url(:full), target: '_blank'} - %img.mod-medium-image-img{src: asset.image.url(:medium)} - %ul.mod-thumb-images{class: ('many-thumbs' if @mod.assets.size > 3)} - - @mod.assets.each do |asset| - %li.mod-thumb-image{'data-thumb-of' => asset.id}<> - %a.mod-thumb-image-link{href: asset.image.url(:full), target: "_blank"} - %img.mod-thumb-image-img{src: asset.image.url(:thumb)} + - @mod.media_links.each_with_index do |media_link, i| + %li.mod-medium-image{class: ('active' if i == 0)} + = media_link.embed + / %a.mod-medium-image-link{href: media_link.direct_url, target: '_blank'} + / %img.mod-medium-image-img{src: asset.direct_url} + %ul.mod-thumb-images{class: ('many-thumbs' if @mod.media_links.size > 3)} + - @mod.media_links.each do |media_link| + %li.mod-thumb-image{'data-thumb-of' => media_link.direct_url}<> + %span.mod-thumb-image-container + %img.mod-thumb-image-img{src: media_link.thumbnail_url} / .mod-thumbnail-container< / - @mod.assets.each_with_index do |asset, i| @@ -31,6 +32,7 @@ .mod-subtitle on = link_to @mod.category.name, [@mod.category, :mods] + %a.edit-mod-link{href: edit_mod_url(@mod)} Edit mod = render partial: 'mod_data_table' = render partial: 'download_button', locals: { mod: @mod } .mod-description diff --git a/config/locales/en.yml b/config/locales/en.yml index b00090a..cbe72d2 100755 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -42,3 +42,6 @@ en: labels: mod: forum_url: 'Forum URL' + + mod_version: + released_at: 'Release day' \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index dd3e0d7..48d9dba 100755 --- a/config/routes.rb +++ b/config/routes.rb @@ -5,37 +5,51 @@ Rails.application.routes.draw do get '/', to: 'admin/dashboard#index' end - # most_recent_mods_url => 'recently-updated-mods/' - # most_recent_category_mods_url => 'recently-updated-mods/' - scope 'recently-updated-mods', sort: 'most_recent', as: :most_recent do - get '/', to: 'mods#index', as: :mods - get '/:category_id', to: 'mods#index', as: :category_mods + # / == /mods/recently-updated + # + # /mods/new + # + # /mods + # /mods/:category_id + # /mods/:category_id/:id + # /mods/:category_id/:id/edit + # + # /mods/recently-updated + # /mods/recently-updated/:category_id + # /mods/most-downloaded + # /mods/most-downloaded/:category_id + + def category_scope + resources :categories, path: '/', only: [] do + yield + end + end + + def sort_scope(name, path = nil, as_name = false) + scope path, sort: name.to_s, as: (name if as_name) do + yield + end + end + + def new_sorting_section(name, path = nil, as_name = false) + sort_scope(name, path, as_name) do + resources :mods, path: '/', only: :index + category_scope do + resources :mods, path: '/', only: :index + end + end end scope 'mods' do - get '/new', to: 'mods#new' - get '/:id/edit', to: 'mods#edit' - post '/', to: 'mods#create' - put '/', to: 'mods#update' - end + resources :mods, path: '/', except: [:index, :show, :edit] - # alpha_mods_url => 'mods/' - # alpha_category_mods_url => 'mods/' - scope 'mods', sort: 'alpha', as: :alpha do - get '/', to: 'mods#index', as: :mods - get '/:category_id', to: 'mods#index', as: :category_mods - end + new_sorting_section(:most_recent, 'recently-updated', true) + new_sorting_section(:alpha, nil, true) # This is to generate the helper URL + new_sorting_section(:alpha) # The default sorting - # mods_url => 'mods/' - # category_mods_url => 'mods/' - scope 'mods', sort: 'alpha' do - get '/', to: 'mods#index', as: :mods - get '/:category_id', to: 'mods#index', as: :category_mods - end - - # category_mod_url => 'mods//' - scope 'mods' do - get '/:category_id/:id', to: 'mods#show', as: :category_mod + category_scope do + resources :mods, path: '/', only: [:show, :edit] + end end get '/how-to-install' => 'static#how_to_install', as: :how_to_install_static diff --git a/guard_with_virtual_display.sh b/guard_with_virtual_display.sh new file mode 100755 index 0000000..e3ef4c8 --- /dev/null +++ b/guard_with_virtual_display.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +DISPLAY=localhost:0.0 xvfb-run -a bundle exec guard diff --git a/spec/features/new_mod_spec.rb b/spec/features/new_mod_spec.rb index c5b9a29..76f88b8 100755 --- a/spec/features/new_mod_spec.rb +++ b/spec/features/new_mod_spec.rb @@ -75,22 +75,49 @@ feature 'Modder creates a new mod' do expect(page).to have_content 'Invalid media links' end - # Fuck this. I'm not gonna install a headless Selenium server today. - # scenario 'user submits mod with a version', js: true do - # sign_in - # create_category 'Potato' - # visit '/mods/new' - # fill_in 'Name', with: 'Valid mod name' - # click_link 'Add version' - # find('.mod-version').fill_in 'Number', with: '123' - # # within '.mod-version:nth-child(1)' do - - # # end - # submit_form - # mod = Mod.first - # expect(current_path).to eq '/mods/potato/valid-mod-name' - # expect(mod.versions[0].number).to eq '123' - # end + scenario 'user submits mod with a version and file', js: true do + sign_in + create_category 'Potato' + create :game_version, number: '1.1.x' + create :game_version, number: '1.2.x' + attachment = File.new(Rails.root.join('spec', 'fixtures', 'test.zip')) + visit '/mods/new' + fill_in 'Name', with: 'Valid mod name' + select 'Potato', from: 'Category' + click_link 'Add version' + within('.mod-version:nth-child(1)') do + fill_in 'Number', with: '123' + fill_in 'Release day', with: '2014-11-09' + select '1.1.x', from: 'Game versions' + select '1.2.x', from: 'Game versions' + click_link 'Add file' + within('.mod-version-file:nth-child(1)') do + attach_file 'Attachment', attachment.path + end + end + submit_form + mod = Mod.first + expect(current_path).to eq '/mods/potato/valid-mod-name' + expect(page).to have_content 'Valid mod name' + expect(mod.versions[0].number).to eq '123' + expect(mod.versions[0].released_at).to eq Time.zone.parse('2014-11-09') + expect(mod.versions[0].game_versions[0].number).to eq '1.1.x' + expect(mod.versions[0].game_versions[1].number).to eq '1.2.x' + expect(mod.versions[0].files[0].attachment_file_size).to eq attachment.size + end + + scenario 'admin user submits a mod selecting an author' do + sign_in_admin + create_category 'Potato' + visit '/mods/new' + fill_in 'Name', with: 'Mod Name' + select 'Potato', from: 'Category' + fill_in 'Author name', with: 'MangoDev' + submit_form + mod = Mod.first + expect(current_path).to eq '/mods/potato/mod-name' + expect(mod.author_name).to eq 'MangoDev' + end def submit_form click_button 'Create Mod' @@ -101,6 +128,11 @@ feature 'Modder creates a new mod' do login_as @user end + def sign_in_admin + @user = create :user, is_admin: true + login_as @user + end + def create_category(name) @category = create :category, name: name end diff --git a/spec/features/show_mod_spec.rb b/spec/features/show_mod_spec.rb index af17603..caa1378 100755 --- a/spec/features/show_mod_spec.rb +++ b/spec/features/show_mod_spec.rb @@ -2,6 +2,31 @@ require 'rails_helper' include Warden::Test::Helpers feature 'Display full mod information' do + scenario 'Mod with just name and category' do + category = create :category, name: 'Potato category' + create :mod, name: 'Super Mod', category: category + visit '/mods/potato-category/super-mod' + expect(page).to have_content 'Super Mod' + expect(page).to have_content 'Potato category' + end + + scenario 'Mod with name, category and media links' do + category = create :category, name: 'Potato category' + create :mod, name: 'Super Mod', category: category, media_links_string: "http://i.imgur.com/qLpt6gI.jpg\nhttp://gfycat.com/EthicalZanyHuman" + visit '/mods/potato-category/super-mod' + expect(page).to have_content 'Super Mod' + expect(page).to have_content 'Potato category' + expect(page).to have_css 'img[src="http://i.imgur.com/qLpt6gI.jpg"]' + expect(page).to have_css 'img[src="http://i.imgur.com/qLpt6gIs.jpg"]' + expect(page).to have_css 'img[src="https://thumbs.gfycat.com/EthicalZanyHuman-poster.jpg"]' + end + + scenario 'Visiting the mod page as the owner of the mod should display a link to edit the mod' do + category = create :category, name: 'Potato category' + create :mod, name: 'Super Mod', category: category + visit '/mods/potato-category/super-mod' + expect(page).to have_link 'Edit mod', '/mods/super-mod/edit' + end # expect(page).to have_content 'Mah super mod' # expect(page).to have_content 'Potato category' # expect(page).to have_link 'factorio-mods/mah-super-mod', href: 'http://github.com/factorio-mods/mah-super-mod' diff --git a/spec/models/mod_file_spec.rb b/spec/models/mod_file_spec.rb index 6567fcc..6eee81c 100755 --- a/spec/models/mod_file_spec.rb +++ b/spec/models/mod_file_spec.rb @@ -65,10 +65,12 @@ RSpec.describe ModFile, :type => :model do end context 'empty #mod_version' do - it 'should not be valid' do + it 'should be valid' do file.mod_version = nil file.name = nil - expect(file).to be_invalid + expect(file).to be_valid + expect(file.name).to eq nil + expect(file.delegated_name).to eq nil end end end diff --git a/spec/models/mod_spec.rb b/spec/models/mod_spec.rb index c4b852d..c58cdd6 100755 --- a/spec/models/mod_spec.rb +++ b/spec/models/mod_spec.rb @@ -104,14 +104,29 @@ RSpec.describe Mod, :type => :model do describe '#has_versions?' do it 'should return false if the mod has no versions' do mod = create :mod, versions: [] - mod.has_versions?.should eq false + expect(mod.has_versions?).to eq false end it 'should return true if the mod has a version' do mod = create :mod, versions: [] create :mod_version, mod: mod mod = Mod.first - mod.has_versions?.should eq true + expect(mod.has_versions?).to eq true + end + end + + describe '#has_files?' do + it 'should return false if the mod has no files' do + mod = create :mod, versions: [] + expect(mod.has_files?).to eq false + end + + it 'should return true if the mod has at least a file' do + mod = create :mod, versions: [] + mod_version = create :mod_version, mod: mod + mod_file = create :mod_file, mod_version: mod_version + mod = Mod.first + expect(mod.has_files?).to eq true end end @@ -493,15 +508,15 @@ RSpec.describe Mod, :type => :model do end it 'should work when sorting by recently updated' do - m1 = create(:mod, name: 'C Potato', versions: [create(:mod_version, released_at: 9.days.ago)]) - m2 = create(:mod, name: 'B Potato', versions: [create(:mod_version, released_at: 8.days.ago)]) - m3 = create(:mod, name: 'A Potato', versions: [create(:mod_version, released_at: 7.days.ago)]) - m4 = create(:mod, summary: 'B Potatou', versions: [create(:mod_version, released_at: 5.days.ago)]) - m5 = create(:mod, summary: 'A Potatou', versions: [create(:mod_version, released_at: 4.days.ago)]) - m6 = create(:mod, summary: 'C Potatou', versions: [create(:mod_version, released_at: 6.days.ago)]) - m7 = create(:mod, description: 'A Potatoeiu', versions: [create(:mod_version, released_at: 1.days.ago)]) - m8 = create(:mod, description: 'C Potatoeiu', versions: [create(:mod_version, released_at: 3.days.ago)]) - m9 = create(:mod, description: 'B Potatoeiu', versions: [create(:mod_version, released_at: 2.days.ago)]) + m1 = create(:mod, name: 'C Potato', versions: [build(:mod_version, released_at: 9.days.ago)]) + m2 = create(:mod, name: 'B Potato', versions: [build(:mod_version, released_at: 8.days.ago)]) + m3 = create(:mod, name: 'A Potato', versions: [build(:mod_version, released_at: 7.days.ago)]) + m4 = create(:mod, summary: 'B Potatou', versions: [build(:mod_version, released_at: 5.days.ago)]) + m5 = create(:mod, summary: 'A Potatou', versions: [build(:mod_version, released_at: 4.days.ago)]) + m6 = create(:mod, summary: 'C Potatou', versions: [build(:mod_version, released_at: 6.days.ago)]) + m7 = create(:mod, description: 'A Potatoeiu', versions: [build(:mod_version, released_at: 1.days.ago)]) + m8 = create(:mod, description: 'C Potatoeiu', versions: [build(:mod_version, released_at: 3.days.ago)]) + m9 = create(:mod, description: 'B Potatoeiu', versions: [build(:mod_version, released_at: 2.days.ago)]) expect(Mod.sort_by_most_recent.filter_by_search_query('potato')).to eq [m3, m2, m1, m5, m4, m6, m7, m9, m8] end diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 6c2881c..7b54098 100755 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -28,10 +28,30 @@ RSpec.configure do |config| # If you're not using ActiveRecord, or you'd prefer not to run each of your # examples within a transaction, remove the following line or assign false # instead of true. - config.use_transactional_fixtures = true + config.use_transactional_fixtures = false config.include Devise::TestHelpers, type: :controller + config.before(:suite) do + DatabaseCleaner.clean_with(:truncation) + end + + config.before(:each) do + DatabaseCleaner.strategy = :transaction + end + + config.before(:each, :js => true) do + DatabaseCleaner.strategy = :truncation + end + + config.before(:each) do + DatabaseCleaner.start + end + + config.after(:each) do + DatabaseCleaner.clean + end + # RSpec Rails can automatically mix in different behaviours to your tests # based on their file location, for example enabling you to call `get` and # `post` in specs under `spec/controllers`. diff --git a/spec/routing/mods_routes_spec.rb b/spec/routing/mods_routes_spec.rb index e69de29..d9a3ca2 100755 --- a/spec/routing/mods_routes_spec.rb +++ b/spec/routing/mods_routes_spec.rb @@ -0,0 +1,7 @@ +require 'rails_helper' + +describe 'Mods routes' do + it 'should be like this' do + + end +end \ No newline at end of file