From 79a0053dbdbbcc92700da03bd0cda28ecafacf4a Mon Sep 17 00:00:00 2001
From: f <f@kefir.red>
Date: Tue, 20 Feb 2018 14:47:11 -0300
Subject: [PATCH] generar los sitios!

---
 .gitignore                          |  3 ++
 Capfile                             |  1 +
 Gemfile                             |  1 +
 Gemfile.lock                        |  4 ++
 _deploy/.keep                       |  0
 app/controllers/sites_controller.rb | 15 ++++++
 app/models/site.rb                  | 73 +++++++++++++++++++++++++++++
 app/views/sites/_enqueue.haml       | 13 +++++
 app/views/sites/index.haml          |  1 +
 bin/jekyll_build_all                | 58 +++++++++++++++++++++++
 bin/whenever                        | 21 +++++++++
 bin/wheneverize                     | 21 +++++++++
 config/deploy.rb                    |  2 +-
 config/locales/en.yml               |  4 ++
 config/locales/es.yml               |  4 ++
 config/routes.rb                    |  5 +-
 config/schedule.rb                  |  6 +++
 17 files changed, 230 insertions(+), 2 deletions(-)
 create mode 100644 _deploy/.keep
 create mode 100644 app/views/sites/_enqueue.haml
 create mode 100755 bin/jekyll_build_all
 create mode 100755 bin/whenever
 create mode 100755 bin/wheneverize
 create mode 100644 config/schedule.rb

diff --git a/.gitignore b/.gitignore
index dcc5b36..74c4390 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,3 +21,6 @@
 /yarn-error.log
 
 .byebug_history
+
+/_sites/*
+/_deploy/*
diff --git a/Capfile b/Capfile
index 13f0079..529ada8 100644
--- a/Capfile
+++ b/Capfile
@@ -10,6 +10,7 @@ require 'capistrano/passenger'
 require 'capistrano/bundler'
 require 'capistrano/rbenv'
 require 'capistrano/rails'
+require 'whenever/capistrano'
 require 'capistrano/scm/git'
 install_plugin Capistrano::SCM::Git
 
diff --git a/Gemfile b/Gemfile
index 91d4789..c8ff125 100644
--- a/Gemfile
+++ b/Gemfile
@@ -38,6 +38,7 @@ gem 'jekyll'
 gem 'jquery-rails'
 gem 'font-awesome-rails'
 gem 'exception_notification'
+gem 'whenever', require: false
 
 group :development, :test do
   gem 'pry'
diff --git a/Gemfile.lock b/Gemfile.lock
index f0c3843..b883fff 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -77,6 +77,7 @@ GEM
       xpath (~> 2.0)
     childprocess (0.8.0)
       ffi (~> 1.0, >= 1.0.11)
+    chronic (0.10.2)
     coderay (1.1.2)
     colorator (1.1.0)
     concurrent-ruby (1.0.5)
@@ -270,6 +271,8 @@ GEM
     websocket-driver (0.6.5)
       websocket-extensions (>= 0.1.0)
     websocket-extensions (0.1.3)
+    whenever (0.10.0)
+      chronic (>= 0.6.3)
     xpath (2.1.0)
       nokogiri (~> 1.3)
 
@@ -307,6 +310,7 @@ DEPENDENCIES
   turbolinks (~> 5)
   uglifier (>= 1.3.0)
   web-console (>= 3.3.0)
+  whenever
 
 BUNDLED WITH
    1.16.0
diff --git a/_deploy/.keep b/_deploy/.keep
new file mode 100644
index 0000000..e69de29
diff --git a/app/controllers/sites_controller.rb b/app/controllers/sites_controller.rb
index 64f8b9b..f8e1cb8 100644
--- a/app/controllers/sites_controller.rb
+++ b/app/controllers/sites_controller.rb
@@ -14,4 +14,19 @@ class SitesController < ApplicationController
 
     redirect_to site_posts_path(site)
   end
+
+  def enqueue
+    @site = find_site
+    @site.enqueue!
+
+    redirect_to sites_path
+  end
+
+  def build_log
+    @site = find_site
+
+    render file: @site.build_log,
+      layout: false,
+      content_type: 'text/plain'
+  end
 end
diff --git a/app/models/site.rb b/app/models/site.rb
index 2a7202f..1d1342b 100644
--- a/app/models/site.rb
+++ b/app/models/site.rb
@@ -86,6 +86,79 @@ class Site
     end.flatten.uniq
   end
 
+  # Las usuarias que tienen acceso a este sitio se guardan en un archivo
+  # `.usuarias` que tiene la dirección de correo de cada una
+  def usuarias_file
+    File.join(path, '.usuarias')
+  end
+
+  # Obtiene las usuarias que gestionan este sitio
+  def usuarias
+    @usuarias ||= File.read(usuarias_file).split("\n").map do |u|
+      Usuaria.find(u)
+    end
+  end
+
+  def failed_file
+    File.join(path, '.failed')
+  end
+
+  def failed?
+    File.exist? failed_file
+  end
+
+  def defail
+    FileUtils.rm failed_file if failed?
+  end
+  alias :defail! :defail
+
+  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
+  alias :queued? :enqueued?
+
+  # El sitio se genera cuando se coloca en una cola de generación, para
+  # que luego lo construya un cronjob
+  def enqueue
+    defail!
+    # TODO ya van tres métodos donde usamos esta idea, convertir en un
+    # helper o algo
+    r = File.open(queue_file, File::RDWR | File::CREAT, 0o640) do |f|
+      # 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
+  alias :enqueue! :enqueue
+
+  # Eliminar de la cola
+  def dequeue
+    FileUtils.rm(queue_file) if enqueued?
+  end
+  alias :dequeue! :dequeue
+
   # El directorio donde se almacenan los sitios
   def self.site_path
     File.join(Rails.root, '_sites')
diff --git a/app/views/sites/_enqueue.haml b/app/views/sites/_enqueue.haml
new file mode 100644
index 0000000..fc69d33
--- /dev/null
+++ b/app/views/sites/_enqueue.haml
@@ -0,0 +1,13 @@
+- if site.enqueued?
+  %span.badge.badge-warning= t('sites.enqueued')
+- else
+  = form_tag site_enqueue_path(site), method: :post, class: 'form-inline' do
+    = button_tag type: 'submit', class: 'btn btn-success' do
+      = fa_icon 'building'
+      = t('sites.enqueue')
+
+- if site.failed?
+  %span.badge.badge-alert= t('sites.failed')
+- if site.build_log?
+  = link_to t('sites.build_log'), site_build_log_path(site),
+    class: 'badge badge-info'
diff --git a/app/views/sites/index.haml b/app/views/sites/index.haml
index 15f59d7..a398034 100644
--- a/app/views/sites/index.haml
+++ b/app/views/sites/index.haml
@@ -12,3 +12,4 @@
           %tr
             %td= link_to site.name, site_path(site)
             %td= link_to t('i18n.edit'), site_i18n_edit_path(site)
+            %td= render 'sites/enqueue', site: site
diff --git a/bin/jekyll_build_all b/bin/jekyll_build_all
new file mode 100755
index 0000000..dc8971b
--- /dev/null
+++ b/bin/jekyll_build_all
@@ -0,0 +1,58 @@
+#!/bin/bash
+# TODO convertir a ruby!
+set -e
+
+rails_root="${PWD}"
+
+# Encontrar todos los sitios únicos con el archivo `.generate`.  Esto
+# significa que la usuaria quiso generar el sitio.
+find -L ./_sites -mindepth 3 -maxdepth 3 -name .generate \
+| sed "s/\/\.generate$//" \
+| while read _path ; do
+  # Como seguimos todos los symlinks y los sitios pueden estar
+  # vinculados entre sí, volvemos a chequear si existe el archivo para
+  # no generarlo dos veces
+  test -f "${_path}/.generate" || continue
+
+  # Obtenemos las direcciones de correo de las responsables
+  _mail=($(cat "${_path}/.usuarias"))
+  _site="$(echo "${_path}" | xargs basename)"
+  _deploy="${rails_root}/_deploy/${_site}"
+
+  # Entrar al directorio del sitio
+  pushd "${_path}" &>/dev/null
+
+  # Crear el sitio con lujo de detalles y guardar un log, pero a la vez
+  # tenerlo en la salida estándar para poder enviar al MAILTO del
+  # cronjob.
+  # 
+  # Ya que estamos, eliminamos la ruta donde estamos paradas para no dar
+  # información sobre la servidora.
+  bundle exec \
+  jekyll build --trace --destination "${_deploy}" \
+  | sed -re "s,${_path},,g" \
+  > "build.log"
+
+  # Acciones posteriores
+  # TODO convertir en un plugin de cada sitio?
+  if test $? -eq 0; then
+    # Si funciona, enviar un mail
+    # TODO enviar un mail más completo y no hardcodear direcciones
+    mail -b "sysadmin@kefir.red" \
+         -r "sutty@kefir.red" \
+         -s "${_site}: :)" \
+         ${_mail[@]}
+  else
+    mail -b "sysadmin@kefir.red" \
+         -r "sutty@kefir.red" \
+         -s "${_site}: :(" \
+         ${_mail[@]}
+    date +%s >.failed
+  fi
+
+  # Eliminar el archivo para sacar el sitio de la cola de compilación
+  rm -f .generate
+
+  # Volver al principio para continuar con el siguiente sitio
+  popd &>/dev/null
+done
diff --git a/bin/whenever b/bin/whenever
new file mode 100755
index 0000000..7350453
--- /dev/null
+++ b/bin/whenever
@@ -0,0 +1,21 @@
+#!/usr/bin/env ruby
+# frozen_string_literal: true
+
+#
+# This file was generated by Bundler.
+#
+# The application 'whenever' is installed as part of a gem, and
+# this file is here to facilitate running it.
+#
+
+bundle_binstub = File.expand_path("../bundle", __FILE__)
+load(bundle_binstub) if File.file?(bundle_binstub)
+
+require "pathname"
+ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
+  Pathname.new(__FILE__).realpath)
+
+require "rubygems"
+require "bundler/setup"
+
+load Gem.bin_path("whenever", "whenever")
diff --git a/bin/wheneverize b/bin/wheneverize
new file mode 100755
index 0000000..de812b6
--- /dev/null
+++ b/bin/wheneverize
@@ -0,0 +1,21 @@
+#!/usr/bin/env ruby
+# frozen_string_literal: true
+
+#
+# This file was generated by Bundler.
+#
+# The application 'wheneverize' is installed as part of a gem, and
+# this file is here to facilitate running it.
+#
+
+bundle_binstub = File.expand_path("../bundle", __FILE__)
+load(bundle_binstub) if File.file?(bundle_binstub)
+
+require "pathname"
+ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
+  Pathname.new(__FILE__).realpath)
+
+require "rubygems"
+require "bundler/setup"
+
+load Gem.bin_path("whenever", "wheneverize")
diff --git a/config/deploy.rb b/config/deploy.rb
index 0f6181d..fec3298 100644
--- a/config/deploy.rb
+++ b/config/deploy.rb
@@ -5,5 +5,5 @@ set :repo_url, 'git@0xacab.org:itacate-kefir/sutty.git'
 set :bundle_flags, '--deployment'
 set :default_env, path: '/usr/lib/passenger/bin:$PATH'
 
-set :linked_dirs, %w{_sites}
+set :linked_dirs, %w{_sites _deploy}
 set :linked_files, %w{.env}
diff --git a/config/locales/en.yml b/config/locales/en.yml
index e930453..a40e5e9 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -9,6 +9,10 @@ en:
   sites:
     title: 'Sites'
     index: 'Sites'
+    enqueued: 'Waiting for build'
+    enqueue: 'Build'
+    failed: 'Failed!'
+    build_log: 'Read log'
   footer:
     powered_by: 'is developed by'
   i18n:
diff --git a/config/locales/es.yml b/config/locales/es.yml
index e3368be..ca14e7c 100644
--- a/config/locales/es.yml
+++ b/config/locales/es.yml
@@ -9,6 +9,10 @@ es:
   sites:
     title: 'Sitios'
     index: 'Sitios'
+    enqueued: 'Esperando compilación'
+    enqueue: 'Compilar'
+    failed: '¡Falló!'
+    build_log: 'Ver registro'
   footer:
     powered_by: 'es desarrollada por'
   i18n:
diff --git a/config/routes.rb b/config/routes.rb
index c6defd0..329fb11 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -4,11 +4,14 @@ Rails.application.routes.draw do
   get 'login/new', to: 'login#new'
   post 'login', to: 'login#create'
 
-  resources :sites do
+  resources :sites, only: [ :index, :show ] do
     resources :posts
 
     get 'i18n', to: 'i18n#index'
     get 'i18n/edit', to: 'i18n#edit'
     post 'i18n', to: 'i18n#update'
+
+    post 'enqueue', to: 'sites#enqueue'
+    get 'build_log', to: 'sites#build_log'
   end
 end
diff --git a/config/schedule.rb b/config/schedule.rb
new file mode 100644
index 0000000..7174101
--- /dev/null
+++ b/config/schedule.rb
@@ -0,0 +1,6 @@
+env 'MAILTO', 'sysadmin@kefir.red'
+job_type :bash, 'cd :path && ./bin/:task'
+
+every 3.minutes do
+  bash 'jekyll_build_all'
+end
