From dcaac06fa4baa30ce1b55bde3b8c1ffdac88f4f8 Mon Sep 17 00:00:00 2001 From: f Date: Mon, 9 Aug 2021 20:12:43 -0300 Subject: [PATCH] =?UTF-8?q?Los=20sitios=20pueden=20tener=20una=20ubicaci?= =?UTF-8?q?=C3=B3n=20can=C3=B3nica?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/site.rb | 76 +------------- app/models/site/deployment.rb | 99 +++++++++++++++++++ config/locales/en.yml | 2 + config/locales/es.yml | 2 + ...809155434_add_canonical_deploy_to_sites.rb | 26 +++++ .../api/v1/contact_controller_test.rb | 5 +- .../api/v1/sites_controller_test.rb | 2 +- test/models/site_test.rb | 8 +- 8 files changed, 140 insertions(+), 80 deletions(-) create mode 100644 app/models/site/deployment.rb create mode 100644 db/migrate/20210809155434_add_canonical_deploy_to_sites.rb diff --git a/app/models/site.rb b/app/models/site.rb index d0c420f1..7668cb6d 100644 --- a/app/models/site.rb +++ b/app/models/site.rb @@ -7,6 +7,7 @@ class Site < ApplicationRecord include Site::Forms include Site::FindAndReplace include Site::Api + include Site::Deployment include Tienda # Cifrar la llave privada que cifra y decifra campos ocultos. Sutty @@ -15,19 +16,11 @@ class Site < ApplicationRecord # protege de acceso al panel de Sutty! encrypts :private_key - # TODO: Hacer que los diferentes tipos de deploy se auto registren - # @see app/services/site_service.rb - DEPLOYS = %i[local private www zip hidden_service].freeze - - validates :name, uniqueness: true, hostname: { - allow_root_label: true - } - validates :design_id, presence: true + validates_uniqueness_of :name validates_inclusion_of :status, in: %w[waiting enqueued building] validates_presence_of :title validates :description, length: { in: 50..160 } - validate :deploy_local_presence validate :compatible_layouts, on: :update attr_reader :incompatible_layouts @@ -38,9 +31,6 @@ class Site < ApplicationRecord belongs_to :licencia has_many :log_entries, dependent: :destroy - has_many :deploys, dependent: :destroy - has_many :access_logs, through: :deploys - has_many :build_stats, through: :deploys has_many :roles, dependent: :destroy has_many :usuaries, -> { where('roles.rol = ?', 'usuarie') }, through: :roles @@ -59,13 +49,11 @@ class Site < ApplicationRecord after_initialize :load_jekyll after_create :load_jekyll, :static_file_migration! # Cambiar el nombre del directorio - before_update :update_name!, :update_deploy_local_hostname!, if: :name_changed? + before_update :update_name!, if: :name_changed? before_save :add_private_key_if_missing! # Guardar la configuración si hubo cambios after_save :sync_attributes_with_config! - accepts_nested_attributes_for :deploys, allow_destroy: true - # El sitio en Jekyll attr_reader :jekyll @@ -86,46 +74,6 @@ class Site < ApplicationRecord @repository ||= Site::Repository.new path end - # El primer deploy del sitio - # - # @return [DeployLocal] - def deploy_local - @deploy_local ||= deploys.order(created_at: :asc).find_by(type: 'DeployLocal') - end - - # Todas las URLs posibles para este sitio, ordenados según fecha de - # creación. - # - # @param :slash [Boolean] Con o sin / al final, por defecto con - # @return [Array] - def urls(slash: true) - deploys.order(created_at: :asc).map(&:url).map do |url| - slash ? "#{url}/" : url - end - end - - # Todos los hostnames, ordenados según fecha de creación. - # - # @return [Array] - def hostnames - deploys.order(created_at: :asc).pluck(:hostname) - end - - # Obtiene la URL principal - # - # @param :slash [Boolean] - # @return [String] - def url(slash: true) - deploy_local.url.tap do |url| - "#{url}/" if slash - end - end - - # TODO: Usar DeployCanonical - def hostname - deploy_local.hostname - end - def invitade?(usuarie) !invitades.find_by(id: usuarie.id).nil? end @@ -468,29 +416,11 @@ class Site < ApplicationRecord config.hostname = hostname end - # Si cambia el nombre queremos actualizarlo en el DeployLocal - def update_deploy_local_hostname! - deploy_local&.update hostname: name - end - # Migra los archivos a Sutty def static_file_migration! Site::StaticFileMigration.new(site: self).migrate! end - # Valida si el sitio tiene al menos una forma de alojamiento asociada - # y es la local - # - # TODO: Volver opcional el alojamiento local, pero ahora mismo está - # atado a la generación del sitio así que no puede faltar - def deploy_local_presence - # Usamos size porque queremos saber la cantidad de deploys sin - # guardar también - return if deploys.size.positive? && deploys.map(&:type).include?('DeployLocal') - - errors.add(:deploys, I18n.t('activerecord.errors.models.site.attributes.deploys.deploy_local_presence')) - end - # Valida que al cambiar de plantilla no tengamos artículos en layouts # inexistentes. def compatible_layouts diff --git a/app/models/site/deployment.rb b/app/models/site/deployment.rb new file mode 100644 index 00000000..f88d2a0c --- /dev/null +++ b/app/models/site/deployment.rb @@ -0,0 +1,99 @@ +# frozen_string_literal: true + +class Site + # Abstrae todo el comportamiento de publicación del sitio en un + # módulo. + module Deployment + extend ActiveSupport::Concern + + included do + # TODO: Hacer que los diferentes tipos de deploy se auto registren + # @see app/services/site_service.rb + DEPLOYS = %i[local private www zip hidden_service].freeze + + validates :name, + format: { with: /\A[0-9a-z\-]+\z/, + message: I18n.t('activerecord.errors.models.site.attributes.name.no_subdomains') } + validates :name, hostname: true + validates_presence_of :canonical_deploy + validate :deploy_local_presence + + has_one :canonical_deploy, class_name: 'Deploy' + has_many :deploys, dependent: :destroy + has_many :access_logs, through: :deploys + has_many :build_stats, through: :deploys + + before_validation :deploy_local_is_default_canonical_deploy!, unless: :canonical_deploy_id? + before_update :update_deploy_local_hostname!, if: :name_changed? + + accepts_nested_attributes_for :deploys, allow_destroy: true + + # El primer deploy del sitio, si no existe en la base de datos es + # porque recién estamos creando el sitio y todavía no se guardó. + # + # @return [DeployLocal] + def deploy_local + @deploy_local ||= deploys.order(created_at: :asc).find_by(type: 'DeployLocal') || deploys.find do |d| + d.type == 'DeployLocal' + end + end + + # Obtiene la URL principal + # + # @param :slash [Boolean] + # @return [String] + def canonical_url(slash: true) + canonical_deploy.url.tap do |url| + "#{url}/" if slash + end + end + alias_method :url, :canonical_url + + # Devuelve todas las URLs posibles + # + # @param :slash [Boolean] + # @return [Array] + def urls(slash: true) + deploys.map(&:url).map do |url| + slash ? "#{url}/" : url + end + end + + # Obtiene el hostname principal + # + # @return [String] + def hostname + canonical_deploy.hostname + end + + private + + # Si cambia el nombre queremos actualizarlo en el DeployLocal y + # recargar el deploy canónico para tomar el nombre que + # corresponda. + def update_deploy_local_hostname! + deploy_local.update(hostname: name) + canonical_deploy.reload if canonical_deploy == deploy_local + end + + # Si no asignamos un deploy canónico en el momento le asignamos el + # deploy local + def deploy_local_is_default_canonical_deploy! + self.canonical_deploy ||= deploy_local + end + + # Valida si el sitio tiene al menos una forma de alojamiento asociada + # y es la local + # + # TODO: Volver opcional el alojamiento local, pero ahora mismo está + # atado a la generación del sitio así que no puede faltar + def deploy_local_presence + # Usamos size porque queremos saber la cantidad de deploys sin + # guardar también + return if deploys.size.positive? && deploys.map(&:type).include?('DeployLocal') + + errors.add(:deploys, I18n.t('activerecord.errors.models.site.attributes.deploys.deploy_local_presence')) + end + end + end +end diff --git a/config/locales/en.yml b/config/locales/en.yml index 25b662bf..b59a5f21 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -145,6 +145,8 @@ en: models: site: attributes: + name: + no_subdomains: 'Name cannot contain dots' deploys: deploy_local_presence: 'We need to be build the site!' design_id: diff --git a/config/locales/es.yml b/config/locales/es.yml index eac8a49d..68ee3669 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -145,6 +145,8 @@ es: models: site: attributes: + name: + no_subdomains: 'El nombre no puede contener puntos' deploys: deploy_local_presence: '¡Necesitamos poder generar el sitio!' design_id: diff --git a/db/migrate/20210809155434_add_canonical_deploy_to_sites.rb b/db/migrate/20210809155434_add_canonical_deploy_to_sites.rb new file mode 100644 index 00000000..f0b24053 --- /dev/null +++ b/db/migrate/20210809155434_add_canonical_deploy_to_sites.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +# Los sitios pueden tener muchos tipos de publicación pero solo uno es +# el principal. Al usar un campo específico, podemos validar mejor su +# presencia y modificación. +# +# El valor por defecto es 0 para poder crear la columna sin modificarla +# después, pero la idea es que nunca haya ceros. +class AddCanonicalDeployToSites < ActiveRecord::Migration[6.1] + def up + add_belongs_to :sites, :canonical_deploy, index: true, null: false, default: 0 + + # Si el sitio tenía un dominio alternativo, usar ese en lugar del + # local, asumiendo que es el primero de todos los posibles. + Site.find_each do |site| + deploy = site.deploys.order(created_at: :asc).find_by_type('DeployAlternativeDomain') + deploy ||= site.deploy_local + + site.update canonical_deploy_id: deploy.id + end + end + + def down + remove_belongs_to :sites, :canonical_deploy, index: true + end +end diff --git a/test/controllers/api/v1/contact_controller_test.rb b/test/controllers/api/v1/contact_controller_test.rb index 2bf12f1f..21159c72 100644 --- a/test/controllers/api/v1/contact_controller_test.rb +++ b/test/controllers/api/v1/contact_controller_test.rb @@ -21,13 +21,14 @@ module Api end test 'el sitio tiene que existir' do + hostname = @site.hostname @site.destroy - get v1_site_contact_cookie_url(@site.hostname, **@host) + get v1_site_contact_cookie_url(hostname, **@host) assert_not cookies[@site.name] - post v1_site_contact_url(site_id: @site.hostname, form: :contacto, **@host), + post v1_site_contact_url(site_id: hostname, form: :contacto, **@host), params: { name: SecureRandom.hex, pronouns: SecureRandom.hex, diff --git a/test/controllers/api/v1/sites_controller_test.rb b/test/controllers/api/v1/sites_controller_test.rb index 5623edca..5007e4f4 100644 --- a/test/controllers/api/v1/sites_controller_test.rb +++ b/test/controllers/api/v1/sites_controller_test.rb @@ -23,7 +23,7 @@ module Api test 'se puede obtener un listado de todos' do get v1_sites_url(host: "api.#{Site.domain}"), headers: @authorization, as: :json - assert_equal Site.all.pluck(:name), JSON.parse(response.body) + assert_equal Deploy.all.pluck(:hostname), JSON.parse(response.body) end end end diff --git a/test/models/site_test.rb b/test/models/site_test.rb index 481c9bbc..cfef44e7 100644 --- a/test/models/site_test.rb +++ b/test/models/site_test.rb @@ -26,18 +26,18 @@ class SiteTest < ActiveSupport::TestCase assert_not site2.valid? end - test 'el nombre del sitio puede contener subdominios' do + test 'el nombre del sitio no puede contener subdominios' do @site = build :site, name: 'hola.chau' site.validate - assert_not site.errors.messages[:name].present? + assert site.errors.messages[:name].present? end - test 'el nombre del sitio puede terminar con punto' do + test 'el nombre del sitio no puede terminar con punto' do @site = build :site, name: 'hola.chau.' site.validate - assert_not site.errors.messages[:name].present? + assert site.errors.messages[:name].present? end test 'el nombre del sitio no puede contener wildcard' do