diff --git a/Gemfile b/Gemfile index 672b0027..537cabc5 100644 --- a/Gemfile +++ b/Gemfile @@ -50,6 +50,7 @@ gem 'hamlit-rails' gem 'jekyll' gem 'jquery-rails' gem 'mini_magick' +gem 'mobility' gem 'pundit' gem 'rails-i18n' gem 'rails_warden' diff --git a/Gemfile.lock b/Gemfile.lock index bef6cfc7..7941b035 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -212,10 +212,13 @@ GEM mime-types-data (~> 3.2015) mime-types-data (3.2018.0812) mimemagic (0.3.3) - mini_magick (4.9.3) + mini_magick (4.9.4) mini_mime (1.0.1) mini_portile2 (2.4.0) minitest (5.11.3) + mobility (0.8.7) + i18n (>= 0.6.10, < 2) + request_store (~> 1.0) multi_json (1.13.1) net-scp (2.0.0) net-ssh (>= 2.6.5, < 6.0.0) @@ -277,6 +280,8 @@ GEM ffi (~> 1.0) rbnacl (4.0.2) ffi + request_store (1.4.1) + rack (>= 1.4) responders (3.0.0) actionpack (>= 5.0) railties (>= 5.0) @@ -409,6 +414,7 @@ DEPENDENCIES letter_opener listen (>= 3.0.5, < 3.2) mini_magick + mobility pry puma (~> 3.7) pundit diff --git a/app/controllers/sites_controller.rb b/app/controllers/sites_controller.rb index ec7ad31d..f3e2a6de 100644 --- a/app/controllers/sites_controller.rb +++ b/app/controllers/sites_controller.rb @@ -137,6 +137,6 @@ class SitesController < ApplicationController private def site_params - params.require(:site).permit(:name) + params.require(:site).permit(:name, :design_id) end end diff --git a/app/models/design.rb b/app/models/design.rb new file mode 100644 index 00000000..aeacb79b --- /dev/null +++ b/app/models/design.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +# El diseño de un sitio es la plantilla/tema. En este modelo cargamos +# las propiedades para poder verlas desde el panel y elegir un diseño +# para el sitio. +# +# TODO: Agregar captura de pantalla con ActiveStorage +class Design < ApplicationRecord + extend Mobility + + translates :name, type: :string, locale_accessors: true + translates :description, type: :text, locale_accessors: true + + has_many :sites + + validates :name, presence: true, uniqueness: true + validates :gem, presence: true, uniqueness: true + validates :description, presence: true +end diff --git a/app/models/site.rb b/app/models/site.rb index 24f80b08..0ed363a8 100644 --- a/app/models/site.rb +++ b/app/models/site.rb @@ -6,9 +6,12 @@ class Site < ApplicationRecord include FriendlyId validates :name, uniqueness: true, hostname: true + validates :design_id, presence: true friendly_id :name, use: %i[finders] + belongs_to :design + has_many :roles has_many :usuaries, -> { where('roles.rol = ?', 'usuarie') }, through: :roles diff --git a/app/views/sites/_form.haml b/app/views/sites/_form.haml index 1f5650bc..927cd2ed 100644 --- a/app/views/sites/_form.haml +++ b/app/views/sites/_form.haml @@ -1,6 +1,29 @@ -= form_for @site do |f| += form_for site do |f| .form-group - = f.label :name + %h2= f.label :name + %p.lead= t('.help.name') = f.text_field :name, class: 'form-control' + .form-group + %h2= t('.design.title') + %p.lead= t('.help.design') + .row + -# Demasiado complejo para un f.collection_radio_buttons + - Design.all.each do |design| + .col + %h3 + = f.radio_button :design_id, design.id, + checked: design.id == site.design_id, + disabled: design.disabled + = f.label "design_id_#{design.id}", design.name + = sanitize_markdown design.description, + tags: %w[p a strong em] + + .btn-group{ role: 'group', 'aria-label': t('.design.actions') } + - if design.url + = link_to t('.design.url'), design.url, + target: '_blank', class: 'btn btn-info' + - if design.license + = link_to t('.design.license'), design.license, + target: '_blank', class: 'btn btn-info' .form-group = f.submit submit, class: 'btn btn-success' diff --git a/app/views/sites/edit.haml b/app/views/sites/edit.haml index a7e96a1c..a461bb50 100644 --- a/app/views/sites/edit.haml +++ b/app/views/sites/edit.haml @@ -7,4 +7,4 @@ .col %h1= t('.title', site: @site.name) - = render 'form', submit: t('.submit') + = render 'form', site: @site, submit: t('.submit') diff --git a/app/views/sites/new.haml b/app/views/sites/new.haml index 0b883c1d..b5760f3c 100644 --- a/app/views/sites/new.haml +++ b/app/views/sites/new.haml @@ -6,4 +6,4 @@ .col %h1= t('.title') - = render 'form', submit: t('.submit') + = render 'form', site: @site, submit: t('.submit') diff --git a/config/initializers/mobility.rb b/config/initializers/mobility.rb new file mode 100644 index 00000000..8176045a --- /dev/null +++ b/config/initializers/mobility.rb @@ -0,0 +1,97 @@ +# frozen_string_literal: true + +Mobility.configure do |config| + # Sets the default backend to use in models. This can be overridden in + # models by passing +backend: ...+ to +translates+. + config.default_backend = :key_value + + # By default, Mobility uses the +translates+ class method in models to + # describe translated attributes, but you can configure this method to + # be whatever you like. This may be useful if using Mobility alongside + # another translation gem which uses the same method name. + config.accessor_method = :translates + + # To query on translated attributes, you need to append a scope to + # your model. The name of this scope is +i18n+ by default, but this + # can be changed to something else. + config.query_method = :i18n + + # Uncomment and remove (or add) items to (from) this list to + # completely disable/enable plugins globally (so they cannot be used + # and are never even loaded). Note that if you remove an item from the + # list, you will not be able to use the plugin at all, and any options + # for the plugin will be ignored by models. (In most cases, you + # probably don't want to change this.) + # + # config.plugins = %i[ + # query + # cache + # dirty + # fallbacks + # presence + # default + # attribute_methods + # fallthrough_accessors + # locale_accessors + # ] + + # The translation cache is on by default, but you can turn it off by + # uncommenting this line. (This may be helpful in debugging.) + # + # config.default_options[:cache] = false + + # Dirty tracking is disabled by default. Uncomment this line to enable + # it. If you enable this, you should also enable +locale_accessors+ + # by default (see below). + # + # config.default_options[:dirty] = true + + # No fallbacks are used by default. To define default fallbacks, + # uncomment and set the default fallback option value here. A "true" + # value will use whatever is defined by +I18n.fallbacks+ (if defined), + # or alternatively will fallback to your +I18n.default_locale+. + # + config.default_options[:fallbacks] = true + + # The Presence plugin converts empty strings to nil when fetching and + # setting translations. By default it is on, uncomment this line to + # turn it off. + # + # config.default_options[:presence] = false + + # Set a default value to use if the translation is nil. By default + # this is off, uncomment and set a default to use it across all models + # (you probably don't want to do that). + # + # config.default_options[:default] = ... + + # Uncomment to enable locale_accessors by default on models. A true + # value will use the locales defined either in + # Rails.application.config.i18n.available_locales or + # I18n.available_locales. If you want something else, pass an array + # of locales instead. + # + # config.default_options[:locale_accessors] = true + + # Uncomment to enable fallthrough accessors by default on models. This + # will allow you to call any method with a suffix like _en or _pt_br, + # and Mobility will catch the suffix and convert it into a locale in + # +method_missing+. If you don't need this kind of open-ended + # fallthrough behavior, it's better to use locale_accessors instead + # (which define methods) since method_missing is very slow. (You can + # use both fallthrough and locale accessor plugins together without + # conflict.) + # + # Note: The dirty plugin enables fallthrough_accessors by default. + # + # config.default_options[:fallthrough_accessors] = true + + # You can also include backend-specific default options. For example, + # if you want to default to using the text-type translation table with + # the KeyValue backend, you can set that as a default by uncommenting + # this line, or change it to :string to default to the string-type + # translation table instead. (For other backends, this option is + # ignored.) + # + # config.default_options[:type] = :text +end diff --git a/config/locales/en.yml b/config/locales/en.yml index 8f64db91..cdb5a457 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -158,6 +158,15 @@ en: edit: title: 'Edit %{site}' submit: 'Save changes' + form: + help: + name: "Your site's name. It can only contain numbers and letters." + design: 'Select the design for your site. You can change it later. We add more designs from time to time.' + design: + title: 'Design' + actions: 'Information about this design' + url: 'Preview' + licencia: 'Read the license' fetch: title: 'Upgrade the site' help: diff --git a/config/locales/es.yml b/config/locales/es.yml index fc37c162..5cf84bd1 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -163,6 +163,15 @@ es: edit: title: 'Editar %{site}' submit: 'Guardar cambios' + form: + help: + name: 'El nombre de tu sitio. Solo puede contener letras y números.' + design: 'Elegí el diseño que va a tener tu sitio aquí. Podés cambiarlo luego. De tanto en tanto vamos sumando diseños nuevos.' + design: + title: 'Diseño' + actions: 'Información sobre este diseño' + url: 'Vista previa' + licencia: 'Leer la licencia' fetch: title: 'Actualizar el sitio' help: diff --git a/db/migrate/20190716195155_create_designs.rb b/db/migrate/20190716195155_create_designs.rb new file mode 100644 index 00000000..b040749a --- /dev/null +++ b/db/migrate/20190716195155_create_designs.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +# Crea la tabla de diseños +class CreateDesigns < ActiveRecord::Migration[5.2] + def change + create_table :designs do |t| + t.timestamps + t.string :name, unique: true + t.text :description + t.string :gem, unique: true + t.string :url + t.string :license + end + end +end diff --git a/db/migrate/20190716195449_add_lang_to_usuaries.rb b/db/migrate/20190716195449_add_lang_to_usuaries.rb new file mode 100644 index 00000000..598de77e --- /dev/null +++ b/db/migrate/20190716195449_add_lang_to_usuaries.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +# Agrega la columna de idioma a cada usuarie para que pueda ver el sitio +# y escribir artículos en su idioma. +class AddLangToUsuaries < ActiveRecord::Migration[5.2] + def change + add_column :usuaries, :lang, :string, default: 'es' + end +end diff --git a/db/migrate/20190716195811_create_text_translations.rb b/db/migrate/20190716195811_create_text_translations.rb new file mode 100644 index 00000000..caa129fc --- /dev/null +++ b/db/migrate/20190716195811_create_text_translations.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +# Tabla de traducción de textos utilizada por Mobility +class CreateTextTranslations < ActiveRecord::Migration[5.2] + def change + create_table :mobility_text_translations do |t| + t.string :locale, null: false + t.string :key, null: false + t.text :value + t.references :translatable, polymorphic: true, index: false + t.timestamps null: false + end + add_index :mobility_text_translations, %i[translatable_id translatable_type locale key], unique: true, name: :index_mobility_text_translations_on_keys + add_index :mobility_text_translations, %i[translatable_id translatable_type key], name: :index_mobility_text_translations_on_translatable_attribute + end +end diff --git a/db/migrate/20190716195812_create_string_translations.rb b/db/migrate/20190716195812_create_string_translations.rb new file mode 100644 index 00000000..5389e025 --- /dev/null +++ b/db/migrate/20190716195812_create_string_translations.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +# Tabla de traducción de cadenas usada por Mobility +class CreateStringTranslations < ActiveRecord::Migration[5.2] + def change + create_table :mobility_string_translations do |t| + t.string :locale, null: false + t.string :key, null: false + t.string :value + t.references :translatable, polymorphic: true, index: false + t.timestamps null: false + end + add_index :mobility_string_translations, %i[translatable_id translatable_type locale key], unique: true, name: :index_mobility_string_translations_on_keys + add_index :mobility_string_translations, %i[translatable_id translatable_type key], name: :index_mobility_string_translations_on_translatable_attribute + add_index :mobility_string_translations, %i[translatable_type key value locale], name: :index_mobility_string_translations_on_query_keys + end +end diff --git a/db/migrate/20190716202024_add_design_to_sites.rb b/db/migrate/20190716202024_add_design_to_sites.rb new file mode 100644 index 00000000..44958ea4 --- /dev/null +++ b/db/migrate/20190716202024_add_design_to_sites.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +# Los sitios tienen un diseño +class AddDesignToSites < ActiveRecord::Migration[5.2] + def change + add_belongs_to :sites, :design, index: true + end +end diff --git a/db/migrate/20190717214308_add_disabled_to_designs.rb b/db/migrate/20190717214308_add_disabled_to_designs.rb new file mode 100644 index 00000000..6432e346 --- /dev/null +++ b/db/migrate/20190717214308_add_disabled_to_designs.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +# Algunos diseños están deshabilitados +class AddDisabledToDesigns < ActiveRecord::Migration[5.2] + def change + add_column :designs, :disabled, :boolean, default: false + end +end diff --git a/db/schema.rb b/db/schema.rb index 98e398c9..0d9fb79f 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -12,7 +12,43 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20_190_712_165_059) do +ActiveRecord::Schema.define(version: 20_190_717_214_308) do + create_table 'designs', force: :cascade do |t| + t.datetime 'created_at', null: false + t.datetime 'updated_at', null: false + t.string 'name' + t.text 'description' + t.string 'gem' + t.string 'url' + t.string 'license' + t.boolean 'disabled', default: false + end + + create_table 'mobility_string_translations', force: :cascade do |t| + t.string 'locale', null: false + t.string 'key', null: false + t.string 'value' + t.string 'translatable_type' + t.integer 'translatable_id' + t.datetime 'created_at', null: false + t.datetime 'updated_at', null: false + t.index %w[translatable_id translatable_type key], name: 'index_mobility_string_translations_on_translatable_attribute' + t.index %w[translatable_id translatable_type locale key], name: 'index_mobility_string_translations_on_keys', unique: true + t.index %w[translatable_type key value locale], name: 'index_mobility_string_translations_on_query_keys' + end + + create_table 'mobility_text_translations', force: :cascade do |t| + t.string 'locale', null: false + t.string 'key', null: false + t.text 'value' + t.string 'translatable_type' + t.integer 'translatable_id' + t.datetime 'created_at', null: false + t.datetime 'updated_at', null: false + t.index %w[translatable_id translatable_type key], name: 'index_mobility_text_translations_on_translatable_attribute' + t.index %w[translatable_id translatable_type locale key], name: 'index_mobility_text_translations_on_keys', unique: true + end + create_table 'roles', force: :cascade do |t| t.datetime 'created_at', null: false t.datetime 'updated_at', null: false @@ -29,6 +65,8 @@ ActiveRecord::Schema.define(version: 20_190_712_165_059) do t.datetime 'created_at', null: false t.datetime 'updated_at', null: false t.string 'name' + t.integer 'design_id' + t.index ['design_id'], name: 'index_sites_on_design_id' t.index ['name'], name: 'index_sites_on_name', unique: true end @@ -56,6 +94,7 @@ ActiveRecord::Schema.define(version: 20_190_712_165_059) do t.string 'invited_by_type' t.integer 'invited_by_id' t.integer 'invitations_count', default: 0 + t.string 'lang', default: 'es' t.index ['confirmation_token'], name: 'index_usuaries_on_confirmation_token', unique: true t.index ['email'], name: 'index_usuaries_on_email', unique: true t.index ['invitation_token'], name: 'index_usuaries_on_invitation_token', unique: true diff --git a/db/seeds.rb b/db/seeds.rb index ebd18895..9e3d2c5b 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -1,9 +1,9 @@ # frozen_string_literal: true -# This file should contain all the record creation needed to seed the database with its default values. -# The data can then be loaded with the rails db:seed command (or created alongside the database with db:setup). -# -# Examples: -# -# movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }]) -# Character.create(name: 'Luke', movie: movies.first) +designs = YAML.safe_load(File.read('db/seeds/designs.yml')) + +designs.each do |d| + design = Design.find_or_create_by(gem: d['gem']) + + design.update_attributes d +end diff --git a/db/seeds/designs.yml b/db/seeds/designs.yml new file mode 100644 index 00000000..26f2e9a1 --- /dev/null +++ b/db/seeds/designs.yml @@ -0,0 +1,22 @@ +--- +- name_en: 'My own design' + name_es: 'Mi propio diseño' + gem: 'sutty-theme-none' + url: 'https://sutty.nl' + disabled: true + description_en: "Your own design. [This feature is in development, help us!]()" + description_es: "Tu propio diseño. [Esta posibilidad está en desarrollo, ¡ayudanos!]()" +- name_en: 'Minima' + name_es: 'Mínima' + gem: 'minima' + url: 'https://jekyll.github.io/minima/' + description_en: "Minima is the default design for Jekyll sites. It's made for general-purpose writing." + description_es: 'Mínima es el diseño oficial de los sitios Jekyll, hecho para escritura de propósitos generales.' + license: 'https://github.com/jekyll/minima/blob/master/LICENSE.txt' +- name_en: 'EDSL' + name_es: 'EDSL' + gem: 'sutty-theme-edsl' + url: 'https://endefensadelsl.org/' + description_en: "_En defensa del software libre_'s design" + description_es: 'El diseño de En defensa del software libre' + license: 'https://endefensadelsl.org/ppl_deed_es.html' diff --git a/test/controllers/sites_controller_test.rb b/test/controllers/sites_controller_test.rb index 2c3c334c..058d20b1 100644 --- a/test/controllers/sites_controller_test.rb +++ b/test/controllers/sites_controller_test.rb @@ -33,7 +33,8 @@ class SitesControllerTest < ActionDispatch::IntegrationTest post sites_url, headers: @authorization, params: { site: { - name: name + name: name, + design_id: create(:design).id } } diff --git a/test/factories/design.rb b/test/factories/design.rb new file mode 100644 index 00000000..b210b3ad --- /dev/null +++ b/test/factories/design.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :design do + name { SecureRandom.hex } + description { SecureRandom.hex } + license { SecureRandom.hex } + gem { SecureRandom.hex } + url { SecureRandom.hex } + disabled { false } + end +end diff --git a/test/factories/site.rb b/test/factories/site.rb index 9d80180c..3593c9fc 100644 --- a/test/factories/site.rb +++ b/test/factories/site.rb @@ -3,5 +3,6 @@ FactoryBot.define do factory :site do name { "test-#{SecureRandom.hex}" } + design end end diff --git a/test/models/design_test.rb b/test/models/design_test.rb new file mode 100644 index 00000000..000be74d --- /dev/null +++ b/test/models/design_test.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class DesignTest < ActiveSupport::TestCase + test 'se pueden crear' do + design = create :design + + assert design.valid? + assert design.persisted? + end +end diff --git a/test/models/site_test.rb b/test/models/site_test.rb index 2040bd94..b74daaf2 100644 --- a/test/models/site_test.rb +++ b/test/models/site_test.rb @@ -27,26 +27,30 @@ class SiteTest < ActiveSupport::TestCase test 'el nombre del sitio puede contener subdominios' do site = build :site, name: 'hola.chau' + site.validate - assert site.valid? + assert_not site.errors.messages[:name].present? end test 'el nombre del sitio no puede terminar con punto' do site = build :site, name: 'hola.chau.' + site.validate - assert_not site.valid? + assert site.errors.messages[:name].present? end test 'el nombre del sitio no puede contener wildcard' do site = build :site, name: '*.chau' + site.validate - assert_not site.valid? + assert site.errors.messages[:name].present? end test 'el nombre del sitio solo tiene letras, numeros y guiones' do site = build :site, name: 'A_Z!' + site.validate - assert_not site.valid? + assert site.errors.messages[:name].present? end test 'al destruir un sitio se eliminan los archivos' do