Los sitios pueden tener una ubicación canónica

This commit is contained in:
f 2021-08-09 20:12:43 -03:00
parent baf6d203b8
commit dcaac06fa4
8 changed files with 140 additions and 80 deletions

View file

@ -7,6 +7,7 @@ class Site < ApplicationRecord
include Site::Forms include Site::Forms
include Site::FindAndReplace include Site::FindAndReplace
include Site::Api include Site::Api
include Site::Deployment
include Tienda include Tienda
# Cifrar la llave privada que cifra y decifra campos ocultos. Sutty # 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! # protege de acceso al panel de Sutty!
encrypts :private_key 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 :design_id, presence: true
validates_uniqueness_of :name
validates_inclusion_of :status, in: %w[waiting enqueued building] validates_inclusion_of :status, in: %w[waiting enqueued building]
validates_presence_of :title validates_presence_of :title
validates :description, length: { in: 50..160 } validates :description, length: { in: 50..160 }
validate :deploy_local_presence
validate :compatible_layouts, on: :update validate :compatible_layouts, on: :update
attr_reader :incompatible_layouts attr_reader :incompatible_layouts
@ -38,9 +31,6 @@ class Site < ApplicationRecord
belongs_to :licencia belongs_to :licencia
has_many :log_entries, dependent: :destroy 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 :roles, dependent: :destroy
has_many :usuaries, -> { where('roles.rol = ?', 'usuarie') }, has_many :usuaries, -> { where('roles.rol = ?', 'usuarie') },
through: :roles through: :roles
@ -59,13 +49,11 @@ class Site < ApplicationRecord
after_initialize :load_jekyll after_initialize :load_jekyll
after_create :load_jekyll, :static_file_migration! after_create :load_jekyll, :static_file_migration!
# Cambiar el nombre del directorio # 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! before_save :add_private_key_if_missing!
# Guardar la configuración si hubo cambios # Guardar la configuración si hubo cambios
after_save :sync_attributes_with_config! after_save :sync_attributes_with_config!
accepts_nested_attributes_for :deploys, allow_destroy: true
# El sitio en Jekyll # El sitio en Jekyll
attr_reader :jekyll attr_reader :jekyll
@ -86,46 +74,6 @@ class Site < ApplicationRecord
@repository ||= Site::Repository.new path @repository ||= Site::Repository.new path
end 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) def invitade?(usuarie)
!invitades.find_by(id: usuarie.id).nil? !invitades.find_by(id: usuarie.id).nil?
end end
@ -468,29 +416,11 @@ class Site < ApplicationRecord
config.hostname = hostname config.hostname = hostname
end 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 # Migra los archivos a Sutty
def static_file_migration! def static_file_migration!
Site::StaticFileMigration.new(site: self).migrate! Site::StaticFileMigration.new(site: self).migrate!
end 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 # Valida que al cambiar de plantilla no tengamos artículos en layouts
# inexistentes. # inexistentes.
def compatible_layouts def compatible_layouts

View file

@ -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

View file

@ -145,6 +145,8 @@ en:
models: models:
site: site:
attributes: attributes:
name:
no_subdomains: 'Name cannot contain dots'
deploys: deploys:
deploy_local_presence: 'We need to be build the site!' deploy_local_presence: 'We need to be build the site!'
design_id: design_id:

View file

@ -145,6 +145,8 @@ es:
models: models:
site: site:
attributes: attributes:
name:
no_subdomains: 'El nombre no puede contener puntos'
deploys: deploys:
deploy_local_presence: '¡Necesitamos poder generar el sitio!' deploy_local_presence: '¡Necesitamos poder generar el sitio!'
design_id: design_id:

View file

@ -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

View file

@ -21,13 +21,14 @@ module Api
end end
test 'el sitio tiene que existir' do test 'el sitio tiene que existir' do
hostname = @site.hostname
@site.destroy @site.destroy
get v1_site_contact_cookie_url(@site.hostname, **@host) get v1_site_contact_cookie_url(hostname, **@host)
assert_not cookies[@site.name] 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: { params: {
name: SecureRandom.hex, name: SecureRandom.hex,
pronouns: SecureRandom.hex, pronouns: SecureRandom.hex,

View file

@ -23,7 +23,7 @@ module Api
test 'se puede obtener un listado de todos' do test 'se puede obtener un listado de todos' do
get v1_sites_url(host: "api.#{Site.domain}"), headers: @authorization, as: :json 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 end
end end

View file

@ -26,18 +26,18 @@ class SiteTest < ActiveSupport::TestCase
assert_not site2.valid? assert_not site2.valid?
end 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 = build :site, name: 'hola.chau'
site.validate site.validate
assert_not site.errors.messages[:name].present? assert site.errors.messages[:name].present?
end 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 = build :site, name: 'hola.chau.'
site.validate site.validate
assert_not site.errors.messages[:name].present? assert site.errors.messages[:name].present?
end end
test 'el nombre del sitio no puede contener wildcard' do test 'el nombre del sitio no puede contener wildcard' do