diff --git a/.prettierignore b/.prettierignore
index 3befa144..d559d3fb 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -1 +1,2 @@
*/**/*.rb
+*/**/*.yml
diff --git a/Gemfile b/Gemfile
index 56de71d2..fbde9705 100644
--- a/Gemfile
+++ b/Gemfile
@@ -52,6 +52,8 @@ gem 'hiredis'
gem 'image_processing'
gem 'inline_svg'
gem 'jekyll'
+gem 'jekyll-data', require: 'jekyll-data',
+ git: 'https://0xacab.org/sutty/jekyll/jekyll-data.git'
gem 'mini_magick'
gem 'mobility'
gem 'pg'
@@ -60,7 +62,6 @@ gem 'rails-i18n'
gem 'rails_warden'
gem 'redis', require: %w[redis redis/connection/hiredis]
gem 'redis-rails'
-gem 'reverse_markdown', git: 'https://0xacab.org/sutty/reverse_markdown.git'
gem 'rubyzip'
gem 'rugged'
gem 'sucker_punch'
@@ -69,6 +70,12 @@ gem 'validates_hostname'
gem 'webpacker'
gem 'yaml_db', git: 'https://0xacab.org/sutty/yaml_db.git'
+group :themes do
+ gem 'editorial-autogestiva-jekyll-theme', require: false
+ gem 'minima', require: false
+ gem 'sutty-jekyll-theme', require: false
+end
+
group :development, :test do
gem 'pry'
# Adds support for Capybara system testing and selenium driver
diff --git a/Gemfile.lock b/Gemfile.lock
index f8e3fd74..5e41526d 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,9 +1,9 @@
GIT
- remote: https://0xacab.org/sutty/reverse_markdown.git
- revision: 5c243096669aa77e0dc173dec8006b4c5fe07683
+ remote: https://0xacab.org/sutty/jekyll/jekyll-data.git
+ revision: 1ad9c175be6bbb31ae6d19cbb8dde18828af90d9
specs:
- reverse_markdown (1.2.0)
- nokogiri
+ jekyll-data (1.1.0)
+ jekyll (>= 3.3, < 5.0.0)
GIT
remote: https://0xacab.org/sutty/yaml_db.git
@@ -16,56 +16,56 @@ GIT
GEM
remote: https://rubygems.org/
specs:
- actioncable (6.0.2.1)
- actionpack (= 6.0.2.1)
+ actioncable (6.0.2.2)
+ actionpack (= 6.0.2.2)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
- actionmailbox (6.0.2.1)
- actionpack (= 6.0.2.1)
- activejob (= 6.0.2.1)
- activerecord (= 6.0.2.1)
- activestorage (= 6.0.2.1)
- activesupport (= 6.0.2.1)
+ actionmailbox (6.0.2.2)
+ actionpack (= 6.0.2.2)
+ activejob (= 6.0.2.2)
+ activerecord (= 6.0.2.2)
+ activestorage (= 6.0.2.2)
+ activesupport (= 6.0.2.2)
mail (>= 2.7.1)
- actionmailer (6.0.2.1)
- actionpack (= 6.0.2.1)
- actionview (= 6.0.2.1)
- activejob (= 6.0.2.1)
+ actionmailer (6.0.2.2)
+ actionpack (= 6.0.2.2)
+ actionview (= 6.0.2.2)
+ activejob (= 6.0.2.2)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0)
- actionpack (6.0.2.1)
- actionview (= 6.0.2.1)
- activesupport (= 6.0.2.1)
+ actionpack (6.0.2.2)
+ actionview (= 6.0.2.2)
+ activesupport (= 6.0.2.2)
rack (~> 2.0, >= 2.0.8)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.2.0)
- actiontext (6.0.2.1)
- actionpack (= 6.0.2.1)
- activerecord (= 6.0.2.1)
- activestorage (= 6.0.2.1)
- activesupport (= 6.0.2.1)
+ actiontext (6.0.2.2)
+ actionpack (= 6.0.2.2)
+ activerecord (= 6.0.2.2)
+ activestorage (= 6.0.2.2)
+ activesupport (= 6.0.2.2)
nokogiri (>= 1.8.5)
- actionview (6.0.2.1)
- activesupport (= 6.0.2.1)
+ actionview (6.0.2.2)
+ activesupport (= 6.0.2.2)
builder (~> 3.1)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.1, >= 1.2.0)
- activejob (6.0.2.1)
- activesupport (= 6.0.2.1)
+ activejob (6.0.2.2)
+ activesupport (= 6.0.2.2)
globalid (>= 0.3.6)
- activemodel (6.0.2.1)
- activesupport (= 6.0.2.1)
- activerecord (6.0.2.1)
- activemodel (= 6.0.2.1)
- activesupport (= 6.0.2.1)
- activestorage (6.0.2.1)
- actionpack (= 6.0.2.1)
- activejob (= 6.0.2.1)
- activerecord (= 6.0.2.1)
+ activemodel (6.0.2.2)
+ activesupport (= 6.0.2.2)
+ activerecord (6.0.2.2)
+ activemodel (= 6.0.2.2)
+ activesupport (= 6.0.2.2)
+ activestorage (6.0.2.2)
+ actionpack (= 6.0.2.2)
+ activejob (= 6.0.2.2)
+ activerecord (= 6.0.2.2)
marcel (~> 0.3.1)
- activesupport (6.0.2.1)
+ activesupport (6.0.2.2)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 0.7, < 2)
minitest (~> 5.1)
@@ -74,7 +74,7 @@ GEM
addressable (2.7.0)
public_suffix (>= 2.0.2, < 5.0)
ast (2.4.0)
- autoprefixer-rails (9.7.3)
+ autoprefixer-rails (9.7.4)
execjs
bcrypt (3.1.13)
bcrypt_pbkdf (1.0.1)
@@ -88,7 +88,7 @@ GEM
autoprefixer-rails (>= 9.1.0)
popper_js (>= 1.14.3, < 2)
sassc-rails (>= 2.0.0)
- brakeman (4.7.2)
+ brakeman (4.8.0)
builder (3.2.4)
capybara (2.18.0)
addressable
@@ -101,11 +101,11 @@ GEM
childprocess (3.0.0)
coderay (1.1.2)
colorator (1.1.0)
- commonmarker (0.20.2)
+ commonmarker (0.21.0)
ruby-enum (~> 0.5)
- concurrent-ruby (1.1.5)
- crass (1.0.5)
- database_cleaner (1.7.0)
+ concurrent-ruby (1.1.6)
+ crass (1.0.6)
+ database_cleaner (1.8.3)
devise (4.7.1)
bcrypt (~> 3.0)
orm_adapter (~> 0.1)
@@ -122,10 +122,19 @@ GEM
dotenv (= 2.7.5)
railties (>= 3.2, < 6.1)
ed25519 (1.2.4)
+ editorial-autogestiva-jekyll-theme (0.2.2)
+ jekyll (~> 4.0)
+ jekyll-data (~> 1.1)
+ jekyll-feed (~> 0.9)
+ jekyll-images (~> 0.2)
+ jekyll-include-cache (~> 0)
+ jekyll-locales (~> 0.1)
+ jekyll-relative-urls (~> 0.0)
+ jekyll-seo-tag (~> 2.1)
em-websocket (0.5.1)
eventmachine (>= 0.12.9)
http_parser.rb (~> 0.6.0)
- email_address (0.1.12)
+ email_address (0.1.15)
netaddr (>= 2.0.4, < 3)
simpleidn
errbase (0.2.0)
@@ -140,7 +149,7 @@ GEM
factory_bot_rails (5.1.1)
factory_bot (~> 5.1.0)
railties (>= 4.2.0)
- ffi (1.11.3)
+ ffi (1.12.2)
forwardable-extended (2.6.0)
friendly_id (5.3.0)
activerecord (>= 4.0.0)
@@ -151,7 +160,7 @@ GEM
tilt
haml-lint (0.999.999)
haml_lint
- haml_lint (0.34.1)
+ haml_lint (0.35.0)
haml (>= 4.0, < 5.2)
rainbow
rubocop (>= 0.50.0)
@@ -167,17 +176,17 @@ GEM
railties (>= 4.0.1)
hiredis (0.6.3)
http_parser.rb (0.6.0)
- i18n (1.7.0)
+ i18n (1.8.2)
concurrent-ruby (~> 1.0)
- image_processing (1.10.0)
+ image_processing (1.10.3)
mini_magick (>= 4.9.5, < 5)
- ruby-vips (>= 2.0.13, < 3)
- inline_svg (1.6.0)
+ ruby-vips (>= 2.0.17, < 3)
+ inline_svg (1.7.1)
activesupport (>= 3.0)
nokogiri (>= 1.6)
jaro_winkler (1.5.4)
- jbuilder (2.9.1)
- activesupport (>= 4.2.0)
+ jbuilder (2.10.0)
+ activesupport (>= 5.0.0)
jekyll (4.0.0)
addressable (~> 2.4)
colorator (~> 1.0)
@@ -193,15 +202,27 @@ GEM
rouge (~> 3.0)
safe_yaml (~> 1.0)
terminal-table (~> 1.8)
- jekyll-sass-converter (2.0.1)
+ jekyll-feed (0.13.0)
+ jekyll (>= 3.7, < 5.0)
+ jekyll-images (0.2.3)
+ ruby-filemagic (~> 0.7)
+ ruby-vips (~> 2)
+ jekyll-include-cache (0.2.0)
+ jekyll (>= 3.7, < 5.0)
+ jekyll-locales (0.1.7)
+ jekyll-relative-urls (0.0.5)
+ jekyll (>= 3.8, < 5)
+ jekyll-sass-converter (2.1.0)
sassc (> 2.0.1, < 3.0)
+ jekyll-seo-tag (2.6.1)
+ jekyll (>= 3.3, < 5.0)
jekyll-watch (2.2.1)
listen (~> 3.0)
kramdown (2.1.0)
kramdown-parser-gfm (1.1.0)
kramdown (~> 2.0)
- launchy (2.4.3)
- addressable (~> 2.3)
+ launchy (2.5.0)
+ addressable (~> 2.7)
letter_opener (1.7.0)
launchy (~> 2.2)
liquid (4.0.3)
@@ -218,31 +239,35 @@ GEM
mimemagic (~> 0.3.2)
mercenary (0.3.6)
method_source (0.9.2)
- mimemagic (0.3.3)
- mini_magick (4.9.5)
+ mimemagic (0.3.4)
+ mini_magick (4.10.1)
mini_mime (1.0.2)
mini_portile2 (2.4.0)
- minitest (5.13.0)
- mobility (0.8.9)
+ minima (2.5.1)
+ jekyll (>= 3.5, < 5.0)
+ jekyll-feed (~> 0.9)
+ jekyll-seo-tag (~> 2.1)
+ minitest (5.14.0)
+ mobility (0.8.10)
i18n (>= 0.6.10, < 2)
request_store (~> 1.0)
netaddr (2.0.4)
nio4r (2.5.2)
- nokogiri (1.10.7)
+ nokogiri (1.10.9)
mini_portile2 (~> 2.4.0)
orm_adapter (0.5.0)
parallel (1.19.1)
- parser (2.7.0.1)
+ parser (2.7.0.4)
ast (~> 2.4.0)
pathutil (0.16.2)
forwardable-extended (~> 2.6)
- pg (1.2.0)
- popper_js (1.14.5)
+ pg (1.2.3)
+ popper_js (1.16.0)
pry (0.12.2)
coderay (~> 1.1.0)
method_source (~> 0.9.0)
- public_suffix (4.0.2)
- puma (4.3.1)
+ public_suffix (4.0.3)
+ puma (4.3.3)
nio4r (~> 2.0)
pundit (2.1.0)
activesupport (>= 3.0.0)
@@ -251,20 +276,20 @@ GEM
rack
rack-test (1.1.0)
rack (>= 1.0, < 3)
- rails (6.0.2.1)
- actioncable (= 6.0.2.1)
- actionmailbox (= 6.0.2.1)
- actionmailer (= 6.0.2.1)
- actionpack (= 6.0.2.1)
- actiontext (= 6.0.2.1)
- actionview (= 6.0.2.1)
- activejob (= 6.0.2.1)
- activemodel (= 6.0.2.1)
- activerecord (= 6.0.2.1)
- activestorage (= 6.0.2.1)
- activesupport (= 6.0.2.1)
+ rails (6.0.2.2)
+ actioncable (= 6.0.2.2)
+ actionmailbox (= 6.0.2.2)
+ actionmailer (= 6.0.2.2)
+ actionpack (= 6.0.2.2)
+ actiontext (= 6.0.2.2)
+ actionview (= 6.0.2.2)
+ activejob (= 6.0.2.2)
+ activemodel (= 6.0.2.2)
+ activerecord (= 6.0.2.2)
+ activestorage (= 6.0.2.2)
+ activesupport (= 6.0.2.2)
bundler (>= 1.3.0)
- railties (= 6.0.2.1)
+ railties (= 6.0.2.2)
sprockets-rails (>= 2.0.0)
rails-dom-testing (2.0.3)
activesupport (>= 4.2.0)
@@ -276,9 +301,9 @@ GEM
railties (>= 6.0.0, < 7)
rails_warden (0.6.0)
warden (>= 1.2.0)
- railties (6.0.2.1)
- actionpack (= 6.0.2.1)
- activesupport (= 6.0.2.1)
+ railties (6.0.2.2)
+ actionpack (= 6.0.2.2)
+ activesupport (= 6.0.2.2)
method_source
rake (>= 0.8.7)
thor (>= 0.20.3, < 2.0)
@@ -290,46 +315,49 @@ GEM
rbnacl (4.0.2)
ffi
redis (4.1.3)
- redis-actionpack (5.1.0)
- actionpack (>= 4.0, < 7)
- redis-rack (>= 1, < 3)
+ redis-actionpack (5.2.0)
+ actionpack (>= 5, < 7)
+ redis-rack (>= 2.1.0, < 3)
redis-store (>= 1.1.0, < 2)
redis-activesupport (5.2.0)
activesupport (>= 3, < 7)
redis-store (>= 1.3, < 2)
- redis-rack (2.0.6)
- rack (>= 1.5, < 3)
+ redis-rack (2.1.2)
+ rack (>= 2.0.8, < 3)
redis-store (>= 1.2, < 2)
redis-rails (5.0.2)
redis-actionpack (>= 5.0, < 6)
redis-activesupport (>= 5.0, < 6)
redis-store (>= 1.2, < 2)
- redis-store (1.8.1)
+ redis-store (1.8.2)
redis (>= 4, < 5)
request_store (1.5.0)
rack (>= 1.4)
responders (3.0.0)
actionpack (>= 5.0)
railties (>= 5.0)
- rouge (3.14.0)
- rubocop (0.78.0)
+ rexml (3.2.4)
+ rouge (3.17.0)
+ rubocop (0.80.1)
jaro_winkler (~> 1.5.1)
parallel (~> 1.10)
- parser (>= 2.6)
+ parser (>= 2.7.0.1)
rainbow (>= 2.2.2, < 4.0)
+ rexml
ruby-progressbar (~> 1.7)
unicode-display_width (>= 1.4.0, < 1.7)
- rubocop-rails (2.4.1)
+ rubocop-rails (2.4.2)
rack (>= 1.1)
rubocop (>= 0.72.0)
ruby-enum (0.7.2)
i18n
+ ruby-filemagic (0.7.2)
ruby-progressbar (1.10.1)
- ruby-vips (2.0.16)
+ ruby-vips (2.0.17)
ffi (~> 1.9)
ruby_dep (1.5.0)
- rubyzip (2.0.0)
- rugged (0.28.4.1)
+ rubyzip (2.3.0)
+ rugged (0.99.0)
safe_yaml (1.0.5)
safely_block (0.3.0)
errbase (>= 0.1.1)
@@ -360,6 +388,13 @@ GEM
sqlite3 (1.4.2)
sucker_punch (2.1.2)
concurrent-ruby (~> 1.0)
+ sutty-jekyll-theme (0.1.0)
+ jekyll (~> 4.0)
+ jekyll-feed (~> 0.9)
+ jekyll-images (~> 0.2)
+ jekyll-include-cache (~> 0)
+ jekyll-relative-urls (~> 0.0)
+ jekyll-seo-tag (~> 2.1)
sysexits (1.2.0)
temple (0.8.2)
terminal-table (1.8.0)
@@ -378,8 +413,8 @@ GEM
unf (0.1.4)
unf_ext
unf_ext (0.0.7.6)
- unicode-display_width (1.6.0)
- validates_hostname (1.0.8)
+ unicode-display_width (1.6.1)
+ validates_hostname (1.0.10)
activerecord (>= 3.0)
activesupport (>= 3.0)
warden (1.2.8)
@@ -398,7 +433,7 @@ GEM
websocket-extensions (0.1.4)
xpath (3.2.0)
nokogiri (~> 1.8)
- zeitwerk (2.2.2)
+ zeitwerk (2.3.0)
PLATFORMS
ruby
@@ -417,6 +452,7 @@ DEPENDENCIES
devise_invitable
dotenv-rails
ed25519
+ editorial-autogestiva-jekyll-theme
email_address
exception_notification
factory_bot_rails
@@ -428,9 +464,11 @@ DEPENDENCIES
inline_svg
jbuilder (~> 2.5)
jekyll
+ jekyll-data!
letter_opener
listen (>= 3.0.5, < 3.2)
mini_magick
+ minima
mobility
pg
pry
@@ -442,7 +480,6 @@ DEPENDENCIES
rbnacl (< 5.0)
redis
redis-rails
- reverse_markdown!
rubocop-rails
rubyzip
rugged
@@ -452,6 +489,7 @@ DEPENDENCIES
spring-watcher-listen (~> 2.0.0)
sqlite3
sucker_punch
+ sutty-jekyll-theme
terminal-table
timecop
turbolinks (~> 5)
diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss
index 3144693d..e244c112 100644
--- a/app/assets/stylesheets/application.scss
+++ b/app/assets/stylesheets/application.scss
@@ -308,3 +308,13 @@ svg {
background-color: $cyan;
}
}
+
+.custom-control-label {
+ font-weight: bold;
+}
+
+.designs {
+ .design {
+ margin-top: 1rem;
+ }
+}
diff --git a/app/controllers/api/v1/base_controller.rb b/app/controllers/api/v1/base_controller.rb
index 6f76e51a..25c10e65 100644
--- a/app/controllers/api/v1/base_controller.rb
+++ b/app/controllers/api/v1/base_controller.rb
@@ -6,6 +6,12 @@ module Api
class BaseController < ActionController::Base
protect_from_forgery with: :null_session
respond_to :json
+
+ private
+
+ def origin
+ request.headers['Origin']
+ end
end
end
end
diff --git a/app/controllers/api/v1/contact_controller.rb b/app/controllers/api/v1/contact_controller.rb
new file mode 100644
index 00000000..1b247250
--- /dev/null
+++ b/app/controllers/api/v1/contact_controller.rb
@@ -0,0 +1,102 @@
+# frozen_string_literal: true
+
+module Api
+ module V1
+ # API para formulario de contacto
+ class ContactController < BaseController
+ # Aplicar algunos chequeos básicos. Deberíamos registrar de
+ # alguna forma los errores pero tampoco queremos que nos usen
+ # recursos.
+ #
+ # TODO: Agregar los mismos chequeos que en PostsController
+ before_action :site_exists?, unless: :performed?
+ before_action :site_is_origin?, unless: :performed?
+ before_action :from_is_address?, unless: :performed?
+ before_action :gave_consent?, unless: :performed?
+
+ # Recibe un mensaje a través del formulario de contacto y lo envía
+ # a les usuaries del sitio.
+ #
+ # Tenemos que verificar que el sitio exista y que algunos campos
+ # estén llenos para detener spambots o DDOS. También nos vamos a
+ # estar apoyando en la limitación de peticiones en el servidor web.
+ def receive
+ # No hacer nada si no se pasaron los chequeos
+ return if performed?
+
+ # Si todo salió bien, enviar los correos y redirigir al sitio.
+ # El sitio nos dice a dónde tenemos que ir.
+ ContactJob.perform_async site_id: site.id,
+ **contact_params.to_h.symbolize_keys
+
+ redirect_to contact_params[:redirect] || site.url
+ end
+
+ private
+
+ # Comprueba que el sitio existe
+ #
+ # TODO: Responder con una zip bomb!
+ def site_exists?
+ render html: body(:site_exists), status: status unless site
+ end
+
+ # Comprueba que el mensaje vino fue enviado desde el sitio
+ def site_is_origin?
+ return if origin.to_s == site.url
+
+ render html: body(:site_is_origin), status: status
+ end
+
+ # Detecta si la dirección de contacto es válida. Además es
+ # opcional.
+ def from_is_address?
+ return unless contact_params[:from]
+ return if EmailAddress.valid? contact_params[:from]
+
+ render html: body(:from_is_address), status: status
+ end
+
+ # No aceptar nada si no dió su consentimiento
+ def gave_consent?
+ return if contact_params[:gdpr].present?
+
+ render html: body(:gave_consent), status: status
+ end
+
+ # Realiza la inversa de Site#hostname
+ def site_id
+ @site_id ||= if params[:site_id].end_with? Site.domain
+ params[:site_id].gsub(/\.#{Site.domain}\z/, '')
+ else
+ "#{params[:site_id]}."
+ end
+ end
+
+ # Encuentra el sitio
+ def site
+ @site ||= Site.find_by(name: site_id)
+ end
+
+ # Parámetros limpios
+ def contact_params
+ @contact_params ||= params.permit(:gdpr, :name, :pronouns, :from,
+ :contact, :body, :redirect)
+ end
+
+ # Para poder testear, enviamos un mensaje en el cuerpo de la
+ # respuesta
+ #
+ # @param [Any] el mensaje
+ def body(message)
+ return message.to_s if Rails.env.test?
+ end
+
+ # No queremos informar nada a los spammers, pero en testeo
+ # queremos saber por qué. :no_content oculta el cuerpo.
+ def status
+ Rails.env.test? ? :unprocessable_entity : :no_content
+ end
+ end
+ end
+end
diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb
index 4d999d9a..fbe9036d 100644
--- a/app/controllers/posts_controller.rb
+++ b/app/controllers/posts_controller.rb
@@ -11,21 +11,21 @@ class PostsController < ApplicationController
@category = session[:category] = params.dig(:category)
@layout = params.dig(:layout).try :to_sym
# TODO: Aplicar policy_scope
- @posts = @site.posts(lang: I18n.locale)
+ @posts = @site.posts(lang: lang)
@posts.sort_by!(:order, :date).reverse!
@usuarie = @site.usuarie? current_usuarie
end
def show
@site = find_site
- @post = @site.posts.find params[:id]
+ @post = @site.posts(lang: lang).find params[:id]
authorize @post
end
def new
authorize Post
@site = find_site
- @post = @site.posts.build(lang: I18n.locale, layout: params[:layout])
+ @post = @site.posts.build(lang: lang, layout: params[:layout])
end
def create
@@ -44,14 +44,14 @@ class PostsController < ApplicationController
def edit
@site = find_site
- @post = @site.posts.find params[:id]
+ @post = @site.posts(lang: lang).find params[:id]
authorize @post
end
def update
@site = find_site
- @post = @site.posts.find params[:id]
+ @post = @site.posts(lang: lang).find params[:id]
authorize @post
@@ -70,7 +70,7 @@ class PostsController < ApplicationController
# Eliminar artículos
def destroy
@site = find_site
- @post = @site.posts.find params[:id]
+ @post = @site.posts(lang: lang).find params[:id]
authorize @post
@@ -96,4 +96,13 @@ class PostsController < ApplicationController
service.reorder
redirect_to site_posts_path(@site)
end
+
+ # Devuelve el idioma solicitado a través de un parámetro, validando
+ # que el sitio soporte ese idioma
+ def lang
+ return unless params[:lang]
+ return unless @site.try(:locales).try(:include?, params[:lang])
+
+ params[:lang]
+ end
end
diff --git a/app/jobs/contact_job.rb b/app/jobs/contact_job.rb
new file mode 100644
index 00000000..849c5079
--- /dev/null
+++ b/app/jobs/contact_job.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+# Envía los mensajes de contacto
+class ContactJob < ApplicationJob
+ def perform(**args)
+ @params = args
+
+ # Enviar de a 10 usuaries para minimizar el riesgo que nos
+ # consideren spammers.
+ #
+ # TODO: #i18n. Agrupar usuaries por su idioma
+
+ usuaries.each_slice(10) do |u|
+ ContactMailer.with(**args.merge(usuaries: u, title: site.title))
+ .notify_usuaries.deliver_now
+ end
+ end
+
+ private
+
+ def site
+ @site ||= Site.find @params[:site_id]
+ end
+
+ # Trae solo les usuaries definitives para eliminar un vector de ataque
+ # donde alguien crea un sitio, agrega a muches usuaries y les envía
+ # correos.
+ #
+ # TODO: Mover a Site#usuaries
+ def usuaries
+ site.roles.where(rol: 'usuarie', temporal: false).includes(:usuarie)
+ .pluck(:email)
+ end
+end
diff --git a/app/jobs/deploy_job.rb b/app/jobs/deploy_job.rb
index 85411cb0..a115720a 100644
--- a/app/jobs/deploy_job.rb
+++ b/app/jobs/deploy_job.rb
@@ -45,9 +45,8 @@ class DeployJob < ApplicationJob
end
def notify_usuaries
- # TODO: existe site.usuaries_ids?
- @site.usuaries.find_each do |usuarie|
- DeployMailer.with(usuarie: usuarie.id, site: @site.id)
+ @site.roles.where(rol: 'usuarie', temporal: false).pluck(:usuaries_id).each do |usuarie|
+ DeployMailer.with(usuarie: usuarie, site: @site.id)
.deployed(@deployed)
.deliver_now
end
diff --git a/app/mailers/contact_mailer.rb b/app/mailers/contact_mailer.rb
new file mode 100644
index 00000000..058608e6
--- /dev/null
+++ b/app/mailers/contact_mailer.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+# Formulario de contacto
+class ContactMailer < ApplicationMailer
+ # Enviar el formulario de contacto a les usuaries
+ def notify_usuaries
+ mail to: params[:usuaries],
+ reply_to: params[:from],
+ subject: I18n.t('contact_mailer.subject', site: params[:title])
+ end
+end
diff --git a/app/models/metadata_boolean.rb b/app/models/metadata_boolean.rb
index f0c64c4f..113b9d5f 100644
--- a/app/models/metadata_boolean.rb
+++ b/app/models/metadata_boolean.rb
@@ -15,14 +15,14 @@ class MetadataBoolean < MetadataTemplate
# En este caso, queremos priorizar el dato enviado por le usuarie
# antes que el generado internamente.
def value
- return false if false?
+ return false if self[:value] == '0'
+ return self[:value] unless self[:value].nil?
- self[:value].present? || document.data.fetch(name.to_s, default_value)
+ document.data.fetch(name.to_s, default_value)
end
- private
-
- def false?
- self[:value] == '0'
+ def save
+ self[:value] = !%w[0 false].include?(self[:value])
+ true
end
end
diff --git a/app/models/metadata_number.rb b/app/models/metadata_number.rb
new file mode 100644
index 00000000..9422ed16
--- /dev/null
+++ b/app/models/metadata_number.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+# Un campo numérico
+class MetadataNumber < MetadataTemplate
+ # Nada
+ def default_value
+ nil
+ end
+
+ def save
+ self[:value] = value.to_i
+
+ true
+ end
+end
diff --git a/app/models/metadata_path.rb b/app/models/metadata_path.rb
index 474ddee7..7f386928 100644
--- a/app/models/metadata_path.rb
+++ b/app/models/metadata_path.rb
@@ -21,6 +21,9 @@ class MetadataPath < MetadataTemplate
File.basename(value, ext)
end
+ # No lo aceptamos en los parámetros
+ def to_param; end
+
private
def ext
diff --git a/app/models/metadata_predefined_array.rb b/app/models/metadata_predefined_array.rb
new file mode 100644
index 00000000..a6e2105f
--- /dev/null
+++ b/app/models/metadata_predefined_array.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+# Una lista de valores predefinidos
+class MetadataPredefinedArray < MetadataArray
+ def values
+ @values ||= layout[:metadata][name]['values'].map do |k, v|
+ { v[I18n.locale.to_s] => k }
+ end.inject(&:merge)
+ end
+end
diff --git a/app/models/metadata_related_posts.rb b/app/models/metadata_related_posts.rb
index 7d2273a0..0ebc8a02 100644
--- a/app/models/metadata_related_posts.rb
+++ b/app/models/metadata_related_posts.rb
@@ -5,7 +5,7 @@
class MetadataRelatedPosts < MetadataArray
# Genera un Hash de { title | slug => uuid }
def values
- site.posts(lang: lang).map do |p|
+ @values ||= site.posts(lang: lang).map do |p|
{ title(p) => p.uuid.value }
end.inject(:merge)
end
diff --git a/app/models/site.rb b/app/models/site.rb
index ffdb4473..058e16e6 100644
--- a/app/models/site.rb
+++ b/app/models/site.rb
@@ -69,6 +69,7 @@ class Site < ApplicationRecord
def hostname
sub = name || I18n.t('deploys.deploy_local.ejemplo')
+
if sub.ends_with? '.'
sub.gsub(/\.\Z/, '')
else
@@ -123,6 +124,11 @@ class Site < ApplicationRecord
config.fetch('locales', I18n.available_locales.map(&:to_s))
end
+ # Similar a site.i18n en jekyll-locales
+ def i18n
+ data[I18n.locale.to_s]
+ end
+
# Devuelve el idioma por defecto del sitio, el primero de la lista.
def default_locale
locales.first
@@ -259,6 +265,8 @@ class Site < ApplicationRecord
end
def reload_jekyll!
+ reset
+
Dir.chdir(path) do
@jekyll = Jekyll::Site.new(jekyll_config)
end
@@ -285,10 +293,13 @@ class Site < ApplicationRecord
'quiet' => true, 'excerpt_separator' => '')
# No necesitamos cargar plugins en este momento
- %w[plugins gems theme].each do |unneeded|
+ %w[plugins gems].each do |unneeded|
configuration[unneeded] = [] if configuration.key? unneeded
end
+ # Eliminar el theme si no es una gema válida
+ configuration.delete 'theme' unless theme_available?
+
# Si estamos usando nuestro propio plugin de i18n, los posts están
# en "colecciones"
locales.each do |i|
@@ -298,6 +309,20 @@ class Site < ApplicationRecord
configuration
end
+ # Lista los nombres de las plantillas disponibles como gemas,
+ # tomándolas dinámicamente de las que agreguemos en el grupo :themes
+ # del Gemfile.
+ def available_themes
+ @available_themes ||= Bundler.load.current_dependencies.select do |gem|
+ gem.groups.include? :themes
+ end.map(&:name)
+ end
+
+ # Detecta si el tema actual es una gema
+ def theme_available?
+ available_themes.include? design.gem
+ end
+
# Devuelve el dominio actual
def self.domain
ENV.fetch('SUTTY', 'sutty.nl')
@@ -308,6 +333,12 @@ class Site < ApplicationRecord
File.join(Rails.root, '_sites')
end
+ def reset
+ @read = false
+ @layouts = nil
+ @layouts_struct = nil
+ end
+
private
# Clona el esqueleto de Sutty para crear el sitio nuevo, no pasa nada
@@ -340,6 +371,7 @@ class Site < ApplicationRecord
config.description = description
config.title = title
config.url = url
+ config.hostname = hostname
end
# Migra los archivos a Sutty
diff --git a/app/services/post_service.rb b/app/services/post_service.rb
index 125d4eeb..4dfdba07 100644
--- a/app/services/post_service.rb
+++ b/app/services/post_service.rb
@@ -102,8 +102,19 @@ PostService = Struct.new(:site, :usuarie, :post, :params, keyword_init: true) do
end
end
+ # Devuelve el idioma solicitado a través de un parámetro, validando
+ # que el sitio soporte ese idioma
+ #
+ # TODO: DRY
def lang
- params[:post][:lang] || I18n.locale
+ return unless params[:lang]
+ return unless site.try(:locales).try(:include?, params[:lang])
+
+ params[:lang]
+ end
+
+ def layout
+ params.dig(:post, :layout) || params[:layout]
end
end
# rubocop:enable Metrics/BlockLength
diff --git a/app/views/contact_mailer/notify_usuaries.html.haml b/app/views/contact_mailer/notify_usuaries.html.haml
new file mode 100644
index 00000000..02a74624
--- /dev/null
+++ b/app/views/contact_mailer/notify_usuaries.html.haml
@@ -0,0 +1,11 @@
+-#
+ Solo enviamos versión de texto para no aceptar HTML en el formulario
+ de contacto
+
+%p
+ %strong= I18n.t('contact_mailer.name', name: sanitize(@params[:name]),
+ pronouns: sanitize(@params[:pronouns]))
+ %strong= I18n.t('contact_mailer.contact', contact: sanitize(@params[:contact]))
+ %strong= I18n.t('contact_mailer.gdpr', gdpr: sanitize(@params[:gdpr]))
+
+%div= sanitize @params[:body]
diff --git a/app/views/contact_mailer/notify_usuaries.text.haml b/app/views/contact_mailer/notify_usuaries.text.haml
new file mode 100644
index 00000000..393ddd62
--- /dev/null
+++ b/app/views/contact_mailer/notify_usuaries.text.haml
@@ -0,0 +1,11 @@
+-#
+ Solo enviamos versión de texto para no aceptar HTML en el formulario
+ de contacto
+
+= I18n.t('contact_mailer.name',
+ name: sanitize(@params[:name]),
+ pronouns: sanitize(@params[:pronouns]))
+= I18n.t('contact_mailer.contact', contact: sanitize(@params[:contact]))
+= I18n.t('contact_mailer.gdpr', gdpr: sanitize(@params[:gdpr]))
+\
+= sanitize @params[:body]
diff --git a/app/views/posts/attribute_ro/_number.haml b/app/views/posts/attribute_ro/_number.haml
new file mode 100644
index 00000000..31dd8f0d
--- /dev/null
+++ b/app/views/posts/attribute_ro/_number.haml
@@ -0,0 +1,3 @@
+%tr{ id: attribute }
+ %th= post_label_t(attribute, post: post)
+ %td= metadata.value
diff --git a/app/views/posts/attribute_ro/_predefined_array.haml b/app/views/posts/attribute_ro/_predefined_array.haml
new file mode 100644
index 00000000..d5d13b2c
--- /dev/null
+++ b/app/views/posts/attribute_ro/_predefined_array.haml
@@ -0,0 +1,5 @@
+%tr{ id: attribute }
+ %th= post_label_t(attribute, post: post)
+ %td
+ - metadata.value.each do |v|
+ %span.badge.badge-primary= metadata.values.key v
diff --git a/app/views/posts/attributes/_boolean.haml b/app/views/posts/attributes/_boolean.haml
index 7814ebbe..46ab667a 100644
--- a/app/views/posts/attributes/_boolean.haml
+++ b/app/views/posts/attributes/_boolean.haml
@@ -1,6 +1,6 @@
.form-check
= hidden_field_tag "post[#{attribute}]", '0'
- = check_box_tag "post[#{attribute}]", metadata.value, metadata.value,
+ = check_box_tag "post[#{attribute}]", '1', metadata.value,
class: "form-check-input #{invalid(post, attribute)}",
aria: { describedby: id_for_help(attribute) },
autofocus: autofocus
diff --git a/app/views/posts/attributes/_geo.haml b/app/views/posts/attributes/_geo.haml
index 839393d8..e3b3ab46 100644
--- a/app/views/posts/attributes/_geo.haml
+++ b/app/views/posts/attributes/_geo.haml
@@ -16,4 +16,4 @@
value: metadata.value['lng'],
**field_options(attribute, metadata))
= render 'posts/attribute_feedback',
- post: post, attribute: [attribute, :lat], metadata: metadata
+ post: post, attribute: [attribute, :lng], metadata: metadata
diff --git a/app/views/posts/attributes/_number.haml b/app/views/posts/attributes/_number.haml
new file mode 100644
index 00000000..ec2adb53
--- /dev/null
+++ b/app/views/posts/attributes/_number.haml
@@ -0,0 +1,6 @@
+.form-group
+ = label_tag "post_#{attribute}", post_label_t(attribute, post: post)
+ = number_field 'post', attribute, value: metadata.value,
+ **field_options(attribute, metadata)
+ = render 'posts/attribute_feedback',
+ post: post, attribute: attribute, metadata: metadata
diff --git a/app/views/posts/attributes/_predefined_array.haml b/app/views/posts/attributes/_predefined_array.haml
new file mode 100644
index 00000000..8984cb4e
--- /dev/null
+++ b/app/views/posts/attributes/_predefined_array.haml
@@ -0,0 +1,19 @@
+.form-group
+ = label_tag "post_#{attribute}", post_label_t(attribute, post: post)
+
+ .mapable{ data: { values: metadata.value.to_json,
+ 'default-values': metadata.values.to_json,
+ name: "post[#{attribute}][]", list: id_for_datalist(attribute),
+ remove: 'false', legend: post_label_t(attribute, post: post),
+ described: id_for_help(attribute) } }
+
+ = text_field(*field_name_for('post', attribute, '[]'),
+ value: metadata.value.join(', '),
+ **field_options(attribute, metadata))
+
+ = render 'posts/attribute_feedback',
+ post: post, attribute: attribute, metadata: metadata
+
+ %datalist{ id: id_for_datalist(attribute) }
+ - metadata.values.keys.each do |value|
+ %option{ value: value }
diff --git a/app/views/posts/index.haml b/app/views/posts/index.haml
index cded824e..ee8355a6 100644
--- a/app/views/posts/index.haml
+++ b/app/views/posts/index.haml
@@ -14,7 +14,7 @@
%h3= t('posts.new')
%ul
- @site.layouts.to_h.keys.each do |layout|
- %li= link_to layout.to_s.humanize,
+ %li= link_to @site.i18n.dig('layouts', layout.to_s) || layout.to_s.humanize,
new_site_post_path(@site, layout: layout)
- if policy(@site).edit?
diff --git a/app/views/sites/_form.haml b/app/views/sites/_form.haml
index eaa400ff..4bb98cfe 100644
--- a/app/views/sites/_form.haml
+++ b/app/views/sites/_form.haml
@@ -36,18 +36,20 @@
.form-group
%h2= t('.design.title')
%p.lead= t('.help.design')
- .row
+ .row.designs
-# Demasiado complejo para un f.collection_radio_buttons
- - Design.all.each do |design|
- .col
- %h3
+ - Design.all.find_each do |design|
+ .design.col-md-4.d-flex.flex-column
+ .custom-control.custom-radio
= f.radio_button :design_id, design.id,
checked: design.id == site.design_id,
disabled: design.disabled,
- required: true
- = f.label "design_id_#{design.id}", design.name
- = sanitize_markdown design.description,
- tags: %w[p a strong em]
+ required: true, class: 'custom-control-input'
+ = f.label "design_id_#{design.id}", design.name,
+ class: 'custom-control-label'
+ .flex-fill
+ = sanitize_markdown design.description,
+ tags: %w[p a strong em]
.btn-group{ role: 'group', 'aria-label': t('.design.actions') }
- if design.url
@@ -58,25 +60,26 @@
target: '_blank', class: 'btn'
%hr/
- .form-group
+ .form-group.licenses
%h2= t('.licencia.title')
%p.lead= t('.help.licencia')
- - Licencia.all.each do |licencia|
- .row
+ - Licencia.all.find_each do |licencia|
+ .row.license
.col
- %h3
- = f.radio_button :licencia_id, licencia.id,
- checked: licencia.id == site.licencia_id,
- required: true
- = f.label "licencia_id_#{licencia.id}" do
- = image_tag licencia.icons, alt: licencia.name
- = licencia.name
- = sanitize_markdown licencia.description,
- tags: %w[p a strong em ul ol li h1 h2 h3 h4 h5 h6]
+ .media.mt-1
+ = image_tag licencia.icons, alt: licencia.name, class: 'mr-3 mt-4'
+ .media-body
+ .custom-control.custom-radio
+ = f.radio_button :licencia_id, licencia.id,
+ checked: licencia.id == site.licencia_id,
+ required: true, class: 'custom-control-input'
+ = f.label "licencia_id_#{licencia.id}", class: 'custom-control-label' do
+ = licencia.name
+ = sanitize_markdown licencia.description,
+ tags: %w[p a strong em ul ol li h1 h2 h3 h4 h5 h6]
- .btn-group{ role: 'group', 'aria-label': t('.licencia.actions') }
- = link_to t('.licencia.url'), licencia.url,
- target: '_blank', class: 'btn'
+ = link_to t('.licencia.url'), licencia.url,
+ target: '_blank', class: 'btn'
%hr/
diff --git a/config/environments/development.rb b/config/environments/development.rb
index 5a47ac0d..ac21523e 100644
--- a/config/environments/development.rb
+++ b/config/environments/development.rb
@@ -64,6 +64,7 @@ Rails.application.configure do
# routes, locales, etc. This feature depends on the listen gem.
config.file_watcher = ActiveSupport::EventedFileUpdateChecker
+ config.action_mailer.default_options = { from: ENV['DEFAULT_FROM'] }
config.action_mailer.perform_caching = false
config.action_mailer.delivery_method = :letter_opener
config.action_mailer.perform_deliveries = true
diff --git a/config/environments/test.rb b/config/environments/test.rb
index f3334615..2f5a5f1f 100644
--- a/config/environments/test.rb
+++ b/config/environments/test.rb
@@ -44,6 +44,7 @@ Rails.application.configure do
# The :test delivery method accumulates sent emails in the
# ActionMailer::Base.deliveries array.
config.action_mailer.delivery_method = :test
+ config.action_mailer.default_options = { from: ENV['DEFAULT_FROM'] }
config.action_mailer.default_url_options = { host: 'localhost',
port: 3000 }
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 00e591b9..20b6aaf6 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -36,6 +36,11 @@ en:
es: Castillian Spanish
en: English
seconds: '%{seconds} seconds'
+ contact_mailer:
+ subject: '[%site] Contact form'
+ name: 'Name: %{name} (%{pronouns})'
+ contact: 'Contact: %{contact}'
+ gdpr: 'Consent: %{gdpr}'
deploy_mailer:
deployed:
subject: "[Sutty] The site %{site} has been built"
@@ -312,7 +317,7 @@ en:
title: 'Design'
actions: 'Information about this design'
url: 'Demo'
- licencia: 'Read the license'
+ licencia: 'License'
licencia:
title: 'License for the site and everything published on it'
url: 'Read the license'
@@ -382,7 +387,7 @@ en:
date: 'date'
order: 'Order'
content: 'Text'
- new: 'New post as'
+ new: 'Add:'
dropdown: 'Toggle dropdown'
categories: 'Everything'
index: 'Posts'
diff --git a/config/locales/es.yml b/config/locales/es.yml
index cd0764a8..7b7cdace 100644
--- a/config/locales/es.yml
+++ b/config/locales/es.yml
@@ -38,6 +38,11 @@ es:
es: Castellano
en: Inglés
seconds: '%{seconds} segundos'
+ contact_mailer:
+ subject: '[%{site}] Formulario de contacto'
+ name: 'Nombre: %{name} (%{pronouns})'
+ contact: 'Contacto: %{contact}'
+ gdpr: 'Consentimiento: %{gdpr}'
deploy_mailer:
deployed:
subject: "[Sutty] El sitio %{site} ha sido generado"
@@ -320,7 +325,7 @@ es:
title: 'Diseño'
actions: 'Información sobre este diseño'
url: 'Demostración'
- license: 'Leer la licencia'
+ license: 'Licencia'
licencia:
title: 'Licencia del sitio y todo lo publicado'
url: 'Leer la licencia'
@@ -392,7 +397,7 @@ es:
content: 'Cuerpo del artículo'
categories: 'Todos'
dropdown: 'Desplegar el menú'
- new: 'Crear artículo en:'
+ new: 'Agregar:'
index: 'Artículos'
edit: 'Editar'
open: 'Nota: Puedes agregar más opciones a medida que las escribes y presionas Entrar'
diff --git a/config/routes.rb b/config/routes.rb
index 9e6c0660..f89bc537 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -24,6 +24,7 @@ Rails.application.routes.draw do
resources :sites, only: %i[index], constraints: { site_id: /[a-z0-9\-\.]+/, id: /[a-z0-9\-\.]+/ } do
get 'invitades/cookie', to: 'invitades#cookie'
resources :posts, only: %i[create]
+ post :contact, to: 'contact#receive'
end
end
end
diff --git a/db/seeds/designs.yml b/db/seeds/designs.yml
index 836f0990..047ef960 100644
--- a/db/seeds/designs.yml
+++ b/db/seeds/designs.yml
@@ -27,6 +27,13 @@
description_en: "A design with Sutty's look & feel"
description_es: 'El diseño de Sutty'
license: 'https://0xacab.org/sutty/jekyll/sutty-jekyll-theme/-/blob/master/LICENSE.txt'
+- name_en: 'Self-managed Book Publisher'
+ name_es: 'Editorial Autogestiva'
+ gem: 'editorial-autogestiva-jekyll-theme'
+ url: 'https://subelamarea.sutty.nl/'
+ description_en: "A theme for self-managed book publishers."
+ description_es: 'Una plantilla para catálogos de editoriales autogestivas.'
+ license: 'https://0xacab.org/sutty/jekyll/editorial-autogestiva-jekyll-theme/-/blob/master/LICENSE.txt'
- name_en: 'Other themes'
name_es: 'Mi propio diseño'
gem: 'sutty-theme-own'
diff --git a/test/controllers/api/v1/contact_controller_test.rb b/test/controllers/api/v1/contact_controller_test.rb
new file mode 100644
index 00000000..e2aabfd3
--- /dev/null
+++ b/test/controllers/api/v1/contact_controller_test.rb
@@ -0,0 +1,124 @@
+# frozen_string_literal: true
+
+require 'test_helper'
+
+module Api
+ module V1
+ class ContactControllerTest < ActionDispatch::IntegrationTest
+ setup do
+ @rol = create :rol
+ @site = @rol.site
+ @usuarie = @rol.usuarie
+ end
+
+ teardown do
+ @site&.destroy
+ end
+
+ test 'el sitio tiene que existir' do
+ @site.destroy
+
+ post v1_site_contact_url(site_id: @site.hostname),
+ params: {
+ name: SecureRandom.hex,
+ pronouns: SecureRandom.hex,
+ contact: SecureRandom.hex,
+ from: "#{SecureRandom.hex}@sutty.nl",
+ body: SecureRandom.hex,
+ gdpr: true
+ }
+
+ assert_response :unprocessable_entity, response.status
+ assert_equal 'site_exists', response.body
+ end
+
+ test 'hay que enviar desde el sitio principal' do
+ post v1_site_contact_url(site_id: @site.hostname),
+ params: {
+ name: SecureRandom.hex,
+ pronouns: SecureRandom.hex,
+ contact: SecureRandom.hex,
+ from: "#{SecureRandom.hex}@sutty.nl",
+ body: SecureRandom.hex,
+ gdpr: true
+ }
+
+ assert_response :unprocessable_entity, response.status
+ assert_equal 'site_is_origin', response.body
+ end
+
+ test 'hay que dar consentimiento' do
+ post v1_site_contact_url(site_id: @site.hostname),
+ headers: {
+ Origin: @site.url
+ },
+ params: {
+ name: SecureRandom.hex,
+ pronouns: SecureRandom.hex,
+ contact: SecureRandom.hex,
+ from: "#{SecureRandom.hex}@sutty.nl",
+ body: SecureRandom.hex
+ }
+
+ assert_response :unprocessable_entity, response.status
+ assert_equal 'gave_consent', response.body
+ end
+
+ test 'enviar un mensaje genera correos' do
+ ActionMailer::Base.deliveries.clear
+
+ redirect = "#{@site.url}/?thanks"
+
+ 10.times do
+ create :rol, site: @site
+ end
+
+ post v1_site_contact_url(site_id: @site.hostname),
+ headers: {
+ Origin: @site.url
+ },
+ params: {
+ name: SecureRandom.hex,
+ pronouns: SecureRandom.hex,
+ contact: SecureRandom.hex,
+ from: "#{SecureRandom.hex}@sutty.nl",
+ body: SecureRandom.hex,
+ gdpr: true,
+ redirect: redirect
+ }
+
+ assert_equal redirect, response.headers['Location']
+ assert_equal 2, ActionMailer::Base.deliveries.size
+ end
+
+ test 'se puede enviar mensajes a dominios propios' do
+ ActionMailer::Base.deliveries.clear
+
+ @site.update name: 'example.org.'
+
+ redirect = "#{@site.url}?thanks"
+
+ 10.times do
+ create :rol, site: @site
+ end
+
+ post v1_site_contact_url(site_id: @site.hostname),
+ headers: {
+ Origin: @site.url
+ },
+ params: {
+ name: SecureRandom.hex,
+ pronouns: SecureRandom.hex,
+ contact: SecureRandom.hex,
+ from: "#{SecureRandom.hex}@sutty.nl",
+ body: SecureRandom.hex,
+ gdpr: true,
+ redirect: redirect
+ }
+
+ assert_equal redirect, response.headers['Location']
+ assert_equal 2, ActionMailer::Base.deliveries.size
+ end
+ end
+ end
+end
diff --git a/test/models/site_test.rb b/test/models/site_test.rb
index bb6a4777..c50bfd20 100644
--- a/test/models/site_test.rb
+++ b/test/models/site_test.rb
@@ -3,14 +3,16 @@
require 'test_helper'
class SiteTest < ActiveSupport::TestCase
+ def site
+ @site ||= create :site
+ end
+
# Asegurarse que el sitio se destruye al terminar de usarlo
teardown do
- @site&.destroy
+ site&.destroy
end
test 'se puede crear un sitio' do
- site = create :site
-
assert site.valid?
# TODO: Mover a la validación del sitio o hacer algo similar
assert File.directory?(site.path)
@@ -19,78 +21,92 @@ class SiteTest < ActiveSupport::TestCase
end
test 'el nombre tiene que ser único' do
- @site = create :site
- site2 = build :site, name: @site.name
+ site2 = build :site, name: site.name
assert_not site2.valid?
end
test 'el nombre del sitio puede contener subdominios' do
- site = build :site, name: 'hola.chau'
+ @site = build :site, name: 'hola.chau'
site.validate
assert_not site.errors.messages[:name].present?
end
test 'el nombre del sitio puede terminar con punto' do
- site = build :site, name: 'hola.chau.'
+ @site = build :site, name: 'hola.chau.'
site.validate
assert_not site.errors.messages[:name].present?
end
test 'el nombre del sitio no puede contener wildcard' do
- site = build :site, name: '*.chau'
+ @site = build :site, name: '*.chau'
site.validate
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 = build :site, name: 'A_Z!'
site.validate
assert site.errors.messages[:name].present?
end
test 'al destruir un sitio se eliminan los archivos' do
- site = create :site
+ @site = create :site
assert site.destroy
assert !File.directory?(site.path)
end
test 'se puede leer un sitio' do
- site = create :site
-
assert site.valid?
assert !site.posts.empty?
end
test 'se pueden renombrar' do
- @site = create :site
- path = @site.path
+ path = site.path
- @site.update_attribute :name, SecureRandom.hex
+ site.update_attribute :name, SecureRandom.hex
- assert_not_equal path, @site.path
- assert File.directory?(@site.path)
+ assert_not_equal path, site.path
+ assert File.directory?(site.path)
assert_not File.directory?(path)
end
test 'no se puede guardar html en title y description' do
- site = build :site
- site.description = "hola"
- site.title = "hola"
+ _site = build :site
+ _site.description = "hola"
+ _site.title = "hola"
- assert_equal 'hola', site.description
- assert_equal 'hola', site.title
+ assert_equal 'hola', _site.description
+ assert_equal 'hola', _site.title
end
test 'el sitio tiene artículos en distintos idiomas' do
- @site = create :site
-
I18n.available_locales.each do |locale|
- assert @site.posts(lang: locale).size.positive?
+ assert site.posts(lang: locale).size.positive?
end
end
+
+ test 'tienen un hostname que puede cambiar' do
+ assert_equal "#{site.name}.#{Site.domain}", site.hostname
+
+ site.name = name = SecureRandom.hex
+
+ assert_equal "#{name}.#{Site.domain}", site.hostname
+ end
+
+ test 'se pueden traer los datos de una plantilla' do
+ @site = create :site, design: Design.find_by(gem: 'editorial-autogestiva-jekyll-theme')
+
+ assert_equal %i[post], site.layouts.to_h.keys
+
+ site.config.write
+ site.reload
+
+ assert_equal %w[book editorial post], site.data['layouts'].keys
+ assert_equal %i[book editorial post], site.layouts.to_h.keys
+ end
end