diff --git a/app/assets/stylesheets/mods/index.sass b/app/assets/stylesheets/mods/index.sass index 70a853e..2146745 100644 --- a/app/assets/stylesheets/mods/index.sass +++ b/app/assets/stylesheets/mods/index.sass @@ -6,7 +6,7 @@ $mods-list-size: 12-$filter-size background: none border-radius: none padding: 0 - + .query-info-box +span($mods-list-size omega of 12) margin-bottom: rhythm() @@ -139,12 +139,12 @@ $mods-list-size: 12-$filter-size padding: space() text-align: right +nowrap - + &-tag display: block position: relative padding-right: 20px - + &-label position: relative display: block @@ -157,7 +157,7 @@ $mods-list-size: 12-$filter-size overflow: visible display: inline z-index: 3 - + .fa position: absolute top: 3px // Sorry, magic pixel @@ -172,9 +172,10 @@ $mods-list-size: 12-$filter-size bottom: space() right: space() z-index: 999 - + + &.mod-install-button + bottom: rhythm(1.6) // THE HORROR. No vertical rhythm compliant! + // Add some spacing when the download button is shown & + .mod-info-tag - margin-bottom: rhythm(1) - - \ No newline at end of file + margin-bottom: rhythm(2.5) diff --git a/app/assets/stylesheets/mods/show.sass b/app/assets/stylesheets/mods/show.sass index 5c9f6b4..9a6dbbc 100644 --- a/app/assets/stylesheets/mods/show.sass +++ b/app/assets/stylesheets/mods/show.sass @@ -52,6 +52,7 @@ .btn-download float: right + margin-left: space() margin-top: rhythm(1, $font-x-larger) > a +font-x-larger @@ -81,4 +82,4 @@ display: block margin-top: -160px width: 100% - height: 1600px \ No newline at end of file + height: 1600px diff --git a/app/controllers/mods_controller.rb b/app/controllers/mods_controller.rb index d95cbc5..b6b8ca7 100644 --- a/app/controllers/mods_controller.rb +++ b/app/controllers/mods_controller.rb @@ -1,6 +1,6 @@ class ModsController < ApplicationController load_and_authorize_resource - + respond_to :html, :json, only: [:index, :show] def index @@ -11,6 +11,11 @@ class ModsController < ApplicationController .visible .page(params[:page]).per(20) + if params[:names].present? + @mods = @mods.filter_by_names(params[:names]) + end + + # This #to_sym is not a DDoS concern since the value is constrained in the router @sort = params[:sort].to_sym case @sort when :alpha @@ -27,7 +32,7 @@ class ModsController < ApplicationController @mods = @mods.sort_by_alpha end - unless params[:v].blank? + if params[:v].present? @game_version = GameVersion.find_by_number(params[:v]) if @game_version @mods = @mods.filter_by_game_version @game_version @@ -36,14 +41,14 @@ class ModsController < ApplicationController end end - unless params[:q].blank? + if params[:q].present? @query = params[:q][0..30] @mods = @mods.filter_by_search_query(@query) end - + @uncategorized_mods_total_count = @mods.total_count @all_mods_count = Mod.count - if params[:category_id] + if params[:category_id].present? @category = Category.find_by_slug params[:category_id] if @category @mods = @mods.filter_by_category @category @@ -54,9 +59,9 @@ class ModsController < ApplicationController @game_versions = GameVersion.sort_by_newer_to_older @categories = Category.order_by_mods_count.order_by_name - + @mods = @mods.decorate - + respond_with @mods end @@ -115,6 +120,8 @@ class ModsController < ApplicationController :summary, :imgur, :authors_list, + :contact, + :info_json_name, category_ids: [], versions_attributes: [ :id, @@ -135,20 +142,20 @@ class ModsController < ApplicationController (permitted << :author_id) if can? :set_owner, Mod (permitted << :visible) if can? :set_visibility, Mod (permitted << :slug) if can? :set_slug, Mod - + permitted_params = params.require(:mod).permit(*permitted) if cannot?(:set_owner, Mod) and current_user permitted_params.merge! author_id: current_user.id end - + if cannot?(:set_visibility, Mod) permitted_params.merge! visible: false end - + permitted_params end - + def fill_with_forum_post_data(mod, mod_version, mod_file) if params[:forum_post_id] forum_post = ForumPost.find params[:forum_post_id] @@ -188,4 +195,4 @@ class ModsController < ApplicationController # else # nil # end -end \ No newline at end of file +end diff --git a/app/decorators/mod_decorator.rb b/app/decorators/mod_decorator.rb index 0087c81..173781d 100644 --- a/app/decorators/mod_decorator.rb +++ b/app/decorators/mod_decorator.rb @@ -1,5 +1,5 @@ class ModDecorator < Draper::Decorator - delegate :id, :name, :forum_url, :subforum_url + delegate :id, :name, :forum_url, :subforum_url, :as_json def authors_count; mod.authors.size end @@ -7,7 +7,7 @@ class ModDecorator < Draper::Decorator return na if mod.game_versions_string.blank? "v" + mod.game_versions_string end - + def forum_link_title if mod.subforum_url.present? h.t('mods.decorator.forum_link_title.subforum') @@ -17,7 +17,7 @@ class ModDecorator < Draper::Decorator h.t('mods.decorator.forum_link_title.vague') end end - + def forum_link(length = :short) # or :long if mod.subforum_url.present? subforum_link = h.link_to h.t('mods.decorator.forum_link.subforum.subforum'), mod.subforum_url @@ -36,22 +36,22 @@ class ModDecorator < Draper::Decorator na end end - + def last_version_date return na unless last_version_date_available? last_version.released_at.to_s(:rfc822) end - + def last_version_date_time_tag return na unless last_version_date_available? h.date_time_tag(last_version.released_at) end - + def first_version_date_time_tag return na unless last_version_date_available? h.date_time_tag(last_version.released_at) end - + def authors_links_list return na if mod.authors.empty? @@ -65,17 +65,17 @@ class ModDecorator < Draper::Decorator end end.join(', ').html_safe end - + def categories_links mod.categories.map{ |cat| category_tag_link(cat) }.join.html_safe end - + def img(size) h.tag :img, src: img_url(size), title: (h.t('helpers.no_image_available') if !mod.imgur) end - + def img_link(size = :large_thumbnail) if ( url = mod.imgur(:normal) ) h.link_to img(size), url @@ -83,51 +83,51 @@ class ModDecorator < Draper::Decorator img(size) # Image missing end end - + def title_link h.link_to(mod.name, mod) + (h.link_to h.t('mods.decorator.admin_edit'), [:edit, mod] if h.can? :edit, mod) end - + def edit_link if h.can? :edit, mod h.link_to h.t('mods.decorator.edit_mod'), [:edit, mod], class: 'edit-link' end end - + def pretty_summary h.simple_format mod.summary end - + def first_release_info return na unless mod.versions.first h.release_info(mod.versions.first) end - + def last_release_info return na unless mod.versions.last h.release_info(mod.versions.last) end - + def github_link return na unless mod.github h.link_to mod.github_url, mod.github_url end - + def forum_iframe_title text = if mod.subforum_url.present? h.t('.mod_subforum') else h.t('.mod_forum_post') end - + (text + ' ' + forum_link(:long)).html_safe end - + def preferred_forum_url mod.subforum_url.presence || mod.forum_url end - + def visibility_notice if not mod.visible? if h.current_user.is_admin? @@ -139,18 +139,22 @@ class ModDecorator < Draper::Decorator end end end - + + def install_protocol_url + "factoriomods://#{Base64.encode64(mod.to_json)}" + end + ### Download button ################### - + def last_version_has_downloads? last_version and last_version.has_files? end - + def first_available_download_url last_version.files.first.available_url end - + def download_files(number = nil) result = [] selected_versions = number ? mod.versions.last(number) : mod.versions @@ -165,13 +169,13 @@ class ModDecorator < Draper::Decorator end result end - + def more_downloads_link(number = nil) if mod.versions.size > number h.content_tag :li, h.link_to(h.t('mods.decorator.more_versions'), h.mod_path(mod, anchor: 'download')) end end - + def has_versions? mod.versions.size > 0 end @@ -179,32 +183,32 @@ class ModDecorator < Draper::Decorator def has_files? mod.files.size > 0 end - + ### Private helpers ################### - + private - + def last_version mod.versions[0] end - + def forum_views mod.forum_post.views_count end - + def forum_comments mod.forum_post.comments_count end - + def na @na ||= h.t('helpers.not_available') end - + def last_version_date_available? last_version.present? and last_version.released_at.present? end - + def category_tag_link(category) h.link_to h.category_filter_url(category), class: 'tag' do h.content_tag(:i, '', class: category.icon_class) + ' ' + category.name @@ -214,4 +218,4 @@ class ModDecorator < Draper::Decorator def img_url(size) mod.imgur(size).presence || h.missing_img_url(size) end -end \ No newline at end of file +end diff --git a/app/models/mod.rb b/app/models/mod.rb index a31b547..998f4ad 100644 --- a/app/models/mod.rb +++ b/app/models/mod.rb @@ -71,6 +71,11 @@ class Mod < ActiveRecord::Base where('mods.name ILIKE ? OR mods.summary ILIKE ? OR mods.description ILIKE ?', "%#{query}%", "%#{query}%", "%#{query}%") end + scope :filter_by_names, ->(names_list) do + names = names_list.split(',').map(&:strip) + where(info_json_name: names) + end + # def self.filter_by_search_query(query) # s1 = s2 = s3 = self @@ -161,6 +166,7 @@ class Mod < ActiveRecord::Base validates :forum_url, allow_blank: true, format: { with: /\Ahttps?:\/\/.*\Z/ } validates :summary, length: { maximum: 1000 } validates :slug, uniqueness: true + validates :info_json_name, presence: true # we shouldn't validate uniqueness though # #categories.count limit validate do @@ -274,6 +280,36 @@ class Mod < ActiveRecord::Base @authors_list ||= authors.map(&:name).join(', ') end + # We should eventually rename the mod attributes to match the ones in the API, + # we use these different attributes to roughly match the ones used in + # the mods info.json files + def as_json(options = {}) + { + title: name, + name: info_json_name, + url: Rails.application.routes.url_helpers.mod_url(self), # Eww + description: summary, + homepage: official_url, + contact: contact, + authors: authors.map(&:name), + releases: versions.map do |version| + { + version: version.number, + released_at: version.released_at, + game_versions: version.game_versions_string.split('-'), # Ideally we should load the #game_versions + dependencies: [], + files: version.files.map do |file| + { + name: file.name, + url: file.download_url, + mirror: file.attachment.present? ? file.attachment.url : '' + } + end + } + end + } + end + private def set_game_versions_string diff --git a/app/models/mod_version.rb b/app/models/mod_version.rb index 93ca15a..9502da7 100644 --- a/app/models/mod_version.rb +++ b/app/models/mod_version.rb @@ -65,11 +65,7 @@ class ModVersion < ActiveRecord::Base ################# def game_versions_string - if precise_game_versions_string.blank? - read_attribute(:game_versions_string) || set_game_versions_string - else - precise_game_versions_string - end + read_attribute(:game_versions_string) || set_game_versions_string end def to_label diff --git a/app/views/mods/_download_button.html.haml b/app/views/mods/_download_button.html.haml index 90be0bd..9b4e742 100644 --- a/app/views/mods/_download_button.html.haml +++ b/app/views/mods/_download_button.html.haml @@ -1,7 +1,11 @@ - if mod.last_version_has_downloads? + .mod-install-button.btn.btn-download{title: t('.install.title')} + %a{href: mod.install_protocol_url} + = icon 'download' + = t(".install.#{params[:action]}") .mod-download-button.btn.btn-download %a{href: mod.first_available_download_url} - = icon 'download' + = icon 'cloud-download' = t('.download') %ul.btn-download-versions - mod.download_files(2) do |version, file| @@ -12,4 +16,4 @@ \/ %span(title='Mod version')= version.number %span(title='Mod file name')= file.name - = mod.more_downloads_link(2) \ No newline at end of file + = mod.more_downloads_link(2) diff --git a/app/views/mods/new.html.haml b/app/views/mods/new.html.haml index fd5cc74..8dcf7df 100644 --- a/app/views/mods/new.html.haml +++ b/app/views/mods/new.html.haml @@ -9,12 +9,14 @@ = semantic_form_for @mod do |f| = f.inputs do = f.input :name + = f.input :info_json_name = f.input :slug if can? :set_slug, @mod = f.input :categories, as: :categories_select = f.input :owner if can? :set_owner, @mod = f.input :authors_list, as: :multi_datalist, collection: @existing_authors_names, placeholder: @existing_authors_names.sample(4).push('etc').join(', ') = f.input :github = f.input :official_url + = f.input :contact = f.input :forum_url = f.input :forum_subforum_url = f.input :imgur @@ -32,4 +34,4 @@ = f.action :submit - if @mod.forum_url.present? .mod-forum - %iframe.mod-forum-iframe{src: @mod.forum_url} \ No newline at end of file + %iframe.mod-forum-iframe{src: @mod.forum_url} diff --git a/config/environments/development.rb b/config/environments/development.rb index 143f035..edcfe75 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -40,4 +40,5 @@ Rails.application.configure do # Raises error for missing translations # config.action_view.raise_on_missing_translations = true + Rails.application.routes.default_url_options[:host] = 'localhost:3000' end diff --git a/config/environments/test.rb b/config/environments/test.rb index 632a483..cecd2ca 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -36,4 +36,6 @@ Rails.application.configure do # Raises error for missing translations config.action_view.raise_on_missing_translations = true + + Rails.application.routes.default_url_options[:host] = 'localhost:3000' end diff --git a/config/locales/en.yml b/config/locales/en.yml index 5465547..1d82c6e 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -50,6 +50,9 @@ en: formtastic: labels: mod: + name: Title + info_json_name: Name + contact: Contact information forum_url: 'Forum post URL' official_url: 'Official URL' media_links_string: 'Pictures or gifs links' @@ -65,6 +68,8 @@ en: hints: mod: + info_json_name: The name in info.json + contact: How to get with you description: Markdown media_links_string: 'Imgur.com URLs of pictures of animated gifs. Maximum: 6 links.' forum_subforum_url: Big mods have their own subforum @@ -74,4 +79,4 @@ en: placeholders: mod: - authors_list: 'Author1, Author2, Author3, etc' \ No newline at end of file + authors_list: 'Author1, Author2, Author3, etc' diff --git a/config/locales/mods.en.yml b/config/locales/mods.en.yml index ccd2023..5feabef 100644 --- a/config/locales/mods.en.yml +++ b/config/locales/mods.en.yml @@ -66,6 +66,10 @@ en: download_button: download: Download + install: + title: Install with the Factorio Mod Manager, or any mod manager that can read the protocol + show: Install with FMM + index: Install filter_bar: any_game_version: Any Factorio version @@ -89,4 +93,4 @@ en: short: "%{views}V / %{comments}C" vague: long: On the Factorio forums - short: Forum \ No newline at end of file + short: Forum diff --git a/config/routes.rb b/config/routes.rb index d2e2183..a616a35 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -72,4 +72,4 @@ Rails.application.routes.draw do get '/how-to-make' => 'static#how_to_make', as: :how_to_make_static root to: 'mods#index', sort: 'most_recent' -end \ No newline at end of file +end diff --git a/db/migrate/20150727151824_add_contact_and_info_json_name_to_mods.rb b/db/migrate/20150727151824_add_contact_and_info_json_name_to_mods.rb new file mode 100644 index 0000000..07e7690 --- /dev/null +++ b/db/migrate/20150727151824_add_contact_and_info_json_name_to_mods.rb @@ -0,0 +1,6 @@ +class AddContactAndInfoJsonNameToMods < ActiveRecord::Migration + def change + add_column :mods, :contact, :string, default: '', null: false + add_column :mods, :info_json_name, :string, default: '', null: false + end +end diff --git a/db/migrate/20150727221452_add_info_json_name_index.rb b/db/migrate/20150727221452_add_info_json_name_index.rb new file mode 100644 index 0000000..6e0feff --- /dev/null +++ b/db/migrate/20150727221452_add_info_json_name_index.rb @@ -0,0 +1,5 @@ +class AddInfoJsonNameIndex < ActiveRecord::Migration + def change + add_index :mods, :info_json_name + end +end diff --git a/db/schema.rb b/db/schema.rb index 195ddb2..c7ba4be 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20150724155022) do +ActiveRecord::Schema.define(version: 20150727221452) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -213,11 +213,14 @@ ActiveRecord::Schema.define(version: 20150724155022) do t.integer "last_version_id" t.datetime "last_release_date" t.boolean "visible", default: true, null: false + t.string "contact", default: "", null: false + t.string "info_json_name", default: "", null: false end add_index "mods", ["author_id"], name: "index_mods_on_author_id", using: :btree add_index "mods", ["category_id"], name: "index_mods_on_category_id", using: :btree add_index "mods", ["forum_post_id"], name: "index_mods_on_forum_post_id", using: :btree + add_index "mods", ["info_json_name"], name: "index_mods_on_info_json_name", using: :btree add_index "mods", ["last_version_id"], name: "index_mods_on_last_version_id", using: :btree add_index "mods", ["slug"], name: "index_mods_on_slug", unique: true, using: :btree diff --git a/lib/fake_data_generator.rb b/lib/fake_data_generator.rb index 54ccce2..cf52975 100644 --- a/lib/fake_data_generator.rb +++ b/lib/fake_data_generator.rb @@ -11,7 +11,7 @@ class FakeDataGenerator Category.create! name: Forgery(:lorem_ipsum).words(1, random: true) + i.to_s end end - + ### Game ################## begin @@ -45,7 +45,7 @@ class FakeDataGenerator gputs "Created user #{user.name} #{user.email}" end users = User.all - + ### Subforum ################## subforum = Subforum.create!(url: 'http://www.factorioforums.com/forum/viewforum.php?f=91') @@ -68,6 +68,7 @@ class FakeDataGenerator rand(30..50).times do |i| github_url = Forgery(:lorem_ipsum).words(1, random: true) + '/' + Forgery(:lorem_ipsum).words(1, random: true) mod = Mod.create! name: Forgery(:lorem_ipsum).words(rand(3..6), random: true), + info_json_name: Forgery(:lorem_ipsum).words(rand(1..2), random: true), authors: users.sample(8), owner: [nil, nil].concat(users).sample, categories: categories.sample(rand(1..4)), @@ -118,4 +119,4 @@ class FakeDataGenerator end end end -end \ No newline at end of file +end diff --git a/spec/controllers/mods_controller_spec.rb b/spec/controllers/mods_controller_spec.rb index 8962f25..733dd1c 100644 --- a/spec/controllers/mods_controller_spec.rb +++ b/spec/controllers/mods_controller_spec.rb @@ -27,11 +27,11 @@ describe ModsController, type: :controller do get 'index', options end - describe "get_index" do + describe 'GET index' do context 'pagination, 25 mods' do it 'should load the first 20 mods on the first page' do first_page_mods = 20.times.map{ create :mod } - second_page_mods = 5.times.map{ create :mod } + 5.times.map{ create :mod } get_index sort: :alpha expect(response).to be_success expect(response).to render_template 'index' @@ -39,7 +39,7 @@ describe ModsController, type: :controller do end it 'should load the last 5 mods on the second page' do - first_page_mods = 20.times.map{ create :mod } + 20.times.map{ create :mod } second_page_mods = 5.times.map{ create :mod } get_index sort: :alpha, page: 2 expect(response).to be_success @@ -141,9 +141,9 @@ describe ModsController, type: :controller do gv1 = create :game_version, number: '1.1.x' gv2 = create :game_version, number: '1.2.x' gv3 = create :game_version, number: '1.3.x' - mv1 = create :mod_version, game_versions: [gv1, gv2], mod: @m1 - mv2 = create :mod_version, game_versions: [gv2, gv3], mod: @m2 - mv3 = create :mod_version, game_versions: [gv3], mod: @m3 + create :mod_version, game_versions: [gv1, gv2], mod: @m1 + create :mod_version, game_versions: [gv2, gv3], mod: @m2 + create :mod_version, game_versions: [gv3], mod: @m3 end context 'version 1.1.x' do @@ -171,7 +171,7 @@ describe ModsController, type: :controller do end end end - + context 'some mods non-visible' do it 'should not load them' do mods[0].update! visible: false @@ -182,9 +182,33 @@ describe ModsController, type: :controller do expect(assigns(:mods)).to match_array mods end end + + context 'searching for specific mods in an API-wise way' do + before :each do + @category = create :category + @mods = [] + @mods << create(:mod, name: 'ccc', info_json_name: 'banana', categories: [@category]) + @mods << create(:mod, name: 'bbb', info_json_name: 'potato') + @mods << create(:mod, name: 'aaa', info_json_name: 'cabbage', categories: [@category]) + @mods << create(:mod, name: 'ddd', info_json_name: 'watermelon') + @mods << create(:mod, name: 'kkk', info_json_name: 'orange') + @mods << create(:mod, name: 'zzz', info_json_name: 'pomegranate') + @mods << create(:mod, name: 'yyy', info_json_name: 'machine-gun') + end + + it 'should get the correct list of mods' do + get_index names: 'banana,orange,machine-gun' + expect(assigns(:mods)).to match_array [@mods[0], @mods[4], @mods[6]] + end + + it 'should with the other filter' do + get_index names: 'banana,cabbage,machine-gun', category_id: @category.to_param, sort: :alpha + expect(assigns(:mods)).to eq [@mods[2], @mods[0]] + end + end end - describe "GET 'show'" do + describe 'GET show' do context 'finds the mod' do before(:each) { get 'show', category_id: mod.categories.first.to_param, id: mod.to_param } @@ -199,12 +223,12 @@ describe ModsController, type: :controller do it { expect(response.status).to eq 404 } end end - - describe 'POST create' do + + describe 'POST create' do def submit_basic(extra_params = {}) - post :create, mod: {name: 'SuperMod', category_ids: [create(:category).id]}.merge(extra_params) + post :create, mod: {name: 'SuperMod', info_json_name: 'supermod', category_ids: [create(:category).id]}.merge(extra_params) end - + def submit_blank(params = {}) post :create, params end @@ -215,21 +239,21 @@ describe ModsController, type: :controller do expect(response).to have_http_status :bad_request end end - + context 'guest user (not registered)' do it 'should not allow it at all 401' do submit_blank mod: {name: 'SuperMod'} expect(response).to have_http_status :unauthorized end end - + context 'user is registered' do it 'should allow it to create a mod' do sign_in create :user submit_basic expect(response).to have_http_status :redirect end - + it 'should not allow it set #visible #owner or #slug' do first_user = create :user second_user = create :user @@ -242,7 +266,7 @@ describe ModsController, type: :controller do expect(mod.slug).to eq 'supermod' end end - + context 'user is a developer' do it 'should allow it set #visible but not #owner or #slug' do first_user = create :dev_user @@ -255,15 +279,15 @@ describe ModsController, type: :controller do expect(mod.owner).to eq first_user expect(mod.slug).to eq 'supermod' end - + it 'should also allow it to set visibility to false' do sign_in create(:dev_user) submit_basic visible: false expect(Mod.first.visible).to eq false end end - - + + context 'user is an admin' do it 'should allow it set #visible, #owner or #slug' do first_user = create :admin_user @@ -276,7 +300,7 @@ describe ModsController, type: :controller do expect(mod.owner).to eq second_user expect(mod.slug).to eq 'rsarsarsa' end - + it 'should also be able to allow those values to be default' do sign_in create(:admin_user) submit_basic visible: false, author_id: nil, slug: '' @@ -287,7 +311,7 @@ describe ModsController, type: :controller do end end - + describe "PATCH update" do def submit_basic(mod, extra_params = {}) put :update, id: mod.id, mod: extra_params @@ -302,7 +326,7 @@ describe ModsController, type: :controller do expect(response).to have_http_status :bad_request end end - + context 'guest user (not registered)' do it 'should not allow it at all 401' do mod = create :mod @@ -310,7 +334,7 @@ describe ModsController, type: :controller do expect(response).to have_http_status :unauthorized end end - + context 'user is registered' do it "should allow it to update a it's own mod" do user = create :user @@ -319,7 +343,7 @@ describe ModsController, type: :controller do submit_basic mod, name: mod.name expect(response).to have_http_status :redirect end - + it "should not allow it to update someone elses mod" do user = create :user mod = create :mod, owner: (create :user) @@ -327,7 +351,7 @@ describe ModsController, type: :controller do submit_basic mod expect(response).to have_http_status :unauthorized end - + it 'should not allow it set #visible #owner or #slug' do first_user = create :user second_user = create :user @@ -341,7 +365,7 @@ describe ModsController, type: :controller do expect(mod.slug).to eq mod.slug end end - + context 'user is a developer' do it 'should allow it set #visible but not #owner or #slug' do first_user = create :dev_user @@ -355,7 +379,7 @@ describe ModsController, type: :controller do expect(mod.owner).to eq first_user expect(mod.slug).to eq mod.slug end - + it 'should not allow it to update someone elses mod' do first_user = create :dev_user second_user = create :user @@ -364,7 +388,7 @@ describe ModsController, type: :controller do submit_basic mod, visible: true, author_id: second_user.id, slug: 'rsarsarsa' expect(response).to have_http_status :unauthorized end - + it 'should not allow it to update a mod without owner' do first_user = create :dev_user mod = create :mod, owner: nil @@ -372,7 +396,7 @@ describe ModsController, type: :controller do submit_basic mod, visible: true, author_id: first_user.id, slug: 'rsarsarsa' expect(response).to have_http_status :unauthorized end - + it 'should also allow it to set visibility to false' do user = create(:dev_user) sign_in user @@ -381,7 +405,7 @@ describe ModsController, type: :controller do expect(Mod.first.visible).to eq false end end - + context 'user is an admin' do it 'should allow it set #visible, #owner or #slug' do first_user = create :admin_user @@ -395,7 +419,7 @@ describe ModsController, type: :controller do expect(mod.owner).to eq second_user expect(mod.slug).to eq 'rsarsarsa' end - + it 'should also be able to allow those values to be default' do user = create(:admin_user) mod = create :mod, owner: user @@ -405,7 +429,7 @@ describe ModsController, type: :controller do expect(Mod.first.owner).to eq nil expect(Mod.first.slug).to eq mod.slug end - + it 'should also allow it to modify a mod with any owner' do first_user = create :admin_user second_user = create :user @@ -418,7 +442,7 @@ describe ModsController, type: :controller do expect(mod.owner).to eq second_user expect(mod.slug).to eq 'rsarsarsa' end - + it 'should not allow it to update a mod without owner' do first_user = create :admin_user second_user = create :user diff --git a/spec/decorators/mod_decorator_spec.rb b/spec/decorators/mod_decorator_spec.rb index 34bc6be..edba168 100644 --- a/spec/decorators/mod_decorator_spec.rb +++ b/spec/decorators/mod_decorator_spec.rb @@ -4,7 +4,7 @@ describe ModDecorator do def create_decorated(*args) create(*args).decorate end - + describe '#authors_links_list' do it 'should return a comma separated authors list links' do mod = create_decorated :mod, authors: 3.times.map{ |i| create :user, name: "Au#{i}" } @@ -28,32 +28,32 @@ describe ModDecorator do expect(mod.authors_links_list).to eq 'N/A' end end - + describe '#forum_link' do context 'only has forum post URL' do it 'should only have the forum URL' do mod = create_decorated :mod, forum_url: 'http://potato.com' expect(URI.extract(mod.forum_link)).to eq ['http://potato.com'] end - + it 'should only have the forum URL, with empty string subforum_url' do mod = create_decorated :mod, forum_url: 'http://potato.com', subforum_url: '' expect(URI.extract(mod.forum_link)).to eq ['http://potato.com'] end end - + context 'has only the subforum URL' do it 'should link to the subforum' do mod = create_decorated :mod, subforum_url: 'http://cabbage.com' expect(URI.extract(mod.forum_link)).to eq ['http://cabbage.com'] end - + it 'should link to the subforum, with empty string forum post URL' do mod = create_decorated :mod, subforum_url: 'http://cabbage.com', forum_url: '' expect(URI.extract(mod.forum_link)).to eq ['http://cabbage.com'] end end - + context 'has both the subforum and the forum post URL' do it 'should link to both' do mod = create_decorated :mod, forum_url: 'http://potato.com', subforum_url: 'http://cabbage.com' @@ -61,7 +61,7 @@ describe ModDecorator do end end end - + describe '#has_versions?' do it 'should return false if the mod has no versions' do mod = create_decorated :mod, versions: [] @@ -86,9 +86,18 @@ describe ModDecorator do mod = build :mod, versions: [] mod.save! mod_version = create :mod_version, mod: mod - mod_file = create :mod_file, mod_version: mod_version + create :mod_file, mod_version: mod_version mod = Mod.first.decorate expect(mod.has_files?).to eq true end end -end \ No newline at end of file + + describe '#install_protocol_url' do + it 'should return the factoriomods:// protocol with the JSON-encoded mod' do + include ActionView::Helpers::TextHelper + mod = create(:mod).decorate + encoded_json = Base64.encode64 mod.to_json + expect(mod.install_protocol_url).to eq "factoriomods://#{encoded_json}" + end + end +end diff --git a/spec/factories/mod_factories.rb b/spec/factories/mod_factories.rb index fef9b00..37b3f54 100644 --- a/spec/factories/mod_factories.rb +++ b/spec/factories/mod_factories.rb @@ -6,6 +6,7 @@ FactoryGirl.define do description '' forum_comments_count 12 downloads_count 15 + sequence(:info_json_name) { |n| "mod-name-#{n}" } end factory :category do @@ -39,4 +40,4 @@ FactoryGirl.define do factory :game do sequence(:name) { |n| "GameName #{n}#{n}" } end -end \ No newline at end of file +end diff --git a/spec/features/mods_index_spec.rb b/spec/features/mods_index_spec.rb index afbd133..1f05409 100644 --- a/spec/features/mods_index_spec.rb +++ b/spec/features/mods_index_spec.rb @@ -43,7 +43,7 @@ feature 'Display an index of mods in certain order' do visit '/most-popular' expect(mod_names).to eq %w{SuperMod6 superMod0 SuperMod2 SuperMod4 superMod3} end - + scenario 'some mods are not visible' do @mods[2].update! visible: false @mods[4].update! visible: false @@ -68,7 +68,7 @@ feature 'Display an index of mods in certain order' do end end end - + # Test those examples for both HTML and JSON context 'when requesting HTML' do it_behaves_like 'mod index' do @@ -77,11 +77,11 @@ feature 'Display an index of mods in certain order' do end end end - + context 'when requesting JSON', driver: :rack_test_json do it_behaves_like 'mod index' do def mod_names - JSON.parse(last_response).map { |hash| hash["name"] } + JSON.parse(last_response).map { |hash| hash["title"] } end end end @@ -91,7 +91,7 @@ feature 'Display an index of mods in certain order' do authors = 5.times.map{ |i| create :user, name: "Au#{i}" } create :mod, name: 'SuperMod', authors: authors visit '/' - expect(page).to have_content /Au0.*Au1.*Au2.*Au3.*Au4/ + expect(page).to have_content(/Au0.*Au1.*Au2.*Au3.*Au4/) expect(page).to have_link 'Au0', '/users/au0' expect(page).to have_link 'Au1', '/users/au1' expect(page).to have_link 'Au2', '/users/au2' @@ -103,7 +103,7 @@ feature 'Display an index of mods in certain order' do authors = 5.times.map{ |i| create :user, name: "Au#{i}" } create :mod, name: 'SuperMod', authors: authors, owner: authors[1] visit '/' - expect(page).to have_content /Au0.*Au1.*(maintainer).*Au2.*Au3.*Au4/ + expect(page).to have_content(/Au0.*Au1.*(maintainer).*Au2.*Au3.*Au4/) end scenario 'Mod with multiple authors with reversed sorting order' do @@ -115,7 +115,7 @@ feature 'Display an index of mods in certain order' do mod.authors_mods[3].update_column :sort_order, 2 mod.authors_mods[4].update_column :sort_order, 1 visit '/' - expect(page).to have_content /Au4.*Au3.*Au2.*Au1.*Au0/ + expect(page).to have_content(/Au4.*Au3.*Au2.*Au1.*Au0/) end end end diff --git a/spec/features/mods_new_spec.rb b/spec/features/mods_new_spec.rb index c44c333..b0585db 100644 --- a/spec/features/mods_new_spec.rb +++ b/spec/features/mods_new_spec.rb @@ -12,8 +12,8 @@ feature 'Modder creates a new mod' do expect(page.status_code).to eq 401 end - - + + scenario 'non-dev user visits the new mod page' do sign_in visit '/mods/new' @@ -25,16 +25,16 @@ feature 'Modder creates a new mod' do scenario 'dev user visits new mod page' do sign_in_dev visit '/mods/new' - + expect(page.status_code).to eq 200 expect(page).to have_content 'Create new mod' end - - + + scenario 'admin user visits new mod page' do sign_in_admin visit '/mods/new' - + expect(page.status_code).to eq 200 expect(page).to have_content 'Create new mod' end @@ -53,13 +53,12 @@ feature 'Modder creates a new mod' do scenario 'user submits a barebones form' do sign_in_dev visit '/mods/new' - fill_in 'mod_name', with: 'Super Mod' - select 'Terrain', from: 'Categories' - fill_in_first_version_and_file + fill_in_minimum 'Super Mod' submit_form expect(current_path).to eq '/mods/super-mod' mod = Mod.first expect(mod.name).to eq 'Super Mod' + expect(mod.info_json_name).to eq 'super mod' expect(mod.categories).to match_array [@category] expect(mod.author).to eq @user end @@ -67,8 +66,7 @@ feature 'Modder creates a new mod' do scenario 'user submits a form without any mod version', js: true do sign_in_dev visit '/mods/new' - fill_in 'mod_name', with: 'Super Mod' - select 'Terrain', from: 'Categories' + fill_in_no_versions_minimum 'Super Mod' first('.remove_fields').click submit_form expect(current_path).to eq '/mods/super-mod' @@ -85,6 +83,7 @@ feature 'Modder creates a new mod' do sign_in_dev visit '/mods/new' fill_in 'mod_name', with: 'ModName' + fill_in 'mod_info_json_name', with: 'mod-name' 9.times{ |i| select "Cat#{i}", from: 'Categories' } fill_in_first_version_and_file submit_form @@ -97,6 +96,7 @@ feature 'Modder creates a new mod' do sign_in_dev visit '/mods/new' fill_in 'mod_name', with: 'ModName' + fill_in 'mod_info_json_name', with: 'mod-name' 7.times{ |i| select "Cat#{i}", from: 'Categories' } fill_in_first_version_and_file submit_form @@ -125,8 +125,7 @@ feature 'Modder creates a new mod' do scenario 'user submits a mod with a mod_version with a perfectly fine number' do sign_in_dev visit '/mods/new' - fill_in 'mod_name', with: 'ModName' - select 'Terrain', from: 'Categories' + fill_in_no_versions_minimum 'ModName' within('.mod-version:nth-child(1)') do fill_in 'Number', with: '1.2.3_5-potato' fill_in 'Release day', with: 3.weeks.ago @@ -142,17 +141,16 @@ feature 'Modder creates a new mod' do scenario 'user submits a mod with all the data but no versions' do sign_in_dev visit '/mods/new' - fill_in 'mod_name', with: 'Mah super mod' - select 'Terrain', from: 'Categories' + fill_in_minimum 'SuperMod' fill_in 'Github', with: 'http://github.com/factorio-mods/mah-super-mod' fill_in 'Forum post URL', with: 'http://www.factorioforums.com/forum/viewtopic.php?f=14&t=5971&sid=1786856d6a687e92f6a12ad9425aeb9e' fill_in 'Official URL', with: 'http://www.factorioforums.com/' fill_in 'Summary', with: 'This is a small mod for testing' - fill_in_first_version_and_file submit_form - expect(current_path).to eq '/mods/mah-super-mod' + expect(current_path).to eq '/mods/supermod' mod = Mod.first - expect(mod.name).to eq 'Mah super mod' + expect(mod.name).to eq 'SuperMod' + expect(mod.info_json_name).to eq 'supermod' expect(mod.categories).to match_array [Category.first] expect(mod.github).to eq 'factorio-mods/mah-super-mod' expect(mod.forum_url).to eq 'http://www.factorioforums.com/forum/viewtopic.php?f=14&t=5971&sid=1786856d6a687e92f6a12ad9425aeb9e' @@ -168,11 +166,10 @@ feature 'Modder creates a new mod' do create :game_version, number: '1.2.x' attachment = File.new(Rails.root.join('spec', 'fixtures', 'test.zip')) visit '/mods/new' - fill_in 'mod_name', with: 'Valid mod name' - select 'Terrain', from: 'Categories' + fill_in_no_versions_minimum within('.mod-version:nth-child(1)') do fill_in 'Number', with: '123' - fill_in 'Release day', with: '2014-11-09' + 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' @@ -182,8 +179,8 @@ feature 'Modder creates a new mod' do end submit_form mod = Mod.first - expect(current_path).to eq '/mods/valid-mod-name' - expect(page).to have_content 'Valid mod name' + expect(current_path).to eq '/mods/supermod' + expect(page).to have_content 'SuperMod' 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' @@ -194,10 +191,8 @@ feature 'Modder creates a new mod' do scenario 'admin user submits a mod selecting an owner' do sign_in_admin visit '/mods/new' - fill_in 'mod_name', with: 'Mod Name' - select 'Terrain', from: 'Categories' + fill_in_minimum 'Mod Name' fill_in 'mod_authors_list', with: 'MangoDev' - fill_in_first_version_and_file submit_form mod = Mod.first expect(current_path).to eq '/mods/mod-name' @@ -230,9 +225,7 @@ feature 'Modder creates a new mod' do mod = create :mod, name: 'SuperMod' visit "/mods/new" - fill_in 'mod_name', with: 'SuperMod' - select 'Terrain', from: 'Categories' - fill_in_first_version_and_file + fill_in_minimum('SuperMod') submit_form expect(current_path).to eq '/mods/supermod-by-yeah' end @@ -240,10 +233,8 @@ feature 'Modder creates a new mod' do scenario 'user submits a mod with valid names in the #authors_list' do sign_in_dev visit '/mods/new' - fill_in 'mod_name', with: 'SuperMod' - select 'Terrain', from: 'Categories' + fill_in_minimum fill_in 'mod_authors_list', with: 'Potato, SuperUser, Salad' - fill_in_first_version_and_file submit_form expect(current_path).to eq '/mods/supermod' expect(Mod.first.authors.map(&:name)).to eq %w{Potato SuperUser Salad} @@ -252,10 +243,8 @@ feature 'Modder creates a new mod' do scenario 'user submits a mod with invalid names in the #authors_list' do sign_in_dev visit '/mods/new' - fill_in 'mod_name', with: 'SuperMod' - select 'Terrain', from: 'Categories' + fill_in_minimum fill_in 'mod_authors_list', with: 'Potato(), SuperUser, Salad' - fill_in_first_version_and_file submit_form expect(current_path).to eq '/mods' expect(page).to have_css '#mod_authors_list_input .inline-errors' @@ -265,16 +254,14 @@ feature 'Modder creates a new mod' do scenario 'user submits a mod too many authors in the #authors_list' do sign_in_dev visit '/mods/new' - fill_in 'mod_name', with: 'SuperMod' - select 'Terrain', from: 'Categories' + fill_in_minimum fill_in 'mod_authors_list', with: 'Potato, SuperUser, Salad, Tururu, Papapa, Aaaaa, Bbbbb, Ccccc, Ddddd' - fill_in_first_version_and_file submit_form expect(current_path).to eq '/mods' expect(page).to have_css '#mod_authors_list_input .inline-errors' expect(page).to have_content /too many/i end - + describe 'visibility toggle' do scenario 'should be hidden for non-dev, and false' do sign_in @@ -284,7 +271,7 @@ feature 'Modder creates a new mod' do submit_form expect(Mod.first.visible).to eq false end - + shared_examples 'admin or dev' do scenario 'should be visible and ON by default' do sign_in_admin_or_dev @@ -295,7 +282,7 @@ feature 'Modder creates a new mod' do submit_form expect(Mod.first.visible).to eq true end - + scenario 'should be visible it should be changeable' do sign_in_admin_or_dev visit '/mods/new' @@ -307,19 +294,19 @@ feature 'Modder creates a new mod' do expect(Mod.first.visible).to eq false end end - + context 'dev user' do it_behaves_like 'admin or dev' do let(:sign_in_admin_or_dev){ sign_in_dev } end end - + context 'admin user' do it_behaves_like 'admin or dev' do let(:sign_in_admin_or_dev){ sign_in_admin } end end - + # context 'dev or admin submits a mod' do # scenario 'should be visible and ON by default' do # sign_in_dev @@ -330,7 +317,7 @@ feature 'Modder creates a new mod' do # submit_form # expect(Mod.first.visible).to eq true # end - + # scenario 'should be visible it should be changeable' do # sign_in_dev # visit '/mods/new' @@ -342,8 +329,8 @@ feature 'Modder creates a new mod' do # expect(Mod.first.visible).to eq false # end # end - - + + # scenario 'should be visible if an admin visits mods#new, and it should be ON by default' do # sign_in # visit '/mods/new' @@ -354,13 +341,18 @@ feature 'Modder creates a new mod' do # expect(Mod.first.visible).to eq true # end end - - def fill_in_minimum - fill_in 'mod_name', with: 'SuperMod' - select 'Terrain', from: 'Categories' + + def fill_in_minimum(*name) + fill_in_no_versions_minimum(*name) fill_in_first_version_and_file end + def fill_in_no_versions_minimum(name = 'SuperMod') + fill_in 'mod_name', with: name + fill_in 'mod_info_json_name', with: name.downcase + select 'Terrain', from: 'Categories' + end + def fill_in_first_version_and_file attachment = File.new(Rails.root.join('spec', 'fixtures', 'test.zip')) within('.mod-version:nth-child(1)') do @@ -379,4 +371,4 @@ feature 'Modder creates a new mod' do def create_category(name) @category = create :category, name: name end -end \ No newline at end of file +end diff --git a/spec/features/the_game_version_string_should_be_updated_after_editing_a_mod_spec.rb b/spec/features/the_game_version_string_should_be_updated_after_editing_a_mod_spec.rb index e5c8357..69730b9 100644 --- a/spec/features/the_game_version_string_should_be_updated_after_editing_a_mod_spec.rb +++ b/spec/features/the_game_version_string_should_be_updated_after_editing_a_mod_spec.rb @@ -7,25 +7,26 @@ feature 'The goddamn game version string should be updated after editing a mod' string is the correct one, but then you mods#edit the mod and you reduce the Factorio versions of the mod_version that is compatible with, then you look at mods#show again and the string should be updated' do - cat1 = create :category, name: 'CategoryOne' + create :category, name: 'CategoryOne' gv1 = create :game_version, number: '0.10', sort_order: 1 gv2 = create :game_version, number: '0.11', sort_order: 2 sign_in_admin visit '/mods/new' - nfind('mod[name]').set 'This Bug, Man' - nfind(:select, 'mod[category_ids][]').select 'CategoryOne' - nfind('mod[authors_list]').set 'Whatever' - nfind('mod[versions_attributes][0][number]').set '1.2.3' - nfind('mod[versions_attributes][0][released_at]').set '2015-01-01' - nfind(:select, 'mod[versions_attributes][0][game_version_ids][]').select '0.10' - nfind(:select, 'mod[versions_attributes][0][game_version_ids][]').select '0.11' - nfind('mod[versions_attributes][0][files_attributes][0][download_url]').set 'http://potato.com' - nfind('commit').click() + fill_in 'mod_name', with: 'This Bug, Man' + fill_in 'mod_info_json_name', with: 'this-bug' + select 'CategoryOne', from: 'mod_category_ids' + fill_in 'mod_authors_list', with: 'Whatever' + fill_in 'mod_versions_attributes_0_number', with: '1.2.3' + fill_in 'mod_versions_attributes_0_released_at', with: '2015-01-01' + select '0.10', from: 'mod_versions_attributes_0_game_version_ids' + select '0.11', from: 'mod_versions_attributes_0_game_version_ids' + fill_in 'mod_versions_attributes_0_files_attributes_0_download_url', with: 'http://potato.com' + find('#mod_submit_action input').click() expect(current_path).to eq '/mods/this-bug-man' expect(page).to have_content '0.10-0.11' click_link 'Edit mod' - nfind(:select, 'mod[versions_attributes][0][game_version_ids][]').unselect '0.10' - nfind('commit').click() + unselect '0.10', from: 'mod_versions_attributes_0_game_version_ids' + find('#mod_submit_action input').click() expect(ModGameVersion.all.size).to eq 1 expect(Mod.first.versions.first.game_versions).to match_array [gv2] expect(Mod.first.game_versions_string).to eq '0.11' @@ -33,4 +34,4 @@ feature 'The goddamn game version string should be updated after editing a mod' # doesn't actually destroys the game version expect(GameVersion.all).to match_array [gv1, gv2] end -end \ No newline at end of file +end diff --git a/spec/models/mod_spec.rb b/spec/models/mod_spec.rb index d8808da..5b32ada 100644 --- a/spec/models/mod_spec.rb +++ b/spec/models/mod_spec.rb @@ -152,6 +152,11 @@ RSpec.describe Mod, :type => :model do mod = build :mod, authors: authors expect(mod).to be_valid end + + it 'should be invalid without #info_json_name' do + mod = build :mod, info_json_name: '' + expect(mod).to be_invalid + end end describe '#author_name' do @@ -476,6 +481,32 @@ RSpec.describe Mod, :type => :model do end describe 'scopes' do + describe '.filter_by_names' do + it 'should return a list of mods by #info_json_name' do + mods = [] + mods.push create :mod, info_json_name: 'potato' + create :mod, info_json_name: 'potato-2' + mods.push create :mod, info_json_name: 'banana-stream' + create :mod, info_json_name: 'i love this keyboard' + mods.push create :mod, info_json_name: 'Atom by github rocks' + found = Mod.filter_by_names 'potato,banana-stream, Atom by github rocks' + expect(found).to match_array mods + end + + it 'should be case sensitive' do + create :mod, info_json_name: 'potato' + mod = create :mod, info_json_name: 'PoTaTo' + expect(Mod.filter_by_names('PoTaTo')).to match_array [mod] + end + + it 'should return all the mods with the same #info_json_name' do + mod1 = create :mod, info_json_name: 'potato' + mod2 = create :mod, info_json_name: 'potato' + create :mod, info_json_name: 'banana' + expect(Mod.filter_by_names('potato')).to match_array [mod1, mod2] + end + end + describe '.filter_by_category' do it 'should filter results by category' do mod1 = create(:mod) @@ -709,4 +740,99 @@ RSpec.describe Mod, :type => :model do end end end + + describe '#as_json' do + it 'should return the public API structure' do + authors = [create(:user, name: 'John Snow Zombie'), create(:user, name: 'THAT Guy')] + mod = create :mod, + name: 'Potato Galaxy', + info_json_name: 'potato-galaxy-mod', + authors: authors, + contact: 'Send a homing pigeon to Castle Black', + official_url: 'http://castleblack.com', + summary: 'This mod adds the ability to farm potatoes on Factorio.' + + gv1 = create :game_version, number: '0.10.x' + gv2 = create :game_version, number: '0.11.x' + gv3 = create :game_version, number: '0.12.x' + + one_month_ago = 1.month.ago + two_weeks_ago = 2.weeks.ago + one_week_ago = 1.week.ago + create :mod_version, + number: '1.2.1', + released_at: one_month_ago, + game_versions: [gv1], + mod: mod, + files: [ + build(:mod_file, name: '', download_url: 'http://thepotatoexperience.com/1.2.1', attachment: nil) + ] + create :mod_version, + number: '1.2.2', + released_at: two_weeks_ago, + game_versions: [gv2, gv3], + mod: mod, + files: [ + build(:mod_file, name: 'win', download_url: 'http://thepotatoexperience.com/1.2.2', attachment: nil), + build(:mod_file, name: 'mac', download_url: 'http://thepotatoexperience.com/1.2.2', attachment: nil) + ] + mv3 = create :mod_version, + number: '1.2.3', + released_at: one_week_ago, + game_versions: [gv3], + mod: mod, + files: [ + build(:mod_file, + name: '', + download_url: 'http://thepotatoexperience.com/1.2.3', + attachment: File.new(Rails.root.join('spec', 'fixtures', 'test.zip')) + ) + ] + + expect(mod.as_json).to eq({ + title: 'Potato Galaxy', + name: 'potato-galaxy-mod', + url: 'http://localhost:3000/mods/potato-galaxy', + description: 'This mod adds the ability to farm potatoes on Factorio.', + homepage: 'http://castleblack.com', + contact: 'Send a homing pigeon to Castle Black', + authors: ['John Snow Zombie', 'THAT Guy'], + releases: [ + { + version: '1.2.3', + released_at: one_week_ago, + game_versions: ['0.12.x'], + dependencies: [], + files: [ + { + name: '', + url: 'http://thepotatoexperience.com/1.2.3', + mirror: mv3.files.first.attachment.url + } + ] + }, + { + version: '1.2.2', + released_at: two_weeks_ago, + game_versions: ['0.11.x', '0.12.x'], + dependencies: [], + files: [ + { name: 'mac', url: 'http://thepotatoexperience.com/1.2.2', mirror: '' }, + { name: 'win', url: 'http://thepotatoexperience.com/1.2.2', mirror: '' } + ] + }, + { + version: '1.2.1', + released_at: one_month_ago, + game_versions: ['0.10.x'], + dependencies: [], + files: [ + { name: '', url: 'http://thepotatoexperience.com/1.2.1', mirror: '' } + ] + } + ] + }) + + end + end end