sutty/app/models/site.rb

351 lines
8.9 KiB
Ruby
Raw Normal View History

2019-03-26 15:32:20 +00:00
# frozen_string_literal: true
2018-01-29 22:19:10 +00:00
# Un sitio es un directorio dentro del directorio de sitios de cada
# Usuaria
2019-07-03 22:04:50 +00:00
class Site < ApplicationRecord
2019-07-03 23:40:24 +00:00
include FriendlyId
2019-07-11 19:00:28 +00:00
validates :name, uniqueness: true, hostname: true
2019-07-17 22:18:48 +00:00
validates :design_id, presence: true
validate :deploy_local_presence
2019-07-26 01:11:01 +00:00
validates_inclusion_of :status, in: %w[waiting enqueued building]
2019-07-03 23:40:24 +00:00
friendly_id :name, use: %i[finders]
2019-07-17 22:18:48 +00:00
belongs_to :design
2019-07-19 22:37:53 +00:00
belongs_to :licencia
2019-07-17 22:18:48 +00:00
has_many :deploys
has_many :roles
has_many :usuaries, -> { where('roles.rol = ?', 'usuarie') },
through: :roles
has_many :invitades, -> { where('roles.rol = ?', 'invitade') },
through: :roles, source: :usuarie
2019-07-03 22:04:50 +00:00
# Clonar el directorio de esqueleto antes de crear el sitio
before_create :clone_skel!
2019-07-12 18:34:16 +00:00
# Elimina el directorio al destruir un sitio
before_destroy :remove_directories!
# Carga el sitio Jekyll una vez que se inicializa el modelo o después
# de crearlo
2019-07-03 23:25:23 +00:00
after_initialize :load_jekyll!
after_create :load_jekyll!
2019-07-13 00:20:36 +00:00
# Cambiar el nombre del directorio
before_update :update_name!
2019-07-03 23:25:23 +00:00
2018-02-23 19:20:51 +00:00
attr_accessor :jekyll, :collections
2019-07-03 23:25:23 +00:00
accepts_nested_attributes_for :deploys, allow_destroy: true
# El repositorio git para este sitio
def repository
@repository ||= Site::Repository.new path
end
# Trae los cambios del skel y verifica que haya cambios
def needs_pull?
!repository.commits.empty?
end
# TODO: Mover esta consulta a la base de datos para no traer un montón
# de cosas a la memoria
def invitade?(usuarie)
invitades.pluck(:id).include? usuarie.id
end
def usuarie?(usuarie)
usuaries.pluck(:id).include? usuarie.id
2019-07-03 23:25:23 +00:00
end
# Traer la ruta del sitio
#
# Equivale a _sites + nombre
def path
2019-07-13 00:20:36 +00:00
File.join(Site.site_path, name)
end
def old_path
File.join(Site.site_path, name_was)
2019-07-03 23:25:23 +00:00
end
2018-01-29 22:19:10 +00:00
2018-09-28 14:34:37 +00:00
# Este sitio acepta invitadxs?
def invitadxs?
config.fetch('invitadxs', false)
2018-09-28 14:34:37 +00:00
end
def cover
"/covers/#{name}.png"
end
2018-02-22 19:01:11 +00:00
# Determina si el sitio está en varios idiomas
def i18n?
!translations.empty?
end
2018-09-05 20:25:47 +00:00
# Define si el sitio tiene un glosario
def glossary?
config.fetch('glossary', false)
2018-09-05 20:25:47 +00:00
end
# Obtiene la lista de traducciones actuales
def translations
config.fetch('i18n', [])
end
# Devuelve el idioma por defecto del sitio
#
# TODO: volver elegante
def default_lang
# Si está traducido, intentamos saber si podemos trabajar en el
# idioma actual de la plataforma.
if i18n?
i18n = I18n.locale.to_s
if translations.include? i18n
# Podemos trabajar en el idioma actual
i18n
else
# Sino, trabajamos con el primer idioma
translations.first
end
else
# Si el sitio no está traducido, estamos trabajando con posts en
# cualquier idioma
#
# XXX: no será un dirty hack?
'posts'
end
2018-02-22 19:01:11 +00:00
end
2018-05-11 16:24:30 +00:00
def layouts
@layouts ||= @jekyll.layouts.keys.sort
end
2018-02-22 19:01:11 +00:00
def name_with_i18n(lang)
[name, lang].join('/')
end
2018-02-19 19:33:28 +00:00
def read
@jekyll.read
end
# Fuerza relectura del sitio, eliminando el sitio actual en favor de
# un sitio nuevo, leído desde cero
def read!
@jekyll = Site.load_jekyll(@jekyll.path)
@jekyll.read
end
2018-02-09 21:28:27 +00:00
def data
if @jekyll.data.empty?
2018-02-19 19:33:28 +00:00
read
2018-02-09 21:28:27 +00:00
Rails.logger.info 'Leyendo data'
end
@jekyll.data
end
def config
@config ||= Site::Config.new(self)
2018-02-09 21:28:27 +00:00
end
# TODO: Cambiar a Site::Config apenas empecemos a testear esto
def collections_names
@jekyll.config['collections'].keys
end
2018-02-22 19:01:11 +00:00
# Los posts de este sitio, si el sitio está traducido, trae los posts
# del idioma actual, porque _posts va a estar vacío
2018-01-29 22:19:10 +00:00
def posts
2018-04-27 18:48:26 +00:00
@posts ||= posts_for('posts')
2018-02-22 19:01:11 +00:00
end
2018-01-29 22:19:10 +00:00
# Obtiene todas las plantillas de artículos
def templates
@templates ||= posts_for('templates') || []
end
2018-02-22 19:01:11 +00:00
# Obtiene todos los posts de una colección determinada
#
# Devuelve nil si la colección no existe
def posts_for(collection)
return unless collections_names.include? collection
2019-03-26 15:32:20 +00:00
2018-04-27 18:48:26 +00:00
# Si pedimos 'posts' pero estamos en un sitio traducido, traemos el
# idioma actual
collection = default_lang if collection == 'posts' && i18n?
2018-04-27 18:48:26 +00:00
2019-07-03 23:25:23 +00:00
@collections ||= {}
2019-07-03 22:04:50 +00:00
c = @collections[collection]
return c if c
2018-02-22 19:01:11 +00:00
Rails.logger.info "Procesando #{collection}"
col = @jekyll.collections[collection].docs
if col.empty?
2018-02-02 22:20:31 +00:00
@jekyll.read
# Queremos saber cuantas veces releemos los articulos
Rails.logger.info 'Leyendo articulos'
end
2018-01-29 22:19:10 +00:00
# Los convertimos a una clase intermedia capaz de acceder a sus
# datos y modificarlos
2018-02-23 19:20:51 +00:00
@collections[collection] = col.map do |post|
2018-02-22 19:01:11 +00:00
Post.new(site: self, post: post, lang: collection)
2018-02-23 19:20:51 +00:00
end
2018-01-29 22:19:10 +00:00
end
def categories(lang: nil)
everything_of :categories, lang: lang
end
def tags(lang: nil)
everything_of :tags, lang: lang
2018-02-08 14:05:05 +00:00
end
def everything_of(attr, lang: nil)
collection = lang || 'posts'
return [] unless collections_names.include? collection
posts_for(collection).map do |p|
2018-02-08 14:05:05 +00:00
p.get_front_matter attr
2018-09-27 18:17:34 +00:00
end.flatten.uniq.compact
end
2019-07-26 01:11:01 +00:00
def enqueue!
!enqueued? && update_attribute(:status, 'enqueued')
2018-02-20 17:47:11 +00:00
end
def enqueued?
2019-07-26 01:11:01 +00:00
status == 'enqueued'
2018-02-20 17:47:11 +00:00
end
2018-04-27 18:48:26 +00:00
# Verifica si los posts están ordenados
def ordered?(collection = 'posts')
2019-03-26 15:32:20 +00:00
posts_for(collection).map(&:order).all?
2018-04-27 18:48:26 +00:00
end
2018-04-30 18:51:39 +00:00
# Reordena la colección usando la posición informada
#
# new_order es un hash cuya key es la posición actual del post y el
# valor la posición nueva
2019-07-03 22:04:50 +00:00
def reorder_collection(collection, new_order)
# Tenemos que pasar el mismo orden
2018-04-30 18:51:39 +00:00
return if new_order.values.map(&:to_i).sort != new_order.keys.map(&:to_i).sort
# Solo traer los posts que vamos a modificar
posts_to_order = posts_for(collection).values_at(*new_order.keys.map(&:to_i))
2018-04-30 18:51:39 +00:00
# Recorre todos los posts y asigna el nuevo orden
posts_to_order.each_with_index do |p, i|
# Usar el index si el artículo no estaba ordenado, para tener una
# ruta de adopción
oo = (p.order || i).to_s
no = new_order[oo].to_i
# No modificar nada si no hace falta
next if p.order == no
p.update_attributes order: no
2018-04-27 18:48:26 +00:00
p.save
end
posts_to_order.map(&:ordered?).all?
2018-04-27 18:48:26 +00:00
end
2018-04-30 18:51:39 +00:00
# Reordena la colección usando la posición actual de los artículos
def reorder_collection!(collection = 'posts')
2019-03-26 15:32:20 +00:00
order = Hash[posts_for(collection).count.times.map { |i| [i.to_s, i.to_s] }]
2018-04-30 18:51:39 +00:00
reorder_collection collection, order
end
2019-03-26 15:32:20 +00:00
alias reorder_posts! reorder_collection!
2018-04-27 18:48:26 +00:00
2018-07-02 22:07:06 +00:00
# Obtener una ruta disponible para Sutty
def get_url_for_sutty(path)
# Remover los puntos para que no nos envíen a ../../
File.join('/', 'sites', id, path.gsub('..', ''))
end
2018-01-29 22:19:10 +00:00
# El directorio donde se almacenan los sitios
def self.site_path
File.join(Rails.root, '_sites')
end
# TODO: En lugar de leer todo junto de una vez, extraer la carga de
# documentos de Jekyll hacia Sutty para que podamos leer los datos que
# necesitamos.
2018-02-19 19:33:28 +00:00
def self.load_jekyll(path)
# Pasamos destination porque configuration() toma el directorio
# actual y se mezclan :/
#
# Especificamos `safe` para no cargar los _plugins, que interfieren
# entre sitios incluso
config = ::Jekyll.configuration('source' => path,
2019-03-26 15:32:20 +00:00
'destination' => File.join(path, '_site'),
'safe' => true,
2019-03-26 15:52:09 +00:00
'watch' => false,
'quiet' => true)
2018-02-19 19:33:28 +00:00
# No necesitamos cargar plugins en este momento
2018-07-09 15:11:13 +00:00
%w[plugins gems theme].each do |unneeded|
2018-02-19 19:33:28 +00:00
config[unneeded] = [] if config.key? unneeded
end
2018-02-22 19:01:11 +00:00
# Si estamos usando nuestro propio plugin de i18n, los posts están
# en "colecciones"
i18n = config.dig('i18n')
2019-03-26 15:32:20 +00:00
i18n&.each do |i|
config['collections'][i] = {}
2018-02-22 19:01:11 +00:00
end
2018-02-19 19:33:28 +00:00
Jekyll::Site.new(config)
end
def self.domain
ENV.fetch('SUTTY', 'sutty.nl')
end
private
# Clona el esqueleto de Sutty para crear el sitio nuevo, no pasa nada
# si el sitio ya existe
def clone_skel!
return if File.directory? path
Rugged::Repository.clone_at ENV['SKEL_SUTTY'], path
end
# Carga el sitio Jekyll
def load_jekyll!
2019-07-12 23:40:44 +00:00
return unless name.present? && File.directory?(path)
Dir.chdir(path) do
@jekyll ||= Site.load_jekyll(Dir.pwd)
end
end
2019-07-12 18:34:16 +00:00
2019-07-12 19:11:07 +00:00
# Elimina el directorio del sitio
2019-07-12 18:34:16 +00:00
def remove_directories!
FileUtils.rm_rf path
end
2019-07-13 00:20:36 +00:00
def update_name!
return unless name_changed?
FileUtils.mv old_path, path
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
2018-01-29 22:19:10 +00:00
end