Los sitios pueden tener una ubicación canónica
This commit is contained in:
parent
baf6d203b8
commit
dcaac06fa4
8 changed files with 140 additions and 80 deletions
|
@ -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
|
||||||
|
|
99
app/models/site/deployment.rb
Normal file
99
app/models/site/deployment.rb
Normal 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
|
|
@ -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:
|
||||||
|
|
|
@ -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:
|
||||||
|
|
26
db/migrate/20210809155434_add_canonical_deploy_to_sites.rb
Normal file
26
db/migrate/20210809155434_add_canonical_deploy_to_sites.rb
Normal 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
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue