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
|
2019-07-11 19:00:28 +00:00
|
|
|
|
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-06 00:21:49 +00:00
|
|
|
has_many :roles
|
2019-07-08 17:08:06 +00:00
|
|
|
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
|
|
|
|
2019-07-12 18:22:37 +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!
|
2019-07-12 18:22:37 +00:00
|
|
|
# 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!
|
2019-07-12 18:22:37 +00:00
|
|
|
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
|
|
|
|
2019-07-16 19:47:44 +00:00
|
|
|
# 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
|
2019-07-04 00:47:59 +00:00
|
|
|
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?
|
|
|
|
jekyll.config.fetch('invitadxs', false)
|
|
|
|
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?
|
2018-05-08 21:14:00 +00:00
|
|
|
!translations.empty?
|
|
|
|
end
|
|
|
|
|
2018-09-05 20:25:47 +00:00
|
|
|
# Define si el sitio tiene un glosario
|
|
|
|
def glossary?
|
|
|
|
jekyll.config.fetch('glossary', false)
|
|
|
|
end
|
|
|
|
|
2018-05-08 21:14:00 +00:00
|
|
|
# Obtiene la lista de traducciones actuales
|
|
|
|
def translations
|
|
|
|
@jekyll.config.dig('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
|
|
|
|
if @jekyll.config.empty?
|
2018-02-19 19:33:28 +00:00
|
|
|
read
|
2018-02-09 21:28:27 +00:00
|
|
|
Rails.logger.info 'Leyendo config'
|
|
|
|
end
|
|
|
|
|
|
|
|
@jekyll.config
|
|
|
|
end
|
|
|
|
|
2018-05-14 17:17:13 +00:00
|
|
|
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
|
|
|
|
2018-05-08 21:57:11 +00:00
|
|
|
# Obtiene todas las plantillas de artículos
|
|
|
|
def templates
|
2018-05-17 18:14:51 +00:00
|
|
|
@templates ||= posts_for('templates') || []
|
2018-05-08 21:57:11 +00:00
|
|
|
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)
|
2018-05-14 17:17:13 +00:00
|
|
|
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
|
2018-05-14 17:17:13 +00:00
|
|
|
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-03 22:37:09 +00:00
|
|
|
|
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
|
|
|
|
|
2018-02-27 23:32:10 +00:00
|
|
|
def categories(lang: nil)
|
|
|
|
everything_of :categories, lang: lang
|
2018-02-03 23:41:02 +00:00
|
|
|
end
|
|
|
|
|
2018-02-27 23:32:10 +00:00
|
|
|
def tags(lang: nil)
|
|
|
|
everything_of :tags, lang: lang
|
2018-02-08 14:05:05 +00:00
|
|
|
end
|
|
|
|
|
2018-02-27 23:32:10 +00:00
|
|
|
def everything_of(attr, lang: nil)
|
|
|
|
collection = lang || 'posts'
|
2018-05-14 17:17:13 +00:00
|
|
|
|
|
|
|
return [] unless collections_names.include? collection
|
|
|
|
|
2018-02-27 23:32:10 +00:00
|
|
|
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
|
2018-02-03 23:41:02 +00:00
|
|
|
end
|
|
|
|
|
2018-02-20 17:47:11 +00:00
|
|
|
def failed_file
|
|
|
|
File.join(path, '.failed')
|
|
|
|
end
|
|
|
|
|
|
|
|
def failed?
|
|
|
|
File.exist? failed_file
|
|
|
|
end
|
|
|
|
|
|
|
|
def defail
|
|
|
|
FileUtils.rm failed_file if failed?
|
|
|
|
end
|
2019-03-26 15:32:20 +00:00
|
|
|
alias defail! defail
|
2018-02-20 17:47:11 +00:00
|
|
|
|
|
|
|
def build_log
|
|
|
|
File.join(path, 'build.log')
|
|
|
|
end
|
|
|
|
|
|
|
|
def build_log?
|
|
|
|
File.exist? build_log
|
|
|
|
end
|
|
|
|
|
|
|
|
def queue_file
|
|
|
|
File.join(path, '.generate')
|
|
|
|
end
|
|
|
|
|
|
|
|
def enqueued?
|
|
|
|
File.exist? queue_file
|
|
|
|
end
|
2019-03-26 15:32:20 +00:00
|
|
|
alias queued? enqueued?
|
2018-02-20 17:47:11 +00:00
|
|
|
|
|
|
|
# El sitio se genera cuando se coloca en una cola de generación, para
|
|
|
|
# que luego lo construya un cronjob
|
|
|
|
def enqueue
|
|
|
|
defail!
|
2019-03-26 15:32:20 +00:00
|
|
|
# TODO: ya van tres métodos donde usamos esta idea, convertir en un
|
2018-02-20 17:47:11 +00:00
|
|
|
# helper o algo
|
2019-07-03 22:04:50 +00:00
|
|
|
File.open(queue_file, File::RDWR | File::CREAT, 0o640) do |f|
|
2018-02-20 17:47:11 +00:00
|
|
|
# Bloquear el archivo para que no sea accedido por otro
|
|
|
|
# proceso u otra editora
|
|
|
|
f.flock(File::LOCK_EX)
|
|
|
|
|
|
|
|
# Empezar por el principio
|
|
|
|
f.rewind
|
|
|
|
|
|
|
|
# Escribir la fecha de creación
|
|
|
|
f.write(Time.now.to_i.to_s)
|
|
|
|
|
|
|
|
# Eliminar el resto
|
|
|
|
f.flush
|
|
|
|
f.truncate(f.pos)
|
|
|
|
end
|
|
|
|
end
|
2019-03-26 15:32:20 +00:00
|
|
|
alias enqueue! enqueue
|
2018-02-20 17:47:11 +00:00
|
|
|
|
|
|
|
# Eliminar de la cola
|
|
|
|
def dequeue
|
|
|
|
FileUtils.rm(queue_file) if enqueued?
|
|
|
|
end
|
2019-03-26 15:32:20 +00:00
|
|
|
alias dequeue! dequeue
|
2018-02-20 17:47:11 +00:00
|
|
|
|
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)
|
2018-05-02 17:23:45 +00:00
|
|
|
# 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
|
|
|
|
|
2018-05-02 17:23:45 +00:00
|
|
|
# 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|
|
2018-05-02 17:23:45 +00:00
|
|
|
# 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
|
|
|
|
|
2018-05-02 17:23:45 +00:00
|
|
|
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
|
|
|
|
|
2019-01-09 20:37:34 +00:00
|
|
|
def get_url_from_site(path)
|
|
|
|
"https://#{name}#{path}"
|
|
|
|
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
|
|
|
|
|
|
|
|
# El directorio de los sitios de una usuaria
|
|
|
|
#
|
|
|
|
# Los sitios se organizan por usuaria, entonces los sitios que
|
|
|
|
# administra pueden encontrarse directamente en su directorio.
|
|
|
|
#
|
|
|
|
# Si comparten gestión con otras usuarias, se hacen links simbólicos
|
|
|
|
# entre sí.
|
2018-09-27 17:43:13 +00:00
|
|
|
def self.site_path_for(site)
|
|
|
|
File.join(Site.site_path, site)
|
2018-01-29 22:19:10 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
# Comprueba que el directorio parezca ser de jekyll
|
|
|
|
def self.jekyll?(dir)
|
|
|
|
File.directory?(dir) && File.exist?(File.join(dir, '_config.yml'))
|
|
|
|
end
|
|
|
|
|
2018-02-19 19:33:28 +00:00
|
|
|
def self.load_jekyll(path)
|
2018-05-08 20:51:14 +00:00
|
|
|
# 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
|
2019-07-04 00:47:59 +00:00
|
|
|
|
|
|
|
private
|
|
|
|
|
2019-07-12 18:22:37 +00:00
|
|
|
# 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
|
|
|
|
|
2019-07-04 00:47:59 +00:00
|
|
|
# Carga el sitio Jekyll
|
|
|
|
def load_jekyll!
|
2019-07-12 23:40:44 +00:00
|
|
|
return unless name.present? && File.directory?(path)
|
2019-07-12 18:22:37 +00:00
|
|
|
|
2019-07-04 00:47:59 +00:00
|
|
|
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
|
2018-01-29 22:19:10 +00:00
|
|
|
end
|