mirror of
https://0xacab.org/sutty/sutty
synced 2024-11-22 21:46:22 +00:00
formularios de contacto!
This commit is contained in:
parent
67b232c626
commit
477b417567
14 changed files with 232 additions and 99 deletions
|
@ -9,6 +9,18 @@ module Api
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
# Realiza la inversa de Site#hostname
|
||||||
|
#
|
||||||
|
# TODO: El sitio sutty.nl no aplica a ninguno de estos y le
|
||||||
|
# tuvimos que poner 'sutty.nl..sutty.nl' para pasar el test.
|
||||||
|
def site_id
|
||||||
|
@site_id ||= if params[:site_id].end_with? Site.domain
|
||||||
|
params[:site_id].sub(/\.#{Site.domain}\z/, '')
|
||||||
|
else
|
||||||
|
params[:site_id] + '.'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def origin
|
def origin
|
||||||
request.headers['Origin']
|
request.headers['Origin']
|
||||||
end
|
end
|
||||||
|
|
|
@ -12,9 +12,18 @@ module Api
|
||||||
# alguna forma los errores pero tampoco queremos que nos usen
|
# alguna forma los errores pero tampoco queremos que nos usen
|
||||||
# recursos.
|
# recursos.
|
||||||
#
|
#
|
||||||
# TODO: Agregar los mismos chequeos que en PostsController
|
# Devolvemos un error 428: Precondition Required dando la
|
||||||
|
# oportunidad a les visitantes de reintentar en el caso de falsos
|
||||||
|
# positivos. No devolvemos contenido para que el servidor web
|
||||||
|
# capture y muestra la página de error específica de cada sitio o
|
||||||
|
# la genérica de Sutty.
|
||||||
|
#
|
||||||
|
# XXX: Ordenar en orden ascendiente según uso de recursos.
|
||||||
|
before_action :cookie_is_valid?, unless: :performed?
|
||||||
|
before_action :valid_authenticity_token_in_cookie?, unless: :performed?
|
||||||
before_action :site_exists?, unless: :performed?
|
before_action :site_exists?, unless: :performed?
|
||||||
before_action :site_is_origin?, unless: :performed?
|
before_action :site_is_origin?, unless: :performed?
|
||||||
|
before_action :form_exists?, unless: :performed?
|
||||||
before_action :from_is_address?, unless: :performed?
|
before_action :from_is_address?, unless: :performed?
|
||||||
before_action :gave_consent?, unless: :performed?
|
before_action :gave_consent?, unless: :performed?
|
||||||
|
|
||||||
|
@ -28,67 +37,91 @@ module Api
|
||||||
# No hacer nada si no se pasaron los chequeos
|
# No hacer nada si no se pasaron los chequeos
|
||||||
return if performed?
|
return if performed?
|
||||||
|
|
||||||
|
# TODO: Verificar que los campos obligatorios hayan llegado!
|
||||||
|
|
||||||
# Si todo salió bien, enviar los correos y redirigir al sitio.
|
# Si todo salió bien, enviar los correos y redirigir al sitio.
|
||||||
# El sitio nos dice a dónde tenemos que ir.
|
# El sitio nos dice a dónde tenemos que ir.
|
||||||
ContactJob.perform_async site_id: site.id,
|
ContactJob.perform_async site_id: site.id,
|
||||||
**contact_params.to_h.symbolize_keys
|
form_name: params[:form],
|
||||||
|
form: contact_params.to_h.symbolize_keys
|
||||||
|
|
||||||
redirect_to contact_params[:redirect] || site.url
|
redirect_to contact_params[:redirect] || origin.to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def site_cookie
|
||||||
|
@site_cookie ||= cookies.encrypted[site_id]
|
||||||
|
end
|
||||||
|
|
||||||
|
# Comprueba que no se haya reutilizado una cookie vencida
|
||||||
|
#
|
||||||
|
# XXX: Si el navegador envió una cookie vencida es porque la está
|
||||||
|
# reutilizando, probablemente de forma maliciosa? Pero también
|
||||||
|
# puede ser que haya tardado más de media hora en enviar el
|
||||||
|
# formulario.
|
||||||
|
def cookie_is_valid?
|
||||||
|
head :precondition_required unless (site_cookie.try(:[], 'expires') || 0) > Time.now.to_i
|
||||||
|
end
|
||||||
|
|
||||||
|
# Queremos comprobar que la cookie corresponda con la sesión. La
|
||||||
|
# cookie puede haber vencido, así que es uno de los chequeos más
|
||||||
|
# simples que hacemos.
|
||||||
|
#
|
||||||
|
# TODO: Pensar una forma de redirigir al origen sin vaciar el
|
||||||
|
# formulario para que le usuarie recargue la cookie.
|
||||||
|
def valid_authenticity_token_in_cookie?
|
||||||
|
return if valid_authenticity_token? session, site_cookie['csrf']
|
||||||
|
|
||||||
|
head :precondition_required
|
||||||
|
end
|
||||||
|
|
||||||
# Comprueba que el sitio existe
|
# Comprueba que el sitio existe
|
||||||
#
|
#
|
||||||
# TODO: Responder con una zip bomb!
|
# TODO: Responder con una zip bomb!
|
||||||
def site_exists?
|
def site_exists?
|
||||||
render html: body(:site_exists), status: status unless site
|
head :precondition_required if site.nil?
|
||||||
end
|
end
|
||||||
|
|
||||||
# Comprueba que el mensaje vino fue enviado desde el sitio
|
# Comprueba que el mensaje vino fue enviado desde el sitio
|
||||||
def site_is_origin?
|
def site_is_origin?
|
||||||
return if site.url.start_with? origin.to_s
|
return if origin.to_s.start_with? site.url(slash: false)
|
||||||
|
|
||||||
render html: body(:site_is_origin), status: status
|
head :precondition_required
|
||||||
end
|
end
|
||||||
|
|
||||||
# Detecta si la dirección de contacto es válida. Además es
|
# Detecta si la dirección de contacto es válida. Además es
|
||||||
# opcional.
|
# opcional.
|
||||||
def from_is_address?
|
def from_is_address?
|
||||||
return unless contact_params[:from]
|
return if contact_params[:from].empty?
|
||||||
return if EmailAddress.valid? contact_params[:from]
|
return if EmailAddress.valid? contact_params[:from]
|
||||||
|
|
||||||
render html: body(:from_is_address), status: status
|
head :precondition_required
|
||||||
end
|
end
|
||||||
|
|
||||||
# No aceptar nada si no dió su consentimiento
|
# No aceptar nada si no dió su consentimiento
|
||||||
def gave_consent?
|
def gave_consent?
|
||||||
return if contact_params[:gdpr].present?
|
return if contact_params[:consent].present?
|
||||||
|
|
||||||
render html: body(:gave_consent), status: status
|
head :precondition_required
|
||||||
end
|
end
|
||||||
|
|
||||||
# Realiza la inversa de Site#hostname
|
# Los campos que se envían tienen que corresponder con un
|
||||||
#
|
# formulario de contacto.
|
||||||
# TODO: El sitio sutty.nl no aplica a ninguno de estos y le
|
def form_exists?
|
||||||
# tuvimos que poner 'sutty.nl..sutty.nl' para pasar el test.
|
return if site.form? params[:form]
|
||||||
def site_id
|
|
||||||
@site_id ||= if params[:site_id].end_with? Site.domain
|
head :precondition_required
|
||||||
params[:site_id].sub(/\.#{Site.domain}\z/, '')
|
|
||||||
else
|
|
||||||
params[:site_id] + '.'
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Encuentra el sitio
|
# Encuentra el sitio o devuelve nulo
|
||||||
def site
|
def site
|
||||||
@site ||= Site.find_by(name: site_id)
|
@site ||= Site.find_by(name: site_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Parámetros limpios
|
# Parámetros limpios
|
||||||
def contact_params
|
def contact_params
|
||||||
@contact_params ||= params.permit(:gdpr, :name, :pronouns, :from,
|
@contact_params ||= params.permit(site.form(params[:form]).params)
|
||||||
:contact, :body, :redirect)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Para poder testear, enviamos un mensaje en el cuerpo de la
|
# Para poder testear, enviamos un mensaje en el cuerpo de la
|
||||||
|
|
|
@ -2,31 +2,48 @@
|
||||||
|
|
||||||
module Api
|
module Api
|
||||||
module V1
|
module V1
|
||||||
class InvitadesController < BaseController
|
|
||||||
# Obtiene una cookie válida por el tiempo especificado por el
|
# Obtiene una cookie válida por el tiempo especificado por el
|
||||||
# sitio.
|
# sitio.
|
||||||
#
|
#
|
||||||
# Aunque visitemos el sitio varias veces enviando la cookie
|
# Aunque visitemos el sitio varias veces enviando la cookie
|
||||||
# anterior, la cookie se renueva.
|
# anterior, la cookie se renueva.
|
||||||
|
class InvitadesController < BaseController
|
||||||
|
# Cookie para el formulario de contacto
|
||||||
|
def contact_cookie
|
||||||
|
@site, contact = Site.where(name: site_id, contact: true)
|
||||||
|
.pluck(:name, :contact)
|
||||||
|
.first
|
||||||
|
|
||||||
|
set_cookie if contact
|
||||||
|
|
||||||
|
render file: Rails.root.join('public', '1x1.png'),
|
||||||
|
content_type: 'image/png',
|
||||||
|
layout: false
|
||||||
|
end
|
||||||
|
|
||||||
|
# Cookie para colaboraciones anónimas
|
||||||
def cookie
|
def cookie
|
||||||
# XXX: Prestar atención a que estas acciones sean lo más rápidas
|
# XXX: Prestar atención a que estas acciones sean lo más rápidas
|
||||||
# y utilicen la menor cantidad posible de recursos, porque son
|
# y utilicen la menor cantidad posible de recursos, porque son
|
||||||
# un vector de DDOS.
|
# un vector de DDOS.
|
||||||
site, anon = Site.where(name: params[:site_id], colaboracion_anonima: true)
|
@site, anon = Site.where(name: site_id, colaboracion_anonima: true)
|
||||||
.pluck(:name, :colaboracion_anonima)
|
.pluck(:name, :colaboracion_anonima)
|
||||||
.first
|
.first
|
||||||
|
|
||||||
|
set_cookie if anon
|
||||||
|
|
||||||
|
render json: {}, status: :ok
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
# La cookie no es accesible a través de JS y todo su contenido
|
# La cookie no es accesible a través de JS y todo su contenido
|
||||||
# está cifrado para que no lo modifiquen les visitantes
|
# está cifrado para que no lo modifiquen les visitantes
|
||||||
#
|
#
|
||||||
# Enviamos un token de protección CSRF
|
# Enviamos un token de protección CSRF
|
||||||
if anon
|
def set_cookie
|
||||||
headers['Access-Control-Allow-Credentials'] = true
|
|
||||||
headers['Access-Control-Allow-Origin'] = "https://#{site}"
|
|
||||||
headers['Vary'] = 'Origin'
|
|
||||||
|
|
||||||
expires = 30.minutes
|
expires = 30.minutes
|
||||||
cookies.encrypted[site] = {
|
cookies.encrypted[@site] = {
|
||||||
httponly: true,
|
httponly: true,
|
||||||
secure: !Rails.env.test?,
|
secure: !Rails.env.test?,
|
||||||
expires: expires,
|
expires: expires,
|
||||||
|
@ -37,9 +54,6 @@ module Api
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
render html: nil, status: :no_content
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,4 +3,10 @@
|
||||||
# Base para trabajos
|
# Base para trabajos
|
||||||
class ApplicationJob < ActiveJob::Base
|
class ApplicationJob < ActiveJob::Base
|
||||||
include SuckerPunch::Job
|
include SuckerPunch::Job
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def site
|
||||||
|
@site ||= Site.find @params[:site_id]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,23 +5,24 @@ class ContactJob < ApplicationJob
|
||||||
def perform(**args)
|
def perform(**args)
|
||||||
@params = args
|
@params = args
|
||||||
|
|
||||||
|
# Sanitizar los valores
|
||||||
|
args[:form].keys.each do |key|
|
||||||
|
args[:form][key] = ActionController::Base.helpers.sanitize args[:form][key]
|
||||||
|
end
|
||||||
|
|
||||||
# Enviar de a 10 usuaries para minimizar el riesgo que nos
|
# Enviar de a 10 usuaries para minimizar el riesgo que nos
|
||||||
# consideren spammers.
|
# consideren spammers.
|
||||||
#
|
#
|
||||||
# TODO: #i18n. Agrupar usuaries por su idioma
|
# TODO: #i18n. Agrupar usuaries por su idioma
|
||||||
|
|
||||||
usuaries.each_slice(10) do |u|
|
usuaries.each_slice(10) do |u|
|
||||||
ContactMailer.with(**args.merge(usuaries: u, title: site.title))
|
ContactMailer.with(**args.merge(usuaries_emails: u))
|
||||||
.notify_usuaries.deliver_now
|
.notify_usuaries.deliver_now
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def site
|
|
||||||
@site ||= Site.find @params[:site_id]
|
|
||||||
end
|
|
||||||
|
|
||||||
# Trae solo les usuaries definitives para eliminar un vector de ataque
|
# Trae solo les usuaries definitives para eliminar un vector de ataque
|
||||||
# donde alguien crea un sitio, agrega a muches usuaries y les envía
|
# donde alguien crea un sitio, agrega a muches usuaries y les envía
|
||||||
# correos.
|
# correos.
|
||||||
|
|
|
@ -9,6 +9,10 @@ class ApplicationMailer < ActionMailer::Base
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def site
|
||||||
|
@site ||= Site.find @params[:site_id]
|
||||||
|
end
|
||||||
|
|
||||||
def inline_logo!
|
def inline_logo!
|
||||||
attachments.inline['logo.png'] ||=
|
attachments.inline['logo.png'] ||=
|
||||||
File.read(Rails.root.join('app', 'assets', 'images', 'logo.png'))
|
File.read(Rails.root.join('app', 'assets', 'images', 'logo.png'))
|
||||||
|
|
|
@ -2,10 +2,35 @@
|
||||||
|
|
||||||
# Formulario de contacto
|
# Formulario de contacto
|
||||||
class ContactMailer < ApplicationMailer
|
class ContactMailer < ApplicationMailer
|
||||||
|
attr_reader :form
|
||||||
|
|
||||||
# Enviar el formulario de contacto a les usuaries
|
# Enviar el formulario de contacto a les usuaries
|
||||||
def notify_usuaries
|
def notify_usuaries
|
||||||
mail to: params[:usuaries],
|
subject = "[#{site.title}] #{params[:form_name].humanize}"
|
||||||
reply_to: params[:from],
|
params[:form_definition] = site.form(params[:form_name])
|
||||||
subject: I18n.t('contact_mailer.subject', site: params[:title])
|
|
||||||
|
attachments[params[:form_name] + '.csv'] = generate_csv
|
||||||
|
|
||||||
|
mail to: params[:usuaries_emails],
|
||||||
|
reply_to: params[:form][:from],
|
||||||
|
subject: subject
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# El CSV es un archivo adjunto con dos filas, una con las etiquetas de
|
||||||
|
# los campos en la cabecera y otra con los valores.
|
||||||
|
def generate_csv
|
||||||
|
csv = ["\xEF\xBB\xBF"]
|
||||||
|
csv << params[:form].keys.map do |field|
|
||||||
|
params[:form_definition].t(field)
|
||||||
|
end.to_csv(col_sep: ';', force_quotes: true)
|
||||||
|
|
||||||
|
csv << params[:form].values.to_csv(col_sep: ';', force_quotes: true)
|
||||||
|
|
||||||
|
{
|
||||||
|
mime_type: 'text/csv; charset=utf-8',
|
||||||
|
content: csv.join
|
||||||
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
# Usuaria
|
# Usuaria
|
||||||
class Site < ApplicationRecord
|
class Site < ApplicationRecord
|
||||||
include FriendlyId
|
include FriendlyId
|
||||||
|
include Site::Forms
|
||||||
|
|
||||||
# TODO: Hacer que los diferentes tipos de deploy se auto registren
|
# TODO: Hacer que los diferentes tipos de deploy se auto registren
|
||||||
# @see app/services/site_service.rb
|
# @see app/services/site_service.rb
|
||||||
|
@ -77,8 +78,16 @@ class Site < ApplicationRecord
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def url
|
# Devuelve la URL siempre actualizada a través del hostname
|
||||||
"https://#{hostname}/"
|
#
|
||||||
|
# @param slash Boolean Agregar / al final o no
|
||||||
|
# @return String La URL con o sin / al final
|
||||||
|
def url(slash: true)
|
||||||
|
if slash
|
||||||
|
'https://' + hostname + '/'
|
||||||
|
else
|
||||||
|
'https://' + hostname
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def invitade?(usuarie)
|
def invitade?(usuarie)
|
||||||
|
@ -399,9 +408,7 @@ class Site < ApplicationRecord
|
||||||
def deploy_local_presence
|
def deploy_local_presence
|
||||||
# Usamos size porque queremos saber la cantidad de deploys sin
|
# Usamos size porque queremos saber la cantidad de deploys sin
|
||||||
# guardar también
|
# guardar también
|
||||||
if deploys.size.positive? && deploys.map(&:type).include?('DeployLocal')
|
return if deploys.size.positive? && deploys.map(&:type).include?('DeployLocal')
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
errors.add(:deploys, I18n.t('activerecord.errors.models.site.attributes.deploys.deploy_local_presence'))
|
errors.add(:deploys, I18n.t('activerecord.errors.models.site.attributes.deploys.deploy_local_presence'))
|
||||||
end
|
end
|
||||||
|
|
56
app/models/site/forms.rb
Normal file
56
app/models/site/forms.rb
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Site
|
||||||
|
module Forms
|
||||||
|
# Esta clase es un Hash que es capaz de convertirse a strong params
|
||||||
|
# según la definición del formulario en el sitio.
|
||||||
|
class Form < Hash
|
||||||
|
# Convierte el formulario a strong params
|
||||||
|
#
|
||||||
|
# @return Array
|
||||||
|
def params
|
||||||
|
map do |field, definition|
|
||||||
|
next if EXCLUDED_FIELDS.include? definition['type']
|
||||||
|
|
||||||
|
if ARRAY_FIELDS.include? definition['type']
|
||||||
|
{ field.to_sym => [] }
|
||||||
|
else
|
||||||
|
field.to_sym
|
||||||
|
end
|
||||||
|
end.compact
|
||||||
|
end
|
||||||
|
|
||||||
|
def t(field)
|
||||||
|
self[field.to_s]['label'][I18n.locale.to_s]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Campos del formulario que no son necesarios
|
||||||
|
EXCLUDED_FIELDS = %w[separator].freeze
|
||||||
|
# Campos que aceptan valores múltiples (checkboxes, input-map,
|
||||||
|
# input-tag)
|
||||||
|
ARRAY_FIELDS = %w[array].freeze
|
||||||
|
|
||||||
|
# Obtiene todos los formularios disponibles
|
||||||
|
#
|
||||||
|
# @return Array Formularios disponibles para este sitio
|
||||||
|
def forms
|
||||||
|
@forms ||= data.dig('forms').try(:keys) || []
|
||||||
|
end
|
||||||
|
|
||||||
|
# El nombre del formulario está disponible
|
||||||
|
#
|
||||||
|
# @param [String|Symbol] El nombre del formulario
|
||||||
|
def form?(name)
|
||||||
|
forms.include? name.to_s
|
||||||
|
end
|
||||||
|
|
||||||
|
# Obtiene un formulario
|
||||||
|
#
|
||||||
|
# @return Site::Forms::Form
|
||||||
|
def form(name)
|
||||||
|
@cached_forms ||= {}
|
||||||
|
@cached_forms[name] ||= Form[data.dig('forms', name)]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,11 +1,4 @@
|
||||||
-#
|
- @params[:form].each do |field, value|
|
||||||
Solo enviamos versión de texto para no aceptar HTML en el formulario
|
|
||||||
de contacto
|
|
||||||
|
|
||||||
%p
|
%p
|
||||||
%strong= I18n.t('contact_mailer.name', name: sanitize(@params[:name]),
|
%strong= @params[:form_definition].t(field) + ':'
|
||||||
pronouns: sanitize(@params[:pronouns]))
|
= value
|
||||||
%strong= I18n.t('contact_mailer.contact', contact: sanitize(@params[:contact]))
|
|
||||||
%strong= I18n.t('contact_mailer.gdpr', gdpr: sanitize(@params[:gdpr]))
|
|
||||||
|
|
||||||
%div= sanitize @params[:body]
|
|
||||||
|
|
|
@ -1,11 +1,3 @@
|
||||||
-#
|
- @params[:form].each do |field, value|
|
||||||
Solo enviamos versión de texto para no aceptar HTML en el formulario
|
= "#{@params[:form_definition].t(field)}: #{value}"
|
||||||
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]
|
|
||||||
|
|
|
@ -44,11 +44,6 @@ en:
|
||||||
document_missing: 'Needs an instance of Jekyll::Document'
|
document_missing: 'Needs an instance of Jekyll::Document'
|
||||||
no_method: '%{method} not allowed'
|
no_method: '%{method} not allowed'
|
||||||
seconds: '%{seconds} seconds'
|
seconds: '%{seconds} seconds'
|
||||||
contact_mailer:
|
|
||||||
subject: '[%site] Contact form'
|
|
||||||
name: 'Name: %{name} (%{pronouns})'
|
|
||||||
contact: 'Contact: %{contact}'
|
|
||||||
gdpr: 'Consent: %{gdpr}'
|
|
||||||
deploy_mailer:
|
deploy_mailer:
|
||||||
deployed:
|
deployed:
|
||||||
subject: "[Sutty] The site %{site} has been built"
|
subject: "[Sutty] The site %{site} has been built"
|
||||||
|
|
|
@ -46,11 +46,6 @@ es:
|
||||||
document_missing: 'Necesita una instancia de Jekyll::Document'
|
document_missing: 'Necesita una instancia de Jekyll::Document'
|
||||||
no_method: '%{method} no está permitido'
|
no_method: '%{method} no está permitido'
|
||||||
seconds: '%{seconds} segundos'
|
seconds: '%{seconds} segundos'
|
||||||
contact_mailer:
|
|
||||||
subject: '[%{site}] Formulario de contacto'
|
|
||||||
name: 'Nombre: %{name} (%{pronouns})'
|
|
||||||
contact: 'Contacto: %{contact}'
|
|
||||||
gdpr: 'Consentimiento: %{gdpr}'
|
|
||||||
deploy_mailer:
|
deploy_mailer:
|
||||||
deployed:
|
deployed:
|
||||||
subject: "[Sutty] El sitio %{site} ha sido generado"
|
subject: "[Sutty] El sitio %{site} ha sido generado"
|
||||||
|
|
|
@ -19,8 +19,8 @@ Rails.application.routes.draw do
|
||||||
resources :sites, only: %i[index], constraints: { site_id: /[a-z0-9\-\.]+/, id: /[a-z0-9\-\.]+/ } do
|
resources :sites, only: %i[index], constraints: { site_id: /[a-z0-9\-\.]+/, id: /[a-z0-9\-\.]+/ } do
|
||||||
get 'invitades/cookie', to: 'invitades#cookie'
|
get 'invitades/cookie', to: 'invitades#cookie'
|
||||||
resources :posts, only: %i[create]
|
resources :posts, only: %i[create]
|
||||||
get :'contact/cookie', to: 'contact#cookie'
|
get :'contact/cookie', to: 'invitades#contact_cookie'
|
||||||
post :contact, to: 'contact#receive'
|
post 'contact/:form', to: 'contact#receive'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue