From 21eea415833895bbf41907e2e835b36b425cd752 Mon Sep 17 00:00:00 2001 From: f Date: Thu, 15 Apr 2021 18:33:37 -0300 Subject: [PATCH 001/234] deprecar la referencia de markdown es una supervivencia de cuando se escribia md a mano --- app/controllers/application_controller.rb | 2 -- config/routes.rb | 2 -- 2 files changed, 4 deletions(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 38b2b72c..bc375dcf 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -23,8 +23,6 @@ class ApplicationController < ActionController::Base redirect_to sites_path end - def markdown; end - private def uuid?(string) diff --git a/config/routes.rb b/config/routes.rb index 3a807285..766df50a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -8,8 +8,6 @@ Rails.application.routes.draw do root 'application#index' - get 'markdown', to: 'application#markdown' - constraints(Constraints::ApiSubdomain.new) do scope module: 'api' do namespace :v1 do From af3d59d34afaadbd625682e4acdb52725d02b582 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 16 Apr 2021 11:06:24 -0300 Subject: [PATCH 002/234] asegurarse que solo podamos encolar el sitio una vez --- test/models/site_test.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/models/site_test.rb b/test/models/site_test.rb index c50bfd20..c894932d 100644 --- a/test/models/site_test.rb +++ b/test/models/site_test.rb @@ -108,5 +108,10 @@ class SiteTest < ActiveSupport::TestCase assert_equal %w[book editorial post], site.data['layouts'].keys assert_equal %i[book editorial post], site.layouts.to_h.keys + + test 'se pueden encolar una sola vez' do + assert site.enqueue! + assert site.enqueued? + assert_not site.enqueue! end end From f60fbd70f4f21da280e428c081ecad4081646711 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 16 Apr 2021 11:07:28 -0300 Subject: [PATCH 003/234] =?UTF-8?q?deshabilitar=20el=20bot=C3=B3n=20de=20p?= =?UTF-8?q?ublicaci=C3=B3n=20una=20vez=20que=20se=20lo=20toca?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controllers/submit_once_controller.js | 15 +++++++++++++++ app/views/sites/_build.haml | 9 ++++++--- 2 files changed, 21 insertions(+), 3 deletions(-) create mode 100644 app/javascript/controllers/submit_once_controller.js diff --git a/app/javascript/controllers/submit_once_controller.js b/app/javascript/controllers/submit_once_controller.js new file mode 100644 index 00000000..b47a33e2 --- /dev/null +++ b/app/javascript/controllers/submit_once_controller.js @@ -0,0 +1,15 @@ +import { Controller } from 'stimulus' + +/* + * Deshabilita el elemento que recibe el evento, para evitar que la + * acción se realice varias veces. + * + *
+ * + *
+ */ +export default class extends Controller { + disable (event) { + event.target.disabled = true + } +} diff --git a/app/views/sites/_build.haml b/app/views/sites/_build.haml index 4115e970..d9d6be83 100644 --- a/app/views/sites/_build.haml +++ b/app/views/sites/_build.haml @@ -4,12 +4,15 @@ tooltip: t('help.sites.enqueued'), text: t('sites.enqueued'), type: 'secondary', - link: nil + link: nil, + disabled: true - else = form_tag site_enqueue_path(site), - method: :post, class: 'form-inline inline' do + method: :post, + data: { controller: 'submit-once' }, + class: 'form-inline inline' do = button_tag type: 'submit', class: 'btn no-border-radius', title: t('help.sites.enqueue'), - data: { toggle: 'tooltip' } do + data: { action: 'submit-once#disable' } do = t('sites.enqueue') From 81a565cd0f78b848980b2be5c4e6cae5679932ac Mon Sep 17 00:00:00 2001 From: f Date: Sun, 18 Apr 2021 19:00:21 -0300 Subject: [PATCH 004/234] =?UTF-8?q?renovar=20la=20cach=C3=A9=20de=20formul?= =?UTF-8?q?arios?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit usando la fecha de modificación del post, esto nos permite renovar la caché haciendo `touch _es/*.markdown` en el repositorio. --- app/models/metadata_template.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/models/metadata_template.rb b/app/models/metadata_template.rb index 58000596..33af8192 100644 --- a/app/models/metadata_template.rb +++ b/app/models/metadata_template.rb @@ -19,10 +19,16 @@ MetadataTemplate = Struct.new(:site, :document, :name, :label, :type, @cache_key ||= 'post/' + post.uuid.value + '/' + name.to_s end + # Genera una versión de caché en base a la fecha de modificación del + # Post, el valor actual y los valores posibles, de forma que cualquier + # cambio permita renovar la caché. + # + # @return [String] def cache_version - value.hash.to_s + values.hash.to_s + post.cache_version + value.hash.to_s + values.hash.to_s end + # @return [String] def cache_key_with_version cache_key + '-' + cache_version end From 78a830a0e7502721657603c6f8f7ac84cee9db53 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 21 Apr 2021 10:18:01 -0300 Subject: [PATCH 005/234] =?UTF-8?q?eliminar=20las=20traducciones=20tambi?= =?UTF-8?q?=C3=A9n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/locales/en.yml | 39 --------------------------------------- config/locales/es.yml | 38 -------------------------------------- 2 files changed, 77 deletions(-) diff --git a/config/locales/en.yml b/config/locales/en.yml index 02570b12..c07f302a 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -224,45 +224,6 @@ en: accept: 'Someone invited you to collaborate on their site. If you accept the invitation, you can access the site.' reject: "If you decline, you won't have access." close: 'Close help' - markdown: - intro: 'The text is formatted using a syntax called Markdown, a - simple format that can be easily written just by remembering some - rules, and that can be converted to a web page, or to PDF and ePub - files. You can use the buttons below for basic formatting. If - you need help, here you have a markdown - cheatsheet.' - back: 'Go back' - input: 'If we write...' - output: 'We get...' - bold: 'Bold' - italic: 'Emphasis' - heading: 'Title' - link: - text: 'A link' - url: 'https://example.org' - quote: 'A quote from a text we liked' - ul: 'Our TODO list' - ol: 'Steps for our machiavelic plans' - img: - text: 'Kéfir island' - url: 'https://kefir.red/images/isla.png' - ltr: 'Introduction' - rtl: 'مقدمة' - dir: "These are tricky. If you want to use an expression in a - language using another direction, like using an Arabic expression - on an English text, or viceversa, you have to inform both - direction and language so the markdown processor understands it - has to change it internally. Otherwise you may see out of order - words, specially in the PDF results." - distraction_free_html: 'You can have a distraction free writing session - by clicking the button' - preview_html: 'Click the preview - button to see the results.' - autocomplete_html: "Some of these fields can be auto-completed. If - you know what to put on them, just start writing and the - auto-complete will suggest available options. If the option doesn't - exist, finish writing and press Enter to add a new one. - To empty the field, click the × button on your right." deploys: deploy_local: title: 'Host at Sutty' diff --git a/config/locales/es.yml b/config/locales/es.yml index 5bb4a221..458b5148 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -227,44 +227,6 @@ es: reject: 'Si rechazas la invitación, no tendrás acceso.' pull: 'Tienes actualizaciones pendientes :)' close: 'Cerrar ayuda' - markdown: - intro: 'El formato del texto se llama Markdown. Es un formato - simple que puede ser escrito recordando unas pocas reglas y que - luego puede ser convertido a una página web, o un PDF, o un ePub. - Puedes usar los botones para dar formato básico a tu texto. Para - cuestiones más avanzadas, aquí tienes un recordatorio de markdown.' - back: 'Volver' - input: 'Si escribimos...' - output: 'Obtenemos...' - bold: 'Negrita' - italic: 'Énfasis' - heading: 'Título' - link: - text: 'Un vínculo' - url: 'https://ejemplo.org' - quote: 'Un recorte de texto que nos gustó' - ul: 'Lista de cosas para hacer' - ol: 'Pasos para un plan maquiavélico' - img: - text: 'La isla de Kéfir' - url: 'https://kefir.red/images/isla.png' - ltr: 'Introducción' - rtl: 'مقدمة' - dir: 'Esta sintaxis puede ser complicada. Si quieres mezclar - expresiones en idiomas que usan otra dirección, como usar una - expresión en árabe en medio de un texto en castellano, o - viceversa, tienes que especificar la dirección y el idioma de esta - manera. De otra forma verás palabras fuera de orden, - especialmente en la versión en PDF.' - distraction_free_html: 'Puedes escribir sin distracciones presionando el botón ' - preview_html: 'Utiliza el botón de previsualización para ver el texto generado' - autocomplete_html: 'Algunos de estos campos se autocompletan. Si ya - sabes qué quieres poner, solo empieza a escribir y el autocompletado - te sugerirá las opciones disponibles. Si no existe lo que quieres - poner, termina de escribir y presiona Entrar para agregar - opciones que aun no existen. Para vaciar las opciones, usa el botón - × a la derecha.' deploys: deploy_local: title: 'Alojar en Sutty' From c9531af77b2a378c5bc9e81952a038ed43f43580 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 21 Apr 2021 15:26:27 -0300 Subject: [PATCH 006/234] =?UTF-8?q?rails=20ya=20soportaba=20deshabilitar?= =?UTF-8?q?=20durante=20el=20env=C3=ADo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controllers/submit_once_controller.js | 15 ----------- app/views/sites/_build.haml | 25 ++++++------------- 2 files changed, 8 insertions(+), 32 deletions(-) delete mode 100644 app/javascript/controllers/submit_once_controller.js diff --git a/app/javascript/controllers/submit_once_controller.js b/app/javascript/controllers/submit_once_controller.js deleted file mode 100644 index b47a33e2..00000000 --- a/app/javascript/controllers/submit_once_controller.js +++ /dev/null @@ -1,15 +0,0 @@ -import { Controller } from 'stimulus' - -/* - * Deshabilita el elemento que recibe el evento, para evitar que la - * acción se realice varias veces. - * - *
- * - *
- */ -export default class extends Controller { - disable (event) { - event.target.disabled = true - } -} diff --git a/app/views/sites/_build.haml b/app/views/sites/_build.haml index d9d6be83..6bc4d11b 100644 --- a/app/views/sites/_build.haml +++ b/app/views/sites/_build.haml @@ -1,18 +1,9 @@ - if policy(site).build? - - if site.enqueued? - = render 'layouts/btn_with_tooltip', - tooltip: t('help.sites.enqueued'), - text: t('sites.enqueued'), - type: 'secondary', - link: nil, - disabled: true - - else - = form_tag site_enqueue_path(site), - method: :post, - data: { controller: 'submit-once' }, - class: 'form-inline inline' do - = button_tag type: 'submit', - class: 'btn no-border-radius', - title: t('help.sites.enqueue'), - data: { action: 'submit-once#disable' } do - = t('sites.enqueue') + = form_tag site_enqueue_path(site), + method: :post, + class: 'form-inline inline' do + = submit_tag site.enqueued? ? t('sites.enqueued') : t('sites.enqueue'), + class: 'btn no-border-radius', + title: site.enqueued? ? t('help.sites.enqueued') : t('help.sites.enqueue'), + data: { disable_with: t('sites.enqueued') }, + disabled: site.enqueued? From 70e64e6e97a2609299a323bf41607b7a2e17550a Mon Sep 17 00:00:00 2001 From: f Date: Thu, 22 Apr 2021 13:09:49 -0300 Subject: [PATCH 007/234] usar el editor en otros campos como reemplazo del campo `markdown` que coloca texto con markdown en el front matter. --- app/models/metadata_content.rb | 10 +++++++--- app/models/metadata_html.rb | 12 ++++++++++++ app/views/posts/attributes/_html.haml | 6 ++++++ 3 files changed, 25 insertions(+), 3 deletions(-) create mode 100644 app/models/metadata_html.rb create mode 100644 app/views/posts/attributes/_html.haml diff --git a/app/models/metadata_content.rb b/app/models/metadata_content.rb index b11818a2..525f38ab 100644 --- a/app/models/metadata_content.rb +++ b/app/models/metadata_content.rb @@ -15,14 +15,18 @@ class MetadataContent < MetadataTemplate false end + def document_value + document.content + end + private # Detectar si el contenido estaba en Markdown y pasarlo a HTML def legacy_content - return unless document.content - return document.content if /^\s* Date: Thu, 22 Apr 2021 13:19:47 -0300 Subject: [PATCH 008/234] vista previa --- app/views/posts/attribute_ro/_html.haml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 app/views/posts/attribute_ro/_html.haml diff --git a/app/views/posts/attribute_ro/_html.haml b/app/views/posts/attribute_ro/_html.haml new file mode 100644 index 00000000..97931960 --- /dev/null +++ b/app/views/posts/attribute_ro/_html.haml @@ -0,0 +1,3 @@ +%tr{ id: attribute } + %th= post_label_t(attribute, post: post) + %td{ lang: locale, dir: dir }= metadata.value.html_safe From b71c6a5fa44e1c7dc39b3c8488a4e30d6b7ef5ee Mon Sep 17 00:00:00 2001 From: f Date: Thu, 22 Apr 2021 19:30:47 -0300 Subject: [PATCH 009/234] no fallar si el idioma de le usuarie no es el del sitio MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit si tenemos el panel en inglés pero gestionamos un sitio en castellano, teníamos un error porque sutty siempre quiere cargar el sitio en el idioma de le usuarie. con este cambio si el sitio no tiene el idioma del panel, usamos el idioma por defecto del sitio. fixes #1168 fixes #1164 fixes #1163 --- app/models/site.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/site.rb b/app/models/site.rb index fe64f57e..1c62e9bc 100644 --- a/app/models/site.rb +++ b/app/models/site.rb @@ -223,8 +223,8 @@ class Site < ApplicationRecord def posts(lang: nil) read - # Traemos los posts del idioma actual por defecto - lang ||= I18n.locale + # Traemos los posts del idioma actual por defecto o el que haya + lang ||= locales.include?(I18n.locale) ? I18n.locale : default_locale lang = lang.to_sym # Crea un Struct dinámico con los valores de los locales, si From 2fbbb212adacd606e028cb404a91d79f235964fd Mon Sep 17 00:00:00 2001 From: f Date: Sat, 24 Apr 2021 19:55:15 -0300 Subject: [PATCH 010/234] validar el sitio antes de subir una imagen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit si `Site` no es válido (por ejemplo la `description` es muy corta), no se pueden guardar los archivos estáticos asociados, porque `ActiveStorage::Attached` solo se copia al disco cuando se guardan a través de `Site#static_files`. el error que se producía es que `MetadataFile` no puede vincular el archivo porque todavía no fue copiado al disco. este cambio have que `MetadataFile` no valide cuando `Site` no valida. queda un error pendiente en `Site::StaticFileMigration` con una situación similar. para eso necesitamos identificar el momento en que el archivo se "sube" al disco, es decir se copia el `IO` al directorio, a través de `ActiveStorage::Service::DiskService#upload`. fixes #1197 fixes #1196 fixes #1195 fixes #1194 fixes #1193 fixes #1192 --- app/models/metadata_file.rb | 1 + config/locales/en.yml | 2 ++ config/locales/es.yml | 2 ++ 3 files changed, 5 insertions(+) diff --git a/app/models/metadata_file.rb b/app/models/metadata_file.rb index b4b144e7..80cefa27 100644 --- a/app/models/metadata_file.rb +++ b/app/models/metadata_file.rb @@ -16,6 +16,7 @@ class MetadataFile < MetadataTemplate def validate super + errors << I18n.t("metadata.#{type}.site_invalid") if site.invalid? errors << I18n.t("metadata.#{type}.path_required") if path_missing? errors << I18n.t("metadata.#{type}.no_file_for_description") if no_file_for_description? diff --git a/config/locales/en.yml b/config/locales/en.yml index 02570b12..94ab7e54 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -34,10 +34,12 @@ en: metadata: cant_be_empty: 'This field cannot be empty' image: + site_invalid: 'The image cannot be stored if the site configuration is not valid' not_an_image: 'Not an image' path_required: 'Missing image for upload' no_file_for_description: "There's a description with no associated image" file: + site_invalid: 'The file cannot be stored if the site configuration is not valid' path_required: "Missing file for upload" no_file_for_description: "There's a description with no associated file" event: diff --git a/config/locales/es.yml b/config/locales/es.yml index 5bb4a221..6d84d7ea 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -34,10 +34,12 @@ es: metadata: cant_be_empty: 'El campo no puede estar vacío' image: + site_invalid: 'La imagen no se puede almacenar si la configuración del sitio no es válida' not_an_image: 'No es una imagen' path_required: 'Se necesita una imagen' no_file_for_description: 'Se envió una descripción sin imagen asociada' file: + site_invalid: 'El archivo no se puede almacenar si la configuración del sitio no es válida' path_required: 'Se necesita un archivo' no_file_for_description: 'se envió una descripción sin archivo asociado' event: From 7d2288ae481fdf7f1992bacf292f7f43f4697f9f Mon Sep 17 00:00:00 2001 From: fauno Date: Mon, 26 Apr 2021 16:04:55 +0000 Subject: [PATCH 011/234] usar hain como entorno de trabajo reproducible --- .ruby-version | 2 +- Gemfile | 15 ++------------- Gemfile.lock | 39 ++++++++++++++++++++++++++++++++++++++- Makefile | 11 +++++++---- 4 files changed, 48 insertions(+), 19 deletions(-) diff --git a/.ruby-version b/.ruby-version index 860487ca..bec3a35e 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -2.7.1 +system diff --git a/Gemfile b/Gemfile index 56b6ab99..63b921c2 100644 --- a/Gemfile +++ b/Gemfile @@ -1,18 +1,7 @@ # frozen_string_literal: true -# TODO: Podríamos usar solo gems.sutty.nl pero por alguna razón bundler -# prefiere x86_64-linux-musl antes que x86_64-linux y ya perdimos mucho -# tiempo buscando soporte para musl -if ENV['RAILS_ENV'] == 'production' - source 'https://gems.sutty.nl' -else - source 'https://rubygems.org' -end - -git_source(:github) do |repo_name| - repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?('/') - "https://github.com/#{repo_name}.git" -end +puts 'Usa haini.sh para generar un entorno de trabajo reproducible' +source 'https://gems.sutty.nl' ruby '~> 2.7' diff --git a/Gemfile.lock b/Gemfile.lock index a9ab7311..5c1d0627 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -92,9 +92,12 @@ GEM autoprefixer-rails (10.2.4.0) execjs bcrypt (3.1.16) + bcrypt (3.1.16-x86_64-linux-musl) bcrypt_pbkdf (1.1.0) + bcrypt_pbkdf (1.1.0-x86_64-linux-musl) benchmark-ips (2.8.4) bindex (0.8.1) + bindex (0.8.1-x86_64-linux-musl) blazer (2.4.2) activerecord (>= 5) chartkick (>= 3.2) @@ -119,9 +122,13 @@ GEM colorator (1.1.0) commonmarker (0.21.2) ruby-enum (~> 0.5) + commonmarker (0.21.2-x86_64-linux-musl) + ruby-enum (~> 0.5) concurrent-ruby (1.1.8) concurrent-ruby-ext (1.1.8) concurrent-ruby (= 1.1.8) + concurrent-ruby-ext (1.1.8-x86_64-linux-musl) + concurrent-ruby (= 1.1.8) crass (1.0.6) database_cleaner (2.0.1) database_cleaner-active_record (~> 2.0.0) @@ -160,6 +167,7 @@ GEM down (5.2.0) addressable (~> 2.5) ed25519 (1.2.4) + ed25519 (1.2.4-x86_64-linux-musl) editorial-autogestiva-jekyll-theme (0.3.0) jekyll (~> 4) jekyll-commonmark (~> 1.3) @@ -185,6 +193,7 @@ GEM errbase (0.2.1) erubi (1.10.0) eventmachine (1.2.7) + eventmachine (1.2.7-x86_64-linux-musl) exception_notification (4.4.3) actionmailer (>= 4.0, < 7) activesupport (>= 4.0, < 7) @@ -195,8 +204,11 @@ GEM factory_bot (~> 6.1.0) railties (>= 5.0.0) fast_blank (1.0.0) + fast_blank (1.0.0-x86_64-linux-musl) fast_jsonparser (0.5.0) + fast_jsonparser (0.5.0-x86_64-linux-musl) ffi (1.15.0) + ffi (1.15.0-x86_64-linux-musl) flamegraph (0.9.5) forwardable-extended (2.6.0) friendly_id (5.4.2) @@ -220,6 +232,10 @@ GEM temple (>= 0.8.2) thor tilt + hamlit (2.15.0-x86_64-linux-musl) + temple (>= 0.8.2) + thor + tilt hamlit-rails (0.2.3) actionpack (>= 4.0.1) activesupport (>= 4.0.1) @@ -228,7 +244,9 @@ GEM heapy (0.2.0) thor hiredis (0.6.3) + hiredis (0.6.3-x86_64-linux-musl) http_parser.rb (0.6.0) + http_parser.rb (0.6.0-x86_64-linux-musl) httparty (0.18.1) mime-types (~> 3.0) multi_xml (>= 0.5.2) @@ -354,9 +372,13 @@ GEM net-ssh (6.1.0) netaddr (2.0.4) nio4r (2.5.7) + nio4r (2.5.7-x86_64-linux-musl) nokogiri (1.11.3) mini_portile2 (~> 2.5.0) racc (~> 1.4) + nokogiri (1.11.3-x86_64-linux-musl) + mini_portile2 (~> 2.5.0) + racc (~> 1.4) orm_adapter (0.5.0) parallel (1.20.1) parser (3.0.1.0) @@ -364,6 +386,7 @@ GEM pathutil (0.16.2) forwardable-extended (~> 2.6) pg (1.2.3) + pg (1.2.3-x86_64-linux-musl) popper_js (1.16.0) prometheus_exporter (0.7.0) webrick @@ -373,9 +396,12 @@ GEM public_suffix (4.0.6) puma (5.2.2) nio4r (~> 2.0) + puma (5.2.2-x86_64-linux-musl) + nio4r (~> 2.0) pundit (2.1.0) activesupport (>= 3.0.0) racc (1.5.2) + racc (1.5.2-x86_64-linux-musl) rack (2.2.3) rack-cors (1.1.1) rack (>= 2.0.0) @@ -492,6 +518,7 @@ GEM ruby-enum (0.9.0) i18n ruby-filemagic (0.7.2) + ruby-filemagic (0.7.2-x86_64-linux-musl) ruby-progressbar (1.11.0) ruby-statistics (2.1.3) ruby-vips (2.1.0) @@ -499,11 +526,14 @@ GEM ruby_dep (1.5.0) rubyzip (2.3.0) rugged (1.1.0) + rugged (1.1.0-x86_64-linux-musl) safe_yaml (1.0.6) safely_block (0.3.0) errbase (>= 0.1.1) sassc (2.4.0) ffi (~> 1.9) + sassc (2.4.0-x86_64-linux-musl) + ffi (~> 1.9) sassc-rails (2.1.2) railties (>= 4.0.0) sassc (>= 2.0) @@ -540,7 +570,9 @@ GEM activesupport (>= 4.0) sprockets (>= 3.0.0) sqlite3 (1.4.2) + sqlite3 (1.4.2-x86_64-linux-musl) stackprof (0.2.16) + stackprof (0.2.16-x86_64-linux-musl) sucker_punch (3.0.1) concurrent-ruby (~> 1.0) sutty-archives (2.5.4) @@ -570,6 +602,7 @@ GEM jekyll-feed (~> 0.9) jekyll-seo-tag (~> 2.1) symbol-fstring (1.0.0) + symbol-fstring (1.0.0-x86_64-linux-musl) sysexits (1.2.0) temple (0.8.2) terminal-table (2.0.0) @@ -587,6 +620,7 @@ GEM unf (0.1.4) unf_ext unf_ext (0.0.7.7) + unf_ext (0.0.7.7-x86_64-linux-musl) unicode-display_width (1.7.0) validates_hostname (1.0.11) activerecord (>= 3.0) @@ -606,6 +640,8 @@ GEM webrick (1.7.0) websocket-driver (0.7.3) websocket-extensions (>= 0.1.0) + websocket-driver (0.7.3-x86_64-linux-musl) + websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) xpath (3.2.0) nokogiri (~> 1.8) @@ -613,6 +649,7 @@ GEM PLATFORMS ruby + x86_64-linux-musl DEPENDENCIES adhesiones-jekyll-theme @@ -706,4 +743,4 @@ RUBY VERSION ruby 2.7.1p83 BUNDLED WITH - 2.1.4 + 2.2.2 diff --git a/Makefile b/Makefile index f05e9cc5..c93d4734 100644 --- a/Makefile +++ b/Makefile @@ -7,21 +7,24 @@ delegate := athshe assets := package.json yarn.lock $(shell find app/assets/ app/javascript/ -type f) -alpine_version := 3.12 +alpine_version := 3.13 +hain ?= ../haini.sh/haini.sh + +export public/packs/manifest.json.br: $(assets) - PANEL_URL=https://panel.sutty.nl RAILS_ENV=production NODE_ENV=production bundle exec rake assets:precompile assets:clean + $(hain) 'cd /Sutty/sutty; PANEL_URL=https://panel.sutty.nl RAILS_ENV=production NODE_ENV=production bundle exec rake assets:precompile assets:clean' assets: public/packs/manifest.json.br serve: /etc/hosts - bundle exec rails s -b "ssl://0.0.0.0:3000?key=../sutty.local/domain/$(SUTTY).key&cert=../sutty.local/domain/$(SUTTY).crt" + $(hain) 'cd /Sutty/sutty; bundle exec rails s -b "ssl://0.0.0.0:3000?key=../sutty.local/domain/$(SUTTY).key&cert=../sutty.local/domain/$(SUTTY).crt"' # Servir JS con el dev server. # Esto acelera la compilación del javascript, tiene que correrse por separado # de serve. serve-js: /etc/hosts - bundle exec ./bin/webpack-dev-server + $(hain) 'cd /Sutty/sutty; bundle exec ./bin/webpack-dev-server' # Limpiar los archivos de testeo clean: From 22f61dc31f860f78d2829da6f75948fcf7d5de82 Mon Sep 17 00:00:00 2001 From: f Date: Tue, 27 Apr 2021 11:25:37 -0300 Subject: [PATCH 012/234] eliminar los espacios en el lugar correcto MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit esto hacía que no se puedan enviar los correos de excepciones porque producían una excepción a su vez. --- app/jobs/backtrace_job.rb | 4 +++- app/views/exception_notifier/_data.text.erb | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/jobs/backtrace_job.rb b/app/jobs/backtrace_job.rb index 50ee155b..0f3f5de8 100644 --- a/app/jobs/backtrace_job.rb +++ b/app/jobs/backtrace_job.rb @@ -30,7 +30,9 @@ class BacktraceJob < ApplicationJob # Encuentra el código fuente del error source = data.dig('sourcesContent', data['sources']&.index(backtrace['file']))&.split("\n") - backtrace['function'] = source[backtrace['line'] - 1] if source.present? + # XXX: Elimina la sangría aunque cambie las columnas porque + # eso lo vamos a ver en el archivo fuente directo. + backtrace['function'] = source[backtrace['line'] - 1].strip if source.present? end end end diff --git a/app/views/exception_notifier/_data.text.erb b/app/views/exception_notifier/_data.text.erb index 3493d68b..09313f4c 100644 --- a/app/views/exception_notifier/_data.text.erb +++ b/app/views/exception_notifier/_data.text.erb @@ -3,7 +3,7 @@ # <%= error['type'] %>: <%= error['message'] %> ``` -<%= Terminal::Table.new headings: error['backtrace'].first.keys, rows: error['backtrace'].map(&:values).map(&:strip) %> +<%= Terminal::Table.new headings: error['backtrace'].first.keys, rows: error['backtrace'].map(&:values) %> ``` <% end %> From c8f11186405be70b947b731299d7f7f67774f8eb Mon Sep 17 00:00:00 2001 From: f Date: Tue, 27 Apr 2021 11:27:17 -0300 Subject: [PATCH 013/234] testear el procesamiento de errores de js --- app/jobs/backtrace_job.rb | 7 +- config/environments/test.rb | 9 ++ test/jobs/backtrace_job_test.rb | 147 ++++++++++++++++++++++++++++++++ 3 files changed, 162 insertions(+), 1 deletion(-) create mode 100644 test/jobs/backtrace_job_test.rb diff --git a/app/jobs/backtrace_job.rb b/app/jobs/backtrace_job.rb index 0f3f5de8..eab9f226 100644 --- a/app/jobs/backtrace_job.rb +++ b/app/jobs/backtrace_job.rb @@ -38,7 +38,7 @@ class BacktraceJob < ApplicationJob end begin - raise BacktraceException, "#{origin}: #{params['errors']&.first&.dig('message')}" + raise BacktraceException, "#{origin}: #{message}" rescue BacktraceException => e ExceptionNotifier.notify_exception(e, data: { site: site.name, params: params, _backtrace: true }) end @@ -104,4 +104,9 @@ class BacktraceJob < ApplicationJob rescue URI::Error params.dig('context', 'url') end + + # @return [String,Nil] + def message + @message ||= params['errors']&.first&.dig('message') + end end diff --git a/config/environments/test.rb b/config/environments/test.rb index f8169828..c58a65ca 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -64,4 +64,13 @@ Rails.application.configure do # Annotate rendered view with file names. # config.action_view.annotate_rendered_view_with_filenames = true + + config.middleware.use ExceptionNotification::Rack, + error_grouping: true, + email: { + email_prefix: '', + sender_address: ENV.fetch('DEFAULT_FROM', "noreply@sutty.nl"), + exception_recipients: ENV.fetch('EXCEPTION_TO', "errors@sutty.nl"), + normalize_subject: true + } end diff --git a/test/jobs/backtrace_job_test.rb b/test/jobs/backtrace_job_test.rb new file mode 100644 index 00000000..89c68609 --- /dev/null +++ b/test/jobs/backtrace_job_test.rb @@ -0,0 +1,147 @@ +# frozen_string_literal: true + +require 'test_helper' + +class DeployJobTest < ActiveSupport::TestCase + def site + @site ||= create :site + end + + # Mockup + def job + job = BacktraceJob.new + job.instance_variable_set :@site, site + job.instance_variable_set :@params, notice + + job + end + + # setTimeout(() => { throw('Prueba') }, 1000) + def notice + @notice ||= { + "errors" => [ + { + "type" => "", + "message" => "Prueba", + "backtrace" => [ + { + "function" => "pt "https://tintalimon.com.ar/assets/js/pack.js", + "line" => 89, + "column" => 74094 + }, + { + "function" => "pt "https://tintalimon.com.ar/assets/js/pack.js", + "line" => 89, + "column" => 74731 + }, + { + "function" => "pt "https://tintalimon.com.ar/assets/js/pack.js", + "line" => 89, + "column" => 71925 + }, + { + "function" => "setTimeout handler*", + "file" => "debugger eval code", + "line" => 1, + "column" => 11 + } + ] + } + ], + "context" => { + "severity" => "error", + "history" => [ + { + "type" => "error", + "target" => "html. > head. > script.[type=\"text/javascript\"][src=\"//stats.habitapp.org/piwik.js\"]", + "date" => "2021-04-26T22:06:58.390Z" + }, + { + "type" => "DOMContentLoaded", + "target" => "[object HTMLDocument]", + "date" => "2021-04-26T22:06:58.510Z" + }, + { + "type" => "load", + "target" => "[object HTMLDocument]", + "date" => "2021-04-26T22:06:58.845Z" + }, + { + "type" => "xhr", + "date" => "2021-04-26T22:06:58.343Z", + "method" => "GET", + "url" => "assets/data/site.json", + "statusCode" => 200, + "duration" => 506 + }, + { + "type" => "xhr", + "date" => "2021-04-26T22:06:58.886Z", + "method" => "GET", + "url" => "assets/templates/cart.html", + "statusCode" => 200, + "duration" => 591 + } + ], + "windowError" => true, + "notifier" => { + "name" => "airbrake-js/browser", + "version" => "1.4.2", + "url" => "https://github.com/airbrake/airbrake-js/tree/master/packages/browser" + }, + "userAgent" => "Mozilla/5.0 (Windows NT 6.1; rv:85.0) Gecko/20100101 Firefox/85.0", + "url" => "https://tintalimon.com.ar/carrito/", + "rootDirectory" => "https://tintalimon.com.ar", + "language" => "JavaScript" + }, + "params" => {}, + "environment" => {}, + "session" => {} + } + + # XXX: Siempre devolvemos un duplicado porque BacktraceJob lo + # modifica + @notice.dup + end + + # Asegurarse que el sitio se destruye al terminar de usarlo + teardown do + site&.destroy + end + + test 'al recibir un backtrace enviamos un error' do + ActionMailer::Base.deliveries.clear + + assert BacktraceJob.perform_now site_id: site.id, params: notice + + email = ActionMailer::Base.deliveries.first + + assert email + assert_equal " (BacktraceJob::BacktraceException) \"tintalimon.com.ar: Prueba\"", email.subject + assert (%r{webpack://} =~ email.body.to_s) + end + + test 'los errores se basan en un sitio' do + assert_equal site, job.send(:site) + end + + test 'los errores tienen archivos fuente' do + assert_equal %w[https://tintalimon.com.ar/assets/js/pack.js], job.send(:sources) + end + + test 'los errores tienen una url de origen' do + assert_equal 'tintalimon.com.ar', job.send(:origin) + end + + test 'los errores tienen un sourcemap' do + local_job = job + sourcemap = local_job.send :sourcemap + + assert_equal SourceMap::Map, sourcemap.class + assert_equal 'assets/js/pack.js', sourcemap.filename + assert sourcemap.sources.size.positive? + end +end From 0bd6d13335269fd92b5fc23c80620fd6b8e21b5e Mon Sep 17 00:00:00 2001 From: f Date: Tue, 27 Apr 2021 11:31:57 -0300 Subject: [PATCH 014/234] testear archivos individuales o todo --- Makefile | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Makefile b/Makefile index c93d4734..913ac105 100644 --- a/Makefile +++ b/Makefile @@ -17,6 +17,12 @@ public/packs/manifest.json.br: $(assets) assets: public/packs/manifest.json.br +test/%_test.rb: always + $(hain) 'cd /Sutty/sutty; bundle exec rake test TEST="$@" RAILS_ENV=test' + +test: always + $(hain) 'cd /Sutty/sutty; bundle exec rake test' + serve: /etc/hosts $(hain) 'cd /Sutty/sutty; bundle exec rails s -b "ssl://0.0.0.0:3000?key=../sutty.local/domain/$(SUTTY).key&cert=../sutty.local/domain/$(SUTTY).crt"' From c2c179c8e77f3478930e81072145a8c963919c3d Mon Sep 17 00:00:00 2001 From: f Date: Tue, 27 Apr 2021 11:33:16 -0300 Subject: [PATCH 015/234] schema.rb --- db/schema.rb | 329 +++++++++++++++++++++++++++------------------------ 1 file changed, 172 insertions(+), 157 deletions(-) diff --git a/db/schema.rb b/db/schema.rb index 4df09176..1e6b0b15 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -2,195 +2,210 @@ # of editing this file, please use the migrations feature of Active Record to # incrementally modify your database, and then regenerate this schema definition. # -# This file is the source Rails uses to define your schema when running `rails -# db:schema:load`. When creating a new database, `rails db:schema:load` tends to +# This file is the source Rails uses to define your schema when running `bin/rails +# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to # be faster and is potentially less error prone than running all of your # migrations from scratch. Old migrations may fail to apply correctly if those # migrations use external dependencies or application code. # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20_200_816_003_344) do - create_table 'action_text_rich_texts', force: :cascade do |t| - t.string 'name', null: false - t.text 'body' - t.string 'record_type', null: false - t.integer 'record_id', null: false - t.datetime 'created_at', precision: 6, null: false - t.datetime 'updated_at', precision: 6, null: false - t.index %w[record_type record_id name], name: 'index_action_text_rich_texts_uniqueness', unique: true +ActiveRecord::Schema.define(version: 2021_04_14_152728) do + + create_table "action_text_rich_texts", force: :cascade do |t| + t.string "name", null: false + t.text "body" + t.string "record_type", null: false + t.integer "record_id", null: false + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.index ["record_type", "record_id", "name"], name: "index_action_text_rich_texts_uniqueness", unique: true end - create_table 'active_storage_attachments', force: :cascade do |t| - t.string 'name', null: false - t.string 'record_type', null: false - t.integer 'record_id', null: false - t.integer 'blob_id', null: false - t.datetime 'created_at', null: false - t.index ['blob_id'], name: 'index_active_storage_attachments_on_blob_id' - t.index %w[record_type record_id name blob_id], name: 'index_active_storage_attachments_uniqueness', unique: true + create_table "active_storage_attachments", force: :cascade do |t| + t.string "name", null: false + t.string "record_type", null: false + t.integer "record_id", null: false + t.integer "blob_id", null: false + t.datetime "created_at", null: false + t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id" + t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true end - create_table 'active_storage_blobs', force: :cascade do |t| - t.string 'key', null: false - t.string 'filename', null: false - t.string 'content_type' - t.text 'metadata' - t.bigint 'byte_size', null: false - t.string 'checksum', null: false - t.datetime 'created_at', null: false - t.index ['key'], name: 'index_active_storage_blobs_on_key', unique: true + create_table "active_storage_blobs", force: :cascade do |t| + t.string "key", null: false + t.string "filename", null: false + t.string "content_type" + t.text "metadata" + t.integer "byte_size", null: false + t.string "checksum", null: false + t.datetime "created_at", null: false + t.string "service_name", null: false + t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true end - create_table 'build_stats', force: :cascade do |t| - t.datetime 'created_at', null: false - t.datetime 'updated_at', null: false - t.integer 'deploy_id' - t.integer 'bytes' - t.float 'seconds' - t.string 'action', null: false - t.text 'log' - t.index ['deploy_id'], name: 'index_build_stats_on_deploy_id' + create_table "active_storage_variant_records", force: :cascade do |t| + t.bigint "blob_id", null: false + t.string "variation_digest", null: false + t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true end - create_table 'deploys', force: :cascade do |t| - t.datetime 'created_at', null: false - t.datetime 'updated_at', null: false - t.integer 'site_id' - t.string 'type' - t.text 'values' - t.index ['site_id'], name: 'index_deploys_on_site_id' - t.index ['type'], name: 'index_deploys_on_type' + create_table "build_stats", force: :cascade do |t| + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.integer "deploy_id" + t.integer "bytes" + t.float "seconds" + t.string "action", null: false + t.text "log" + t.boolean "status", default: false + t.index ["deploy_id"], name: "index_build_stats_on_deploy_id" end - create_table 'designs', force: :cascade do |t| - t.datetime 'created_at', null: false - t.datetime 'updated_at', null: false - t.string 'name' - t.text 'description' - t.string 'gem' - t.string 'url' - t.string 'license' - t.boolean 'disabled', default: false - t.text 'credits' - t.string 'designer_url' + create_table "deploys", force: :cascade do |t| + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.integer "site_id" + t.string "type" + t.text "values" + t.index ["site_id"], name: "index_deploys_on_site_id" + t.index ["type"], name: "index_deploys_on_type" end - create_table 'licencias', force: :cascade do |t| - t.datetime 'created_at', null: false - t.datetime 'updated_at', null: false - t.string 'name' - t.text 'description' - t.text 'deed' - t.string 'url' - t.string 'icons' + create_table "designs", force: :cascade do |t| + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "name" + t.text "description" + t.string "gem" + t.string "url" + t.string "license" + t.boolean "disabled", default: false + t.text "credits" + t.string "designer_url" end - create_table 'log_entries', force: :cascade do |t| - t.datetime 'created_at', precision: 6, null: false - t.datetime 'updated_at', precision: 6, null: false - t.integer 'site_id' - t.text 'text' - t.index ['site_id'], name: 'index_log_entries_on_site_id' + create_table "licencias", force: :cascade do |t| + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "name" + t.text "description" + t.text "deed" + t.string "url" + t.string "icons" end - create_table 'maintenances', force: :cascade do |t| - t.datetime 'created_at', precision: 6, null: false - t.datetime 'updated_at', precision: 6, null: false - t.text 'message' - t.datetime 'estimated_from' - t.datetime 'estimated_to' - t.boolean 'are_we_back', default: false + create_table "log_entries", force: :cascade do |t| + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.integer "site_id" + t.text "text" + t.boolean "sent", default: false + t.index ["site_id"], name: "index_log_entries_on_site_id" end - create_table 'mobility_string_translations', force: :cascade do |t| - t.string 'locale', null: false - t.string 'key', null: false - t.string 'value' - t.string 'translatable_type' - t.integer 'translatable_id' - t.datetime 'created_at', null: false - t.datetime 'updated_at', null: false - t.index %w[translatable_id translatable_type key], name: 'index_mobility_string_translations_on_translatable_attribute' - t.index %w[translatable_id translatable_type locale key], name: 'index_mobility_string_translations_on_keys', unique: true - t.index %w[translatable_type key value locale], name: 'index_mobility_string_translations_on_query_keys' + create_table "maintenances", force: :cascade do |t| + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.text "message" + t.datetime "estimated_from" + t.datetime "estimated_to" + t.boolean "are_we_back", default: false end - create_table 'mobility_text_translations', force: :cascade do |t| - t.string 'locale', null: false - t.string 'key', null: false - t.text 'value' - t.string 'translatable_type' - t.integer 'translatable_id' - t.datetime 'created_at', null: false - t.datetime 'updated_at', null: false - t.index %w[translatable_id translatable_type key], name: 'index_mobility_text_translations_on_translatable_attribute' - t.index %w[translatable_id translatable_type locale key], name: 'index_mobility_text_translations_on_keys', unique: true + create_table "mobility_string_translations", force: :cascade do |t| + t.string "locale", null: false + t.string "key", null: false + t.string "value" + t.string "translatable_type" + t.integer "translatable_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["translatable_id", "translatable_type", "key"], name: "index_mobility_string_translations_on_translatable_attribute" + t.index ["translatable_id", "translatable_type", "locale", "key"], name: "index_mobility_string_translations_on_keys", unique: true + t.index ["translatable_type", "key", "value", "locale"], name: "index_mobility_string_translations_on_query_keys" end - create_table 'roles', force: :cascade do |t| - t.datetime 'created_at', null: false - t.datetime 'updated_at', null: false - t.integer 'site_id' - t.integer 'usuarie_id' - t.string 'rol' - t.boolean 'temporal' - t.index %w[site_id usuarie_id], name: 'index_roles_on_site_id_and_usuarie_id', unique: true - t.index ['site_id'], name: 'index_roles_on_site_id' - t.index ['usuarie_id'], name: 'index_roles_on_usuarie_id' + create_table "mobility_text_translations", force: :cascade do |t| + t.string "locale", null: false + t.string "key", null: false + t.text "value" + t.string "translatable_type" + t.integer "translatable_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["translatable_id", "translatable_type", "key"], name: "index_mobility_text_translations_on_translatable_attribute" + t.index ["translatable_id", "translatable_type", "locale", "key"], name: "index_mobility_text_translations_on_keys", unique: true end - create_table 'sites', force: :cascade do |t| - t.datetime 'created_at', null: false - t.datetime 'updated_at', null: false - t.string 'name' - t.integer 'design_id' - t.integer 'licencia_id' - t.string 'status', default: 'waiting' - t.text 'description' - t.string 'title' - t.boolean 'colaboracion_anonima', default: false - t.boolean 'contact', default: false - t.string 'private_key_ciphertext' - t.index ['design_id'], name: 'index_sites_on_design_id' - t.index ['licencia_id'], name: 'index_sites_on_licencia_id' - t.index ['name'], name: 'index_sites_on_name', unique: true + create_table "roles", force: :cascade do |t| + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.integer "site_id" + t.integer "usuarie_id" + t.string "rol" + t.boolean "temporal" + t.index ["site_id", "usuarie_id"], name: "index_roles_on_site_id_and_usuarie_id", unique: true + t.index ["site_id"], name: "index_roles_on_site_id" + t.index ["usuarie_id"], name: "index_roles_on_usuarie_id" end - create_table 'usuaries', force: :cascade do |t| - t.datetime 'created_at', null: false - t.datetime 'updated_at', null: false - t.string 'email', default: '', null: false - t.string 'encrypted_password', default: '', null: false - t.string 'reset_password_token' - t.datetime 'reset_password_sent_at' - t.datetime 'remember_created_at' - t.string 'confirmation_token' - t.datetime 'confirmed_at' - t.datetime 'confirmation_sent_at' - t.string 'unconfirmed_email' - t.integer 'failed_attempts', default: 0, null: false - t.string 'unlock_token' - t.datetime 'locked_at' - t.boolean 'acepta_politicas_de_privacidad', default: false - t.string 'invitation_token' - t.datetime 'invitation_created_at' - t.datetime 'invitation_sent_at' - t.datetime 'invitation_accepted_at' - t.integer 'invitation_limit' - t.string 'invited_by_type' - t.integer 'invited_by_id' - t.integer 'invitations_count', default: 0 - t.string 'lang', default: 'es' - t.index ['confirmation_token'], name: 'index_usuaries_on_confirmation_token', unique: true - t.index ['email'], name: 'index_usuaries_on_email', unique: true - t.index ['invitation_token'], name: 'index_usuaries_on_invitation_token', unique: true - t.index ['invitations_count'], name: 'index_usuaries_on_invitations_count' - t.index ['invited_by_id'], name: 'index_usuaries_on_invited_by_id' - t.index %w[invited_by_type invited_by_id], name: 'index_usuaries_on_invited_by_type_and_invited_by_id' - t.index ['reset_password_token'], name: 'index_usuaries_on_reset_password_token', unique: true - t.index ['unlock_token'], name: 'index_usuaries_on_unlock_token', unique: true + create_table "sites", force: :cascade do |t| + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "name" + t.integer "design_id" + t.integer "licencia_id" + t.string "status", default: "waiting" + t.text "description" + t.string "title" + t.boolean "colaboracion_anonima", default: false + t.boolean "contact", default: false + t.string "private_key_ciphertext" + t.boolean "acepta_invitades", default: false + t.string "tienda_api_key_ciphertext", default: "" + t.string "tienda_url", default: "" + t.string "api_key_ciphertext" + t.index ["design_id"], name: "index_sites_on_design_id" + t.index ["licencia_id"], name: "index_sites_on_licencia_id" + t.index ["name"], name: "index_sites_on_name", unique: true end - add_foreign_key 'active_storage_attachments', 'active_storage_blobs', column: 'blob_id' + create_table "usuaries", force: :cascade do |t| + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "email", default: "", null: false + t.string "encrypted_password", default: "", null: false + t.string "reset_password_token" + t.datetime "reset_password_sent_at" + t.datetime "remember_created_at" + t.string "confirmation_token" + t.datetime "confirmed_at" + t.datetime "confirmation_sent_at" + t.string "unconfirmed_email" + t.integer "failed_attempts", default: 0, null: false + t.string "unlock_token" + t.datetime "locked_at" + t.boolean "acepta_politicas_de_privacidad", default: false + t.string "invitation_token" + t.datetime "invitation_created_at" + t.datetime "invitation_sent_at" + t.datetime "invitation_accepted_at" + t.integer "invitation_limit" + t.string "invited_by_type" + t.integer "invited_by_id" + t.integer "invitations_count", default: 0 + t.string "lang", default: "es" + t.index ["confirmation_token"], name: "index_usuaries_on_confirmation_token", unique: true + t.index ["email"], name: "index_usuaries_on_email", unique: true + t.index ["invitation_token"], name: "index_usuaries_on_invitation_token", unique: true + t.index ["invitations_count"], name: "index_usuaries_on_invitations_count" + t.index ["invited_by_id"], name: "index_usuaries_on_invited_by_id" + t.index ["invited_by_type", "invited_by_id"], name: "index_usuaries_on_invited_by_type_and_invited_by_id" + t.index ["reset_password_token"], name: "index_usuaries_on_reset_password_token", unique: true + t.index ["unlock_token"], name: "index_usuaries_on_unlock_token", unique: true + end + + add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id" + add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id" end From f2683c3a5ab1b4184ec7038d6fda84603db45983 Mon Sep 17 00:00:00 2001 From: f Date: Tue, 27 Apr 2021 11:52:02 -0300 Subject: [PATCH 016/234] hotfixes via make MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit en realidad deberíamos hacer esto vía git pero hay que hacer algunos cambios en el dockerfile para eso --- Makefile | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 913ac105..15603bf2 100644 --- a/Makefile +++ b/Makefile @@ -46,9 +46,6 @@ save: date +%F | xargs git tag -f @echo -e "\a" -load: - ssh root@sutty.nl sh -c "gunzip -c sutty.latest.gz | docker load" - # Crear el directorio donde se almacenan las gemas binarias ../gems/: mkdir -p $@ @@ -99,6 +96,12 @@ ota: assets sudo chgrp -R 82 public/ rsync -av public/ athshe:/srv/sutty/srv/http/data/_public/ +# Hotfixes +ota-rb: + umask 022; git format-patch $(commit) + scp ./0*.patch root@athshe.sutty.nl:/tmp/ + rm ./0*.patch + /etc/hosts: always @echo "Chequeando si es necesario agregar el dominio local $(SUTTY)" @grep -q " $(SUTTY)$$" $@ || echo -e "127.0.0.1 $(SUTTY)\n::1 $(SUTTY)" | sudo tee -a $@ From 11c4713839e57f5be0ace50965c57fb0a1cbcab5 Mon Sep 17 00:00:00 2001 From: f Date: Tue, 27 Apr 2021 11:52:35 -0300 Subject: [PATCH 017/234] entorno por defecto --- .env.example | 1 + config/environments/production.rb | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.env.example b/.env.example index a664acb7..56075093 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,7 @@ RAILS_ENV= IMAP_SERVER= DEFAULT_FROM= +EXCEPTION_TO= SKEL_SUTTY=https://0xacab.org/sutty/skel.sutty.nl # XXX: Si cambiás esta variable, tenés que editar config/webpacker.yml también :( SUTTY=sutty.local diff --git a/config/environments/production.rb b/config/environments/production.rb index b2f91a3d..a51e1a59 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -145,14 +145,14 @@ Rails.application.configure do domain: ENV.fetch('SUTTY', 'sutty.nl'), enable_starttls_auto: false } - config.action_mailer.default_options = { from: ENV['DEFAULT_FROM'] } + config.action_mailer.default_options = { from: ENV.fetch('DEFAULT_FROM', "noreply@sutty.nl") } config.middleware.use ExceptionNotification::Rack, error_grouping: true, email: { email_prefix: '', - sender_address: ENV['DEFAULT_FROM'], - exception_recipients: ENV['EXCEPTION_TO'], + sender_address: ENV.fetch('DEFAULT_FROM', "noreply@sutty.nl"), + exception_recipients: ENV.fetch('EXCEPTION_TO', "errors@sutty.nl"), normalize_subject: true } From f46637e6ff26266f21362d20dbbe8d3724c24c2c Mon Sep 17 00:00:00 2001 From: f Date: Tue, 27 Apr 2021 11:53:03 -0300 Subject: [PATCH 018/234] schema.rb las tablas que no se pudieron exportar son porque sqlite no soporta el tipo de campo uuid --- db/schema.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/db/schema.rb b/db/schema.rb index 1e6b0b15..2a93c5f1 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -12,6 +12,9 @@ ActiveRecord::Schema.define(version: 2021_04_14_152728) do +# Could not dump table "access_logs" because of following StandardError +# Unknown type '' for column 'id' + create_table "action_text_rich_texts", force: :cascade do |t| t.string "name", null: false t.text "body" @@ -62,6 +65,9 @@ ActiveRecord::Schema.define(version: 2021_04_14_152728) do t.index ["deploy_id"], name: "index_build_stats_on_deploy_id" end +# Could not dump table "csp_reports" because of following StandardError +# Unknown type 'uuid' for column 'id' + create_table "deploys", force: :cascade do |t| t.datetime "created_at", null: false t.datetime "updated_at", null: false @@ -162,6 +168,7 @@ ActiveRecord::Schema.define(version: 2021_04_14_152728) do t.boolean "colaboracion_anonima", default: false t.boolean "contact", default: false t.string "private_key_ciphertext" + t.boolean "invitades", default: false t.boolean "acepta_invitades", default: false t.string "tienda_api_key_ciphertext", default: "" t.string "tienda_url", default: "" From 2448ed20e6d99b672fafc06ba917d1ab3e45829f Mon Sep 17 00:00:00 2001 From: f Date: Tue, 27 Apr 2021 11:53:39 -0300 Subject: [PATCH 019/234] alpine 3.13.5 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 68fe8edf..a8dd7ed7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ # el mismo repositorio de trabajo. Cuando tengamos CI/CD algunas cosas # como el tarball van a tener que cambiar porque ya vamos a haber hecho # un clone/pull limpio. -FROM alpine:3.13.4 AS build +FROM alpine:3.13.5 AS build MAINTAINER "f " ARG RAILS_MASTER_KEY From df38e12e3c669291928803f8ef82f342ab340ee9 Mon Sep 17 00:00:00 2001 From: void Date: Wed, 28 Apr 2021 18:00:56 +0000 Subject: [PATCH 020/234] fallar si no existe panel.sutty MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fixes #1279 antes fallaba silenciosamente y rompía airbrake que en consecuencia rompía el resto del javacript --- app/views/env/index.js.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/env/index.js.haml b/app/views/env/index.js.haml index 68ea73a4..f4bd69cf 100644 --- a/app/views/env/index.js.haml +++ b/app/views/env/index.js.haml @@ -1,7 +1,7 @@ = cache @site do :plain window.env = { - AIRBRAKE_SITE_ID: #{@site&.id || 1}, - AIRBRAKE_API_KEY: "#{@site&.airbrake_api_key}", + AIRBRAKE_SITE_ID: #{@site.id}, + AIRBRAKE_API_KEY: "#{@site.airbrake_api_key}", PANEL_URL: "#{ENV['PANEL_URL']}" } From 525a6fc68045a82fc923e84bd2dfbf93e3fc820b Mon Sep 17 00:00:00 2001 From: void Date: Wed, 28 Apr 2021 18:48:50 +0000 Subject: [PATCH 021/234] style: correr prettier sobre el editor --- app/javascript/editor/editor.ts | 516 +++++++++++--------- app/javascript/editor/storage.ts | 36 +- app/javascript/editor/types.ts | 218 +++++---- app/javascript/editor/types/blocks.ts | 130 ++--- app/javascript/editor/types/link.ts | 62 +-- app/javascript/editor/types/mark.ts | 77 ++- app/javascript/editor/types/marks.ts | 160 +++--- app/javascript/editor/types/multimedia.ts | 368 +++++++------- app/javascript/editor/types/parentBlocks.ts | 127 ++--- app/javascript/editor/utils.ts | 134 ++--- 10 files changed, 969 insertions(+), 859 deletions(-) diff --git a/app/javascript/editor/editor.ts b/app/javascript/editor/editor.ts index 167d40db..6ec06f3c 100644 --- a/app/javascript/editor/editor.ts +++ b/app/javascript/editor/editor.ts @@ -1,18 +1,23 @@ -import { storeContent, restoreContent, forgetContent } from 'editor/storage' +import { storeContent, restoreContent, forgetContent } from "editor/storage"; import { - isDirectChild, moveChildren, safeGetSelection, safeGetRangeAt, - setAuxiliaryToolbar, parentBlockNames, clearSelected, -} from 'editor/utils' -import { types, getValidChildren, getType } from 'editor/types' -import { setupButtons as setupMarksButtons } from 'editor/types/marks' -import { setupButtons as setupBlocksButtons } from 'editor/types/blocks' -import { setupButtons as setupParentBlocksButtons } from 'editor/types/parentBlocks' -import { setupAuxiliaryToolbar as setupLinkAuxiliaryToolbar } from 'editor/types/link' + isDirectChild, + moveChildren, + safeGetSelection, + safeGetRangeAt, + setAuxiliaryToolbar, + parentBlockNames, + clearSelected, +} from "editor/utils"; +import { types, getValidChildren, getType } from "editor/types"; +import { setupButtons as setupMarksButtons } from "editor/types/marks"; +import { setupButtons as setupBlocksButtons } from "editor/types/blocks"; +import { setupButtons as setupParentBlocksButtons } from "editor/types/parentBlocks"; +import { setupAuxiliaryToolbar as setupLinkAuxiliaryToolbar } from "editor/types/link"; import { - setupAuxiliaryToolbar as setupMultimediaAuxiliaryToolbar, - setupButtons as setupMultimediaButtons, -} from 'editor/types/multimedia' -import { setupAuxiliaryToolbar as setupMarkAuxiliaryToolbar } from 'editor/types/mark' + setupAuxiliaryToolbar as setupMultimediaAuxiliaryToolbar, + setupButtons as setupMultimediaButtons, +} from "editor/types/multimedia"; +import { setupAuxiliaryToolbar as setupMarkAuxiliaryToolbar } from "editor/types/mark"; // Esta funcion corrije errores que pueden haber como: // * que un nodo que no tiene 'text' permitido no tenga children (se les @@ -22,79 +27,76 @@ import { setupAuxiliaryToolbar as setupMarkAuxiliaryToolbar } from 'editor/types // * convierte y en y // Lo hace para que siga la estructura del documento y que no se borren por // cleanContent luego. -function fixContent (editor: Editor, node: Element = editor.contentEl): void { - if (node.tagName === 'SCRIPT' || node.tagName === 'STYLE') { - node.parentElement?.removeChild(node) - return - } +function fixContent(editor: Editor, node: Element = editor.contentEl): void { + if (node.tagName === "SCRIPT" || node.tagName === "STYLE") { + node.parentElement?.removeChild(node); + return; + } - if (node.tagName === 'I') { - const el = document.createElement('em') - moveChildren(node, el, null) - node.parentElement?.replaceChild(el, node) - node = el - } - if (node.tagName === 'B') { - const el = document.createElement('strong') - moveChildren(node, el, null) - node.parentElement?.replaceChild(el, node) - node = el - } + if (node.tagName === "I") { + const el = document.createElement("em"); + moveChildren(node, el, null); + node.parentElement?.replaceChild(el, node); + node = el; + } + if (node.tagName === "B") { + const el = document.createElement("strong"); + moveChildren(node, el, null); + node.parentElement?.replaceChild(el, node); + node = el; + } - if (node instanceof HTMLImageElement) { - node.dataset.multimediaInner = '' - const figureEl = types.multimedia.create(editor) + if (node instanceof HTMLImageElement) { + node.dataset.multimediaInner = ""; + const figureEl = types.multimedia.create(editor); - let targetEl = node.parentElement - if (!targetEl) throw new Error('No encontré lx objetivo') - while (true) { - const type = getType(targetEl) - if (!type) throw new Error('lx objetivo tiene tipo') - if (type.type.allowedChildren.includes('multimedia')) break - if (!targetEl.parentElement) throw new Error('No encontré lx objetivo') - targetEl = targetEl.parentElement - } + let targetEl = node.parentElement; + if (!targetEl) throw new Error("No encontré lx objetivo"); + while (true) { + const type = getType(targetEl); + if (!type) throw new Error("lx objetivo tiene tipo"); + if (type.type.allowedChildren.includes("multimedia")) break; + if (!targetEl.parentElement) throw new Error("No encontré lx objetivo"); + targetEl = targetEl.parentElement; + } - let parentEl = [...targetEl.childNodes].find( - el => el.contains(node) - ) - if (!parentEl) throw new Error('no encontré lx pariente') - targetEl.insertBefore(figureEl, parentEl) + let parentEl = [...targetEl.childNodes].find((el) => el.contains(node)); + if (!parentEl) throw new Error("no encontré lx pariente"); + targetEl.insertBefore(figureEl, parentEl); - const innerEl = figureEl.querySelector('[data-multimedia-inner]') - if (!innerEl) throw new Error('Raro.') - figureEl.replaceChild(node, innerEl) + const innerEl = figureEl.querySelector("[data-multimedia-inner]"); + if (!innerEl) throw new Error("Raro."); + figureEl.replaceChild(node, innerEl); - node = figureEl - } + node = figureEl; + } - const _type = getType(node) - if (!_type) return + const _type = getType(node); + if (!_type) return; - const { typeName, type } = _type + const { typeName, type } = _type; - if (type.allowedChildren !== 'ignore-children') { - const sel = safeGetSelection(editor) - const range = sel && safeGetRangeAt(sel) + if (type.allowedChildren !== "ignore-children") { + const sel = safeGetSelection(editor); + const range = sel && safeGetRangeAt(sel); - if (getValidChildren(node, type).length == 0) { - if (typeof type.handleEmpty !== 'string') { - const el = type.handleEmpty.create(editor) - // mover cosas que pueden haber - // por ejemplo: cuando convertís a un
    , queda texto fuera del li que - // creamos acá - moveChildren(node, el, null) - node.appendChild(el) - if (range?.intersectsNode(node)) - sel?.collapse(el) - } - } - - for (const child of node.childNodes) { - if (!(child instanceof Element)) continue - fixContent(editor, child) - } - } + if (getValidChildren(node, type).length == 0) { + if (typeof type.handleEmpty !== "string") { + const el = type.handleEmpty.create(editor); + // mover cosas que pueden haber + // por ejemplo: cuando convertís a un
      , queda texto fuera del li que + // creamos acá + moveChildren(node, el, null); + node.appendChild(el); + if (range?.intersectsNode(node)) sel?.collapse(el); + } + } + + for (const child of node.childNodes) { + if (!(child instanceof Element)) continue; + fixContent(editor, child); + } + } } // Esta funcion hace que los elementos del editor sigan la estructura. @@ -102,205 +104,231 @@ function fixContent (editor: Editor, node: Element = editor.contentEl): void { // Edge cases: // * no borramos los
      por que se requieren para que los navegadores // funcionen bien al escribir. no se deberían mostrar de todas maneras -function cleanContent (editor: Editor, node: Element = editor.contentEl): void { - const _type = getType(node) - if (!_type) { - node.parentElement?.removeChild(node) - return - } +function cleanContent(editor: Editor, node: Element = editor.contentEl): void { + const _type = getType(node); + if (!_type) { + node.parentElement?.removeChild(node); + return; + } - const { type } = _type + const { type } = _type; - if (type.allowedChildren !== 'ignore-children') { - for (const child of node.childNodes) { - if (child.nodeType === Node.TEXT_NODE - && !type.allowedChildren.includes('text') - ) { - node.removeChild(child) - continue - } + if (type.allowedChildren !== "ignore-children") { + for (const child of node.childNodes) { + if ( + child.nodeType === Node.TEXT_NODE && + !type.allowedChildren.includes("text") + ) { + node.removeChild(child); + continue; + } - if (!(child instanceof Element)) continue + if (!(child instanceof Element)) continue; - const childType = getType(child) - if (childType?.typeName === 'br') continue - if (!childType || !type.allowedChildren.includes(childType.typeName)) { - // XXX: esto extrae las cosas de adentro para que no sea destructivo - moveChildren(child, node, child) - node.removeChild(child) - return - } + const childType = getType(child); + if (childType?.typeName === "br") continue; + if (!childType || !type.allowedChildren.includes(childType.typeName)) { + // XXX: esto extrae las cosas de adentro para que no sea destructivo + moveChildren(child, node, child); + node.removeChild(child); + return; + } - cleanContent(editor, child) - } + cleanContent(editor, child); + } - // solo contar children válido para ese nodo - const validChildrenLength = getValidChildren(node, type).length + // solo contar children válido para ese nodo + const validChildrenLength = getValidChildren(node, type).length; - const sel = safeGetSelection(editor) - const range = sel && safeGetRangeAt(sel) - if (type.handleEmpty === 'remove' - && validChildrenLength == 0 - //&& (!range || !range.intersectsNode(node)) - ) { - node.parentNode?.removeChild(node) - return - } - } + const sel = safeGetSelection(editor); + const range = sel && safeGetRangeAt(sel); + if ( + type.handleEmpty === "remove" && + validChildrenLength == 0 + //&& (!range || !range.intersectsNode(node)) + ) { + node.parentNode?.removeChild(node); + return; + } + } } -function routine (editor: Editor): void { - try { - fixContent(editor) - cleanContent(editor) - storeContent(editor) +function routine(editor: Editor): void { + try { + fixContent(editor); + cleanContent(editor); + storeContent(editor); - editor.htmlEl.value = editor.contentEl.innerHTML - } catch (error) { - console.error('Hubo un problema corriendo la rutina', editor, error) - } + editor.htmlEl.value = editor.contentEl.innerHTML; + } catch (error) { + console.error("Hubo un problema corriendo la rutina", editor, error); + } } export interface Editor { - editorEl: HTMLElement, - toolbarEl: HTMLElement, - toolbar: { - auxiliary: { - mark: { - parentEl: HTMLElement, - colorEl: HTMLInputElement, - }, - multimedia: { - parentEl: HTMLElement, - fileEl: HTMLInputElement, - uploadEl: HTMLButtonElement, - altEl: HTMLInputElement, - removeEl: HTMLButtonElement, - }, - link: { - parentEl: HTMLElement, - urlEl: HTMLInputElement, - }, - }, - }, - contentEl: HTMLElement, - wordAlertEl: HTMLElement, - htmlEl: HTMLTextAreaElement, + editorEl: HTMLElement; + toolbarEl: HTMLElement; + toolbar: { + auxiliary: { + mark: { + parentEl: HTMLElement; + colorEl: HTMLInputElement; + }; + multimedia: { + parentEl: HTMLElement; + fileEl: HTMLInputElement; + uploadEl: HTMLButtonElement; + altEl: HTMLInputElement; + removeEl: HTMLButtonElement; + }; + link: { + parentEl: HTMLElement; + urlEl: HTMLInputElement; + }; + }; + }; + contentEl: HTMLElement; + wordAlertEl: HTMLElement; + htmlEl: HTMLTextAreaElement; } function getSel(parentEl: HTMLElement, selector: string): T { - const el = parentEl.querySelector(selector) - if (!el) throw new Error(`No pude encontrar un componente \`${selector}\``) - return el + const el = parentEl.querySelector(selector); + if (!el) throw new Error(`No pude encontrar un componente \`${selector}\``); + return el; } -function setupEditor (editorEl: HTMLElement): void { - // XXX: ¡Esto afecta a todo el documento! ¿Quizás usar un iframe para el editor? - document.execCommand('defaultParagraphSeparator', false, 'p') +function setupEditor(editorEl: HTMLElement): void { + // XXX: ¡Esto afecta a todo el documento! ¿Quizás usar un iframe para el editor? + document.execCommand("defaultParagraphSeparator", false, "p"); - const editor: Editor = { - editorEl, - toolbarEl: getSel(editorEl, '.editor-toolbar'), - toolbar: { - auxiliary: { - mark: { - parentEl: getSel(editorEl, '[data-editor-auxiliary=mark]'), - colorEl: getSel(editorEl, '[data-editor-auxiliary=mark] [name=mark-color]'), - }, - multimedia: { - parentEl: getSel(editorEl, '[data-editor-auxiliary=multimedia]'), - fileEl: getSel(editorEl, '[data-editor-auxiliary=multimedia] [name=multimedia-file]'), - uploadEl: getSel(editorEl, '[data-editor-auxiliary=multimedia] [name=multimedia-file-upload]'), - altEl: getSel(editorEl, '[data-editor-auxiliary=multimedia] [name=multimedia-alt]'), - removeEl: getSel(editorEl, '[data-editor-auxiliary=multimedia] [name=multimedia-remove]'), - }, - link: { - parentEl: getSel(editorEl, '[data-editor-auxiliary=link]'), - urlEl: getSel(editorEl, '[data-editor-auxiliary=link] [name=link-url]'), - }, - }, - }, - contentEl: getSel(editorEl, '.editor-content'), - wordAlertEl: getSel(editorEl, '.editor-aviso-word'), - htmlEl: getSel(editorEl, 'textarea'), - } - console.debug('iniciando editor', editor) + const editor: Editor = { + editorEl, + toolbarEl: getSel(editorEl, ".editor-toolbar"), + toolbar: { + auxiliary: { + mark: { + parentEl: getSel(editorEl, "[data-editor-auxiliary=mark]"), + colorEl: getSel( + editorEl, + "[data-editor-auxiliary=mark] [name=mark-color]" + ), + }, + multimedia: { + parentEl: getSel(editorEl, "[data-editor-auxiliary=multimedia]"), + fileEl: getSel( + editorEl, + "[data-editor-auxiliary=multimedia] [name=multimedia-file]" + ), + uploadEl: getSel( + editorEl, + "[data-editor-auxiliary=multimedia] [name=multimedia-file-upload]" + ), + altEl: getSel( + editorEl, + "[data-editor-auxiliary=multimedia] [name=multimedia-alt]" + ), + removeEl: getSel( + editorEl, + "[data-editor-auxiliary=multimedia] [name=multimedia-remove]" + ), + }, + link: { + parentEl: getSel(editorEl, "[data-editor-auxiliary=link]"), + urlEl: getSel( + editorEl, + "[data-editor-auxiliary=link] [name=link-url]" + ), + }, + }, + }, + contentEl: getSel(editorEl, ".editor-content"), + wordAlertEl: getSel(editorEl, ".editor-aviso-word"), + htmlEl: getSel(editorEl, "textarea"), + }; + console.debug("iniciando editor", editor); - // Recuperar el contenido si hay algo guardado, si tuviéramos un campo - // de última edición podríamos saber si el artículo fue editado - // después o la versión local es la última. - // - // TODO: Preguntar si se lo quiere recuperar. - restoreContent(editor) + // Recuperar el contenido si hay algo guardado, si tuviéramos un campo + // de última edición podríamos saber si el artículo fue editado + // después o la versión local es la última. + // + // TODO: Preguntar si se lo quiere recuperar. + restoreContent(editor); - // Word alert - editor.contentEl.addEventListener('paste', () => { - editor.wordAlertEl.style.display = 'block' - }) + // Word alert + editor.contentEl.addEventListener("paste", () => { + editor.wordAlertEl.style.display = "block"; + }); - // Setup routine listeners - const observer = new MutationObserver(() => routine(editor)) - observer.observe(editor.contentEl, { - childList: true, - attributes: true, - subtree: true, - characterData: true, - }) + // Setup routine listeners + const observer = new MutationObserver(() => routine(editor)); + observer.observe(editor.contentEl, { + childList: true, + attributes: true, + subtree: true, + characterData: true, + }); - document.addEventListener("selectionchange", () => routine(editor)) + document.addEventListener("selectionchange", () => routine(editor)); - // Capture onClick - editor.contentEl.addEventListener('click', event => { - const target = event.target! as Element - const type = getType(target) - if (!type || !type.type.onClick) { - setAuxiliaryToolbar(editor, null) - clearSelected(editor) - return true - } - type.type.onClick(editor, target) - return false - }, true) + // Capture onClick + editor.contentEl.addEventListener( + "click", + (event) => { + const target = event.target! as Element; + const type = getType(target); + if (!type || !type.type.onClick) { + setAuxiliaryToolbar(editor, null); + clearSelected(editor); + return true; + } + type.type.onClick(editor, target); + return false; + }, + true + ); - // Clean seleted - const selectedEl = editor.contentEl.querySelector('[data-editor-selected]') - if (selectedEl) delete (selectedEl as HTMLElement).dataset.editorSelected + // Clean seleted + const selectedEl = editor.contentEl.querySelector("[data-editor-selected]"); + if (selectedEl) delete (selectedEl as HTMLElement).dataset.editorSelected; - // Setup botones - setupMarksButtons(editor) - setupBlocksButtons(editor) - setupParentBlocksButtons(editor) - setupMultimediaButtons(editor) + // Setup botones + setupMarksButtons(editor); + setupBlocksButtons(editor); + setupParentBlocksButtons(editor); + setupMultimediaButtons(editor); - setupLinkAuxiliaryToolbar(editor) - setupMultimediaAuxiliaryToolbar(editor) - setupMarkAuxiliaryToolbar(editor) + setupLinkAuxiliaryToolbar(editor); + setupMultimediaAuxiliaryToolbar(editor); + setupMarkAuxiliaryToolbar(editor); - // Finally... - routine(editor) + // Finally... + routine(editor); } document.addEventListener("turbolinks:load", () => { - const flash = document.querySelector('.js-flash') + const flash = document.querySelector(".js-flash"); - if (flash) { - const keys = JSON.parse(flash.dataset.keys || '[]') + if (flash) { + const keys = JSON.parse(flash.dataset.keys || "[]"); - switch (flash.dataset.target) { - case 'editor': - switch (flash.dataset.action) { - case 'forget-content': - keys.forEach(forgetContent) - } - } - } + switch (flash.dataset.target) { + case "editor": + switch (flash.dataset.action) { + case "forget-content": + keys.forEach(forgetContent); + } + } + } - for (const editorEl of document.querySelectorAll('.editor[data-editor]')) { - try { - setupEditor(editorEl) - } catch (error) { - // TODO: mostrar error - console.error('no se pudo iniciar el editor, error completo', error) - } - } -}) + for (const editorEl of document.querySelectorAll( + ".editor[data-editor]" + )) { + try { + setupEditor(editorEl); + } catch (error) { + // TODO: mostrar error + console.error("no se pudo iniciar el editor, error completo", error); + } + } +}); diff --git a/app/javascript/editor/storage.ts b/app/javascript/editor/storage.ts index df27d59c..e914a242 100644 --- a/app/javascript/editor/storage.ts +++ b/app/javascript/editor/storage.ts @@ -1,4 +1,4 @@ -import { Editor } from 'editor/editor' +import { Editor } from "editor/editor"; /* * Guarda una copia local de los cambios para poder recuperarlos @@ -6,27 +6,33 @@ import { Editor } from 'editor/editor' * * Usamos la URL completa sin anchors. */ -function getStorageKey (editor: Editor): string { - const keyEl = editor.editorEl.querySelector('[data-target="storage-key"]') - if (!keyEl) throw new Error('No encuentro la llave para guardar los artículos') - return keyEl.value +function getStorageKey(editor: Editor): string { + const keyEl = editor.editorEl.querySelector( + '[data-target="storage-key"]' + ); + if (!keyEl) + throw new Error("No encuentro la llave para guardar los artículos"); + return keyEl.value; } -export function forgetContent (storedKey: string): void { - window.localStorage.removeItem(storedKey) +export function forgetContent(storedKey: string): void { + window.localStorage.removeItem(storedKey); } -export function storeContent (editor: Editor): void { - if (editor.contentEl.innerText.trim().length === 0) return +export function storeContent(editor: Editor): void { + if (editor.contentEl.innerText.trim().length === 0) return; - window.localStorage.setItem(getStorageKey(editor), editor.contentEl.innerHTML) + window.localStorage.setItem( + getStorageKey(editor), + editor.contentEl.innerHTML + ); } -export function restoreContent (editor: Editor): void { - const content = window.localStorage.getItem(getStorageKey(editor)) +export function restoreContent(editor: Editor): void { + const content = window.localStorage.getItem(getStorageKey(editor)); - if (!content) return - if (content.trim().length === 0) return + if (!content) return; + if (content.trim().length === 0) return; - editor.contentEl.innerHTML = content + editor.contentEl.innerHTML = content; } diff --git a/app/javascript/editor/types.ts b/app/javascript/editor/types.ts index 8034e3e4..ac3030ce 100644 --- a/app/javascript/editor/types.ts +++ b/app/javascript/editor/types.ts @@ -1,126 +1,140 @@ -import { Editor } from 'editor/editor' -import { marks } from 'editor/types/marks' -import { blocks, li, EditorBlock } from 'editor/types/blocks' -import { parentBlocks } from 'editor/types/parentBlocks' -import { multimedia } from 'editor/types/multimedia' -import { blockNames, parentBlockNames, safeGetRangeAt, safeGetSelection } from 'editor/utils' +import { Editor } from "editor/editor"; +import { marks } from "editor/types/marks"; +import { blocks, li, EditorBlock } from "editor/types/blocks"; +import { parentBlocks } from "editor/types/parentBlocks"; +import { multimedia } from "editor/types/multimedia"; +import { + blockNames, + parentBlockNames, + safeGetRangeAt, + safeGetSelection, +} from "editor/utils"; export interface EditorNode { - selector: string, - // la string es el nombre en la gran lista de types O 'text' - // XXX: esto es un hack para no poner EditorNode dentro de EditorNode, - // quizás podemos hacer que esto sea una función que retorna bool - allowedChildren: string[] | 'ignore-children', + selector: string; + // la string es el nombre en la gran lista de types O 'text' + // XXX: esto es un hack para no poner EditorNode dentro de EditorNode, + // quizás podemos hacer que esto sea una función que retorna bool + allowedChildren: string[] | "ignore-children"; - // * si es 'do-nothing', no hace nada si está vacío (esto es para cuando - // permitís 'text' entonces se puede tipear adentro, ej: párrafo vacío) - // * si es 'remove', sacamos el coso si está vacío. - // ej: strong: { handleNothing: 'remove' } - // * si es un block, insertamos el bloque y movemos la selección ahí - // ej: ul: { handleNothing: li } - handleEmpty: 'do-nothing' | 'remove' | EditorBlock, + // * si es 'do-nothing', no hace nada si está vacío (esto es para cuando + // permitís 'text' entonces se puede tipear adentro, ej: párrafo vacío) + // * si es 'remove', sacamos el coso si está vacío. + // ej: strong: { handleNothing: 'remove' } + // * si es un block, insertamos el bloque y movemos la selección ahí + // ej: ul: { handleNothing: li } + handleEmpty: "do-nothing" | "remove" | EditorBlock; - // esta función puede ser llamada para cosas que no necesariamente sea la - // creación del nodo con el botón; por ejemplo, al intentar recuperar - // el formato. esto es importante por que, por ejemplo, no deberíamos - // cambiar la selección acá. - create: (editor: Editor) => HTMLElement, + // esta función puede ser llamada para cosas que no necesariamente sea la + // creación del nodo con el botón; por ejemplo, al intentar recuperar + // el formato. esto es importante por que, por ejemplo, no deberíamos + // cambiar la selección acá. + create: (editor: Editor) => HTMLElement; - onClick?: (editor: Editor, target: Element) => void, + onClick?: (editor: Editor, target: Element) => void; } export const types: { [propName: string]: EditorNode } = { - ...marks, - ...blocks, - li, - ...parentBlocks, - contentEl: { - selector: '.editor-content', - allowedChildren: [...blockNames, ...parentBlockNames, 'multimedia'], - handleEmpty: blocks.paragraph, - create: () => { throw new Error('se intentó crear contentEl') } - }, - br: { - selector: 'br', - allowedChildren: [], - handleEmpty: 'do-nothing', - create: () => { throw new Error('se intentó crear br') } - }, - multimedia, -} + ...marks, + ...blocks, + li, + ...parentBlocks, + contentEl: { + selector: ".editor-content", + allowedChildren: [...blockNames, ...parentBlockNames, "multimedia"], + handleEmpty: blocks.paragraph, + create: () => { + throw new Error("se intentó crear contentEl"); + }, + }, + br: { + selector: "br", + allowedChildren: [], + handleEmpty: "do-nothing", + create: () => { + throw new Error("se intentó crear br"); + }, + }, + multimedia, +}; -export function getType (node: Element): { typeName: string, type: EditorNode } | null { - for (let [typeName, type] of Object.entries(types)) { - if (node.matches(type.selector)) { - return { typeName, type } - } - } - - return null +export function getType( + node: Element +): { typeName: string; type: EditorNode } | null { + for (let [typeName, type] of Object.entries(types)) { + if (node.matches(type.selector)) { + return { typeName, type }; + } + } + + return null; } // encuentra el primer pariente que pueda tener al type, y retorna un array // donde // array[0] = elemento que matchea el type // array[array.len - 1] = primer elemento seleccionado -export function getValidParentInSelection (args: { - editor: Editor, - type: string, +export function getValidParentInSelection(args: { + editor: Editor; + type: string; }): Element[] { - const sel = safeGetSelection(args.editor) - if (!sel) throw new Error('No se donde insertar esto') - const range = safeGetRangeAt(sel) - if (!range) throw new Error('No se donde insertar esto') + const sel = safeGetSelection(args.editor); + if (!sel) throw new Error("No se donde insertar esto"); + const range = safeGetRangeAt(sel); + if (!range) throw new Error("No se donde insertar esto"); - let list: Element[] = [] - - if (!sel.anchorNode) { - throw new Error('No se donde insertar esto') - } else if (sel.anchorNode instanceof Element) { - list = [sel.anchorNode] - } else if (sel.anchorNode.parentElement) { - list = [sel.anchorNode.parentElement] - } else { - throw new Error('No se donde insertar esto') - } + let list: Element[] = []; - while (true) { - const el = list[0] - if (!args.editor.contentEl.contains(el) - && el != args.editor.contentEl) - throw new Error('No se donde insertar esto') - const type = getType(el) + if (!sel.anchorNode) { + throw new Error("No se donde insertar esto"); + } else if (sel.anchorNode instanceof Element) { + list = [sel.anchorNode]; + } else if (sel.anchorNode.parentElement) { + list = [sel.anchorNode.parentElement]; + } else { + throw new Error("No se donde insertar esto"); + } - if (type) { - //if (type.typeName === 'contentEl') break - //if (parentBlockNames.includes(type.typeName)) break - if ((type.type.allowedChildren instanceof Array) - && type.type.allowedChildren.includes(args.type)) break - } - if (el.parentElement) { - list = [el.parentElement, ...list] - } else { - throw new Error('No se donde insertar esto') - } - } - - return list + while (true) { + const el = list[0]; + if (!args.editor.contentEl.contains(el) && el != args.editor.contentEl) + throw new Error("No se donde insertar esto"); + const type = getType(el); + + if (type) { + //if (type.typeName === 'contentEl') break + //if (parentBlockNames.includes(type.typeName)) break + if ( + type.type.allowedChildren instanceof Array && + type.type.allowedChildren.includes(args.type) + ) + break; + } + if (el.parentElement) { + list = [el.parentElement, ...list]; + } else { + throw new Error("No se donde insertar esto"); + } + } + + return list; } -export function getValidChildren (node: Element, type: EditorNode): Node[] { - if (type.allowedChildren === 'ignore-children') - throw new Error('se llamó a getValidChildren con un type que no lo permite!') - return [...node.childNodes].filter(n => { - // si permite texto y esto es un texto, es válido - if (n.nodeType === Node.TEXT_NODE) - return type.allowedChildren.includes('text') && n.textContent?.length +export function getValidChildren(node: Element, type: EditorNode): Node[] { + if (type.allowedChildren === "ignore-children") + throw new Error( + "se llamó a getValidChildren con un type que no lo permite!" + ); + return [...node.childNodes].filter((n) => { + // si permite texto y esto es un texto, es válido + if (n.nodeType === Node.TEXT_NODE) + return type.allowedChildren.includes("text") && n.textContent?.length; - // si no es un elemento, no es válido - if (!(n instanceof Element)) - return false + // si no es un elemento, no es válido + if (!(n instanceof Element)) return false; - const t = getType(n) - if (!t) return false - return type.allowedChildren.includes(t.typeName) - }) + const t = getType(n); + if (!t) return false; + return type.allowedChildren.includes(t.typeName); + }); } diff --git a/app/javascript/editor/types/blocks.ts b/app/javascript/editor/types/blocks.ts index 52ad157a..2e2dea7e 100644 --- a/app/javascript/editor/types/blocks.ts +++ b/app/javascript/editor/types/blocks.ts @@ -1,72 +1,76 @@ -import { Editor } from 'editor/editor' +import { Editor } from "editor/editor"; import { - safeGetSelection, safeGetRangeAt, - moveChildren, - markNames, blockNames, parentBlockNames, -} from 'editor/utils' -import { EditorNode, getType, getValidParentInSelection } from 'editor/types' + safeGetSelection, + safeGetRangeAt, + moveChildren, + markNames, + blockNames, + parentBlockNames, +} from "editor/utils"; +import { EditorNode, getType, getValidParentInSelection } from "editor/types"; -export interface EditorBlock extends EditorNode { +export interface EditorBlock extends EditorNode {} + +function makeBlock(tag: string): EditorBlock { + return { + selector: tag, + allowedChildren: [...markNames, "text"], + handleEmpty: "do-nothing", + create: () => document.createElement(tag), + }; } -function makeBlock (tag: string): EditorBlock { - return { - selector: tag, - allowedChildren: [...markNames, 'text'], - handleEmpty: 'do-nothing', - create: () => document.createElement(tag), - } -} - -export const li: EditorBlock = makeBlock('li') +export const li: EditorBlock = makeBlock("li"); // XXX: si agregás algo acá, agregalo a blockNames // (y probablemente le quieras hacer un botón en app/views/posts/attributes/_content.haml) export const blocks: { [propName: string]: EditorBlock } = { - paragraph: makeBlock('p'), - h1: makeBlock('h1'), - h2: makeBlock('h2'), - h3: makeBlock('h3'), - h4: makeBlock('h4'), - h5: makeBlock('h5'), - h6: makeBlock('h6'), - unordered_list: { - ...makeBlock('ul'), - allowedChildren: ['li'], - handleEmpty: li, - }, - ordered_list: { - ...makeBlock('ol'), - allowedChildren: ['li'], - handleEmpty: li, - }, -} - -export function setupButtons (editor: Editor): void { - for (const [ name, type ] of Object.entries(blocks)) { - const buttonEl = editor.toolbarEl.querySelector(`[data-editor-button="block-${name}"]`) - if (!buttonEl) continue - buttonEl.addEventListener("click", event => { - event.preventDefault() - - const list = getValidParentInSelection({ editor, type: name }) - - // No borrar cosas como multimedia - if (blockNames.indexOf(getType(list[1])!.typeName) === -1) { - return - } - - let replacementType = list[1].matches(type.selector) - ? blocks.paragraph - : type - - const el = replacementType.create(editor) - replacementType.onClick && replacementType.onClick(editor, el) - moveChildren(list[1], el, null) - list[0].replaceChild(el, list[1]) - window.getSelection()?.collapse(el) - - return false - }) - } + paragraph: makeBlock("p"), + h1: makeBlock("h1"), + h2: makeBlock("h2"), + h3: makeBlock("h3"), + h4: makeBlock("h4"), + h5: makeBlock("h5"), + h6: makeBlock("h6"), + unordered_list: { + ...makeBlock("ul"), + allowedChildren: ["li"], + handleEmpty: li, + }, + ordered_list: { + ...makeBlock("ol"), + allowedChildren: ["li"], + handleEmpty: li, + }, +}; + +export function setupButtons(editor: Editor): void { + for (const [name, type] of Object.entries(blocks)) { + const buttonEl = editor.toolbarEl.querySelector( + `[data-editor-button="block-${name}"]` + ); + if (!buttonEl) continue; + buttonEl.addEventListener("click", (event) => { + event.preventDefault(); + + const list = getValidParentInSelection({ editor, type: name }); + + // No borrar cosas como multimedia + if (blockNames.indexOf(getType(list[1])!.typeName) === -1) { + return; + } + + let replacementType = list[1].matches(type.selector) + ? blocks.paragraph + : type; + + const el = replacementType.create(editor); + replacementType.onClick && replacementType.onClick(editor, el); + moveChildren(list[1], el, null); + list[0].replaceChild(el, list[1]); + window.getSelection()?.collapse(el); + + return false; + }); + } } diff --git a/app/javascript/editor/types/link.ts b/app/javascript/editor/types/link.ts index 40a26e1e..eb85db90 100644 --- a/app/javascript/editor/types/link.ts +++ b/app/javascript/editor/types/link.ts @@ -1,37 +1,37 @@ -import { Editor } from 'editor/editor' -import { EditorNode } from 'editor/types' -import { markNames, setAuxiliaryToolbar, clearSelected } from 'editor/utils' +import { Editor } from "editor/editor"; +import { EditorNode } from "editor/types"; +import { markNames, setAuxiliaryToolbar, clearSelected } from "editor/utils"; -function select (editor: Editor, el: HTMLAnchorElement): void { - clearSelected(editor) - el.dataset.editorSelected = '' - editor.toolbar.auxiliary.link.urlEl.value = el.href - setAuxiliaryToolbar(editor, editor.toolbar.auxiliary.link.parentEl) +function select(editor: Editor, el: HTMLAnchorElement): void { + clearSelected(editor); + el.dataset.editorSelected = ""; + editor.toolbar.auxiliary.link.urlEl.value = el.href; + setAuxiliaryToolbar(editor, editor.toolbar.auxiliary.link.parentEl); } export const link: EditorNode = { - selector: 'a', - allowedChildren: [...markNames.filter(n => n !== 'link'), 'text'], - handleEmpty: 'remove', - create: () => document.createElement('a'), - onClick (editor, el) { - if (!(el instanceof HTMLAnchorElement)) - throw new Error('oh no') - select(editor, el) - }, -} + selector: "a", + allowedChildren: [...markNames.filter((n) => n !== "link"), "text"], + handleEmpty: "remove", + create: () => document.createElement("a"), + onClick(editor, el) { + if (!(el instanceof HTMLAnchorElement)) throw new Error("oh no"); + select(editor, el); + }, +}; -export function setupAuxiliaryToolbar (editor: Editor): void { - editor.toolbar.auxiliary.link.urlEl.addEventListener('input', event => { - const url = editor.toolbar.auxiliary.link.urlEl.value - const selectedEl = editor.contentEl - .querySelector('a[data-editor-selected]') - if (!selectedEl) - throw new Error('No pude encontrar el link para setear el enlace') - - selectedEl.href = url - }) - editor.toolbar.auxiliary.link.urlEl.addEventListener('keydown', event => { - if (event.keyCode == 13) event.preventDefault() - }) +export function setupAuxiliaryToolbar(editor: Editor): void { + editor.toolbar.auxiliary.link.urlEl.addEventListener("input", (event) => { + const url = editor.toolbar.auxiliary.link.urlEl.value; + const selectedEl = editor.contentEl.querySelector( + "a[data-editor-selected]" + ); + if (!selectedEl) + throw new Error("No pude encontrar el link para setear el enlace"); + + selectedEl.href = url; + }); + editor.toolbar.auxiliary.link.urlEl.addEventListener("keydown", (event) => { + if (event.keyCode == 13) event.preventDefault(); + }); } diff --git a/app/javascript/editor/types/mark.ts b/app/javascript/editor/types/mark.ts index 1e63e368..2607cae5 100644 --- a/app/javascript/editor/types/mark.ts +++ b/app/javascript/editor/types/mark.ts @@ -1,49 +1,48 @@ -import { Editor } from 'editor/editor' -import { EditorNode } from 'editor/types' -import { markNames, setAuxiliaryToolbar, clearSelected } from 'editor/utils' +import { Editor } from "editor/editor"; +import { EditorNode } from "editor/types"; +import { markNames, setAuxiliaryToolbar, clearSelected } from "editor/utils"; -const hex = (x: string) => ("0" + parseInt(x).toString(16)).slice(-2) +const hex = (x: string) => ("0" + parseInt(x).toString(16)).slice(-2); // https://stackoverflow.com/a/3627747 // TODO: cambiar por una solución más copada -function rgbToHex (rgb: string): string { - const matches = rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/) - if (!matches) throw new Error('no pude parsear el rgb()') - return "#" + hex(matches[1]) + hex(matches[2]) + hex(matches[3]) +function rgbToHex(rgb: string): string { + const matches = rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/); + if (!matches) throw new Error("no pude parsear el rgb()"); + return "#" + hex(matches[1]) + hex(matches[2]) + hex(matches[3]); } -function select (editor: Editor, el: HTMLElement): void { - clearSelected(editor) - el.dataset.editorSelected = '' - editor.toolbar.auxiliary.mark.colorEl.value - = el.style.backgroundColor - ? rgbToHex(el.style.backgroundColor) - : '#f206f9' - setAuxiliaryToolbar(editor, editor.toolbar.auxiliary.mark.parentEl) +function select(editor: Editor, el: HTMLElement): void { + clearSelected(editor); + el.dataset.editorSelected = ""; + editor.toolbar.auxiliary.mark.colorEl.value = el.style.backgroundColor + ? rgbToHex(el.style.backgroundColor) + : "#f206f9"; + setAuxiliaryToolbar(editor, editor.toolbar.auxiliary.mark.parentEl); } export const mark: EditorNode = { - selector: 'mark', - allowedChildren: [...markNames.filter(n => n !== 'mark'), 'text'], - handleEmpty: 'remove', - create: () => document.createElement('mark'), - onClick (editor, el) { - if (!(el instanceof HTMLElement)) - throw new Error('oh no') - select(editor, el) - }, -} + selector: "mark", + allowedChildren: [...markNames.filter((n) => n !== "mark"), "text"], + handleEmpty: "remove", + create: () => document.createElement("mark"), + onClick(editor, el) { + if (!(el instanceof HTMLElement)) throw new Error("oh no"); + select(editor, el); + }, +}; -export function setupAuxiliaryToolbar (editor: Editor): void { - editor.toolbar.auxiliary.mark.colorEl.addEventListener('input', event => { - const color = editor.toolbar.auxiliary.mark.colorEl.value - const selectedEl = editor.contentEl - .querySelector('mark[data-editor-selected]') - if (!selectedEl) - throw new Error('No pude encontrar el mark para setear el color') - - selectedEl.style.backgroundColor = color - }) - editor.toolbar.auxiliary.mark.colorEl.addEventListener('keydown', event => { - if (event.keyCode == 13) event.preventDefault() - }) +export function setupAuxiliaryToolbar(editor: Editor): void { + editor.toolbar.auxiliary.mark.colorEl.addEventListener("input", (event) => { + const color = editor.toolbar.auxiliary.mark.colorEl.value; + const selectedEl = editor.contentEl.querySelector( + "mark[data-editor-selected]" + ); + if (!selectedEl) + throw new Error("No pude encontrar el mark para setear el color"); + + selectedEl.style.backgroundColor = color; + }); + editor.toolbar.auxiliary.mark.colorEl.addEventListener("keydown", (event) => { + if (event.keyCode == 13) event.preventDefault(); + }); } diff --git a/app/javascript/editor/types/marks.ts b/app/javascript/editor/types/marks.ts index 3790c749..0ea5a5ad 100644 --- a/app/javascript/editor/types/marks.ts +++ b/app/javascript/editor/types/marks.ts @@ -1,96 +1,102 @@ -import { Editor } from 'editor/editor' -import { EditorNode } from 'editor/types' +import { Editor } from "editor/editor"; +import { EditorNode } from "editor/types"; import { - safeGetSelection, safeGetRangeAt, - moveChildren, - markNames, -} from 'editor/utils' -import { link } from 'editor/types/link' -import { mark } from 'editor/types/mark' + safeGetSelection, + safeGetRangeAt, + moveChildren, + markNames, +} from "editor/utils"; +import { link } from "editor/types/link"; +import { mark } from "editor/types/mark"; -function makeMark (name: string, tag: string): EditorNode { - return { - selector: tag, - allowedChildren: [...markNames.filter(n => n !== name), 'text'], - handleEmpty: 'remove', - create: () => document.createElement(tag), - } +function makeMark(name: string, tag: string): EditorNode { + return { + selector: tag, + allowedChildren: [...markNames.filter((n) => n !== name), "text"], + handleEmpty: "remove", + create: () => document.createElement(tag), + }; } // XXX: si agregás algo acá, agregalo a markNames export const marks: { [propName: string]: EditorNode } = { - bold: makeMark('bold', 'strong'), - italic: makeMark('italic', 'em'), - deleted: makeMark('deleted', 'del'), - underline: makeMark('underline', 'u'), - sub: makeMark('sub', 'sub'), - super: makeMark('super', 'sup'), - mark, - link, - small: makeMark('small', 'small'), -} + bold: makeMark("bold", "strong"), + italic: makeMark("italic", "em"), + deleted: makeMark("deleted", "del"), + underline: makeMark("underline", "u"), + sub: makeMark("sub", "sub"), + super: makeMark("super", "sup"), + mark, + link, + small: makeMark("small", "small"), +}; -function recursiveFilterSelection ( - node: Element, - selection: Selection, - selector: string, +function recursiveFilterSelection( + node: Element, + selection: Selection, + selector: string ): Element[] { - let output: Element[] = [] - for (const child of [...node.children]) { - if (child.matches(selector) - && selection.containsNode(child) - ) output.push(child) - output = [...output, ...recursiveFilterSelection(child, selection, selector)] - } - return output + let output: Element[] = []; + for (const child of [...node.children]) { + if (child.matches(selector) && selection.containsNode(child)) + output.push(child); + output = [ + ...output, + ...recursiveFilterSelection(child, selection, selector), + ]; + } + return output; } -export function setupButtons (editor: Editor): void { - for (const [ name, type ] of Object.entries(marks)) { - const buttonEl = editor.toolbarEl.querySelector(`[data-editor-button="mark-${name}"]`) - if (!buttonEl) continue - buttonEl.addEventListener("click", event => { - event.preventDefault() +export function setupButtons(editor: Editor): void { + for (const [name, type] of Object.entries(marks)) { + const buttonEl = editor.toolbarEl.querySelector( + `[data-editor-button="mark-${name}"]` + ); + if (!buttonEl) continue; + buttonEl.addEventListener("click", (event) => { + event.preventDefault(); - const sel = safeGetSelection(editor) - if (!sel) return - const range = safeGetRangeAt(sel) - if (!range) return + const sel = safeGetSelection(editor); + if (!sel) return; + const range = safeGetRangeAt(sel); + if (!range) return; - let parentEl = range.commonAncestorContainer - while (!(parentEl instanceof Element)) { - if (!parentEl.parentElement) return - parentEl = parentEl.parentElement - } + let parentEl = range.commonAncestorContainer; + while (!(parentEl instanceof Element)) { + if (!parentEl.parentElement) return; + parentEl = parentEl.parentElement; + } - const existingMarks = recursiveFilterSelection( - parentEl, - sel, - type.selector, - ) - console.debug('marks encontradas:', existingMarks) + const existingMarks = recursiveFilterSelection( + parentEl, + sel, + type.selector + ); + console.debug("marks encontradas:", existingMarks); - if (existingMarks.length > 0) { - const mark = existingMarks[0] - if (!mark.parentElement) - throw new Error(':/') - moveChildren(mark, mark.parentElement, mark) - mark.parentElement.removeChild(mark) - } else { - if (range.commonAncestorContainer === editor.contentEl) - // TODO: mostrar error - return console.error("No puedo marcar cosas a través de distintos bloques!") + if (existingMarks.length > 0) { + const mark = existingMarks[0]; + if (!mark.parentElement) throw new Error(":/"); + moveChildren(mark, mark.parentElement, mark); + mark.parentElement.removeChild(mark); + } else { + if (range.commonAncestorContainer === editor.contentEl) + // TODO: mostrar error + return console.error( + "No puedo marcar cosas a través de distintos bloques!" + ); - const tagEl = type.create(editor) - type.onClick && type.onClick(editor, tagEl) + const tagEl = type.create(editor); + type.onClick && type.onClick(editor, tagEl); - tagEl.appendChild(range.extractContents()) + tagEl.appendChild(range.extractContents()); - range.insertNode(tagEl) - range.selectNode(tagEl) - } + range.insertNode(tagEl); + range.selectNode(tagEl); + } - return false - }) - } + return false; + }); + } } diff --git a/app/javascript/editor/types/multimedia.ts b/app/javascript/editor/types/multimedia.ts index 54c430f3..2af9643a 100644 --- a/app/javascript/editor/types/multimedia.ts +++ b/app/javascript/editor/types/multimedia.ts @@ -1,206 +1,230 @@ -import * as ActiveStorage from '@rails/activestorage' -import { Editor } from 'editor/editor' -import { EditorNode, getValidParentInSelection } from 'editor/types' +import * as ActiveStorage from "@rails/activestorage"; +import { Editor } from "editor/editor"; +import { EditorNode, getValidParentInSelection } from "editor/types"; import { - safeGetSelection, safeGetRangeAt, - markNames, parentBlockNames, - setAuxiliaryToolbar, clearSelected, -} from 'editor/utils' + safeGetSelection, + safeGetRangeAt, + markNames, + parentBlockNames, + setAuxiliaryToolbar, + clearSelected, +} from "editor/utils"; -function uploadFile (file: File): Promise { - return new Promise((resolve, reject) => { - const upload = new ActiveStorage.DirectUpload( - file, - origin + '/rails/active_storage/direct_uploads', - ) +function uploadFile(file: File): Promise { + return new Promise((resolve, reject) => { + const upload = new ActiveStorage.DirectUpload( + file, + origin + "/rails/active_storage/direct_uploads" + ); - upload.create((error: any, blob: any) => { - if (error) { - reject(error) - } else { - const url = `${origin}/rails/active_storage/blobs/${blob.signed_id}/${blob.filename}` - resolve(url) - } - }) - }) + upload.create((error: any, blob: any) => { + if (error) { + reject(error); + } else { + const url = `${origin}/rails/active_storage/blobs/${blob.signed_id}/${blob.filename}`; + resolve(url); + } + }); + }); } -function getAlt (multimediaInnerEl: HTMLElement): string | null { - switch (multimediaInnerEl.tagName) { - case 'VIDEO': - case 'AUDIO': - return multimediaInnerEl.getAttribute('aria-label') - case 'IMG': - return (multimediaInnerEl as HTMLImageElement).alt - case 'IFRAME': - return multimediaInnerEl.title - default: - throw new Error('no pude conseguir el alt') - } +function getAlt(multimediaInnerEl: HTMLElement): string | null { + switch (multimediaInnerEl.tagName) { + case "VIDEO": + case "AUDIO": + return multimediaInnerEl.getAttribute("aria-label"); + case "IMG": + return (multimediaInnerEl as HTMLImageElement).alt; + case "IFRAME": + return multimediaInnerEl.title; + default: + throw new Error("no pude conseguir el alt"); + } } -function setAlt (multimediaInnerEl: HTMLElement, value: string): void { - switch (multimediaInnerEl.tagName) { - case 'VIDEO': - case 'AUDIO': - multimediaInnerEl.setAttribute('aria-label', value) - break - case 'IMG': - (multimediaInnerEl as HTMLImageElement).alt = value - break - case 'IFRAME': - multimediaInnerEl.title = value - break - default: - throw new Error('no pude setear el alt') - } +function setAlt(multimediaInnerEl: HTMLElement, value: string): void { + switch (multimediaInnerEl.tagName) { + case "VIDEO": + case "AUDIO": + multimediaInnerEl.setAttribute("aria-label", value); + break; + case "IMG": + (multimediaInnerEl as HTMLImageElement).alt = value; + break; + case "IFRAME": + multimediaInnerEl.title = value; + break; + default: + throw new Error("no pude setear el alt"); + } } -function select (editor: Editor, el: HTMLElement): void { - clearSelected(editor) - el.dataset.editorSelected = '' +function select(editor: Editor, el: HTMLElement): void { + clearSelected(editor); + el.dataset.editorSelected = ""; - const innerEl = el.querySelector('[data-multimedia-inner]') - if (!innerEl) throw new Error('No hay multimedia válida') - if (innerEl.tagName === "P") { - editor.toolbar.auxiliary.multimedia.altEl.value = ""; - editor.toolbar.auxiliary.multimedia.altEl.disabled = true; - } else { - editor.toolbar.auxiliary.multimedia.altEl.value = getAlt(innerEl) || ""; - editor.toolbar.auxiliary.multimedia.altEl.disabled = false; - } + const innerEl = el.querySelector("[data-multimedia-inner]"); + if (!innerEl) throw new Error("No hay multimedia válida"); + if (innerEl.tagName === "P") { + editor.toolbar.auxiliary.multimedia.altEl.value = ""; + editor.toolbar.auxiliary.multimedia.altEl.disabled = true; + } else { + editor.toolbar.auxiliary.multimedia.altEl.value = getAlt(innerEl) || ""; + editor.toolbar.auxiliary.multimedia.altEl.disabled = false; + } - setAuxiliaryToolbar(editor, editor.toolbar.auxiliary.multimedia.parentEl) + setAuxiliaryToolbar(editor, editor.toolbar.auxiliary.multimedia.parentEl); } export const multimedia: EditorNode = { - selector: 'figure[data-multimedia]', - allowedChildren: 'ignore-children', - handleEmpty: 'remove', - create: () => { - const figureEl = document.createElement('figure') - figureEl.dataset.multimedia = '' - figureEl.contentEditable = 'false' + selector: "figure[data-multimedia]", + allowedChildren: "ignore-children", + handleEmpty: "remove", + create: () => { + const figureEl = document.createElement("figure"); + figureEl.dataset.multimedia = ""; + figureEl.contentEditable = "false"; - const placeholderEl = document.createElement('p') - placeholderEl.dataset.multimediaInner = '' - // TODO i18n - placeholderEl.append('¡Clickeame para subir un archivo!') - figureEl.appendChild(placeholderEl) + const placeholderEl = document.createElement("p"); + placeholderEl.dataset.multimediaInner = ""; + // TODO i18n + placeholderEl.append("¡Clickeame para subir un archivo!"); + figureEl.appendChild(placeholderEl); - const descriptionEl = document.createElement('figcaption') - descriptionEl.contentEditable = 'true' - // TODO i18n - descriptionEl.append('Escribí acá la descripción del archivo.') - figureEl.appendChild(descriptionEl) + const descriptionEl = document.createElement("figcaption"); + descriptionEl.contentEditable = "true"; + // TODO i18n + descriptionEl.append("Escribí acá la descripción del archivo."); + figureEl.appendChild(descriptionEl); - return figureEl - }, - onClick (editor, el) { - if (!(el instanceof HTMLElement)) - throw new Error('oh no') - select(editor, el) - }, -} -function createElementWithFile (url: string, type: string): HTMLElement { - if (type.match(/^image\/.+$/)) { - const el = document.createElement('img') - el.dataset.multimediaInner = '' - el.src = url - return el - } else if (type.match(/^video\/.+$/)) { - const el = document.createElement('video') - el.controls = true - el.dataset.multimediaInner = '' - el.src = url - return el - } else if (type.match(/^audio\/.+$/)) { - const el = document.createElement('audio') - el.controls = true - el.dataset.multimediaInner = '' - el.src = url - return el - } else if (type.match(/^application\/pdf$/)) { - const el = document.createElement('iframe') - el.dataset.multimediaInner = '' - el.src = url - return el - } else { - // TODO: chequear si el archivo es válido antes de subir - throw new Error('Tipo de archivo no reconocido') - } + return figureEl; + }, + onClick(editor, el) { + if (!(el instanceof HTMLElement)) throw new Error("oh no"); + select(editor, el); + }, +}; +function createElementWithFile(url: string, type: string): HTMLElement { + if (type.match(/^image\/.+$/)) { + const el = document.createElement("img"); + el.dataset.multimediaInner = ""; + el.src = url; + return el; + } else if (type.match(/^video\/.+$/)) { + const el = document.createElement("video"); + el.controls = true; + el.dataset.multimediaInner = ""; + el.src = url; + return el; + } else if (type.match(/^audio\/.+$/)) { + const el = document.createElement("audio"); + el.controls = true; + el.dataset.multimediaInner = ""; + el.src = url; + return el; + } else if (type.match(/^application\/pdf$/)) { + const el = document.createElement("iframe"); + el.dataset.multimediaInner = ""; + el.src = url; + return el; + } else { + // TODO: chequear si el archivo es válido antes de subir + throw new Error("Tipo de archivo no reconocido"); + } } -export function setupAuxiliaryToolbar (editor: Editor): void { - editor.toolbar.auxiliary.multimedia.uploadEl.addEventListener('click', event => { - const files = editor.toolbar.auxiliary.multimedia.fileEl.files - if (!files || !files.length) throw new Error('no hay archivos para subir') - const file = files[0] +export function setupAuxiliaryToolbar(editor: Editor): void { + editor.toolbar.auxiliary.multimedia.uploadEl.addEventListener( + "click", + (event) => { + const files = editor.toolbar.auxiliary.multimedia.fileEl.files; + if (!files || !files.length) + throw new Error("no hay archivos para subir"); + const file = files[0]; - const selectedEl = editor.contentEl - .querySelector('figure[data-editor-selected]') - if (!selectedEl) - throw new Error('No pude encontrar el elemento para setear el archivo') + const selectedEl = editor.contentEl.querySelector( + "figure[data-editor-selected]" + ); + if (!selectedEl) + throw new Error("No pude encontrar el elemento para setear el archivo"); - selectedEl.dataset.editorLoading = '' - uploadFile(file) - .then(url => { - const innerEl = selectedEl.querySelector('[data-multimedia-inner]') - if (!innerEl) throw new Error('No hay multimedia a reemplazar') + selectedEl.dataset.editorLoading = ""; + uploadFile(file) + .then((url) => { + const innerEl = selectedEl.querySelector("[data-multimedia-inner]"); + if (!innerEl) throw new Error("No hay multimedia a reemplazar"); - const el = createElementWithFile(url, file.type) - setAlt(el, editor.toolbar.auxiliary.multimedia.altEl.value) - selectedEl.replaceChild(el, innerEl) + const el = createElementWithFile(url, file.type); + setAlt(el, editor.toolbar.auxiliary.multimedia.altEl.value); + selectedEl.replaceChild(el, innerEl); - select(editor, selectedEl) + select(editor, selectedEl); - delete selectedEl.dataset.editorError - }) - .catch(err => { - console.error(err) - // TODO: mostrar error - selectedEl.dataset.editorError = '' - }) - .finally(() => { delete selectedEl.dataset.editorLoading }) - }) + delete selectedEl.dataset.editorError; + }) + .catch((err) => { + console.error(err); + // TODO: mostrar error + selectedEl.dataset.editorError = ""; + }) + .finally(() => { + delete selectedEl.dataset.editorLoading; + }); + } + ); - editor.toolbar.auxiliary.multimedia.removeEl.addEventListener('click', event => { - const selectedEl = editor.contentEl - .querySelector('figure[data-editor-selected]') - if (!selectedEl) - throw new Error('No pude encontrar el elemento para borrar') + editor.toolbar.auxiliary.multimedia.removeEl.addEventListener( + "click", + (event) => { + const selectedEl = editor.contentEl.querySelector( + "figure[data-editor-selected]" + ); + if (!selectedEl) + throw new Error("No pude encontrar el elemento para borrar"); - selectedEl.parentElement?.removeChild(selectedEl) - setAuxiliaryToolbar(editor, null) - }) + selectedEl.parentElement?.removeChild(selectedEl); + setAuxiliaryToolbar(editor, null); + } + ); - editor.toolbar.auxiliary.multimedia.altEl.addEventListener('input', event => { - const selectedEl = editor.contentEl - .querySelector('figure[data-editor-selected]') - if (!selectedEl) - throw new Error('No pude encontrar el multimedia para setear el alt') + editor.toolbar.auxiliary.multimedia.altEl.addEventListener( + "input", + (event) => { + const selectedEl = editor.contentEl.querySelector( + "figure[data-editor-selected]" + ); + if (!selectedEl) + throw new Error("No pude encontrar el multimedia para setear el alt"); - const innerEl = selectedEl.querySelector('[data-multimedia-inner]') - if (!innerEl) throw new Error('No hay multimedia a para setear el alt') + const innerEl = selectedEl.querySelector( + "[data-multimedia-inner]" + ); + if (!innerEl) throw new Error("No hay multimedia a para setear el alt"); - setAlt(innerEl, editor.toolbar.auxiliary.multimedia.altEl.value) - }) - editor.toolbar.auxiliary.multimedia.altEl.addEventListener('keydown', event => { - if (event.keyCode == 13) event.preventDefault() - }) + setAlt(innerEl, editor.toolbar.auxiliary.multimedia.altEl.value); + } + ); + editor.toolbar.auxiliary.multimedia.altEl.addEventListener( + "keydown", + (event) => { + if (event.keyCode == 13) event.preventDefault(); + } + ); } -export function setupButtons (editor: Editor): void { - const buttonEl = editor.toolbarEl.querySelector('[data-editor-button="multimedia"]') - if (!buttonEl) throw new Error('No encontre el botón de multimedia') - buttonEl.addEventListener('click', event => { - event.preventDefault() +export function setupButtons(editor: Editor): void { + const buttonEl = editor.toolbarEl.querySelector( + '[data-editor-button="multimedia"]' + ); + if (!buttonEl) throw new Error("No encontre el botón de multimedia"); + buttonEl.addEventListener("click", (event) => { + event.preventDefault(); - const list = getValidParentInSelection({ editor, type: 'multimedia' }) + const list = getValidParentInSelection({ editor, type: "multimedia" }); - const el = multimedia.create(editor) - list[0].insertBefore(el, list[1].nextElementSibling) - select(editor, el) + const el = multimedia.create(editor); + list[0].insertBefore(el, list[1].nextElementSibling); + select(editor, el); - return false - }) + return false; + }); } diff --git a/app/javascript/editor/types/parentBlocks.ts b/app/javascript/editor/types/parentBlocks.ts index 55a8c3d8..d77df2bd 100644 --- a/app/javascript/editor/types/parentBlocks.ts +++ b/app/javascript/editor/types/parentBlocks.ts @@ -1,70 +1,75 @@ -import { Editor } from 'editor/editor' +import { Editor } from "editor/editor"; import { - safeGetSelection, safeGetRangeAt, - moveChildren, - blockNames, parentBlockNames, -} from 'editor/utils' -import { EditorNode, getType, getValidParentInSelection } from 'editor/types' + safeGetSelection, + safeGetRangeAt, + moveChildren, + blockNames, + parentBlockNames, +} from "editor/utils"; +import { EditorNode, getType, getValidParentInSelection } from "editor/types"; -function makeParentBlock (tag: string, create: EditorNode["create"]): EditorNode { - return { - selector: tag, - allowedChildren: [...blockNames, 'multimedia'], - handleEmpty: 'remove', - create, - } +function makeParentBlock( + tag: string, + create: EditorNode["create"] +): EditorNode { + return { + selector: tag, + allowedChildren: [...blockNames, "multimedia"], + handleEmpty: "remove", + create, + }; } // TODO: añadir blockquote // XXX: si agregás algo acá, probablemente le quieras hacer un botón // en app/views/posts/attributes/_content.haml export const parentBlocks: { [propName: string]: EditorNode } = { - left: makeParentBlock('div[data-align=left]', () => { - const el = document.createElement('div') - el.dataset.align = 'left' - return el - }), - center: makeParentBlock('div[data-align=center]', () => { - const el = document.createElement('div') - el.dataset.align = 'center' - return el - }), - right: makeParentBlock('div[data-align=right]', () => { - const el = document.createElement('div') - el.dataset.align = 'right' - return el - }), -} - -export function setupButtons (editor: Editor): void { - for (const [ name, type ] of Object.entries(parentBlocks)) { - const buttonEl = editor.toolbarEl.querySelector( - `[data-editor-button="parentBlock-${name}"]` - ) - if (!buttonEl) continue - buttonEl.addEventListener("click", event => { - event.preventDefault() - - // TODO: Esto solo mueve el bloque en el que está el final de la selección - // (anchorNode). quizás lo podemos hacer al revés (iterar desde contentEl - // para encontrar los bloques que están seleccionados y moverlos/cambiarles - // el parentBlock) - - const list = getValidParentInSelection({ editor, type: name }) - - const replacementEl = type.create(editor) - if (list[0] == editor.contentEl) { - // no está en un parentBlock - editor.contentEl.insertBefore(replacementEl, list[1]) - replacementEl.appendChild(list[1]) - } else { - // está en un parentBlock - moveChildren(list[0], replacementEl, null) - editor.contentEl.replaceChild(replacementEl, list[0]) - } - window.getSelection()?.collapse(replacementEl) - - return false - }) - } + left: makeParentBlock("div[data-align=left]", () => { + const el = document.createElement("div"); + el.dataset.align = "left"; + return el; + }), + center: makeParentBlock("div[data-align=center]", () => { + const el = document.createElement("div"); + el.dataset.align = "center"; + return el; + }), + right: makeParentBlock("div[data-align=right]", () => { + const el = document.createElement("div"); + el.dataset.align = "right"; + return el; + }), +}; + +export function setupButtons(editor: Editor): void { + for (const [name, type] of Object.entries(parentBlocks)) { + const buttonEl = editor.toolbarEl.querySelector( + `[data-editor-button="parentBlock-${name}"]` + ); + if (!buttonEl) continue; + buttonEl.addEventListener("click", (event) => { + event.preventDefault(); + + // TODO: Esto solo mueve el bloque en el que está el final de la selección + // (anchorNode). quizás lo podemos hacer al revés (iterar desde contentEl + // para encontrar los bloques que están seleccionados y moverlos/cambiarles + // el parentBlock) + + const list = getValidParentInSelection({ editor, type: name }); + + const replacementEl = type.create(editor); + if (list[0] == editor.contentEl) { + // no está en un parentBlock + editor.contentEl.insertBefore(replacementEl, list[1]); + replacementEl.appendChild(list[1]); + } else { + // está en un parentBlock + moveChildren(list[0], replacementEl, null); + editor.contentEl.replaceChild(replacementEl, list[0]); + } + window.getSelection()?.collapse(replacementEl); + + return false; + }); + } } diff --git a/app/javascript/editor/utils.ts b/app/javascript/editor/utils.ts index 7ac4c186..167c0a6d 100644 --- a/app/javascript/editor/utils.ts +++ b/app/javascript/editor/utils.ts @@ -1,77 +1,101 @@ -import { Editor } from 'editor/editor' +import { Editor } from "editor/editor"; -export const blockNames = ['paragraph', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'unordered_list', 'ordered_list'] -export const markNames = ['bold', 'italic', 'deleted', 'underline', 'sub', 'super', 'mark', 'link', 'small'] -export const parentBlockNames = ['left', 'center', 'right'] +export const blockNames = [ + "paragraph", + "h1", + "h2", + "h3", + "h4", + "h5", + "h6", + "unordered_list", + "ordered_list", +]; +export const markNames = [ + "bold", + "italic", + "deleted", + "underline", + "sub", + "super", + "mark", + "link", + "small", +]; +export const parentBlockNames = ["left", "center", "right"]; -export function moveChildren (from: Element, to: Element, toRef: Node | null) { - while (from.firstChild) to.insertBefore(from.firstChild, toRef) +export function moveChildren(from: Element, to: Element, toRef: Node | null) { + while (from.firstChild) to.insertBefore(from.firstChild, toRef); } -export function isDirectChild (node: Node, supposedChild: Node): boolean { - for (const child of node.childNodes) { - if (child == supposedChild) return true - } - return false +export function isDirectChild(node: Node, supposedChild: Node): boolean { + for (const child of node.childNodes) { + if (child == supposedChild) return true; + } + return false; } -export function safeGetSelection (editor: Editor): Selection | null { - const sel = window.getSelection() - if (!sel) return null - // XXX: no damos la selección si esta fuera o _es_ el contentEl, ¿quizás - // deberíamos mostrar un error? - if ( - !editor.contentEl.contains(sel.anchorNode) - || !editor.contentEl.contains(sel.focusNode) - || sel.anchorNode == editor.contentEl - || sel.focusNode == editor.contentEl - ) return null - return sel +export function safeGetSelection(editor: Editor): Selection | null { + const sel = window.getSelection(); + if (!sel) return null; + // XXX: no damos la selección si esta fuera o _es_ el contentEl, ¿quizás + // deberíamos mostrar un error? + if ( + !editor.contentEl.contains(sel.anchorNode) || + !editor.contentEl.contains(sel.focusNode) || + sel.anchorNode == editor.contentEl || + sel.focusNode == editor.contentEl + ) + return null; + return sel; } -export function safeGetRangeAt (selection: Selection, num = 0): Range | null { - try { - return selection.getRangeAt(num) - } catch (error) { - return null - } +export function safeGetRangeAt(selection: Selection, num = 0): Range | null { + try { + return selection.getRangeAt(num); + } catch (error) { + return null; + } } interface SplitNode { - range: Range, - node: Node, + range: Range; + node: Node; } -export function splitNode (node: Element, range: Range): [SplitNode, SplitNode] { - const [left, right] = [ - { range: document.createRange(), node: node.cloneNode(false) }, - { range: document.createRange(), node: node.cloneNode(false) }, - ] +export function splitNode(node: Element, range: Range): [SplitNode, SplitNode] { + const [left, right] = [ + { range: document.createRange(), node: node.cloneNode(false) }, + { range: document.createRange(), node: node.cloneNode(false) }, + ]; - if (node.firstChild) left.range.setStartBefore(node.firstChild) - left.range.setEnd(range.startContainer, range.startOffset) - left.range.surroundContents(left.node) + if (node.firstChild) left.range.setStartBefore(node.firstChild); + left.range.setEnd(range.startContainer, range.startOffset); + left.range.surroundContents(left.node); - right.range.setStart(range.endContainer, range.endOffset) - if (node.lastChild) right.range.setEndAfter(node.lastChild) - right.range.surroundContents(right.node) + right.range.setStart(range.endContainer, range.endOffset); + if (node.lastChild) right.range.setEndAfter(node.lastChild); + right.range.surroundContents(right.node); - if (!node.parentElement) - throw new Error('No pude separar los nodos por que no tiene parentNode') + if (!node.parentElement) + throw new Error("No pude separar los nodos por que no tiene parentNode"); - moveChildren(node, node.parentElement, node) - node.parentElement.removeChild(node) + moveChildren(node, node.parentElement, node); + node.parentElement.removeChild(node); - return [left, right] + return [left, right]; } -export function setAuxiliaryToolbar (editor: Editor, bar: HTMLElement | null): void { - for (const { parentEl } of Object.values(editor.toolbar.auxiliary)) { - delete parentEl.dataset.editorAuxiliaryActive - } - if (bar) bar.dataset.editorAuxiliaryActive = 'active' +export function setAuxiliaryToolbar( + editor: Editor, + bar: HTMLElement | null +): void { + for (const { parentEl } of Object.values(editor.toolbar.auxiliary)) { + delete parentEl.dataset.editorAuxiliaryActive; + } + if (bar) bar.dataset.editorAuxiliaryActive = "active"; } -export function clearSelected (editor: Editor): void { - const selectedEl = editor.contentEl.querySelector('[data-editor-selected]') - if (selectedEl) delete (selectedEl as HTMLElement).dataset.editorSelected +export function clearSelected(editor: Editor): void { + const selectedEl = editor.contentEl.querySelector("[data-editor-selected]"); + if (selectedEl) delete (selectedEl as HTMLElement).dataset.editorSelected; } From 57f885360fc1a97545576f433fb169e7b2761ac5 Mon Sep 17 00:00:00 2001 From: void Date: Wed, 28 Apr 2021 18:54:55 +0000 Subject: [PATCH 022/234] permitir cambiar el color del texto en fixes #1160 --- app/javascript/editor/editor.ts | 5 +++++ app/javascript/editor/types/mark.ts | 18 ++++++++++++++++++ app/views/posts/attributes/_content.haml | 2 ++ config/locales/en.yml | 1 + config/locales/es.yml | 1 + 5 files changed, 27 insertions(+) diff --git a/app/javascript/editor/editor.ts b/app/javascript/editor/editor.ts index 6ec06f3c..880547de 100644 --- a/app/javascript/editor/editor.ts +++ b/app/javascript/editor/editor.ts @@ -173,6 +173,7 @@ export interface Editor { mark: { parentEl: HTMLElement; colorEl: HTMLInputElement; + textColorEl: HTMLInputElement; }; multimedia: { parentEl: HTMLElement; @@ -213,6 +214,10 @@ function setupEditor(editorEl: HTMLElement): void { editorEl, "[data-editor-auxiliary=mark] [name=mark-color]" ), + textColorEl: getSel( + editorEl, + "[data-editor-auxiliary=mark] [name=mark-text-color]" + ), }, multimedia: { parentEl: getSel(editorEl, "[data-editor-auxiliary=multimedia]"), diff --git a/app/javascript/editor/types/mark.ts b/app/javascript/editor/types/mark.ts index 2607cae5..4735c799 100644 --- a/app/javascript/editor/types/mark.ts +++ b/app/javascript/editor/types/mark.ts @@ -17,6 +17,9 @@ function select(editor: Editor, el: HTMLElement): void { editor.toolbar.auxiliary.mark.colorEl.value = el.style.backgroundColor ? rgbToHex(el.style.backgroundColor) : "#f206f9"; + editor.toolbar.auxiliary.mark.textColorEl.value = el.style.color + ? rgbToHex(el.style.color) + : "#000000"; setAuxiliaryToolbar(editor, editor.toolbar.auxiliary.mark.parentEl); } @@ -42,6 +45,21 @@ export function setupAuxiliaryToolbar(editor: Editor): void { selectedEl.style.backgroundColor = color; }); + editor.toolbar.auxiliary.mark.textColorEl.addEventListener( + "input", + (event) => { + const color = editor.toolbar.auxiliary.mark.textColorEl.value; + const selectedEl = editor.contentEl.querySelector( + "mark[data-editor-selected]" + ); + if (!selectedEl) + throw new Error( + "No pude encontrar el mark para setear el color del text" + ); + + selectedEl.style.color = color; + } + ); editor.toolbar.auxiliary.mark.colorEl.addEventListener("keydown", (event) => { if (event.keyCode == 13) event.preventDefault(); }); diff --git a/app/views/posts/attributes/_content.haml b/app/views/posts/attributes/_content.haml index 235dca30..4ae70ba0 100644 --- a/app/views/posts/attributes/_content.haml +++ b/app/views/posts/attributes/_content.haml @@ -101,6 +101,8 @@ .form-group{ data: { editor_auxiliary: 'mark' } } %label{ for: 'mark-color' }= t('editor.color') %input.form-control{ type: 'color', name: 'mark-color' }/ + %label{ for: 'mark-text-color' }= t('editor.text-color') + %input.form-control{ type: 'color', name: 'mark-text-color' }/ %div{ data: { editor_auxiliary: 'multimedia' } } .form-group diff --git a/config/locales/en.yml b/config/locales/en.yml index 02570b12..9366f9fc 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -599,6 +599,7 @@ en: right: Right center: Center color: Color + text-color: Text color multimedia: Media multimedia-select: Select file multimedia-upload: Upload diff --git a/config/locales/es.yml b/config/locales/es.yml index 5bb4a221..3a64e7ee 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -612,6 +612,7 @@ es: right: Derecha center: Centro color: Color + text-color: Color del texto multimedia: Multimedia multimedia-select: Seleccionar archivo multimedia-upload: Subir From f798b5781e02b560d92cb6dfbf7e5e1ae90756e4 Mon Sep 17 00:00:00 2001 From: void Date: Thu, 29 Apr 2021 14:13:31 +0000 Subject: [PATCH 023/234] =?UTF-8?q?setear=20style=20en=20los=20divs=20de?= =?UTF-8?q?=20alineaci=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit arregla el estilo en sitios que no tienen css para los [data-align] --- app/javascript/editor/types/parentBlocks.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/javascript/editor/types/parentBlocks.ts b/app/javascript/editor/types/parentBlocks.ts index d77df2bd..ffe40bdf 100644 --- a/app/javascript/editor/types/parentBlocks.ts +++ b/app/javascript/editor/types/parentBlocks.ts @@ -27,16 +27,19 @@ export const parentBlocks: { [propName: string]: EditorNode } = { left: makeParentBlock("div[data-align=left]", () => { const el = document.createElement("div"); el.dataset.align = "left"; + el.style.textAlign = "left"; return el; }), center: makeParentBlock("div[data-align=center]", () => { const el = document.createElement("div"); el.dataset.align = "center"; + el.style.textAlign = "center"; return el; }), right: makeParentBlock("div[data-align=right]", () => { const el = document.createElement("div"); el.dataset.align = "right"; + el.style.textAlign = "right"; return el; }), }; From cc2b4a29719c249110908d953d4c330d279c2f06 Mon Sep 17 00:00:00 2001 From: f Date: Thu, 6 May 2021 12:26:36 -0300 Subject: [PATCH 024/234] dependencias del buscador MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pg_search: habilita búsquedas en postgresql hairtrigger: permite crear acciones automáticas en postgresql --- Gemfile | 6 +++++- Gemfile.lock | 15 +++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 63b921c2..93a8a22e 100644 --- a/Gemfile +++ b/Gemfile @@ -51,7 +51,6 @@ gem 'sutty-liquid' gem 'lockbox' gem 'mini_magick' gem 'mobility' -gem 'pg' gem 'pundit' gem 'rails-i18n' gem 'rails_warden' @@ -67,6 +66,11 @@ gem 'validates_hostname' gem 'webpacker' gem 'yaml_db', git: 'https://0xacab.org/sutty/yaml_db.git' +# database +gem 'hairtrigger' +gem 'pg' +gem 'pg_search' + # performance gem 'flamegraph' gem 'memory_profiler' diff --git a/Gemfile.lock b/Gemfile.lock index 5c1d0627..5ddaeaaa 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -217,6 +217,10 @@ GEM ffi (~> 1.0) globalid (0.4.2) activesupport (>= 4.2.0) + hairtrigger (0.2.24) + activerecord (>= 5.0, < 7) + ruby2ruby (~> 2.4) + ruby_parser (~> 3.10) haml (5.2.1) temple (>= 0.8.0) tilt @@ -387,6 +391,9 @@ GEM forwardable-extended (~> 2.6) pg (1.2.3) pg (1.2.3-x86_64-linux-musl) + pg_search (2.3.5) + activerecord (>= 5.2) + activesupport (>= 5.2) popper_js (1.16.0) prometheus_exporter (0.7.0) webrick @@ -523,7 +530,12 @@ GEM ruby-statistics (2.1.3) ruby-vips (2.1.0) ffi (~> 1.12) + ruby2ruby (2.4.4) + ruby_parser (~> 3.1) + sexp_processor (~> 4.6) ruby_dep (1.5.0) + ruby_parser (3.15.1) + sexp_processor (~> 4.9) rubyzip (2.3.0) rugged (1.1.0) rugged (1.1.0-x86_64-linux-musl) @@ -544,6 +556,7 @@ GEM childprocess (>= 0.5, < 4.0) rubyzip (>= 1.2.2) semantic_range (3.0.0) + sexp_processor (4.15.2) share-to-fediverse-jekyll-theme (0.1.4) jekyll (~> 4.0) jekyll-data (~> 1.1) @@ -677,6 +690,7 @@ DEPENDENCIES fast_jsonparser flamegraph friendly_id + hairtrigger haml-lint hamlit-rails hiredis @@ -699,6 +713,7 @@ DEPENDENCIES mobility net-ssh pg + pg_search prometheus_exporter pry puma From 655e07cd4091a61a54d170af9e0d27b0ecd29acb Mon Sep 17 00:00:00 2001 From: f Date: Thu, 6 May 2021 12:29:48 -0300 Subject: [PATCH 025/234] =?UTF-8?q?configuraci=C3=B3n=20de=20las=20bases?= =?UTF-8?q?=20de=20datos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ahora el testeo y desarrollo también se hace en postgresql. nos queda pendiente que cada quien pueda usar la base de datos que quiera y que si nos posible indexar documentos, que sea opcional. --- config/database.yml | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/config/database.yml b/config/database.yml index 28e195b8..9e72da07 100644 --- a/config/database.yml +++ b/config/database.yml @@ -5,25 +5,24 @@ # gem 'sqlite3' # default: &default - adapter: sqlite3 + adapter: postgresql pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> timeout: 5000 + database: sutty + user: postgres + host: postgresql + encoding: unicode development: <<: *default - database: db/development.sqlite3 # Warning: The database defined as "test" will be erased and # re-generated from your development database when you run "rake". # Do not set this db to the same as development or production. test: <<: *default - database: db/test.sqlite3 + database: sutty_test production: - adapter: postgresql - pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> - database: sutty + <<: *default user: sutty - host: postgresql - encoding: unicode From 26d186721d6df1f76a602caf176fd444e54f0a91 Mon Sep 17 00:00:00 2001 From: f Date: Thu, 6 May 2021 12:33:28 -0300 Subject: [PATCH 026/234] algunos metadatos son indexables MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit solo los de texto que no se refieran a otros artículos por ahora --- app/models/metadata_array.rb | 8 ++++++++ app/models/metadata_content.rb | 8 ++++++++ app/models/metadata_document_date.rb | 4 ++++ app/models/metadata_related_posts.rb | 4 ++++ app/models/metadata_string.rb | 4 ++++ app/models/metadata_template.rb | 6 ++++++ 6 files changed, 34 insertions(+) diff --git a/app/models/metadata_array.rb b/app/models/metadata_array.rb index 5c0b16f7..5f43b790 100644 --- a/app/models/metadata_array.rb +++ b/app/models/metadata_array.rb @@ -13,6 +13,14 @@ class MetadataArray < MetadataTemplate false end + def indexable? + true + end + + def to_s + value.join(', ') + end + private # TODO: Sanitizar otros valores diff --git a/app/models/metadata_content.rb b/app/models/metadata_content.rb index 525f38ab..546e08c8 100644 --- a/app/models/metadata_content.rb +++ b/app/models/metadata_content.rb @@ -19,6 +19,14 @@ class MetadataContent < MetadataTemplate document.content end + def indexable? + true + end + + def to_s + sanitizer.sanitize value, tags: [], attributes: [] + end + private # Detectar si el contenido estaba en Markdown y pasarlo a HTML diff --git a/app/models/metadata_document_date.rb b/app/models/metadata_document_date.rb index 39e68735..a52cd051 100644 --- a/app/models/metadata_document_date.rb +++ b/app/models/metadata_document_date.rb @@ -11,6 +11,10 @@ class MetadataDocumentDate < MetadataTemplate document.date end + def indexable? + true + end + # El valor puede ser un Date, Time o una String en el formato # "yyyy-mm-dd" def value diff --git a/app/models/metadata_related_posts.rb b/app/models/metadata_related_posts.rb index 9a73dea9..bcc18d86 100644 --- a/app/models/metadata_related_posts.rb +++ b/app/models/metadata_related_posts.rb @@ -18,6 +18,10 @@ class MetadataRelatedPosts < MetadataArray false end + def indexable? + false + end + private # Obtiene todos los posts y opcionalmente los filtra diff --git a/app/models/metadata_string.rb b/app/models/metadata_string.rb index ed50bc88..724c2ef3 100644 --- a/app/models/metadata_string.rb +++ b/app/models/metadata_string.rb @@ -7,6 +7,10 @@ class MetadataString < MetadataTemplate super || '' end + def indexable? + true + end + private # No se permite HTML en las strings diff --git a/app/models/metadata_template.rb b/app/models/metadata_template.rb index 58000596..2fa8a61e 100644 --- a/app/models/metadata_template.rb +++ b/app/models/metadata_template.rb @@ -7,6 +7,12 @@ MetadataTemplate = Struct.new(:site, :document, :name, :label, :type, :value, :help, :required, :errors, :post, :layout, keyword_init: true) do + + # Determina si el campo es indexable + def indexable? + false + end + def inspect "#<#{self.class} site=#{site.name.inspect} post=#{post.id.inspect} value=#{value.inspect}>" end From ceaadeb7bf00e1907a2ff993253b18a127381bc6 Mon Sep 17 00:00:00 2001 From: f Date: Thu, 6 May 2021 12:45:33 -0300 Subject: [PATCH 027/234] =?UTF-8?q?crear=20la=20tabla=20de=20indexaci?= =?UTF-8?q?=C3=B3n=20de=20posts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit los posts se siguen guardando en el sitio jekyll, lo que guardamos en la base de datos es una representación indexable que tiene los datos mínimos de los posts para buscarlos por distintos parámetros. esto nos permite cargar la lista de artículos y filtrarla de distintas formas sin cargar todo jekyll en memoria, lo que reduciría el consumo de recursos y aceleraría el panel. ya tenemos caché así que el problema estaba mitigado, pero igual es un avance. ya que migramos la base de datos a postgresql, aparecieron todas las tablas y campos en el schema.rb, que es lo que usa rails para configurar una base de datos desde cero. --- ...10504224144_create_pg_search_extensions.rb | 9 + .../20210504224343_create_indexed_posts.rb | 44 ++++ db/schema.rb | 203 ++++++++++++++++-- 3 files changed, 235 insertions(+), 21 deletions(-) create mode 100644 db/migrate/20210504224144_create_pg_search_extensions.rb create mode 100644 db/migrate/20210504224343_create_indexed_posts.rb diff --git a/db/migrate/20210504224144_create_pg_search_extensions.rb b/db/migrate/20210504224144_create_pg_search_extensions.rb new file mode 100644 index 00000000..18eebe95 --- /dev/null +++ b/db/migrate/20210504224144_create_pg_search_extensions.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +# Habilitar las extensiones de búsqueda de texto libre +class CreatePgSearchExtensions < ActiveRecord::Migration[6.1] + def change + enable_extension 'plpgsql' + enable_extension 'pg_trgm' + end +end diff --git a/db/migrate/20210504224343_create_indexed_posts.rb b/db/migrate/20210504224343_create_indexed_posts.rb new file mode 100644 index 00000000..9cf21538 --- /dev/null +++ b/db/migrate/20210504224343_create_indexed_posts.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +# Crea la tabla donde se indexa el contenido de los artículos, los +# IndexedPosts van a estar relacionados con un Post del mismo UUID. +# +# Solo contienen la información mínima necesaria para mostrar los +# resultados de búsqueda. +class CreateIndexedPosts < ActiveRecord::Migration[6.1] + def change + # Necesario para gen_random_uuid() + # + # XXX: En realidad no lo necesitamos porque cada IndexedPost va a + # tener el UUID del Post correspondiente. + enable_extension 'pgcrypto' + + create_table :indexed_posts, id: false do |t| + t.primary_key :id, :uuid, default: 'public.gen_random_uuid()' + t.belongs_to :site, index: true + t.timestamps + + # Filtramos por idioma + t.string :locale, default: 'simple', index: true + # Vamos a querer filtrar por layout + t.string :layout, null: false, index: true + # Esta es la ruta al artículo + t.string :path, null: false + # Queremos mostrar el título por separado + t.string :title, default: '' + # También vamos a mostrar las categorías + t.jsonb :front_matter, default: '{}' + t.string :content, default: '' + t.tsvector :indexed_content + + t.index :indexed_content, using: 'gin' + t.index :front_matter, using: 'gin' + end + + # Crea un trigger que actualiza el índice tsvector con el título y + # contenido del artículo y su idioma. + create_trigger(compatibility: 1).on(:indexed_posts).before(:insert, :update) do + "new.indexed_content := to_tsvector(('pg_catalog.' || new.locale)::regconfig, coalesce(new.title, '') || '\n' || coalesce(new.content,''));" + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 2a93c5f1..fed6934b 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,16 +10,74 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2021_04_14_152728) do +ActiveRecord::Schema.define(version: 2021_05_04_224343) do -# Could not dump table "access_logs" because of following StandardError -# Unknown type '' for column 'id' + # These are extensions that must be enabled in order to support this database + enable_extension "pg_trgm" + enable_extension "pgcrypto" + enable_extension "plpgsql" + + create_table "access_logs", id: :uuid, default: nil, force: :cascade do |t| + t.string "host" + t.float "msec" + t.string "server_protocol" + t.string "request_method" + t.string "request_completion" + t.string "uri" + t.string "query_string" + t.integer "status" + t.string "sent_http_content_type" + t.string "sent_http_content_encoding" + t.string "sent_http_etag" + t.string "sent_http_last_modified" + t.string "http_accept" + t.string "http_accept_encoding" + t.string "http_accept_language" + t.string "http_pragma" + t.string "http_cache_control" + t.string "http_if_none_match" + t.string "http_dnt" + t.string "http_user_agent" + t.string "http_origin" + t.float "request_time" + t.integer "bytes_sent" + t.integer "body_bytes_sent" + t.integer "request_length" + t.string "http_connection" + t.string "pipe" + t.integer "connection_requests" + t.string "geoip2_data_country_name" + t.string "geoip2_data_city_name" + t.string "ssl_server_name" + t.string "ssl_protocol" + t.string "ssl_early_data" + t.string "ssl_session_reused" + t.string "ssl_curves" + t.string "ssl_ciphers" + t.string "ssl_cipher" + t.string "sent_http_x_xss_protection" + t.string "sent_http_x_frame_options" + t.string "sent_http_x_content_type_options" + t.string "sent_http_strict_transport_security" + t.string "nginx_version" + t.integer "pid" + t.string "remote_user" + t.boolean "crawler", default: false + t.string "http_referer" + t.index ["geoip2_data_city_name"], name: "index_access_logs_on_geoip2_data_city_name" + t.index ["geoip2_data_country_name"], name: "index_access_logs_on_geoip2_data_country_name" + t.index ["host"], name: "index_access_logs_on_host" + t.index ["http_origin"], name: "index_access_logs_on_http_origin" + t.index ["http_user_agent"], name: "index_access_logs_on_http_user_agent" + t.index ["status"], name: "index_access_logs_on_status" + t.index ["uri"], name: "index_access_logs_on_uri" + end create_table "action_text_rich_texts", force: :cascade do |t| t.string "name", null: false t.text "body" t.string "record_type", null: false - t.integer "record_id", null: false + t.bigint "record_id", null: false t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false t.index ["record_type", "record_id", "name"], name: "index_action_text_rich_texts_uniqueness", unique: true @@ -28,8 +86,8 @@ ActiveRecord::Schema.define(version: 2021_04_14_152728) do create_table "active_storage_attachments", force: :cascade do |t| t.string "name", null: false t.string "record_type", null: false - t.integer "record_id", null: false - t.integer "blob_id", null: false + t.bigint "record_id", null: false + t.bigint "blob_id", null: false t.datetime "created_at", null: false t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id" t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true @@ -40,7 +98,7 @@ ActiveRecord::Schema.define(version: 2021_04_14_152728) do t.string "filename", null: false t.string "content_type" t.text "metadata" - t.integer "byte_size", null: false + t.bigint "byte_size", null: false t.string "checksum", null: false t.datetime "created_at", null: false t.string "service_name", null: false @@ -53,10 +111,65 @@ ActiveRecord::Schema.define(version: 2021_04_14_152728) do t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true end + create_table "blazer_audits", force: :cascade do |t| + t.bigint "user_id" + t.bigint "query_id" + t.text "statement" + t.string "data_source" + t.datetime "created_at" + t.index ["query_id"], name: "index_blazer_audits_on_query_id" + t.index ["user_id"], name: "index_blazer_audits_on_user_id" + end + + create_table "blazer_checks", force: :cascade do |t| + t.bigint "creator_id" + t.bigint "query_id" + t.string "state" + t.string "schedule" + t.text "emails" + t.text "slack_channels" + t.string "check_type" + t.text "message" + t.datetime "last_run_at" + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.index ["creator_id"], name: "index_blazer_checks_on_creator_id" + t.index ["query_id"], name: "index_blazer_checks_on_query_id" + end + + create_table "blazer_dashboard_queries", force: :cascade do |t| + t.bigint "dashboard_id" + t.bigint "query_id" + t.integer "position" + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.index ["dashboard_id"], name: "index_blazer_dashboard_queries_on_dashboard_id" + t.index ["query_id"], name: "index_blazer_dashboard_queries_on_query_id" + end + + create_table "blazer_dashboards", force: :cascade do |t| + t.bigint "creator_id" + t.text "name" + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.index ["creator_id"], name: "index_blazer_dashboards_on_creator_id" + end + + create_table "blazer_queries", force: :cascade do |t| + t.bigint "creator_id" + t.string "name" + t.text "description" + t.text "statement" + t.string "data_source" + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.index ["creator_id"], name: "index_blazer_queries_on_creator_id" + end + create_table "build_stats", force: :cascade do |t| t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.integer "deploy_id" + t.bigint "deploy_id" t.integer "bytes" t.float "seconds" t.string "action", null: false @@ -65,13 +178,27 @@ ActiveRecord::Schema.define(version: 2021_04_14_152728) do t.index ["deploy_id"], name: "index_build_stats_on_deploy_id" end -# Could not dump table "csp_reports" because of following StandardError -# Unknown type 'uuid' for column 'id' + create_table "csp_reports", id: :uuid, default: nil, force: :cascade do |t| + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.string "disposition" + t.string "referrer" + t.string "blocked_uri" + t.string "document_uri" + t.string "effective_directive" + t.string "original_policy" + t.string "script_sample" + t.string "status_code" + t.string "violated_directive" + t.integer "column_number" + t.integer "line_number" + t.string "source_file" + end create_table "deploys", force: :cascade do |t| t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.integer "site_id" + t.bigint "site_id" t.string "type" t.text "values" t.index ["site_id"], name: "index_deploys_on_site_id" @@ -91,6 +218,24 @@ ActiveRecord::Schema.define(version: 2021_04_14_152728) do t.string "designer_url" end + create_table "indexed_posts", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + t.bigint "site_id" + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.string "locale", default: "simple" + t.string "layout", null: false + t.string "path", null: false + t.string "title", default: "" + t.jsonb "front_matter", default: "{}" + t.string "content", default: "" + t.tsvector "indexed_content" + t.index ["front_matter"], name: "index_indexed_posts_on_front_matter", using: :gin + t.index ["indexed_content"], name: "index_indexed_posts_on_indexed_content", using: :gin + t.index ["layout"], name: "index_indexed_posts_on_layout" + t.index ["locale"], name: "index_indexed_posts_on_locale" + t.index ["site_id"], name: "index_indexed_posts_on_site_id" + end + create_table "licencias", force: :cascade do |t| t.datetime "created_at", null: false t.datetime "updated_at", null: false @@ -104,7 +249,7 @@ ActiveRecord::Schema.define(version: 2021_04_14_152728) do create_table "log_entries", force: :cascade do |t| t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false - t.integer "site_id" + t.bigint "site_id" t.text "text" t.boolean "sent", default: false t.index ["site_id"], name: "index_log_entries_on_site_id" @@ -124,7 +269,7 @@ ActiveRecord::Schema.define(version: 2021_04_14_152728) do t.string "key", null: false t.string "value" t.string "translatable_type" - t.integer "translatable_id" + t.bigint "translatable_id" t.datetime "created_at", null: false t.datetime "updated_at", null: false t.index ["translatable_id", "translatable_type", "key"], name: "index_mobility_string_translations_on_translatable_attribute" @@ -137,7 +282,7 @@ ActiveRecord::Schema.define(version: 2021_04_14_152728) do t.string "key", null: false t.text "value" t.string "translatable_type" - t.integer "translatable_id" + t.bigint "translatable_id" t.datetime "created_at", null: false t.datetime "updated_at", null: false t.index ["translatable_id", "translatable_type", "key"], name: "index_mobility_text_translations_on_translatable_attribute" @@ -147,8 +292,8 @@ ActiveRecord::Schema.define(version: 2021_04_14_152728) do create_table "roles", force: :cascade do |t| t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.integer "site_id" - t.integer "usuarie_id" + t.bigint "site_id" + t.bigint "usuarie_id" t.string "rol" t.boolean "temporal" t.index ["site_id", "usuarie_id"], name: "index_roles_on_site_id_and_usuarie_id", unique: true @@ -160,15 +305,14 @@ ActiveRecord::Schema.define(version: 2021_04_14_152728) do t.datetime "created_at", null: false t.datetime "updated_at", null: false t.string "name" - t.integer "design_id" - t.integer "licencia_id" + t.bigint "design_id" + t.bigint "licencia_id" t.string "status", default: "waiting" t.text "description" t.string "title" t.boolean "colaboracion_anonima", default: false t.boolean "contact", default: false t.string "private_key_ciphertext" - t.boolean "invitades", default: false t.boolean "acepta_invitades", default: false t.string "tienda_api_key_ciphertext", default: "" t.string "tienda_url", default: "" @@ -200,7 +344,7 @@ ActiveRecord::Schema.define(version: 2021_04_14_152728) do t.datetime "invitation_accepted_at" t.integer "invitation_limit" t.string "invited_by_type" - t.integer "invited_by_id" + t.bigint "invited_by_id" t.integer "invitations_count", default: 0 t.string "lang", default: "es" t.index ["confirmation_token"], name: "index_usuaries_on_confirmation_token", unique: true @@ -208,11 +352,28 @@ ActiveRecord::Schema.define(version: 2021_04_14_152728) do t.index ["invitation_token"], name: "index_usuaries_on_invitation_token", unique: true t.index ["invitations_count"], name: "index_usuaries_on_invitations_count" t.index ["invited_by_id"], name: "index_usuaries_on_invited_by_id" - t.index ["invited_by_type", "invited_by_id"], name: "index_usuaries_on_invited_by_type_and_invited_by_id" + t.index ["invited_by_type", "invited_by_id"], name: "index_usuaries_on_invited_by" t.index ["reset_password_token"], name: "index_usuaries_on_reset_password_token", unique: true t.index ["unlock_token"], name: "index_usuaries_on_unlock_token", unique: true end add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id" add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id" + # no candidate create_trigger statement could be found, creating an adapter-specific one + execute(<<-SQL) +CREATE OR REPLACE FUNCTION public.indexed_posts_before_insert_update_row_tr() + RETURNS trigger + LANGUAGE plpgsql +AS $function$ +BEGIN + new.indexed_content := to_tsvector(('pg_catalog.' || new.locale)::regconfig, coalesce(new.title, '') || ' + ' || coalesce(new.content,'')); + RETURN NEW; +END; +$function$ + SQL + + # no candidate create_trigger statement could be found, creating an adapter-specific one + execute("CREATE TRIGGER indexed_posts_before_insert_update_row_tr BEFORE INSERT OR UPDATE ON \"indexed_posts\" FOR EACH ROW EXECUTE PROCEDURE indexed_posts_before_insert_update_row_tr()") + end From 86ae7fb8f81bb9cb74c7a76215cbef49624e0d09 Mon Sep 17 00:00:00 2001 From: f Date: Thu, 6 May 2021 12:52:30 -0300 Subject: [PATCH 028/234] los posts pueden ser indexados MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `IndexedPost` es una representación indexada por PG de `Post`. ambos están relacionados por el UUID de `Post`, de forma que se puede traer el artículo completo (por ejemplo al previsualizar o editar). cada artículo está indexado según su idioma. para eso convertimos el locale en el equivalente en el diccionario de PG. `Site#index_posts` es un método para indexar todos los artículos en masa. `Post#to_index` genera el `IndexedPost` correspondiente `IndexedPost.search(:es, 'hola')` busca "hola" en todos los artículos utilizando el diccionario de castellano. esto no quiere decir que busque en todos los artículos en castellano. por ahora para eso hay que hacer algo como: ```ruby site = Site.find 1 site.indexed_posts.where(locale: :english).search(:en, 'hello') ``` para encontrar todos los artículos en inglés del sitio con id 1 --- app/models/indexed_post.rb | 38 ++++++++++++++++++++++++ app/models/post.rb | 4 +++ app/models/post/indexable.rb | 56 ++++++++++++++++++++++++++++++++++++ app/models/site.rb | 2 ++ 4 files changed, 100 insertions(+) create mode 100644 app/models/indexed_post.rb create mode 100644 app/models/post/indexable.rb diff --git a/app/models/indexed_post.rb b/app/models/indexed_post.rb new file mode 100644 index 00000000..6456a824 --- /dev/null +++ b/app/models/indexed_post.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +# La representación indexable de un artículo +class IndexedPost < ApplicationRecord + include PgSearch::Model + + # La traducción del locale según Sutty al locale según PostgreSQL + DICTIONARIES = { + es: 'spanish', + en: 'english' + }.freeze + + # TODO: Los indexed posts tienen que estar scopeados al idioma actual, + # no buscar sobre todos + pg_search_scope :search, + lambda { |locale, query| + { + against: :content, + query: query, + using: { + tsearch: { + dictionary: IndexedPost.to_dictionary(locale: locale), + tsvector_column: 'indexed_content' + } + } + } + } + + belongs_to :site + + # Convertir locale a direccionario de PG + # + # @param [String,Symbol] + # @return [String] + def self.to_dictionary(locale:) + DICTIONARIES[locale.to_sym] || 'simple' + end +end diff --git a/app/models/post.rb b/app/models/post.rb index a0e16706..6ef7a2ec 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -17,6 +17,8 @@ class Post attr_reader :attributes, :errors, :layout, :site, :document + include Post::Indexable + class << self # Obtiene el layout sin leer el Document # @@ -191,6 +193,8 @@ class Post post: self, required: true) end + alias locale lang + # TODO: Mover a method_missing def uuid @metadata[:uuid] ||= MetadataUuid.new(document: document, site: site, layout: layout, name: :uuid, type: :uuid, diff --git a/app/models/post/indexable.rb b/app/models/post/indexable.rb new file mode 100644 index 00000000..f25f28d2 --- /dev/null +++ b/app/models/post/indexable.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +class Post + # Vuelve indexables a los Posts + module Indexable + extend ActiveSupport::Concern + + included do + # Devuelve una versión indexable del Post + # + # @return [IndexedPosts] + def to_index + @to_index ||= IndexedPost.find_or_create_by(id: uuid.value).tap do |indexed_post| + indexed_post.layout = layout.name + indexed_post.site_id = site.id + indexed_post.path = path.basename + indexed_post.locale = IndexedPost.to_dictionary(locale: locale.value) + indexed_post.title = title.value + indexed_post.front_matter = indexable_front_matter + indexed_post.content = indexable_content + end + end + + private + + # Los metadatos que se almacenan como objetos JSON. Empezamos con + # las categorías porque se usan para filtrar en el listado de + # artículos. + # + # @return [Hash] + def indexable_front_matter + return {} unless attribute? :categories + + { categories: categories.indexable_values } + end + + # Devuelve un documento indexable en texto plano + # + # XXX: No memoizamos para permitir actualizaciones, aunque + # probablemente se indexe una sola vez. + # + # @return [String] + def indexable_content + indexable_attributes.map do |attr| + self[attr].to_s + end.join("\n") + end + + def indexable_attributes + @indexable_attributes ||= attributes.select do |attr| + self[attr].indexable? + end + end + end + end +end diff --git a/app/models/site.rb b/app/models/site.rb index 1c62e9bc..14238e1a 100644 --- a/app/models/site.rb +++ b/app/models/site.rb @@ -7,6 +7,7 @@ class Site < ApplicationRecord include Site::Forms include Site::FindAndReplace include Site::Api + include Site::Index include Tienda # Cifrar la llave privada que cifra y decifra campos ocultos. Sutty @@ -37,6 +38,7 @@ class Site < ApplicationRecord belongs_to :design belongs_to :licencia + has_many :indexed_posts, dependent: :destroy has_many :log_entries, dependent: :destroy has_many :deploys, dependent: :destroy has_many :build_stats, through: :deploys From a149c870e2023f0b0f41957efebc114b0e50925c Mon Sep 17 00:00:00 2001 From: f Date: Thu, 6 May 2021 13:00:05 -0300 Subject: [PATCH 029/234] =?UTF-8?q?las=20categor=C3=ADas=20se=20guardan=20?= =?UTF-8?q?en=20indexed=20posts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit algunas categorías son otros artículos relacionados, con este método garantizamos que sólo se guardan los títulos. --- app/models/metadata_array.rb | 2 ++ app/models/metadata_related_posts.rb | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/app/models/metadata_array.rb b/app/models/metadata_array.rb index 5f43b790..8d951a14 100644 --- a/app/models/metadata_array.rb +++ b/app/models/metadata_array.rb @@ -21,6 +21,8 @@ class MetadataArray < MetadataTemplate value.join(', ') end + alias indexable_values values + private # TODO: Sanitizar otros valores diff --git a/app/models/metadata_related_posts.rb b/app/models/metadata_related_posts.rb index bcc18d86..4c022fff 100644 --- a/app/models/metadata_related_posts.rb +++ b/app/models/metadata_related_posts.rb @@ -22,6 +22,10 @@ class MetadataRelatedPosts < MetadataArray false end + def indexable_values + values.keys + end + private # Obtiene todos los posts y opcionalmente los filtra From f37ed785e32b1dd3b39aafa91bc892a5f2a0b000 Mon Sep 17 00:00:00 2001 From: f Date: Thu, 6 May 2021 15:19:34 -0300 Subject: [PATCH 030/234] correr rake y rails dentro de hainish ```bash make rails args="g migration CreateTable" make rake args="routes" ``` --- Makefile | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 15603bf2..757be734 100644 --- a/Makefile +++ b/Makefile @@ -26,6 +26,13 @@ test: always serve: /etc/hosts $(hain) 'cd /Sutty/sutty; bundle exec rails s -b "ssl://0.0.0.0:3000?key=../sutty.local/domain/$(SUTTY).key&cert=../sutty.local/domain/$(SUTTY).crt"' +# make rails args="db:migrate" +rails: + $(hain) 'cd /Sutty/sutty; bundle exec rails $(args)' + +rake: + $(hain) 'cd /Sutty/sutty; bundle exec rake $(args)' + # Servir JS con el dev server. # Esto acelera la compilación del javascript, tiene que correrse por separado # de serve. @@ -94,7 +101,7 @@ fa: app/assets/fonts/forkawesome-webfont.woff2 ## Fork Awesome ota: assets sudo chgrp -R 82 public/ - rsync -av public/ athshe:/srv/sutty/srv/http/data/_public/ + rsync -av --delete-after public/ athshe:/srv/sutty/srv/http/data/_public/ # Hotfixes ota-rb: From bd3be549b495c913eba3079e297b4e88c6349100 Mon Sep 17 00:00:00 2001 From: Maki Date: Thu, 6 May 2021 15:43:54 -0300 Subject: [PATCH 031/234] texto mis sitios --- config/locales/en.yml | 10 +++------- config/locales/es.yml | 4 ++-- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/config/locales/en.yml b/config/locales/en.yml index 9366f9fc..61d6eb5b 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -335,9 +335,9 @@ en: static_file_migration: 'File migration' find_and_replace: 'Search and replace' index: - title: 'Sites' + title: 'My Sites' pull: 'Upgrade' - help: 'This is the list of sites you can edit' + help: 'Here are all the sites you can edit' visit: 'Visit the site' welcome: | # Welcome! @@ -345,12 +345,8 @@ en: You have no sites yet. You can generate all the sites you want with the **Create site** button. - Para ver los cambios, usa el botón **Publicar cambios** en cada - sitio y espera unos segundos. También recibirás un correo de - notificación. - To see your changes, use the **Publish changes** button on each - site and wait a few seconds. You'll also receive an e-mail + site and wait a few seconds. You will also receive an e-mail notification. [Create my first site](/sites/new) diff --git a/config/locales/es.yml b/config/locales/es.yml index 3a64e7ee..2c3eeb61 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -340,9 +340,9 @@ es: static_file_migration: 'Migración de archivos' find_and_replace: 'Búsqueda y reemplazo' index: - title: 'Sitios' + title: 'Mis sitios' pull: 'Actualizar' - help: 'Este es el listado de sitios que puedes editar' + help: 'Acá están todos los sitios que puedes editar' visit: 'Visitar el sitio' welcome: | # ¡Bienvenide! From 18a05133971e61853d6f678d887472366e43fefd Mon Sep 17 00:00:00 2001 From: Maki Date: Thu, 6 May 2021 16:03:23 -0300 Subject: [PATCH 032/234] idiomas --- config/locales/en.yml | 3 +++ config/locales/es.yml | 3 +++ 2 files changed, 6 insertions(+) diff --git a/config/locales/en.yml b/config/locales/en.yml index 61d6eb5b..210aeb12 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1,5 +1,8 @@ en: dir: ltr + en: English + es: Castellano + es-AR: Castellano rioplatense locales: es: name: Castillian Spanish diff --git a/config/locales/es.yml b/config/locales/es.yml index 2c3eeb61..403389ab 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -1,4 +1,7 @@ es: + es: Castellano + en: English + es-AR: Castellano Rioplatense dir: ltr locales: es: From 2efb416136d4e96234c03b56602852745fd317e2 Mon Sep 17 00:00:00 2001 From: Maki Date: Thu, 6 May 2021 17:29:24 -0300 Subject: [PATCH 033/234] correcciones en.yml --- config/locales/en.yml | 174 +++++++++++++++--------------------------- 1 file changed, 62 insertions(+), 112 deletions(-) diff --git a/config/locales/en.yml b/config/locales/en.yml index 210aeb12..586ea842 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -39,22 +39,22 @@ en: image: not_an_image: 'Not an image' path_required: 'Missing image for upload' - no_file_for_description: "There's a description with no associated image" + no_file_for_description: "Description with no associated image" file: path_required: "Missing file for upload" - no_file_for_description: "There's a description with no associated file" + no_file_for_description: "Description with no associated file" event: - zone_missing: 'Timezone is incorrect' + zone_missing: 'Inexistent timezone' date_missing: 'Event date is required' date_non_parseable: 'Time is not in the correct format' time_non_parseable: 'Date is not in the correct format' - end_in_the_past: "Event end can't happen before the start" + end_in_the_past: "Event can't end before it starts" belongs_to: missing_post: "Couldn't find the related post" has_many: missing_posts: "Couldn't find some related posts" date: - invalid_format: "It seems that your browser doesn't support dates and the date is on the incorrect format, please use yyyy-mm-dd, ie. 2021-01-31" + invalid_format: "Incorrect date format, please use yyyy-mm-dd, ie. 2021-01-31" exceptions: post: site_missing: 'Needs an instance of Site' @@ -67,10 +67,10 @@ en: subject: "[Sutty] The site %{site} has been built" hi: "Hi!" explanation: | - This e-mail is to notify you that Sutty has built your site and - it's available at . + This e-mail is to notify you that Sutty has built your site, which is + available at . - You'll find details bellow. + You'll find details below. th: type: Type status: Status @@ -98,18 +98,18 @@ en: title: Alternative domain name success: Success! error: Error - help: You can contact us by replying this e-mail + help: You can contact us by replying to this e-mail maintenance_mailer: notice: subject: 'Maintenance notice' hi: 'Hi!' - message: "We're getting in contact with you to let you know we'll be doing maintenance work in our servers." + message: "We're getting in contact with you to let you know we'll be doing maintenance work on our servers." reason: 'The reason is:' estimated_from: 'The maintenance period starts at %{from} (visit %{time_is} to convert into your time zone)' estimated_to: 'Up to %{to} (approximately, visit %{time_is} to convert into your time zone)' estimated_from_html: 'The maintenance period starts at %{from} (convert into your time zone)' estimated_to_html: 'Up to %{to} (approximately, convert into your time zone)' - thanks: 'Thanks for your patience' + thanks: 'Thank you for your patience' were_back: subject: 'Maintenance period ended' hi: 'Hi!' @@ -144,15 +144,15 @@ en: invitadx: attributes: email: - taken: 'This e-mail address is already taken, please choose another' + taken: 'This e-mail address is already taken, please choose a different one' password_confirmation: confirmation: "The passwords don't match" acepta_politicas_de_privacidad: no_acepta_politicas_de_privacidad: "Please read and accept the privacy policy" design_id: layout_incompatible: - error: "Design can't be changed because there're posts with incompatible layouts" - help: "Your site has posts with layout only compatible to the current design. If you change it, the site won't work as you expect. If you're trying out designs, you can delete posts in the following incompatible layouts:: %{layouts}." + error: "Design can't be changed because there are posts with incompatible layouts" + help: "Your site has posts with layouts only compatible with the current design. If you change it, the site won't work as you expect. If you're trying out designs, you can delete posts in the following incompatible layouts:: %{layouts}." errors: argument_error: 'Argument `%{argument}` must be an instance of %{class}' unknown_locale: 'Unknown %{locale} locale' @@ -182,7 +182,7 @@ en: category: 'Category' i18n: top: 'Back to top' - index: "Here you can edit your site's texts that don't belong to a post, like its description, sections, buttons... If you change languages up there in the title to be the same, you can edit them. If they're different, you can translate from one into the other." + index: "Here is where you edit the text on your site that doesn't belong to a post, such as its description, sections, buttons... If you change languages up there in the title to be the same, you can edit them. If they're different, you can translate from one into the other." count: 'This is the amount of texts.' toc: 'Jump to this section' meta: 'Metadata' @@ -204,76 +204,26 @@ en: recursos: 'Resources' contacta: 'Contact us' agradecimientos: 'Acknowledgments' - sesion: 'Sessions' - sesiones: 'Sessions' - anexo: 'Appendix' - simple: 'Simple' sites: index: 'This is the list of sites you can edit.' - edit_translations: "You can edit texts from your site other than - posts', and you can also translate them to other languages." - edit_posts: "When you enter here, you'll see a list of every - article and edit them. You can also create new ones." enqueued: "The site is on queue to be generated. Once this process finishes, you'll get an email telling you the status. If everything went well, your site will be published :)" enqueue: "When you finish making changes to your site, you can publish them with this action. You'll receive an email when it finishes." - build_log: "This is the log for what happened during site - generation. If there was an issue, you'll see it here." - invitade: "Invited users can only add and modify entries but can't publish until reviewed by a user" invitations: - accept: 'Someone invited you to collaborate on their site. If you accept the invitation, you can access the site.' + accept: "Someone invited you to collaborate on their site. If you accept the invitation, you can access the site's edit mode." reject: "If you decline, you won't have access." close: 'Close help' - markdown: - intro: 'The text is formatted using a syntax called Markdown, a - simple format that can be easily written just by remembering some - rules, and that can be converted to a web page, or to PDF and ePub - files. You can use the buttons below for basic formatting. If - you need help, here you have a markdown - cheatsheet.' - back: 'Go back' - input: 'If we write...' - output: 'We get...' - bold: 'Bold' - italic: 'Emphasis' - heading: 'Title' - link: - text: 'A link' - url: 'https://example.org' - quote: 'A quote from a text we liked' - ul: 'Our TODO list' - ol: 'Steps for our machiavelic plans' - img: - text: 'Kéfir island' - url: 'https://kefir.red/images/isla.png' - ltr: 'Introduction' - rtl: 'مقدمة' - dir: "These are tricky. If you want to use an expression in a - language using another direction, like using an Arabic expression - on an English text, or viceversa, you have to inform both - direction and language so the markdown processor understands it - has to change it internally. Otherwise you may see out of order - words, specially in the PDF results." - distraction_free_html: 'You can have a distraction free writing session - by clicking the button' - preview_html: 'Click the preview - button to see the results.' - autocomplete_html: "Some of these fields can be auto-completed. If - you know what to put on them, just start writing and the - auto-complete will suggest available options. If the option doesn't - exist, finish writing and press Enter to add a new one. - To empty the field, click the × button on your right." deploys: deploy_local: title: 'Host at Sutty' help: | The site will be available at . - We're working out the details for allowing your own site - domains, you can help us! + We're working out the details to allow you to use your own site + domains, you can [help us]("https://sutty.nl/en/index.html#contact")! ejemplo: 'example' deploy_private: title: 'Generate private version' @@ -288,20 +238,20 @@ en: When you enable this option, your site will also be available under . - The www prefix to web addresses has been a way of refering to - computers that are available on the World Wide Web. But since - the Web has become the hegemonic way of accessing the Internet, - it has become less used. Even so, people still uses them. + The www prefix has been a way of referring to + computers that are available on the World Wide Web. Since + the Web became the hegemonic mode of accessing the Internet, + it has lost popularity. All the same, some people still use them. deploy_zip: title: 'Generate a ZIP file' help: | - ZIP files contain and compress all the files of your site. With - this option you can download and also share your whole site + ZIP files contain and compress all your site's files. With + this option you can download and also share your entire site through the address, keep it as backup - or have an strategy of solidarity hosting, were many people - shares a copy of your site. + or have a strategy of solidary hosting, where many people + share a copy of your site. - It also helps with site archival for historical purposes :) + It also helps with site archiving for historical purposes! ejemplo: 'example' deploy_hidden_service: title: 'Host as Tor Hidden Service' @@ -313,7 +263,7 @@ en: anonymous**. Visitors can still access your site publicly at <%{public_url}>. - [Know more](https://sutty.nl/en/hidden-sites-with-tor/) + [Learn more](https://sutty.nl/en/hidden-sites-with-tor/) help_2: | The hidden address for your site is: @@ -325,7 +275,7 @@ en: index: title: Statistics help: | - Statistics show information about how your site is generated and + These statistics show information about how your site is generated and how many resources it uses. build: average: 'Average building time' @@ -345,12 +295,12 @@ en: welcome: | # Welcome! - You have no sites yet. You can generate all the sites you want + You don't have any sites yet. You can generate as many sites as you want with the **Create site** button. - To see your changes, use the **Publish changes** button on each - site and wait a few seconds. You will also receive an e-mail - notification. + To see your changes, use the **Publish changes** button that corresponds to the + site you've modified and wait a few seconds. You will receive an e-mail + notification when the changes are loaded. [Create my first site](/sites/new) repository: @@ -374,11 +324,11 @@ en: submit: 'Save changes' form: errors: - title: There're errors and we couldn't save your changes :( + title: There were errors and we couldn't save your changes :( help: Please, look for the invalid fields to fix them help: - name: "Your site's name. It can only contain numbers and letters." - design: 'Select the design for your site. You can change it later. We add more designs from time to time.' + name: "The name of your site. It can only include numbers and letters." + design: 'Select the design for your site. You can change it later. We add more designs from time to time!' licencia: 'Everything we publish has automatic copyright. This means nobody can use our works without explicit permission. By using licenses, we stablish conditions by which we want to share @@ -386,14 +336,14 @@ en: privacidad: | The [privacy policy](https://sutty.nl/en/privacy-policy/) and [code of conduct](https://sutty.nl/en/code-of-conduct/) inform - your visitors about their privacy and expected conduct of the + your visitors about their privacy and the conduct expected from members of the site's community. We suggest you use the same documents Sutty uses. You can modify them as articles after creating the site. deploys: | Sutty allows you to host your site in different places at the - same time. This strategy makes your site available even when - some of them become unavailable. + same time. This strategy makes your site available even if + one of them goes down. design: title: 'Design' actions: 'Information about this design' @@ -408,26 +358,26 @@ en: title: 'Where do you want your site to be hosted?' colaboracion_anonima: title: 'Accept anonymous collaboration' - help: 'By allowing anonymous collaboration, you enable visitors to send articles without a Sutty account. Nothing is published without your consent, so make sure to check drafts regularly. This feature can expose you to attacks and violence, so we recommend you enable it with care.' + help: 'By allowing anonymous collaboration, you enable visitors to send articles without a Sutty account. Nothing is published without your consent, so make sure to check drafts regularly. This feature can expose you to attacks and violence, so we recommend you enable it with careful consideration.' acepta_invitades: title: 'Accept collaboration' help: 'By enabling this option, you can invite other people to collaborate on your site.' contact: title: 'Enable contact forms' - help: 'If your site has contact forms, you can enable them here. If your site is under spam or trolls attack, you can disable them temporarily.' + help: 'If your site has contact forms, you can enable them here. If your site is under a spam or troll attack, you can disable them temporarily.' tienda: title: Store - first_time_html: 'The store is an optional service that allows you to commertialize through your Sutty web site. To configure it, we invite you to contact us :).' - help: 'Puedes configurar tu tienda aquí.' + first_time_html: 'The store is an optional service that allows you to commercialize through your Sutty web site. To configure it, we invite you to contact us :).' + help: 'You can configure your store here.' fetch: title: 'Upgrade the site' help: - fetch: 'Any changes made to the site are saved into a _git_ repository. Git saves the differences between previous and current versions of files so we can explore them as the history of the project. Also, we can bring and send changes between repositories. In this case, every site managed with Sutty share a common root that we call [skeleton](https://0xacab.org/sutty/skel.sutty.nl). When we upgrade this skeleton, you can explore the changes here and accept them to make your site better.' + fetch: 'Any changes made to the site are saved into a _git_ repository. Git saves the differences between previous and current versions of files so we can explore them as the history of the project. Additionally, we can bring and send changes between repositories. In this case, every site managed with Sutty shares a common root that we call [skeleton](https://0xacab.org/sutty/skel.sutty.nl). When we upgrade this skeleton, you can explore the changes here and accept them to improve your site.' toc: 'Table of contents' merge: request: 'Upgrade my site with these changes' success: 'Site upgrade has been completed. Your next build will run this upgrade :)' - error: "There was an error when we were trying to upgrade your site. This could be due to conflicts that couldn't be solved automatically. We've sent a report of the issue to Sutty's admins so they already know about it. Sorry! :(" + error: "There was an error when trying to upgrade your site. This could be due to conflicts that couldn't be solved automatically. A report of the issue has already been sent to our admins. Sorry for the inconvenience! :(" message: 'Skeleton upgrade' footer: powered_by: 'is developed by' @@ -435,14 +385,14 @@ en: index: 'Translations' edit: 'Edit texts and translations' edit_same: 'Edit texts in' - translate_into: '. Translate into' + translate_into: '. Translate to' save: 'Save' change: 'Change' translate: 'Translate' jump: 'Jump to section' translating: from: 'Translating from' - to: 'into' + to: 'to' es: 'Castillian Spanish' en: 'English' ar: 'Arabic' @@ -463,7 +413,7 @@ en: label: Language date: label: Date - help: Date for this post. If you use a date in the future the post won't be published until you publish changes on that day. + help: Date for this post. If you choose a future date, the post won't be published until you publish changes on that day. required: label: ' (required)' feedback: 'This field cannot be empty!' @@ -505,8 +455,8 @@ en: edit: 'Edit' preview: btn: 'Preliminary version' - alert: 'Not every article type has a preliminary version :)' - message: 'This is a preliminary version, use the Publish changes button back on the panel to publish the article on your site.' + alert: 'Not every article type has a preliminary version' + message: 'This is a preliminary version, use the Publish changes button back on the panel to publish the article onto your site.' open: 'Tip: You can add new options by typing them and pressing Enter' private: '🔒 The values of this field will remain private' select: @@ -541,17 +491,17 @@ en: demote: Removes privileges for this user promote: Gives privileges to this guest invite: 'Invites %{invite_as} to this site' - public_invite: Copy this address and share it with everyone you want to accept public collaborations. These guests can only create and modify their own posts and any change needs to be approved. + public_invite: Copy this address and share it with everyone you want to collaborate on your site. These guests can only create and modify their own posts and any change needs to be approved. title: Users and Guests usuaries: Users invitades: Guests destroy: text: 'Remove access' - confirm: "Remove access to this site? The account itself is not deleted, but it won't be able to make changes to this site." + confirm: "Remove access to this site? The account itself will not be deleted, but it won't be able to make changes to this site." denied: 'The site needs at least one user!' demote: text: 'Convert to guest' - confirm: 'Convert to guest? They can only edit their own posts and will need approval from other user to publish them.' + confirm: 'Convert to guest? They can only edit their own posts and will need approval from another user to publish them.' denied: 'The site needs at least one user!' promote: text: 'Convert to user' @@ -573,10 +523,10 @@ en: lockbox: help: title: Encrypted content - description: The field contents are encrypted before being stored and won't be available on the public website or its source code. You can save private information here and it will only be readable to this site's users through Sutty's panel. - decryption_error: There was an error trying to decrypt the content, Sutty's team has been notified! + description: The field contents are encrypted before being stored and won't be available on the public website or its source code. You can save private information here and it will only be readable to this site's users through the Sutty panel. + decryption_error: There was an error trying to decrypt the content. The Sutty team has been notified. editor: - alert: "Hi! This our new editor, supporting more formats. Editors are complex machines and we can only polish them with your help. If you have a few minutes, [we'll like to read about your experience](https://sutty.nl/en/#contact) :)" + alert: "Hi! This our new editor, which supports more formats. Editors are complex machines and we can only polish them with your help. If you have a few minutes, [we would like to read about your experience](https://sutty.nl/en/#contact) :)" bold: Bold italic: Emphasis deleted: Strikethrough @@ -603,10 +553,10 @@ en: multimedia-select: Select file multimedia-upload: Upload multimedia-remove: Remove media - description: Description for blind people and search engines + description: Description for the visually impaired and search engines url: Address more: More heading levels - word: "If you're pasting from an hegemonic word processor, please be patient with us, it's not always simple to recover the whole format :)" + word: "If you're pasting from another word processor, please be patient with us, as it isn't always simple to recover the whole format" email_address: address_unknown: "the address is unknown" domain_does_not_accept_email: "this domain is not configured to accept email" @@ -622,8 +572,8 @@ en: ip_address_no_localhost: "localhost IP addresses are not allowed" ipv4_address_invalid: "the address is not a valid IPv4 address" ipv6_address_invalid: "the address is not a valid IPv6 address" - local_size_long: "the account name is too long" - local_size_short: "the account name is too short" + local_size_long: "account name too long" + local_size_short: "account name too short" local_invalid: "format is incorrect" - not_allowed: "is not welcome here" - server_not_available: "the remote email server is not available" + not_allowed: "that email provider is not welcome here" + server_not_available: "remote email server not available" From 20d2cf684054536df4c67fcaca1ff10cf6ecea04 Mon Sep 17 00:00:00 2001 From: f Date: Thu, 6 May 2021 17:29:29 -0300 Subject: [PATCH 034/234] =?UTF-8?q?no=20fallar=20si=20no=20tiene=20un=20di?= =?UTF-8?q?se=C3=B1o=20asignado?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/site.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/site.rb b/app/models/site.rb index 1c62e9bc..3a699233 100644 --- a/app/models/site.rb +++ b/app/models/site.rb @@ -390,7 +390,7 @@ class Site < ApplicationRecord # Detecta si el tema actual es una gema def theme_available? - available_themes.include? design.gem + available_themes.include? design&.gem end # Devuelve el dominio actual From 5441fe5503eb615c15cc2ca15da5c06e4eca2c43 Mon Sep 17 00:00:00 2001 From: Maki Date: Thu, 6 May 2021 17:37:25 -0300 Subject: [PATCH 035/234] saque comillas en md --- config/locales/en.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/locales/en.yml b/config/locales/en.yml index 586ea842..dd9f8309 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -223,7 +223,7 @@ en: The site will be available at . We're working out the details to allow you to use your own site - domains, you can [help us]("https://sutty.nl/en/index.html#contact")! + domains, you can [help us](https://sutty.nl/en/index.html#contact)! ejemplo: 'example' deploy_private: title: 'Generate private version' From 5880c4767639860855224955d61e48c4bf004355 Mon Sep 17 00:00:00 2001 From: f Date: Thu, 6 May 2021 17:54:49 -0300 Subject: [PATCH 036/234] rubocop --- .ruby-version | 1 - app/models/metadata_template.rb | 6 +++--- app/models/site.rb | 12 +++++++----- config/environments/production.rb | 2 +- 4 files changed, 11 insertions(+), 10 deletions(-) delete mode 100644 .ruby-version diff --git a/.ruby-version b/.ruby-version deleted file mode 100644 index bec3a35e..00000000 --- a/.ruby-version +++ /dev/null @@ -1 +0,0 @@ -system diff --git a/app/models/metadata_template.rb b/app/models/metadata_template.rb index 58000596..62e6da2d 100644 --- a/app/models/metadata_template.rb +++ b/app/models/metadata_template.rb @@ -14,9 +14,9 @@ MetadataTemplate = Struct.new(:site, :document, :name, :label, :type, # Queremos que los artículos nuevos siempre cacheen, si usamos el UUID # siempre vamos a obtener un item nuevo. def cache_key - return layout.value + '/' + name.to_s if post.new? + return "#{layout.value}/#{name}" if post.new? - @cache_key ||= 'post/' + post.uuid.value + '/' + name.to_s + @cache_key ||= "post/#{post.uuid.value}/#{name}" end def cache_version @@ -24,7 +24,7 @@ MetadataTemplate = Struct.new(:site, :document, :name, :label, :type, end def cache_key_with_version - cache_key + '-' + cache_version + "#{cache_key}-#{cache_version}" end # XXX: Deberíamos sanitizar durante la asignación? diff --git a/app/models/site.rb b/app/models/site.rb index 3a699233..7a8b4b4b 100644 --- a/app/models/site.rb +++ b/app/models/site.rb @@ -97,7 +97,7 @@ class Site < ApplicationRecord # @param slash Boolean Agregar / al final o no # @return String La URL con o sin / al final def url(slash: true) - 'https://' + hostname + (slash ? '/' : '') + "https://#{hostname}#{slash ? '/' : ''}" end # Obtiene los dominios alternativos @@ -105,7 +105,7 @@ class Site < ApplicationRecord # @return Array def alternative_hostnames deploys.where(type: 'DeployAlternativeDomain').map(&:hostname).map do |h| - h.end_with?('.') ? h[0..-2] : h + '.' + Site.domain + h.end_with?('.') ? h[0..-2] : "#{h}.#{Site.domain}" end end @@ -114,7 +114,7 @@ class Site < ApplicationRecord # @return Array def alternative_urls(slash: true) alternative_hostnames.map do |h| - 'https://' + h + (slash ? '/' : '') + "https://#{h}#{slash ? '/' : ''}" end end @@ -275,7 +275,9 @@ class Site < ApplicationRecord # NoMethodError @layouts_struct ||= Struct.new(*layout_keys, keyword_init: true) @layouts ||= @layouts_struct.new(**data['layouts'].map do |name, metadata| - [name.to_sym, Layout.new(site: self, name: name.to_sym, meta: metadata.delete('meta')&.with_indifferent_access, metadata: metadata.with_indifferent_access)] + [name.to_sym, + Layout.new(site: self, name: name.to_sym, meta: metadata.delete('meta')&.with_indifferent_access, + metadata: metadata.with_indifferent_access)] end.to_h) end @@ -404,7 +406,7 @@ class Site < ApplicationRecord end def self.default - find_by(name: Site.domain + '.') + find_by(name: "#{Site.domain}.") end def reset diff --git a/config/environments/production.rb b/config/environments/production.rb index a51e1a59..c1269fb2 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -100,7 +100,7 @@ Rails.application.configure do .new(Syslog::Logger.new('sutty')) if ENV['RAILS_LOG_TO_STDOUT'].present? - logger = ActiveSupport::Logger.new(STDOUT) + logger = ActiveSupport::Logger.new($stdout) logger.formatter = config.log_formatter config.logger = ActiveSupport::TaggedLogging.new(logger) end From dcc47ad258e7aa605770dc33a9a16a38a512631c Mon Sep 17 00:00:00 2001 From: f Date: Thu, 6 May 2021 17:55:03 -0300 Subject: [PATCH 037/234] poder trabajar con git+ssh en el contenedor y aplicar parches --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index a8dd7ed7..1aa97b16 100644 --- a/Dockerfile +++ b/Dockerfile @@ -81,7 +81,7 @@ RUN echo 'http://dl-cdn.alpinelinux.org/alpine/edge/testing' >> /etc/apk/reposit RUN apk add --no-cache libxslt libxml2 tzdata ruby ruby-bundler ruby-json ruby-bigdecimal ruby-rake ruby-irb RUN apk add --no-cache postgresql-libs libssh2 file rsync git jpegoptim vips RUN apk add --no-cache ffmpeg imagemagick pandoc tectonic oxipng jemalloc -RUN apk add --no-cache git-lfs +RUN apk add --no-cache git-lfs openssh-client patch # Chequear que la versión de ruby sea la correcta RUN test "2.7.3" = `ruby -e 'puts RUBY_VERSION'` From 9b255a23e01cb7bcaf4b7af33c85dbea298aabf1 Mon Sep 17 00:00:00 2001 From: f Date: Thu, 6 May 2021 17:56:34 -0300 Subject: [PATCH 038/234] ajustes en el makefile --- Makefile | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/Makefile b/Makefile index 757be734..a00b18e8 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,4 @@ +.SHELL := /bin/bash # Incluir las variables de entorno mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST))) root_dir := $(patsubst %/,%,$(dir $(mkfile_path))) @@ -53,10 +54,12 @@ save: date +%F | xargs git tag -f @echo -e "\a" -# Crear el directorio donde se almacenan las gemas binarias +# proyectos. ../gems/: mkdir -p $@ +# Crear el directorio donde se almacenan las gemas binarias +# TODO: Mover a un proyecto propio, porque lo utilizamos en todos los gem_dir := $(shell readlink -f ../gems) gem_cache_dir := $(gem_dir)/cache gem_binary_dir := $(gem_dir)/$(alpine_version) @@ -92,18 +95,12 @@ dirs := $(patsubst %,root/%,data sites deploy public) $(dirs): mkdir -p $@ -app/assets/fonts/forkawesome-webfont.woff2: fa.txt - which glyphhanger || npm i -g glyphhanger - grep -v "^#" fa.txt | sed "s/^/U+/" | cut -d " " -f 1 | tr "\n" "," | xargs -rI {} glyphhanger --subset=node_modules/fork-awesome/fonts/forkawesome-webfont.ttf --formats=woff2 --whitelist="{}" - mv node_modules/fork-awesome/fonts/forkawesome-webfont-subset.woff2 $@ - -fa: app/assets/fonts/forkawesome-webfont.woff2 ## Fork Awesome - ota: assets sudo chgrp -R 82 public/ - rsync -av --delete-after public/ athshe:/srv/sutty/srv/http/data/_public/ + rsync -avi --delete-after public/ athshe:/srv/sutty/srv/http/data/_public/ # Hotfixes +commit ?= origin/rails ota-rb: umask 022; git format-patch $(commit) scp ./0*.patch root@athshe.sutty.nl:/tmp/ From 6b8472040184df3edcb7aeb0701f3df77f15d12d Mon Sep 17 00:00:00 2001 From: f Date: Thu, 6 May 2021 18:10:31 -0300 Subject: [PATCH 039/234] =?UTF-8?q?si=20los=20art=C3=ADculos=20son=20nuevo?= =?UTF-8?q?s=20no=20tienen=20llave=20para=20cachearlos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/post.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/models/post.rb b/app/models/post.rb index a0e16706..461733f9 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -110,7 +110,7 @@ class Post end def cache_version - updated_at.utc.to_s(:usec) + (updated_at || modified_at).utc.to_s(:usec) end # Agregar el timestamp para saber si cambió, siguiendo el módulo @@ -127,6 +127,8 @@ class Post # Fecha de última modificación del archivo def updated_at + return if new? + File.mtime(path.absolute) end From 91cc5fe465a9da0fcabed1037daf88c5c3ac03da Mon Sep 17 00:00:00 2001 From: f Date: Thu, 6 May 2021 18:11:10 -0300 Subject: [PATCH 040/234] variable de entorno de testeo para los tests --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index a00b18e8..e2e320d6 100644 --- a/Makefile +++ b/Makefile @@ -22,7 +22,7 @@ test/%_test.rb: always $(hain) 'cd /Sutty/sutty; bundle exec rake test TEST="$@" RAILS_ENV=test' test: always - $(hain) 'cd /Sutty/sutty; bundle exec rake test' + $(hain) 'cd /Sutty/sutty; RAILS_ENV=test bundle exec rake test' serve: /etc/hosts $(hain) 'cd /Sutty/sutty; bundle exec rails s -b "ssl://0.0.0.0:3000?key=../sutty.local/domain/$(SUTTY).key&cert=../sutty.local/domain/$(SUTTY).crt"' From d61d1cad565636c800f4f7642c065ad62d99de9d Mon Sep 17 00:00:00 2001 From: f Date: Thu, 6 May 2021 19:07:11 -0300 Subject: [PATCH 041/234] =?UTF-8?q?respetar=20el=20orden=20de=20los=20art?= =?UTF-8?q?=C3=ADculos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit siempre ordenamos primero por número de orden y fecha de creación, siempre decrecientes. esto permite que les usuaries prioricen contenido usando las herramientas de reordenamiento. pg_search no soporta esto, siempre ordena por cuánto corresponde el resultado con la búsqueda, así que lo emparchamos para que respete el orden que necesitamos. el reporte de error relacionado es este: https://github.com/Casecommons/pg_search/issues/467 --- app/models/indexed_post.rb | 3 +++ app/models/post/indexable.rb | 2 ++ config/initializers/core_extensions.rb | 13 +++++++++++++ .../20210506212356_add_order_to_indexed_posts.rb | 9 +++++++++ db/schema.rb | 3 ++- 5 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20210506212356_add_order_to_indexed_posts.rb diff --git a/app/models/indexed_post.rb b/app/models/indexed_post.rb index 6456a824..c76a5c85 100644 --- a/app/models/indexed_post.rb +++ b/app/models/indexed_post.rb @@ -26,6 +26,9 @@ class IndexedPost < ApplicationRecord } } + # Trae los IndexedPost en el orden en que van a terminar en el sitio. + default_scope lambda { order(order: :desc, created_at: :desc) } + belongs_to :site # Convertir locale a direccionario de PG diff --git a/app/models/post/indexable.rb b/app/models/post/indexable.rb index f25f28d2..9b92111f 100644 --- a/app/models/post/indexable.rb +++ b/app/models/post/indexable.rb @@ -18,6 +18,8 @@ class Post indexed_post.title = title.value indexed_post.front_matter = indexable_front_matter indexed_post.content = indexable_content + indexed_post.created_at = date.value + indexed_post.order = attribute?(:order) ? order.value : 0 end end diff --git a/config/initializers/core_extensions.rb b/config/initializers/core_extensions.rb index e37b2be4..84dea42e 100644 --- a/config/initializers/core_extensions.rb +++ b/config/initializers/core_extensions.rb @@ -66,3 +66,16 @@ module Jekyll end end end + +# No aplicar el orden por ranking para poder obtener los artículos en el +# orden que tendrían en el sitio final. +module PgSearch + ScopeOptions.class_eval do + def apply(scope) + scope = include_table_aliasing_for_rank(scope) + rank_table_alias = scope.pg_search_rank_table_alias(include_counter: true) + + scope.joins(rank_join(rank_table_alias)) + end + end +end diff --git a/db/migrate/20210506212356_add_order_to_indexed_posts.rb b/db/migrate/20210506212356_add_order_to_indexed_posts.rb new file mode 100644 index 00000000..4b1a9fcf --- /dev/null +++ b/db/migrate/20210506212356_add_order_to_indexed_posts.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +# Agrega el orden y fecha del post en el post indexado, de forma que los +# resultados se puedan obtener en el mismo orden. +class AddOrderToIndexedPosts < ActiveRecord::Migration[6.1] + def change + add_column :indexed_posts, :order, :integer, default: 0 + end +end diff --git a/db/schema.rb b/db/schema.rb index fed6934b..ed629401 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2021_05_04_224343) do +ActiveRecord::Schema.define(version: 2021_05_06_212356) do # These are extensions that must be enabled in order to support this database enable_extension "pg_trgm" @@ -229,6 +229,7 @@ ActiveRecord::Schema.define(version: 2021_05_04_224343) do t.jsonb "front_matter", default: "{}" t.string "content", default: "" t.tsvector "indexed_content" + t.integer "order", default: 0 t.index ["front_matter"], name: "index_indexed_posts_on_front_matter", using: :gin t.index ["indexed_content"], name: "index_indexed_posts_on_indexed_content", using: :gin t.index ["layout"], name: "index_indexed_posts_on_layout" From fd7ab8d7ef1190ce460eb967f922182992bdacdf Mon Sep 17 00:00:00 2001 From: f Date: Thu, 6 May 2021 19:46:36 -0300 Subject: [PATCH 042/234] =?UTF-8?q?actualizar=20el=20=C3=ADndice=20cuando?= =?UTF-8?q?=20se=20agrega=20o=20modifica=20un=20post?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit además, activa callbacks de activerecord de forma que Post se va pareciendo cada vez más a un modelo de rails :D --- app/models/post.rb | 7 +++++-- app/models/post/indexable.rb | 12 +++++++++++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/app/models/post.rb b/app/models/post.rb index 790ee1a2..7964d4ee 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -17,6 +17,7 @@ class Post attr_reader :attributes, :errors, :layout, :site, :document + include ActiveRecord::Callbacks include Post::Indexable class << self @@ -285,8 +286,10 @@ class Post end end - return false unless save_attributes! - return false unless write + run_callbacks :save do + return false unless save_attributes! + return false unless write + end # Vuelve a leer el post para tomar los cambios read diff --git a/app/models/post/indexable.rb b/app/models/post/indexable.rb index 9b92111f..b7b5a7fa 100644 --- a/app/models/post/indexable.rb +++ b/app/models/post/indexable.rb @@ -6,11 +6,14 @@ class Post extend ActiveSupport::Concern included do + # Indexa o reindexa el Post + after_save :index! + # Devuelve una versión indexable del Post # # @return [IndexedPosts] def to_index - @to_index ||= IndexedPost.find_or_create_by(id: uuid.value).tap do |indexed_post| + IndexedPost.find_or_create_by(id: uuid.value).tap do |indexed_post| indexed_post.layout = layout.name indexed_post.site_id = site.id indexed_post.path = path.basename @@ -25,6 +28,13 @@ class Post private + # Indexa o reindexa el Post + # + # @return [Boolean] + def index! + to_index.save + end + # Los metadatos que se almacenan como objetos JSON. Empezamos con # las categorías porque se usan para filtrar en el listado de # artículos. From 34a05e860d7248174ad197436cab8adf121107e7 Mon Sep 17 00:00:00 2001 From: f Date: Thu, 6 May 2021 19:47:43 -0300 Subject: [PATCH 043/234] =?UTF-8?q?elimina=20el=20espacio=20vac=C3=ADo=20d?= =?UTF-8?q?el=20contenido?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/post/indexable.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/post/indexable.rb b/app/models/post/indexable.rb index b7b5a7fa..b887deac 100644 --- a/app/models/post/indexable.rb +++ b/app/models/post/indexable.rb @@ -54,8 +54,8 @@ class Post # @return [String] def indexable_content indexable_attributes.map do |attr| - self[attr].to_s - end.join("\n") + self[attr].to_s.remove("\n") + end.join("\n").squeeze("\n") end def indexable_attributes From 83bf42c2a08fa7555320035ed7ad7c5b537a029f Mon Sep 17 00:00:00 2001 From: Maki Date: Fri, 7 May 2021 15:52:11 -0300 Subject: [PATCH 044/234] designs.yml --- db/seeds/designs.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/db/seeds/designs.yml b/db/seeds/designs.yml index 9d0ddd8a..e6cc31f5 100644 --- a/db/seeds/designs.yml +++ b/db/seeds/designs.yml @@ -6,12 +6,12 @@ disabled: true description_en: "Upload your own theme. [This feature is in development, help us!](https://sutty.nl/en/#contact)" description_es: "Subir tu propio diseño. [Esta posibilidad está en desarrollo, ¡ayudanos!](https://sutty.nl/#contacto)" -- name_en: 'I want you to create a site for me' +- name_en: 'I want you to develop a site for me' name_es: 'Quiero que desarrollen mi sitio' gem: 'sutty-theme-custom' url: 'https://sutty.nl' disabled: true - description_en: "If you want us to create your site, you're welcome to [contact us!](https://sutty.nl/en/#contact) :)" + description_en: "If you want us to develop your site, you're welcome to [contact us!](https://sutty.nl/en/#contact) :)" description_es: "Si querés que desarrollemos tu sitio, [escribinos](https://sutty.nl/#contacto) :)" - name_en: 'Minima' name_es: 'Mínima' @@ -24,7 +24,7 @@ name_es: 'Sutty' gem: 'sutty-jekyll-theme' url: 'https://rubygems.org/gems/sutty-jekyll-theme/' - description_en: "Sutty's design" + description_en: "The Sutty design" description_es: 'El diseño de Sutty' license: 'https://0xacab.org/sutty/jekyll/sutty-jekyll-theme/-/blob/master/LICENSE.txt' credits_es: 'Sutty es parte de la economía solidaria :)' @@ -42,11 +42,11 @@ name_es: 'Donaciones' gem: 'sutty-donaciones-jekyll-theme' url: 'https://donaciones.sutty.nl/' - description_en: "Make your own donations campaign with payment buttons." + description_en: "Make your own donation campaign with payment buttons." description_es: 'Realizá campañas de donaciones con botones de pago.' license: 'https://0xacab.org/sutty/jekyll/sutty-donaciones-jekyll-theme/-/blob/master/LICENSE.txt' credits_es: 'Diseñamos esta plantilla para [visibilizar campañas de donaciones](https://sutty.nl/plantilla-para-donaciones/) durante la cuarentena.' - credits_en: 'We designed this theme to increase [requests for donations visibility](https://sutty.nl/template-for-donations/) during the quarantine.' + credits_en: 'We designed this theme to increase [visibility for donation requests](https://sutty.nl/template-for-donations/) during the quarantine.' - name_en: 'Support campaign' name_es: 'Adhesiones' gem: 'adhesiones-jekyll-theme' @@ -55,29 +55,29 @@ description_es: 'Realizá campañas de adhesión.' license: 'https://0xacab.org/sutty/jekyll/adhesiones-jekyll-theme/-/blob/master/LICENSE.txt' credits_es: 'Desarrollamos esta plantilla junto con [Librenauta](https://sutty.nl/plantilla-para-campa%C3%B1as-de-adhesiones/)' - credits_en: 'We made this template with Librenauta :)' + credits_en: 'This template was made in collaboration with Librenauta' designer_url: 'https://copiona.com/donaunbit/' - name_en: 'Community Radio' name_es: 'Radio comunitaria' gem: 'radios-comunitarias-jekyll-theme' url: 'https://radio.sutty.nl/' - description_en: "A theme with streaming support, designed for community radios" + description_en: "A theme with live streaming support, designed for community radios" description_es: 'Con soporte para transmisión en vivo, pensada para radios comunitarias' license: 'https://0xacab.org/sutty/jekyll/radios-comunitarias-jekyll-theme/-/blob/master/LICENSE.txt' credits_es: 'Desarrollamos esta plantilla junto con Librenauta en 15 horas :)' - credits_en: 'We made this template with Librenauta in 15 hours :)' + credits_en: 'This template was made in collaboration with Librenauta in 15 hours!' designer_url: 'https://copiona.com/donaunbit/' - name_en: 'Resource toolkit' name_es: 'Recursero' gem: 'recursero-jekyll-theme' url: 'https://recursero.info/' disabled: true - description_en: "We're working to add more themes for you to use. [Contact us!](https://sutty.nl/en/#contact)" + description_en: "We're working towards adding more themes for you to use. [Contact us!](https://sutty.nl/en/#contact)" description_es: "Estamos trabajando para que puedas tener más diseños. [¡Escribinos!](https://sutty.nl/#contacto)" - name_en: 'Other themes' name_es: 'Mi propio diseño' gem: 'sutty-theme-own' url: 'https://jekyllthemes.org' disabled: true - description_en: "We're working to add more themes for you to use. [Contact us!](https://sutty.nl/en/#contact)" + description_en: "We're working towards adding more themes for you to use. [Contact us!](https://sutty.nl/en/#contact)" description_es: "Estamos trabajando para que puedas tener más diseños. [¡Escribinos!](https://sutty.nl/#contacto)" From e305c8c9585799cd808418be0b5831e612670f17 Mon Sep 17 00:00:00 2001 From: Maki Date: Fri, 7 May 2021 15:52:27 -0300 Subject: [PATCH 045/234] licencias.yml --- db/seeds/licencias.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/db/seeds/licencias.yml b/db/seeds/licencias.yml index fb5c8f0a..cbe3bace 100644 --- a/db/seeds/licencias.yml +++ b/db/seeds/licencias.yml @@ -104,7 +104,7 @@ description_en: "This license gives everyone the freedom to use, adapt, and redistribute the contents of your site by requiring attribution only. We recommend this license if you're publishing - articles that require maximum diffusion, even in commercial media, but + articles that require maximum dissemination, even in commercial media, but you want to be attributed. Users of the site will have to mention the source and indicate if they made changes to it." url_en: 'https://creativecommons.org/licenses/by/4.0/' @@ -198,13 +198,13 @@ url_es: 'https://creativecommons.org/licenses/by-sa/4.0/deed.es' description_en: "This license is the same as the CC-BY 4.0 but it adds a requirement of sharing the work and its derivatives under the same - license. This is a reciprocitary, _copyleft_, license that keeps + license. This is a reciprocal, _copyleft_, license that keeps culture free. Though commercial uses are allowed, they must be shared under the same license, so any modifications done for profit are free as well." description_es: "Esta licencia es igual que la CC-BY 4.0 con el requisito agregado de compartir la obra y sus obras derivadas con la - misma licencia. Esta es una licencia reciprocitaria, _copyleft_, que + misma licencia. Esta es una licencia recíproca, _copyleft_, que mantiene y profundiza la cultura libre. Aunque los usos comerciales están permitidos, las mejoras hechas con fines de lucro deben ser compartidas bajo la misma licencia." From 47aaf8a5fa7292c98ee0ad40548ede6f6a8a05cc Mon Sep 17 00:00:00 2001 From: Maki Date: Fri, 7 May 2021 15:57:19 -0300 Subject: [PATCH 046/234] designs.yml ajuste --- db/seeds/designs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/seeds/designs.yml b/db/seeds/designs.yml index e6cc31f5..126a9b12 100644 --- a/db/seeds/designs.yml +++ b/db/seeds/designs.yml @@ -28,7 +28,7 @@ description_es: 'El diseño de Sutty' license: 'https://0xacab.org/sutty/jekyll/sutty-jekyll-theme/-/blob/master/LICENSE.txt' credits_es: 'Sutty es parte de la economía solidaria :)' - credits_en: 'Sutty is a solidarity economy project :)' + credits_en: 'Sutty is a solidarity economy project!' - name_en: 'Self-managed Book Publisher' name_es: 'Editorial Autogestiva' gem: 'editorial-autogestiva-jekyll-theme' From ad871baca63bf0b1e5454575473fcdfa7d04e09d Mon Sep 17 00:00:00 2001 From: f Date: Fri, 7 May 2021 16:17:25 -0300 Subject: [PATCH 047/234] implementar el buscador en el panel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ahora el índice de artículos incorporar buscador de texto libre. además todos los filtros de búsqueda se mantienen entre búsquedas, entonces al filtrar por tipo de artículo y término, se aplican ambos y al cambiar el tipo se mantiene la búsqueda de texto. --- app/assets/stylesheets/application.scss | 4 ++ app/controllers/posts_controller.rb | 38 ++++++----- app/models/indexed_post.rb | 2 + app/models/post/indexable.rb | 13 +++- app/policies/post_policy.rb | 4 +- app/views/posts/index.haml | 87 ++++++++++++------------- config/locales/en.yml | 1 + config/locales/es.yml | 1 + 8 files changed, 82 insertions(+), 68 deletions(-) diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index 0a215c48..1f9b634e 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -21,6 +21,10 @@ $form-feedback-invalid-color: $magenta; $form-feedback-icon-valid-color: $black; $component-active-bg: $magenta; +$spacers: ( + 2-plus: 0.75rem +); + @import "bootstrap"; @import "editor"; diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index a4b47a16..524335a5 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -16,29 +16,25 @@ class PostsController < ApplicationController authorize Post @site = find_site - @category = params.dig(:category) - @layout = params.dig(:layout) - @locale = locale + @q = params[:q] + dictionary = IndexedPost.to_dictionary(locale: locale) # XXX: Cada vez que cambiamos un Post tocamos el sitio con lo que es # más simple saber si hubo cambios. - if @category || @layout || stale?(@site) - @posts = @site.posts(lang: locale) - @posts = @posts.where(categories: @category) if @category - @posts = @posts.where(layout: @layout) if @layout + if filter_params.present? || stale?(@site) + # Todos los artículos de este sitio para el idioma actual + @posts = @site.indexed_posts.where(locale: dictionary) + # De este tipo + @posts = @posts.where(layout: filter_params[:layout]) if filter_params[:layout] + # Que estén dentro de la categoría + @posts = @posts.in_category(filter_params[:category]) if filter_params[:category] + # Aplicar los parámetros de búsqueda + @posts = @posts.search(locale, filter_params[:q]) if filter_params[:q].present? + # A los que este usuarie tiene acceso @posts = PostPolicy::Scope.new(current_usuarie, @posts).resolve - @category_name = if uuid?(@category) - @site.posts(lang: locale).find(@category, uuid: true)&.title&.value - else - @category - end - # Filtrar los posts que les invitades no pueden ver @usuarie = @site.usuarie? current_usuarie - - # Orden descendiente por número y luego por fecha - @posts.sort_by!(:order, :date).reverse! end end @@ -169,4 +165,14 @@ class PostsController < ApplicationController def forget_content flash[:js] = { target: 'editor', action: 'forget-content', keys: (params[:storage_keys] || []).to_json } end + + private + + # Los parámetros de filtros que vamos a mantener en todas las URLs, + # solo los que no estén vacíos. + # + # @return [Hash] + def filter_params + @filter_params ||= params.permit(:q, :category, :layout).to_h.select { |_,v| v.present? } + end end diff --git a/app/models/indexed_post.rb b/app/models/indexed_post.rb index c76a5c85..7bf1ec7a 100644 --- a/app/models/indexed_post.rb +++ b/app/models/indexed_post.rb @@ -28,6 +28,8 @@ class IndexedPost < ApplicationRecord # Trae los IndexedPost en el orden en que van a terminar en el sitio. default_scope lambda { order(order: :desc, created_at: :desc) } + scope :in_category, lambda { |category| where("front_matter->'categories' ? :category", category: category.to_s) } + scope :by_usuarie, lambda { |usuarie| where("front_matter->'usuaries' @> :usuarie::jsonb", usuarie: usuarie.to_s) } belongs_to :site diff --git a/app/models/post/indexable.rb b/app/models/post/indexable.rb index b887deac..bee02e54 100644 --- a/app/models/post/indexable.rb +++ b/app/models/post/indexable.rb @@ -11,7 +11,7 @@ class Post # Devuelve una versión indexable del Post # - # @return [IndexedPosts] + # @return [IndexedPost] def to_index IndexedPost.find_or_create_by(id: uuid.value).tap do |indexed_post| indexed_post.layout = layout.name @@ -41,9 +41,16 @@ class Post # # @return [Hash] def indexable_front_matter - return {} unless attribute? :categories + {}.tap do |indexable_front_matter| + indexable_front_matter = { + usuaries: usuaries.map(&:id), + draft: attribute?(:draft) ? draft.value : false + } - { categories: categories.indexable_values } + if attribute? :categories + indexable_front_matter[:categories] = categories.indexable_values + end + end end # Devuelve un documento indexable en texto plano diff --git a/app/policies/post_policy.rb b/app/policies/post_policy.rb index c22202af..69ecb188 100644 --- a/app/policies/post_policy.rb +++ b/app/policies/post_policy.rb @@ -59,9 +59,7 @@ class PostPolicy def resolve return scope if scope&.first&.site&.usuarie? usuarie - scope.select do |post| - post.usuaries.include? usuarie - end + scope.by_usuarie(usuarie.id) end end end diff --git a/app/views/posts/index.haml b/app/views/posts/index.haml index b2f3f661..9c43e431 100644 --- a/app/views/posts/index.haml +++ b/app/views/posts/index.haml @@ -3,7 +3,7 @@ @site.name, link_to(t('posts.index'), site_posts_path(@site)), - @category_name] + @category] %main.row %aside.menu.col-md-3 @@ -14,15 +14,13 @@ %table.mb-3 - @site.layouts.each do |layout| - next if layout.hidden? - - filter = params[:layout] == layout.value %tr %th= layout.humanized_name - %td.pl-3= link_to t('posts.add'), - new_site_post_path(@site, layout: layout.name), - class: 'badge badge-secondary' - %td= link_to t(filter ? 'posts.remove_filter' : 'posts.filter'), - site_posts_path(@site, layout: (filter ? nil : layout.value)), - class: 'badge badge-' + (filter ? 'primary' : 'secondary') + %td.pl-3= link_to t('posts.add'), new_site_post_path(@site, **@filter_params), class: 'badge badge-secondary' + - if @filter_params[:layout] == layout.value + %td= link_to t('posts.remove_filter'), site_posts_path(@site, **@filter_params.merge(layout: nil)), class: 'badge badge-primary' + - else + %td= link_to t('posts.filter'), site_posts_path(@site, **@filter_params.merge(layout: layout.value)), class: 'badge badge-secondary' - if policy(@site).edit? = link_to t('sites.edit.btn', site: @site.title), edit_site_path(@site), class: 'btn' @@ -48,19 +46,24 @@ %section.col = render 'layouts/flash' + .d-flex.justify-content-between.align-items-center.pl-2-plus.pr-2-plus.mb-2 + %form + - @filter_params.each do |param, value| + - next if param == 'q' + %input{ type: 'input', name: param, value: value } + .form-group.flex-grow-0.m-0 + %input.form-control.border.border-magenta{ type: 'search', placeholder: 'Buscar', name: 'q', value: @q } + %input.sr-only{ type: 'submit' } + - if @site.locales.size > 1 + + %nav#locales + - @site.locales.each do |locale| + = link_to t("locales.#{locale}.name"), site_posts_path(@site, **@filter_params.merge(locale: locale)), + class: "mr-2 mt-2 mb-2 #{locale == @locale ? 'active font-weight-bold' : ''}" - if @posts.empty? - %h2= t('posts.none') + %h2= t('posts.empty') - else = form_tag site_posts_reorder_path, method: :post do - .d-flex.justify-content-between.align-items-center - -# - TODO: Pensar una interfaz mejor para cuando haya más de tres - idiomas - - unless @site.locales.length == 1 - .locales - - @site.locales.each do |locale| - = link_to t("locales.#{locale}.name"), site_posts_path(@site, locale: locale), - class: "mr-2 mt-2 mb-2#{locale == @locale ? 'active font-weight-bold' : ''}" %table.table{ data: { controller: 'reorder' } } %caption.sr-only= t('posts.caption') %thead @@ -76,6 +79,7 @@ %button.btn{ data: { action: 'reorder#bottom' } }= t('posts.reorder.bottom') %tbody - dir = t("locales.#{@locale}.dir") + - size = @posts.size - @posts.each_with_index do |post, i| -# TODO: Solo les usuaries cachean porque tenemos que separar @@ -84,45 +88,36 @@ TODO: Verificar qué pasa cuando se gestiona el sitio en distintos idiomas a la vez - cache_if @usuarie, post do - - checkbox_id = "checkbox-#{post.uuid.value}" - %tr{ id: post.uuid.value, data: { target: 'reorder.row' } } + - checkbox_id = "checkbox-#{post.id}" + %tr{ id: post.id, data: { target: 'reorder.row' } } %td .custom-control.custom-checkbox %input.custom-control-input{ id: checkbox_id, type: 'checkbox', autocomplete: 'off', data: { action: 'reorder#select' } } %label.custom-control-label{ for: checkbox_id } %span.sr-only= t('posts.reorder.select') -# Orden más alto es mayor prioridad - = hidden_field 'post[reorder]', post.uuid.value, - value: @posts.length - i, + = hidden_field 'post[reorder]', post.id, + value: size - i, data: { reorder: true } %td.w-100{ class: dir } - = link_to site_post_path(@site, post.id) do - %span{ lang: post.lang.value, dir: dir }= post.title.value - - if post.attributes.include? :draft - - if post.draft.value - %span.badge.badge-primary - = post_label_t(:draft, post: post) - - if post.attributes.include? :categories - - unless post.categories.value.empty? - %br - %small - - (post.categories.respond_to?(:belongs_to) ? post.categories.belongs_to : post.categories.value).each do |c| - = link_to site_posts_path(@site, category: (c.respond_to?(:uuid) ? c.uuid.value : c)) do - %span{ lang: post.lang.value, dir: dir }= (c.respond_to?(:title) ? c.title.value : c) + = link_to site_post_path(@site, post.path) do + %span{ lang: post.locale, dir: dir }= post.title + - if post.front_matter['draft'].present? + %span.badge.badge-primary + = post_label_t(:draft, post: post) + - if post.front_matter['categories'].present? + %br + %small + - post.front_matter['categories'].each do |category| + = link_to site_posts_path(@site, **@filter_params.merge(category: category)) do + %span{ lang: post.locale, dir: dir }= category %td - = post.date.value.strftime('%F') + = post.created_at.strftime('%F') %br/ - - if post.attribute? :order - = post.order.value + = post.order %td - if @usuarie || policy(post).edit? - = link_to t('posts.edit'), - edit_site_post_path(@site, post.id), - class: 'btn btn-block' + = link_to t('posts.edit'), edit_site_post_path(@site, post.path), class: 'btn btn-block' - if @usuarie || policy(post).destroy? - = link_to t('posts.destroy'), - site_post_path(@site, post.id), - class: 'btn btn-block', - method: :delete, - data: { confirm: t('posts.confirm_destroy') } + = link_to t('posts.destroy'), site_post_path(@site, post.path), class: 'btn btn-block', method: :delete, data: { confirm: t('posts.confirm_destroy') } diff --git a/config/locales/en.yml b/config/locales/en.yml index dd9f8309..54b6115c 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -397,6 +397,7 @@ en: en: 'English' ar: 'Arabic' posts: + empty: "There are no results for those search parameters." attribute_ro: file: download: Download file diff --git a/config/locales/es.yml b/config/locales/es.yml index 403389ab..b98ae149 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -459,6 +459,7 @@ es: en: 'inglés' ar: 'árabe' posts: + empty: No hay artículos con estos parámetros de búsqueda. caption: Lista de artículos attribute_ro: file: From 4f2e602822bce8db394265c75b5e51abb195db11 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 7 May 2021 16:21:44 -0300 Subject: [PATCH 048/234] =?UTF-8?q?tambi=C3=A9n=20buscar=20por=20palabras?= =?UTF-8?q?=20similares?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/indexed_post.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/models/indexed_post.rb b/app/models/indexed_post.rb index 7bf1ec7a..ede5b539 100644 --- a/app/models/indexed_post.rb +++ b/app/models/indexed_post.rb @@ -21,6 +21,9 @@ class IndexedPost < ApplicationRecord tsearch: { dictionary: IndexedPost.to_dictionary(locale: locale), tsvector_column: 'indexed_content' + }, + trigram: { + word_similarity: true } } } From 2d3f5b21aef17ddef38c8fa53b8c850936a93a24 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 7 May 2021 16:21:59 -0300 Subject: [PATCH 049/234] =?UTF-8?q?al=20eliminar=20los=20saltos=20de=20l?= =?UTF-8?q?=C3=ADnea=20se=20quedaban=20pegadas=20algunas=20palabras?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/post/indexable.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/post/indexable.rb b/app/models/post/indexable.rb index bee02e54..8a12e40c 100644 --- a/app/models/post/indexable.rb +++ b/app/models/post/indexable.rb @@ -61,7 +61,7 @@ class Post # @return [String] def indexable_content indexable_attributes.map do |attr| - self[attr].to_s.remove("\n") + self[attr].to_s.tr("\n", ' ') end.join("\n").squeeze("\n") end From 6df7f09c26789ea98aa2bb83bd38b69c34348321 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 7 May 2021 17:11:08 -0300 Subject: [PATCH 050/234] =?UTF-8?q?darle=20estilo=20de=20bot=C3=B3n=20a=20?= =?UTF-8?q?los=20filtros?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit como eran badges con links no tenían sombra en el foco ni color al pasarles por encima. --- app/views/posts/index.haml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/views/posts/index.haml b/app/views/posts/index.haml index 9c43e431..a9f868e9 100644 --- a/app/views/posts/index.haml +++ b/app/views/posts/index.haml @@ -16,11 +16,11 @@ - next if layout.hidden? %tr %th= layout.humanized_name - %td.pl-3= link_to t('posts.add'), new_site_post_path(@site, **@filter_params), class: 'badge badge-secondary' + %td.pl-3= link_to t('posts.add'), new_site_post_path(@site, **@filter_params), class: 'btn btn-secondary badge' - if @filter_params[:layout] == layout.value - %td= link_to t('posts.remove_filter'), site_posts_path(@site, **@filter_params.merge(layout: nil)), class: 'badge badge-primary' + %td= link_to t('posts.remove_filter'), site_posts_path(@site, **@filter_params.merge(layout: nil)), class: 'btn btn-primary badge' - else - %td= link_to t('posts.filter'), site_posts_path(@site, **@filter_params.merge(layout: layout.value)), class: 'badge badge-secondary' + %td= link_to t('posts.filter'), site_posts_path(@site, **@filter_params.merge(layout: layout.value)), class: 'btn btn-secondary badge' - if policy(@site).edit? = link_to t('sites.edit.btn', site: @site.title), edit_site_path(@site), class: 'btn' From 418ffb846376852f11bf8d21de67c21a271c3086 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 7 May 2021 17:31:13 -0300 Subject: [PATCH 051/234] =?UTF-8?q?cambiar=20btn-sm=20para=20que=20tenga?= =?UTF-8?q?=20el=20tama=C3=B1o=20de=20badge?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/assets/stylesheets/application.scss | 4 ++++ app/views/posts/index.haml | 6 +++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index 1f9b634e..e059ecd1 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -214,6 +214,10 @@ svg { } } +.btn-sm { + @extend .badge +} + .black-bg { color: $white; background-color: $black; diff --git a/app/views/posts/index.haml b/app/views/posts/index.haml index a9f868e9..11e6efcb 100644 --- a/app/views/posts/index.haml +++ b/app/views/posts/index.haml @@ -16,11 +16,11 @@ - next if layout.hidden? %tr %th= layout.humanized_name - %td.pl-3= link_to t('posts.add'), new_site_post_path(@site, **@filter_params), class: 'btn btn-secondary badge' + %td.pl-3= link_to t('posts.add'), new_site_post_path(@site, **@filter_params), class: 'btn btn-secondary btn-sm' - if @filter_params[:layout] == layout.value - %td= link_to t('posts.remove_filter'), site_posts_path(@site, **@filter_params.merge(layout: nil)), class: 'btn btn-primary badge' + %td= link_to t('posts.remove_filter'), site_posts_path(@site, **@filter_params.merge(layout: nil)), class: 'btn btn-primary btn-sm' - else - %td= link_to t('posts.filter'), site_posts_path(@site, **@filter_params.merge(layout: layout.value)), class: 'btn btn-secondary badge' + %td= link_to t('posts.filter'), site_posts_path(@site, **@filter_params.merge(layout: layout.value)), class: 'btn btn-secondary btn-sm' - if policy(@site).edit? = link_to t('sites.edit.btn', site: @site.title), edit_site_path(@site), class: 'btn' From 051e40f64bc876a9bef335743451b3d489da5c3b Mon Sep 17 00:00:00 2001 From: f Date: Fri, 7 May 2021 17:31:50 -0300 Subject: [PATCH 052/234] el formulario de busqueda tiene que incorporar los filtros MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit para poder volverlos a enviar. me había olvidado de convertirlos en campos ocultos. --- app/views/posts/index.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/posts/index.haml b/app/views/posts/index.haml index 11e6efcb..8fcb86b5 100644 --- a/app/views/posts/index.haml +++ b/app/views/posts/index.haml @@ -50,12 +50,12 @@ %form - @filter_params.each do |param, value| - next if param == 'q' - %input{ type: 'input', name: param, value: value } + %input{ type: 'hidden', name: param, value: value } .form-group.flex-grow-0.m-0 %input.form-control.border.border-magenta{ type: 'search', placeholder: 'Buscar', name: 'q', value: @q } %input.sr-only{ type: 'submit' } - - if @site.locales.size > 1 + - if @site.locales.size > 1 %nav#locales - @site.locales.each do |locale| = link_to t("locales.#{locale}.name"), site_posts_path(@site, **@filter_params.merge(locale: locale)), From 754e8fbbcc19b9df0793694f1415f137a8a74c34 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 7 May 2021 17:32:54 -0300 Subject: [PATCH 053/234] =?UTF-8?q?mostrar=20cu=C3=A1les=20son=20los=20fil?= =?UTF-8?q?tros=20actuales=20y=20poder=20removerlos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit además, crear una sección de notas al pie con definiciones disponibles para lectores de pantalla. (no quiere decir que sean completas, `title` solo funciona con usuaries de mouse) --- app/views/posts/index.haml | 16 ++++++++++++++++ config/locales/en.yml | 1 + config/locales/es.yml | 1 + 3 files changed, 18 insertions(+) diff --git a/app/views/posts/index.haml b/app/views/posts/index.haml index 8fcb86b5..717460c3 100644 --- a/app/views/posts/index.haml +++ b/app/views/posts/index.haml @@ -60,6 +60,16 @@ - @site.locales.each do |locale| = link_to t("locales.#{locale}.name"), site_posts_path(@site, **@filter_params.merge(locale: locale)), class: "mr-2 mt-2 mb-2 #{locale == @locale ? 'active font-weight-bold' : ''}" + .pl-2-plus + - @filter_params.each do |param, value| + - if param == 'layout' + - value = @site.layouts[value.to_sym].humanized_name + = link_to site_posts_path(@site, **@filter_params.reject { |k, _| k == param }), + class: 'btn btn-secondary btn-sm', + title: t('posts.remove_filter_help', filter: value), + aria: { labelledby: "help-filter-#{param}" } do + = value + × - if @posts.empty? %h2= t('posts.empty') - else @@ -121,3 +131,9 @@ = link_to t('posts.edit'), edit_site_post_path(@site, post.path), class: 'btn btn-block' - if @usuarie || policy(post).destroy? = link_to t('posts.destroy'), site_post_path(@site, post.path), class: 'btn btn-block', method: :delete, data: { confirm: t('posts.confirm_destroy') } + +#footnotes{ hidden: true } + - @filter_params.each do |param, value| + - if param == 'layout' + - value = @site.layouts[value.to_sym].humanized_name + %label{ id: "help-filter-#{param}" }= t('posts.remove_filter_help', filter: value) diff --git a/config/locales/en.yml b/config/locales/en.yml index 54b6115c..da1028f5 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -451,6 +451,7 @@ en: add: 'Add' filter: 'Filter' remove_filter: 'Back' + remove_filter_help: 'Remove the filter: %{filter}' categories: 'Everything' index: 'Posts' edit: 'Edit' diff --git a/config/locales/es.yml b/config/locales/es.yml index b98ae149..a9e8cacb 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -515,6 +515,7 @@ es: add: 'Agregar' filter: 'Filtrar' remove_filter: 'Volver' + remove_filter_help: 'Quitar este filtro: %{filter}' index: 'Artículos' edit: 'Editar' preview: From e1055cc91240653ba37cb11dfaa4c767bab43510 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 7 May 2021 17:45:07 -0300 Subject: [PATCH 054/234] necesitamos el locale para poder marcar el idioma actual --- app/controllers/posts_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index 524335a5..4acf7656 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -16,7 +16,7 @@ class PostsController < ApplicationController authorize Post @site = find_site - @q = params[:q] + @locale = locale dictionary = IndexedPost.to_dictionary(locale: locale) # XXX: Cada vez que cambiamos un Post tocamos el sitio con lo que es From f9a2d12803a132fc0536df2f4726803dfd425544 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 7 May 2021 17:46:49 -0300 Subject: [PATCH 055/234] mostrar el texto buscado en el buscador para poder editarlo si hace falta --- app/views/posts/index.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/posts/index.haml b/app/views/posts/index.haml index 717460c3..23772237 100644 --- a/app/views/posts/index.haml +++ b/app/views/posts/index.haml @@ -52,7 +52,7 @@ - next if param == 'q' %input{ type: 'hidden', name: param, value: value } .form-group.flex-grow-0.m-0 - %input.form-control.border.border-magenta{ type: 'search', placeholder: 'Buscar', name: 'q', value: @q } + %input.form-control.border.border-magenta{ type: 'search', placeholder: 'Buscar', name: 'q', value: @filter_params[:q] } %input.sr-only{ type: 'submit' } - if @site.locales.size > 1 From 13b6b7a452a3f6ffd2bda523c7b1e905b180f2d7 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 7 May 2021 17:54:45 -0300 Subject: [PATCH 056/234] =?UTF-8?q?cambiar=20la=20leyenda=20del=20bot?= =?UTF-8?q?=C3=B3n=20en=20el=20filtro?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit no cambia el resaltado porque la definición de la clase .btn hace conflicto, para arreglar eso hay que verificar todo el panel. --- app/views/posts/index.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/posts/index.haml b/app/views/posts/index.haml index 23772237..26a0f3ee 100644 --- a/app/views/posts/index.haml +++ b/app/views/posts/index.haml @@ -17,7 +17,7 @@ %tr %th= layout.humanized_name %td.pl-3= link_to t('posts.add'), new_site_post_path(@site, **@filter_params), class: 'btn btn-secondary btn-sm' - - if @filter_params[:layout] == layout.value + - if @filter_params[:layout] == layout.name.to_s %td= link_to t('posts.remove_filter'), site_posts_path(@site, **@filter_params.merge(layout: nil)), class: 'btn btn-primary btn-sm' - else %td= link_to t('posts.filter'), site_posts_path(@site, **@filter_params.merge(layout: layout.value)), class: 'btn btn-secondary btn-sm' From d2ae406023af3d54c0201ff73db31af79fd363ab Mon Sep 17 00:00:00 2001 From: f Date: Fri, 7 May 2021 18:06:49 -0300 Subject: [PATCH 057/234] =?UTF-8?q?eliminar=20del=20=C3=ADndice=20al=20eli?= =?UTF-8?q?minar=20el=20art=C3=ADculo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/post.rb | 15 ++++++++++++--- app/models/post/indexable.rb | 5 +++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/app/models/post.rb b/app/models/post.rb index 7964d4ee..a64bd551 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -17,6 +17,9 @@ class Post attr_reader :attributes, :errors, :layout, :site, :document + # TODO: Modificar el historial de Git con callbacks en lugar de + # services. De esta forma podríamos agregar soporte para distintos + # backends. include ActiveRecord::Callbacks include Post::Indexable @@ -258,11 +261,17 @@ class Post end # Eliminar el artículo del repositorio y de la lista de artículos del - # sitio + # sitio. + # + # TODO: Si el callback falla deberíamos recuperar el archivo. + # + # @return [Post] def destroy - FileUtils.rm_f path.absolute + run_callbacks :destroy do + FileUtils.rm_f path.absolute - site.delete_post self + site.delete_post self + end end alias destroy! destroy diff --git a/app/models/post/indexable.rb b/app/models/post/indexable.rb index 8a12e40c..0baa8012 100644 --- a/app/models/post/indexable.rb +++ b/app/models/post/indexable.rb @@ -8,6 +8,7 @@ class Post included do # Indexa o reindexa el Post after_save :index! + after_destroy :remove_from_index! # Devuelve una versión indexable del Post # @@ -35,6 +36,10 @@ class Post to_index.save end + def remove_from_index! + to_index.destroy.destroyed? + end + # Los metadatos que se almacenan como objetos JSON. Empezamos con # las categorías porque se usan para filtrar en el listado de # artículos. From edff238a36ddb75e4ab925db66c9a1163cb9f4ff Mon Sep 17 00:00:00 2001 From: f Date: Fri, 7 May 2021 19:02:15 -0300 Subject: [PATCH 058/234] optimizar la lectura de datos desde jekyll MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit durante el proceso de compilación de jekyll se cargan todos los datos en memoria, buscando e interpretando todos los archivos del sitio. en el caso de sutty, solo queremos leer alguna información por vez. trabajando en el buscador me dí cuenta que aunque el panel cargue los posts desde la base de datos, sutty seguía leyendo la información completa del sitio, porque respetaba el proceso de lectura de jekyll. con este cambio podemos leer los _data/ por separado de los _posts/ con lo que la carga del sitio es mucho más rápida. --- app/models/site.rb | 36 ++++++++---------------- config/initializers/core_extensions.rb | 38 ++++++++++++++++++++++++-- 2 files changed, 47 insertions(+), 27 deletions(-) diff --git a/app/models/site.rb b/app/models/site.rb index 7a8b4b4b..901d70c6 100644 --- a/app/models/site.rb +++ b/app/models/site.rb @@ -175,30 +175,17 @@ class Site < ApplicationRecord end alias default_lang default_locale - def read? - @read ||= false - end - - # Lee el sitio y todos los artículos - def read - # No hacer nada si ya se leyó antes - return if read? - - @jekyll.read - @read = true - end - # Trae los datos del directorio _data dentro del sitio - # - # XXX: Leer directamente sin pasar por Jekyll def data - read + unless @jekyll.data.present? + @jekyll.reader.read_data - # Define los valores por defecto según la llave buscada - @jekyll.data.default_proc = proc do |data, key| - data[key] = case key - when 'layout' then {} - end + # Define los valores por defecto según la llave buscada + @jekyll.data.default_proc = proc do |data, key| + data[key] = case key + when 'layout' then {} + end + end end @jekyll.data @@ -207,7 +194,10 @@ class Site < ApplicationRecord # Traer las colecciones. Todos los artículos van a estar dentro de # colecciones. def collections - read + unless @read + @jekyll.reader.read_collections + @read = true + end @jekyll.collections end @@ -221,8 +211,6 @@ class Site < ApplicationRecord # # @param lang: [String|Symbol] traer los artículos de este idioma def posts(lang: nil) - read - # Traemos los posts del idioma actual por defecto o el que haya lang ||= locales.include?(I18n.locale) ? I18n.locale : default_locale lang = lang.to_sym diff --git a/config/initializers/core_extensions.rb b/config/initializers/core_extensions.rb index e37b2be4..8bbcbe71 100644 --- a/config/initializers/core_extensions.rb +++ b/config/initializers/core_extensions.rb @@ -32,31 +32,52 @@ module ActionDispatch end # Lazy Loading de Jekyll, deshabilitando la instanciación de elementos -# que no necesitamos +# que no necesitamos. Esto permite que podamos leer el sitio por partes +# en lugar de todo junto. # # TODO: Aplicar monkey patches en otro lado... module Jekyll Reader.class_eval do + # No necesitamos otros posts def retrieve_posts(_); end + # No necesitamos otros directorios def retrieve_dirs(_, _, _); end + # No necesitamos las páginas def retrieve_pages(_, _); end + # No necesitamos los archivos estáticos def retrieve_static_files(_, _); end + + # Solo lee los datos + def read_data + @site.data = DataReader.new(site).read(site.config["data_dir"]) + end + + # Lee todos los artículos del sitio + def read_collections + read_directories + read_included_excludes + sort_files! + CollectionReader.new(site).read + end end + # No necesitamos los archivos de la plantilla ThemeAssetsReader.class_eval do def read; end end - # Prevenir la lectura del documento + # Aplazar la lectura del documento Document.class_eval do alias_method :read!, :read def read; end end - # https://github.com/jekyll/jekyll/pull/8425 + # Prevenir la instanciación de Time + # + # @see {https://github.com/jekyll/jekyll/pull/8425} Utils.class_eval do def parse_date(input, msg = 'Input could not be parsed.') @parse_date_cache ||= {} @@ -66,3 +87,14 @@ module Jekyll end end end + +# JekyllData::Reader del plugin jekyll-data modifica Jekyll::Site#reader +# para también leer los datos que vienen en el theme. +module JekyllData + Reader.class_eval do + def read_data + super + read_theme_data + end + end +end From 6396841b2c4401d37a345dc351a73c2c3abe4582 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 7 May 2021 19:25:09 -0300 Subject: [PATCH 059/234] evitar confusiones entre diccionario y locale el diccionario es lo que usa internamente pg para indexar, el locale es el idioma que asignamos en sutty. --- app/controllers/posts_controller.rb | 3 +- app/models/indexed_post.rb | 2 +- app/models/post/indexable.rb | 3 +- ...7221120_add_dictionary_to_indexed_posts.rb | 29 +++++++++++++++++++ db/schema.rb | 27 +++++++---------- 5 files changed, 43 insertions(+), 21 deletions(-) create mode 100644 db/migrate/20210507221120_add_dictionary_to_indexed_posts.rb diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index 4acf7656..f865bd50 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -17,13 +17,12 @@ class PostsController < ApplicationController @site = find_site @locale = locale - dictionary = IndexedPost.to_dictionary(locale: locale) # XXX: Cada vez que cambiamos un Post tocamos el sitio con lo que es # más simple saber si hubo cambios. if filter_params.present? || stale?(@site) # Todos los artículos de este sitio para el idioma actual - @posts = @site.indexed_posts.where(locale: dictionary) + @posts = @site.indexed_posts.where(locale: locale) # De este tipo @posts = @posts.where(layout: filter_params[:layout]) if filter_params[:layout] # Que estén dentro de la categoría diff --git a/app/models/indexed_post.rb b/app/models/indexed_post.rb index ede5b539..16858a66 100644 --- a/app/models/indexed_post.rb +++ b/app/models/indexed_post.rb @@ -19,7 +19,7 @@ class IndexedPost < ApplicationRecord query: query, using: { tsearch: { - dictionary: IndexedPost.to_dictionary(locale: locale), + dictionary: dictionary, tsvector_column: 'indexed_content' }, trigram: { diff --git a/app/models/post/indexable.rb b/app/models/post/indexable.rb index 0baa8012..b1d62504 100644 --- a/app/models/post/indexable.rb +++ b/app/models/post/indexable.rb @@ -18,7 +18,8 @@ class Post indexed_post.layout = layout.name indexed_post.site_id = site.id indexed_post.path = path.basename - indexed_post.locale = IndexedPost.to_dictionary(locale: locale.value) + indexed_post.locale = locale.value + indexed_post.dictionary = IndexedPost.to_dictionary(locale: locale.value) indexed_post.title = title.value indexed_post.front_matter = indexable_front_matter indexed_post.content = indexable_content diff --git a/db/migrate/20210507221120_add_dictionary_to_indexed_posts.rb b/db/migrate/20210507221120_add_dictionary_to_indexed_posts.rb new file mode 100644 index 00000000..f79309fd --- /dev/null +++ b/db/migrate/20210507221120_add_dictionary_to_indexed_posts.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +# Para no estar calculando todo el tiempo el diccionario del idioma, +# agregamos una columna más. +class AddDictionaryToIndexedPosts < ActiveRecord::Migration[6.1] + LOCALES = { + 'english' => 'en', + 'spanish' => 'es' + } + + def up + add_column :indexed_posts, :dictionary, :string + + create_trigger(compatibility: 1).on(:indexed_posts).before(:insert, :update) do + "new.indexed_content := to_tsvector(('pg_catalog.' || new.dictionary)::regconfig, coalesce(new.title, '') || '\n' || coalesce(new.content,''));" + end + + IndexedPost.find_each do |post| + locale = post.locale + + post.update dictionary: locale, locale: LOCALES[locale] + end + end + + def down + remove_column :indexed_posts, :locale + rename_column :indexed_posts, :dictionary, :locale + end +end diff --git a/db/schema.rb b/db/schema.rb index ed629401..0a8dd080 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2021_05_06_212356) do +ActiveRecord::Schema.define(version: 2021_05_07_221120) do # These are extensions that must be enabled in order to support this database enable_extension "pg_trgm" @@ -230,6 +230,7 @@ ActiveRecord::Schema.define(version: 2021_05_06_212356) do t.string "content", default: "" t.tsvector "indexed_content" t.integer "order", default: 0 + t.string "dictionary" t.index ["front_matter"], name: "index_indexed_posts_on_front_matter", using: :gin t.index ["indexed_content"], name: "index_indexed_posts_on_indexed_content", using: :gin t.index ["layout"], name: "index_indexed_posts_on_layout" @@ -360,21 +361,13 @@ ActiveRecord::Schema.define(version: 2021_05_06_212356) do add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id" add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id" - # no candidate create_trigger statement could be found, creating an adapter-specific one - execute(<<-SQL) -CREATE OR REPLACE FUNCTION public.indexed_posts_before_insert_update_row_tr() - RETURNS trigger - LANGUAGE plpgsql -AS $function$ -BEGIN - new.indexed_content := to_tsvector(('pg_catalog.' || new.locale)::regconfig, coalesce(new.title, '') || ' - ' || coalesce(new.content,'')); - RETURN NEW; -END; -$function$ - SQL - - # no candidate create_trigger statement could be found, creating an adapter-specific one - execute("CREATE TRIGGER indexed_posts_before_insert_update_row_tr BEFORE INSERT OR UPDATE ON \"indexed_posts\" FOR EACH ROW EXECUTE PROCEDURE indexed_posts_before_insert_update_row_tr()") + create_trigger("indexed_posts_before_insert_update_row_tr", :compatibility => 1). + on("indexed_posts"). + before(:insert, :update) do + <<-SQL_ACTIONS +new.indexed_content := to_tsvector(('pg_catalog.' || new.dictionary)::regconfig, coalesce(new.title, '') || ' +' || coalesce(new.content,'')); + SQL_ACTIONS + end end From 478730e8822bdce353e8131aaa34691fa88a1876 Mon Sep 17 00:00:00 2001 From: Maki Date: Sat, 8 May 2021 17:37:57 +0000 Subject: [PATCH 060/234] =?UTF-8?q?Ajustes=20traducci=C3=B3n=20de=20archiv?= =?UTF-8?q?os=20de=20db/seeds?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- db/seeds/designs.yml | 22 +++++++++++----------- db/seeds/licencias.yml | 6 +++--- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/db/seeds/designs.yml b/db/seeds/designs.yml index 9d0ddd8a..126a9b12 100644 --- a/db/seeds/designs.yml +++ b/db/seeds/designs.yml @@ -6,12 +6,12 @@ disabled: true description_en: "Upload your own theme. [This feature is in development, help us!](https://sutty.nl/en/#contact)" description_es: "Subir tu propio diseño. [Esta posibilidad está en desarrollo, ¡ayudanos!](https://sutty.nl/#contacto)" -- name_en: 'I want you to create a site for me' +- name_en: 'I want you to develop a site for me' name_es: 'Quiero que desarrollen mi sitio' gem: 'sutty-theme-custom' url: 'https://sutty.nl' disabled: true - description_en: "If you want us to create your site, you're welcome to [contact us!](https://sutty.nl/en/#contact) :)" + description_en: "If you want us to develop your site, you're welcome to [contact us!](https://sutty.nl/en/#contact) :)" description_es: "Si querés que desarrollemos tu sitio, [escribinos](https://sutty.nl/#contacto) :)" - name_en: 'Minima' name_es: 'Mínima' @@ -24,11 +24,11 @@ name_es: 'Sutty' gem: 'sutty-jekyll-theme' url: 'https://rubygems.org/gems/sutty-jekyll-theme/' - description_en: "Sutty's design" + description_en: "The Sutty design" description_es: 'El diseño de Sutty' license: 'https://0xacab.org/sutty/jekyll/sutty-jekyll-theme/-/blob/master/LICENSE.txt' credits_es: 'Sutty es parte de la economía solidaria :)' - credits_en: 'Sutty is a solidarity economy project :)' + credits_en: 'Sutty is a solidarity economy project!' - name_en: 'Self-managed Book Publisher' name_es: 'Editorial Autogestiva' gem: 'editorial-autogestiva-jekyll-theme' @@ -42,11 +42,11 @@ name_es: 'Donaciones' gem: 'sutty-donaciones-jekyll-theme' url: 'https://donaciones.sutty.nl/' - description_en: "Make your own donations campaign with payment buttons." + description_en: "Make your own donation campaign with payment buttons." description_es: 'Realizá campañas de donaciones con botones de pago.' license: 'https://0xacab.org/sutty/jekyll/sutty-donaciones-jekyll-theme/-/blob/master/LICENSE.txt' credits_es: 'Diseñamos esta plantilla para [visibilizar campañas de donaciones](https://sutty.nl/plantilla-para-donaciones/) durante la cuarentena.' - credits_en: 'We designed this theme to increase [requests for donations visibility](https://sutty.nl/template-for-donations/) during the quarantine.' + credits_en: 'We designed this theme to increase [visibility for donation requests](https://sutty.nl/template-for-donations/) during the quarantine.' - name_en: 'Support campaign' name_es: 'Adhesiones' gem: 'adhesiones-jekyll-theme' @@ -55,29 +55,29 @@ description_es: 'Realizá campañas de adhesión.' license: 'https://0xacab.org/sutty/jekyll/adhesiones-jekyll-theme/-/blob/master/LICENSE.txt' credits_es: 'Desarrollamos esta plantilla junto con [Librenauta](https://sutty.nl/plantilla-para-campa%C3%B1as-de-adhesiones/)' - credits_en: 'We made this template with Librenauta :)' + credits_en: 'This template was made in collaboration with Librenauta' designer_url: 'https://copiona.com/donaunbit/' - name_en: 'Community Radio' name_es: 'Radio comunitaria' gem: 'radios-comunitarias-jekyll-theme' url: 'https://radio.sutty.nl/' - description_en: "A theme with streaming support, designed for community radios" + description_en: "A theme with live streaming support, designed for community radios" description_es: 'Con soporte para transmisión en vivo, pensada para radios comunitarias' license: 'https://0xacab.org/sutty/jekyll/radios-comunitarias-jekyll-theme/-/blob/master/LICENSE.txt' credits_es: 'Desarrollamos esta plantilla junto con Librenauta en 15 horas :)' - credits_en: 'We made this template with Librenauta in 15 hours :)' + credits_en: 'This template was made in collaboration with Librenauta in 15 hours!' designer_url: 'https://copiona.com/donaunbit/' - name_en: 'Resource toolkit' name_es: 'Recursero' gem: 'recursero-jekyll-theme' url: 'https://recursero.info/' disabled: true - description_en: "We're working to add more themes for you to use. [Contact us!](https://sutty.nl/en/#contact)" + description_en: "We're working towards adding more themes for you to use. [Contact us!](https://sutty.nl/en/#contact)" description_es: "Estamos trabajando para que puedas tener más diseños. [¡Escribinos!](https://sutty.nl/#contacto)" - name_en: 'Other themes' name_es: 'Mi propio diseño' gem: 'sutty-theme-own' url: 'https://jekyllthemes.org' disabled: true - description_en: "We're working to add more themes for you to use. [Contact us!](https://sutty.nl/en/#contact)" + description_en: "We're working towards adding more themes for you to use. [Contact us!](https://sutty.nl/en/#contact)" description_es: "Estamos trabajando para que puedas tener más diseños. [¡Escribinos!](https://sutty.nl/#contacto)" diff --git a/db/seeds/licencias.yml b/db/seeds/licencias.yml index fb5c8f0a..cbe3bace 100644 --- a/db/seeds/licencias.yml +++ b/db/seeds/licencias.yml @@ -104,7 +104,7 @@ description_en: "This license gives everyone the freedom to use, adapt, and redistribute the contents of your site by requiring attribution only. We recommend this license if you're publishing - articles that require maximum diffusion, even in commercial media, but + articles that require maximum dissemination, even in commercial media, but you want to be attributed. Users of the site will have to mention the source and indicate if they made changes to it." url_en: 'https://creativecommons.org/licenses/by/4.0/' @@ -198,13 +198,13 @@ url_es: 'https://creativecommons.org/licenses/by-sa/4.0/deed.es' description_en: "This license is the same as the CC-BY 4.0 but it adds a requirement of sharing the work and its derivatives under the same - license. This is a reciprocitary, _copyleft_, license that keeps + license. This is a reciprocal, _copyleft_, license that keeps culture free. Though commercial uses are allowed, they must be shared under the same license, so any modifications done for profit are free as well." description_es: "Esta licencia es igual que la CC-BY 4.0 con el requisito agregado de compartir la obra y sus obras derivadas con la - misma licencia. Esta es una licencia reciprocitaria, _copyleft_, que + misma licencia. Esta es una licencia recíproca, _copyleft_, que mantiene y profundiza la cultura libre. Aunque los usos comerciales están permitidos, las mejoras hechas con fines de lucro deben ser compartidas bajo la misma licencia." From de92cc117a5b0b33fc8a2bf426d3a5c529f1c0ec Mon Sep 17 00:00:00 2001 From: Maki Date: Sat, 8 May 2021 17:19:13 -0300 Subject: [PATCH 061/234] deselect all en en.yml --- config/locales/en.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/locales/en.yml b/config/locales/en.yml index dd9f8309..645a1e3b 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -435,7 +435,7 @@ en: reorder: submit: 'Save order' select: 'Select this post' - unselect: 'Deselected all' + unselect: 'Deselect all' top: 'Send to top' bottom: 'Send to bottom' up: 'Up' From 98df0ceb3a1ef23a5f7d444f23176da5de86c446 Mon Sep 17 00:00:00 2001 From: f Date: Sun, 9 May 2021 12:25:16 -0300 Subject: [PATCH 062/234] los datos privados no se indexan --- app/models/metadata_array.rb | 4 +++- app/models/metadata_content.rb | 2 +- app/models/metadata_document_date.rb | 2 +- app/models/metadata_string.rb | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app/models/metadata_array.rb b/app/models/metadata_array.rb index 8d951a14..9f5a84b6 100644 --- a/app/models/metadata_array.rb +++ b/app/models/metadata_array.rb @@ -13,8 +13,10 @@ class MetadataArray < MetadataTemplate false end + # Solo los datos públicos se indexan, aunque MetadataArray no se cifra + # aun, dejamos esto preparado para la posteridad. def indexable? - true + true && !private? end def to_s diff --git a/app/models/metadata_content.rb b/app/models/metadata_content.rb index 546e08c8..437a0dd9 100644 --- a/app/models/metadata_content.rb +++ b/app/models/metadata_content.rb @@ -20,7 +20,7 @@ class MetadataContent < MetadataTemplate end def indexable? - true + true && !private? end def to_s diff --git a/app/models/metadata_document_date.rb b/app/models/metadata_document_date.rb index a52cd051..c741e3be 100644 --- a/app/models/metadata_document_date.rb +++ b/app/models/metadata_document_date.rb @@ -12,7 +12,7 @@ class MetadataDocumentDate < MetadataTemplate end def indexable? - true + true && !private? end # El valor puede ser un Date, Time o una String en el formato diff --git a/app/models/metadata_string.rb b/app/models/metadata_string.rb index 724c2ef3..95aac4d4 100644 --- a/app/models/metadata_string.rb +++ b/app/models/metadata_string.rb @@ -8,7 +8,7 @@ class MetadataString < MetadataTemplate end def indexable? - true + true && !private? end private From 35518ba48abc2833c31640d8f753dfb2b12a6085 Mon Sep 17 00:00:00 2001 From: f Date: Sun, 9 May 2021 12:52:26 -0300 Subject: [PATCH 063/234] =?UTF-8?q?no=20hacen=20falta=20los=20parametros?= =?UTF-8?q?=20al=20crear=20un=20art=C3=ADculo=20nuevo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/posts/index.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/posts/index.haml b/app/views/posts/index.haml index 26a0f3ee..814ae042 100644 --- a/app/views/posts/index.haml +++ b/app/views/posts/index.haml @@ -16,7 +16,7 @@ - next if layout.hidden? %tr %th= layout.humanized_name - %td.pl-3= link_to t('posts.add'), new_site_post_path(@site, **@filter_params), class: 'btn btn-secondary btn-sm' + %td.pl-3= link_to t('posts.add'), new_site_post_path(@site, layout: layout.value), class: 'btn btn-secondary btn-sm' - if @filter_params[:layout] == layout.name.to_s %td= link_to t('posts.remove_filter'), site_posts_path(@site, **@filter_params.merge(layout: nil)), class: 'btn btn-primary btn-sm' - else From cb8032d9e10fd5ed8cba3355cabdb6189bddc96a Mon Sep 17 00:00:00 2001 From: f Date: Sun, 9 May 2021 12:59:33 -0300 Subject: [PATCH 064/234] usar loaf para configurar las breadcrumbs --- Gemfile | 1 + Gemfile.lock | 3 +++ 2 files changed, 4 insertions(+) diff --git a/Gemfile b/Gemfile index 63b921c2..3f100e6b 100644 --- a/Gemfile +++ b/Gemfile @@ -48,6 +48,7 @@ gem 'jekyll-commonmark' gem 'jekyll-images' gem 'jekyll-include-cache' gem 'sutty-liquid' +gem 'loaf' gem 'lockbox' gem 'mini_magick' gem 'mobility' diff --git a/Gemfile.lock b/Gemfile.lock index 5c1d0627..e80d3ccf 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -338,6 +338,8 @@ GEM rb-fsevent (~> 0.9, >= 0.9.4) rb-inotify (~> 0.9, >= 0.9.7) ruby_dep (~> 1.2) + loaf (0.10.0) + railties (>= 3.2) lockbox (0.6.4) lograge (0.11.2) actionpack (>= 4) @@ -691,6 +693,7 @@ DEPENDENCIES jekyll-include-cache letter_opener listen (>= 3.0.5, < 3.2) + loaf lockbox lograge memory_profiler From 0a5908d02d871eeea42390144b35e92603dc26fe Mon Sep 17 00:00:00 2001 From: f Date: Sun, 9 May 2021 13:02:09 -0300 Subject: [PATCH 065/234] eliminar las migas de pan casero --- app/views/devise/confirmations/new.haml | 2 -- app/views/devise/invitations/edit.haml | 2 -- app/views/devise/invitations/new.haml | 2 -- app/views/devise/passwords/edit.haml | 2 -- app/views/devise/passwords/new.haml | 2 -- app/views/devise/registrations/edit.haml | 2 -- app/views/devise/registrations/new.haml | 2 -- app/views/devise/sessions/new.haml | 2 -- app/views/devise/unlocks/new.haml | 2 -- app/views/i18n/edit.haml | 7 ------- app/views/layouts/_breadcrumb.haml | 21 +++++++-------------- app/views/layouts/application.html.haml | 1 + app/views/posts/edit.haml | 7 ------- app/views/posts/index.haml | 7 ------- app/views/posts/new.haml | 6 ------ app/views/posts/show.haml | 6 ------ app/views/sites/edit.haml | 3 --- app/views/sites/fetch.haml | 3 --- app/views/sites/index.haml | 2 -- app/views/sites/new.haml | 3 --- app/views/usuaries/index.haml | 5 ----- app/views/usuaries/invite.haml | 6 ------ 22 files changed, 8 insertions(+), 87 deletions(-) delete mode 100644 app/views/i18n/edit.haml diff --git a/app/views/devise/confirmations/new.haml b/app/views/devise/confirmations/new.haml index b1080788..59568cb7 100644 --- a/app/views/devise/confirmations/new.haml +++ b/app/views/devise/confirmations/new.haml @@ -1,5 +1,3 @@ -= render 'layouts/breadcrumb', crumbs: nil - = content_for :body do - 'black-bg' diff --git a/app/views/devise/invitations/edit.haml b/app/views/devise/invitations/edit.haml index b8bb4315..565429a8 100644 --- a/app/views/devise/invitations/edit.haml +++ b/app/views/devise/invitations/edit.haml @@ -1,5 +1,3 @@ -= render 'layouts/breadcrumb', crumbs: nil - = content_for :body do - 'black-bg' diff --git a/app/views/devise/invitations/new.haml b/app/views/devise/invitations/new.haml index 8a0e318e..44ceec2e 100644 --- a/app/views/devise/invitations/new.haml +++ b/app/views/devise/invitations/new.haml @@ -1,5 +1,3 @@ -= render 'layouts/breadcrumb', crumbs: nil - = content_for :body do - 'black-bg' diff --git a/app/views/devise/passwords/edit.haml b/app/views/devise/passwords/edit.haml index d5e0778c..7f7b16fb 100644 --- a/app/views/devise/passwords/edit.haml +++ b/app/views/devise/passwords/edit.haml @@ -1,5 +1,3 @@ -= render 'layouts/breadcrumb', crumbs: nil - = content_for :body do - 'black-bg' diff --git a/app/views/devise/passwords/new.haml b/app/views/devise/passwords/new.haml index 75e22859..3c80b8a0 100644 --- a/app/views/devise/passwords/new.haml +++ b/app/views/devise/passwords/new.haml @@ -1,5 +1,3 @@ -= render 'layouts/breadcrumb', crumbs: nil - = content_for :body do - 'black-bg' diff --git a/app/views/devise/registrations/edit.haml b/app/views/devise/registrations/edit.haml index ece85540..1db0329c 100644 --- a/app/views/devise/registrations/edit.haml +++ b/app/views/devise/registrations/edit.haml @@ -1,5 +1,3 @@ -= render 'layouts/breadcrumb', - crumbs: [link_to(t('.index'), sites_path), t('.title')] = content_for :body do - 'black-bg' diff --git a/app/views/devise/registrations/new.haml b/app/views/devise/registrations/new.haml index 92a44aec..cb6ff0d1 100644 --- a/app/views/devise/registrations/new.haml +++ b/app/views/devise/registrations/new.haml @@ -1,5 +1,3 @@ -= render 'layouts/breadcrumb', crumbs: nil - = content_for :body do - 'black-bg' diff --git a/app/views/devise/sessions/new.haml b/app/views/devise/sessions/new.haml index 2826be44..b5223e5f 100644 --- a/app/views/devise/sessions/new.haml +++ b/app/views/devise/sessions/new.haml @@ -1,5 +1,3 @@ -= render 'layouts/breadcrumb', crumbs: nil - = content_for :body do - 'black-bg' diff --git a/app/views/devise/unlocks/new.haml b/app/views/devise/unlocks/new.haml index af5bf50b..ac511115 100644 --- a/app/views/devise/unlocks/new.haml +++ b/app/views/devise/unlocks/new.haml @@ -1,5 +1,3 @@ -= render 'layouts/breadcrumb', crumbs: nil - = content_for :body do - 'black-bg' diff --git a/app/views/i18n/edit.haml b/app/views/i18n/edit.haml deleted file mode 100644 index 3e44af6f..00000000 --- a/app/views/i18n/edit.haml +++ /dev/null @@ -1,7 +0,0 @@ -= render 'layouts/breadcrumb', - crumbs: [link_to(t('sites.index.title'), sites_path), - link_to(@site.name, site_path(@site)), - t('i18n.index'), - t('i18n.edit')] - -= render 'i18n/form' diff --git a/app/views/layouts/_breadcrumb.haml b/app/views/layouts/_breadcrumb.haml index 01765d0d..29201b0e 100644 --- a/app/views/layouts/_breadcrumb.haml +++ b/app/views/layouts/_breadcrumb.haml @@ -3,21 +3,14 @@ = inline_svg_tag 'sutty.svg', class: 'black', aria: true, title: t('svg.sutty.title'), desc: t('svg.sutty.desc') - - if crumbs - %nav{ aria: { label: t('.title') }, role: 'navigation' } - %ol.breadcrumb - %li.breadcrumb-item - = link_to edit_usuarie_registration_path, - data: { toggle: 'tooltip' }, - title: t('help.usuarie.edit') do - = current_usuarie.email - - - crumbs.compact.each do |crumb| - - if crumb == crumbs.last - %li.breadcrumb-item.active{ aria: { current: 'page' } } - = crumb + %nav{ aria: { label: t('.title') } } + %ol.breadcrumb + - breadcrumb_trail do |crumb| + %li.breadcrumb-item{ class: crumb.current? ? 'active' : '' } + - if crumb.current? + %span{ aria: { current: 'page' } }= crumb.name - else - %li.breadcrumb-item= crumb + %span= link_to crumb.name, crumb.url - if current_usuarie %ul.navbar-nav diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 891d635c..85d5ab22 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -20,6 +20,7 @@ %body{ class: yield(:body) } .container-fluid#sutty + = render 'layouts/breadcrumb' = yield - if flash[:js] .js-flash.d-none{ data: flash[:js] } diff --git a/app/views/posts/edit.haml b/app/views/posts/edit.haml index 282d9d05..6ec252fe 100644 --- a/app/views/posts/edit.haml +++ b/app/views/posts/edit.haml @@ -1,10 +1,3 @@ -= render 'layouts/breadcrumb', - crumbs: [link_to(t('sites.index.title'), sites_path), - link_to(@site.name, site_posts_path(@site)), - link_to(t('posts.index'), site_posts_path(@site)), - link_to(@post.title.value, site_post_path(@site, @post.id)), - t('posts.edit')] - .row.justify-content-center .col-md-8 = render 'posts/form', site: @site, post: @post diff --git a/app/views/posts/index.haml b/app/views/posts/index.haml index b2f3f661..291945ae 100644 --- a/app/views/posts/index.haml +++ b/app/views/posts/index.haml @@ -1,10 +1,3 @@ -= render 'layouts/breadcrumb', - crumbs: [link_to(t('sites.index.title'), sites_path), - @site.name, - link_to(t('posts.index'), - site_posts_path(@site)), - @category_name] - %main.row %aside.menu.col-md-3 %h1= link_to @site.title, @site.url diff --git a/app/views/posts/new.haml b/app/views/posts/new.haml index adcc843d..6ec252fe 100644 --- a/app/views/posts/new.haml +++ b/app/views/posts/new.haml @@ -1,9 +1,3 @@ -= render 'layouts/breadcrumb', - crumbs: [link_to(t('sites.index.title'), sites_path), - @site.name, - link_to(t('posts.index'), - site_posts_path(@site)), t('posts.new')] - .row.justify-content-center .col-md-8 = render 'posts/form', site: @site, post: @post diff --git a/app/views/posts/show.haml b/app/views/posts/show.haml index 9dd4faa0..9d6f37cd 100644 --- a/app/views/posts/show.haml +++ b/app/views/posts/show.haml @@ -1,9 +1,3 @@ -= render 'layouts/breadcrumb', - crumbs: [link_to(t('sites.index.title'), sites_path), - @site.name, - link_to(t('posts.index'), site_posts_path(@site)), - @post.title.value] - - dir = t("locales.#{@locale}.dir") .row.justify-content-center .col-md-8 diff --git a/app/views/sites/edit.haml b/app/views/sites/edit.haml index cc5977cf..4ae7308d 100644 --- a/app/views/sites/edit.haml +++ b/app/views/sites/edit.haml @@ -1,6 +1,3 @@ -= render 'layouts/breadcrumb', - crumbs: [link_to(t('sites.index.title'), sites_path), - t('.title', site: @site.name)] .row.justify-content-center .col-md-8 %h1= t('.title', site: @site.name) diff --git a/app/views/sites/fetch.haml b/app/views/sites/fetch.haml index 6a16e2e0..f5d049c8 100644 --- a/app/views/sites/fetch.haml +++ b/app/views/sites/fetch.haml @@ -1,6 +1,3 @@ -= render 'layouts/breadcrumb', - crumbs: [link_to(t('sites.index.title'), sites_path), t('.title')] - .row.justify-content-center .col-md-8#pull %h1= t('.title') diff --git a/app/views/sites/index.haml b/app/views/sites/index.haml index 07ea670a..dfcc2203 100644 --- a/app/views/sites/index.haml +++ b/app/views/sites/index.haml @@ -1,5 +1,3 @@ -= render 'layouts/breadcrumb', crumbs: [t('sites.index.title')] - %main.row %aside.col-md-3 %h1= t('.title') diff --git a/app/views/sites/new.haml b/app/views/sites/new.haml index fa724421..68c17882 100644 --- a/app/views/sites/new.haml +++ b/app/views/sites/new.haml @@ -1,6 +1,3 @@ -= render 'layouts/breadcrumb', - crumbs: [link_to(t('sites.index.title'), sites_path), t('.title')] - .row.justify-content-center .col-md-8 %h1= t('.title') diff --git a/app/views/usuaries/index.haml b/app/views/usuaries/index.haml index c3c6c3b4..c0eedefd 100644 --- a/app/views/usuaries/index.haml +++ b/app/views/usuaries/index.haml @@ -1,8 +1,3 @@ -= render 'layouts/breadcrumb', - crumbs: [link_to(t('sites.index.title'), sites_path), - link_to(@site.name, @site), - t('.title')] - .row .col %h1= t('.title') diff --git a/app/views/usuaries/invite.haml b/app/views/usuaries/invite.haml index 62552e70..919ae92b 100644 --- a/app/views/usuaries/invite.haml +++ b/app/views/usuaries/invite.haml @@ -1,11 +1,5 @@ - invite_as = t("usuaries.invite_as.#{params[:invite_as]}") -= render 'layouts/breadcrumb', - crumbs: [link_to(t('sites.index.title'), sites_path), - @site.name, - link_to(t('posts.index'), site_usuaries_path(@site)), - t('.title', invite_as: invite_as)] - .row .col %h1= t('.title', invite_as: invite_as) From 88051425ae18ca29b4641a00151acfadf1beec13 Mon Sep 17 00:00:00 2001 From: f Date: Sun, 9 May 2021 13:08:18 -0300 Subject: [PATCH 066/234] =?UTF-8?q?agregar=20migas=20de=20pan=20comunes=20?= =?UTF-8?q?y=20en=20cada=20acci=C3=B3=C3=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit las migas que van dentro de cada acción son strings vacías porque no las estamos vinculando a nada, así que nos ahorramos el proceso. --- app/controllers/posts_controller.rb | 36 ++++++++++++-------------- app/controllers/sites_controller.rb | 9 +++++++ app/controllers/usuaries_controller.rb | 7 +++++ 3 files changed, 32 insertions(+), 20 deletions(-) diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index a4b47a16..57c6348a 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -7,6 +7,11 @@ class PostsController < ApplicationController before_action :authenticate_usuarie! + # TODO: Traer los comunes desde ApplicationController + breadcrumb -> { current_usuarie.email }, :edit_usuarie_registration_path + breadcrumb 'sites.index', :sites_path, match: :exact + breadcrumb -> { site.title }, -> { site_posts_path(site, locale: locale) }, match: :exact + # Las URLs siempre llevan el idioma actual o el de le usuarie def default_url_options { locale: params[:locale] || current_usuarie&.lang || I18n.locale } @@ -43,44 +48,34 @@ class PostsController < ApplicationController end def show - @site = find_site - @post = @site.posts(lang: locale).find params[:id] - - authorize @post - @locale = locale - - fresh_when @post + authorize post + breadcrumb post.title.value, '' + fresh_when post end # Genera una previsualización del artículo. - # - # TODO: No todos los artículos tienen previsualización! def preview - @site = find_site - @post = @site.posts(lang: locale).find params[:post_id] + authorize post - authorize @post - - render html: @post.render + render html: post.render end def new authorize Post - @site = find_site - @post = @site.posts.build(lang: locale, layout: params[:layout]) - @locale = locale + @post = site.posts.build(lang: locale, layout: params[:layout]) + + breadcrumb I18n.t('loaf.breadcrumbs.posts.new', layout: @post.layout.humanized_name.downcase), '' end def create authorize Post - @site = find_site - service = PostService.new(site: @site, + service = PostService.new(site: site, usuarie: current_usuarie, params: params) @post = service.create if @post.persisted? - @site.touch + site.touch forget_content redirect_to site_post_path(@site, @post) @@ -96,6 +91,7 @@ class PostsController < ApplicationController authorize @post @locale = locale + breadcrumb @post.title.value, site_post_path(@site, @post, locale: locale), match: :exact end def update diff --git a/app/controllers/sites_controller.rb b/app/controllers/sites_controller.rb index d7d2f9f6..f3114d9a 100644 --- a/app/controllers/sites_controller.rb +++ b/app/controllers/sites_controller.rb @@ -7,6 +7,9 @@ class SitesController < ApplicationController before_action :authenticate_usuarie! + breadcrumb -> { current_usuarie.email }, :edit_usuarie_registration_path + breadcrumb 'sites.index', :sites_path, match: :exact + # Ver un listado de sitios def index authorize Site @@ -24,6 +27,8 @@ class SitesController < ApplicationController end def new + breadcrumb 'sites.new', :new_site_path + @site = Site.new authorize @site @@ -43,6 +48,10 @@ class SitesController < ApplicationController def edit authorize site + + breadcrumb site.title, site_posts_path(site), match: :exact + breadcrumb 'sites.edit', site_path(site) + SiteService.new(site: site).build_deploys end diff --git a/app/controllers/usuaries_controller.rb b/app/controllers/usuaries_controller.rb index 71deee91..fdfa66a8 100644 --- a/app/controllers/usuaries_controller.rb +++ b/app/controllers/usuaries_controller.rb @@ -7,12 +7,19 @@ class UsuariesController < ApplicationController include Pundit before_action :authenticate_usuarie! + # TODO: Traer los comunes desde ApplicationController + breadcrumb -> { current_usuarie.email }, :edit_usuarie_registration_path + breadcrumb 'sites.index', :sites_path, match: :exact + breadcrumb -> { site.title }, -> { site_posts_path(site, locale: locale) }, match: :exact + # Mostrar todes les usuaries e invitades de un sitio def index @site = find_site site_usuarie = SiteUsuarie.new(@site, current_usuarie) authorize site_usuarie + breadcrumb 'usuaries.index', '' + @policy = policy(site_usuarie) end From 8fe343ae45338d6434671ea103cf34aaf853c5ff Mon Sep 17 00:00:00 2001 From: f Date: Sun, 9 May 2021 13:10:00 -0300 Subject: [PATCH 067/234] hay migas que van dentro de sus vistas porque modificar el controlador de devise solo para agregar la miga de pan es demasiado trabajo --- app/views/devise/registrations/edit.haml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/views/devise/registrations/edit.haml b/app/views/devise/registrations/edit.haml index 1db0329c..6a25da65 100644 --- a/app/views/devise/registrations/edit.haml +++ b/app/views/devise/registrations/edit.haml @@ -1,3 +1,4 @@ +- breadcrumb 'sites.index', sites_path = content_for :body do - 'black-bg' From a9da4b3f2c3ef3a298dd4b75ec18752f39538dc3 Mon Sep 17 00:00:00 2001 From: f Date: Sun, 9 May 2021 13:11:15 -0300 Subject: [PATCH 068/234] =?UTF-8?q?ya=20que=20estaba,=20un=20poco=20de=20r?= =?UTF-8?q?efactorizaci=C3=B3n=20para=20no=20escribir=20tantas=20@?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit memoizamos site y post cuando podemos --- app/controllers/posts_controller.rb | 57 +++++++++++++------------- app/controllers/usuaries_controller.rb | 7 +++- 2 files changed, 33 insertions(+), 31 deletions(-) diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index 57c6348a..e8ceeebf 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -85,31 +85,24 @@ class PostsController < ApplicationController end def edit - @site = find_site - @post = @site.posts(lang: locale).find params[:id] - - authorize @post - - @locale = locale - breadcrumb @post.title.value, site_post_path(@site, @post, locale: locale), match: :exact + authorize post + breadcrumb post.title.value, site_post_path(site, post, locale: locale), match: :exact + breadcrumb 'posts.edit', '' end def update - @site = find_site - @post = @site.posts(lang: locale).find params[:id] + authorize post - authorize @post - - service = PostService.new(site: @site, - post: @post, + service = PostService.new(site: site, + post: post, usuarie: current_usuarie, params: params) if service.update.persisted? - @site.touch + site.touch forget_content - redirect_to site_post_path(@site, @post) + redirect_to site_post_path(site, post) else render 'posts/edit' end @@ -117,34 +110,30 @@ class PostsController < ApplicationController # Eliminar artículos def destroy - @site = find_site - @post = @site.posts(lang: locale).find params[:id] + authorize post - authorize @post - - service = PostService.new(site: @site, - post: @post, + service = PostService.new(site: site, + post: post, usuarie: current_usuarie, params: params) # TODO: Notificar si se pudo o no service.destroy - @site.touch - redirect_to site_posts_path(@site) + site.touch + redirect_to site_posts_path(site) end # Reordenar los artículos def reorder - @site = find_site - authorize @site + authorize site - service = PostService.new(site: @site, + service = PostService.new(site: site, usuarie: current_usuarie, params: params) service.reorder - @site.touch - redirect_to site_posts_path(@site) + site.touch + redirect_to site_posts_path(site) end # Devuelve el idioma solicitado a través de un parámetro, validando @@ -155,7 +144,7 @@ class PostsController < ApplicationController # solicite a le usuarie crear el nuevo idioma y que esto lo agregue al # _config.yml del sitio en lugar de mezclar idiomas. def locale - @site&.locales&.find(-> { I18n.locale }) do |l| + @locale ||= site&.locales&.find(-> { I18n.locale }) do |l| l.to_s == params[:locale] end end @@ -165,4 +154,14 @@ class PostsController < ApplicationController def forget_content flash[:js] = { target: 'editor', action: 'forget-content', keys: (params[:storage_keys] || []).to_json } end + + private + + def site + @site ||= find_site + end + + def post + @post ||= site.posts(lang: locale).find(params[:post_id] || params[:id]) + end end diff --git a/app/controllers/usuaries_controller.rb b/app/controllers/usuaries_controller.rb index fdfa66a8..6d02a35a 100644 --- a/app/controllers/usuaries_controller.rb +++ b/app/controllers/usuaries_controller.rb @@ -14,8 +14,7 @@ class UsuariesController < ApplicationController # Mostrar todes les usuaries e invitades de un sitio def index - @site = find_site - site_usuarie = SiteUsuarie.new(@site, current_usuarie) + site_usuarie = SiteUsuarie.new(site, current_usuarie) authorize site_usuarie breadcrumb 'usuaries.index', '' @@ -163,4 +162,8 @@ class UsuariesController < ApplicationController 'invitade' end end + + def site + @site ||= find_site + end end From 1a4d1b13f932fe045966479283e3836d72c38279 Mon Sep 17 00:00:00 2001 From: f Date: Sun, 9 May 2021 13:11:50 -0300 Subject: [PATCH 069/234] traducciones --- config/locales/en.yml | 11 +++++++++++ config/locales/es.yml | 11 +++++++++++ 2 files changed, 22 insertions(+) diff --git a/config/locales/en.yml b/config/locales/en.yml index 192a57c6..7e7f2646 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -579,3 +579,14 @@ en: local_invalid: "format is incorrect" not_allowed: "that email provider is not welcome here" server_not_available: "remote email server not available" + loaf: + breadcrumbs: + sites: + index: 'My sites' + new: 'Create' + edit: 'Configure' + posts: + new: 'New %{layout}' + edit: 'Editing' + usuaries: + index: 'Users' diff --git a/config/locales/es.yml b/config/locales/es.yml index 4cdca67e..c9c42c60 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -608,3 +608,14 @@ es: local_invalid: "el formato es incorrecto" not_allowed: "no es bienvenida" server_not_available: "el proveedor no está disponible" + loaf: + breadcrumbs: + sites: + index: 'Mis sitios' + new: 'Crear' + edit: 'Configurar' + posts: + new: 'Nuevo %{layout}' + edit: 'Editando' + usuaries: + index: 'Usuaries' From c2b150df121b11cb11ad0aa4bda12e3eb059afc7 Mon Sep 17 00:00:00 2001 From: f Date: Sun, 9 May 2021 13:12:06 -0300 Subject: [PATCH 070/234] migas responsivas --- app/assets/stylesheets/application.scss | 7 +++++++ app/views/layouts/_breadcrumb.haml | 6 +++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index 0a215c48..5886a2a7 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -355,6 +355,13 @@ $bezier: cubic-bezier(0.75, 0, 0.25, 1); .text-column-#{$size} { column-count: $size; } + + .line-clamp-#{$size} { + overflow: hidden; + display: -webkit-box; + -webkit-line-clamp: $size; + -webkit-box-orient: vertical; + } } /* diff --git a/app/views/layouts/_breadcrumb.haml b/app/views/layouts/_breadcrumb.haml index 29201b0e..c4920bc7 100644 --- a/app/views/layouts/_breadcrumb.haml +++ b/app/views/layouts/_breadcrumb.haml @@ -4,13 +4,13 @@ title: t('svg.sutty.title'), desc: t('svg.sutty.desc') %nav{ aria: { label: t('.title') } } - %ol.breadcrumb + %ol.breadcrumb.m-0.flex-wrap - breadcrumb_trail do |crumb| %li.breadcrumb-item{ class: crumb.current? ? 'active' : '' } - if crumb.current? - %span{ aria: { current: 'page' } }= crumb.name + %span.line-clamp-1{ aria: { current: 'page' } }= crumb.name - else - %span= link_to crumb.name, crumb.url + %span.line-clamp-1= link_to crumb.name, crumb.url - if current_usuarie %ul.navbar-nav From f3fcf11da3155f01208f5205ef94791e42a7abbc Mon Sep 17 00:00:00 2001 From: f Date: Sun, 9 May 2021 13:12:22 -0300 Subject: [PATCH 071/234] =?UTF-8?q?acomodar=20la=20secci=C3=B3n=20de=20usu?= =?UTF-8?q?aries=20al=20resto=20del=20sitio?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/usuaries/index.haml | 37 ++++++++++++++++------------------ app/views/usuaries/invite.haml | 6 ++---- 2 files changed, 19 insertions(+), 24 deletions(-) diff --git a/app/views/usuaries/index.haml b/app/views/usuaries/index.haml index c0eedefd..124fb04b 100644 --- a/app/views/usuaries/index.haml +++ b/app/views/usuaries/index.haml @@ -1,27 +1,24 @@ -.row - .col +.row.justify-content-center + .col.col-md-8 %h1= t('.title') -.row - .col -# Una tabla de usuaries y otra de invitades, con acciones - %i[usuaries invitades].each do |u| - %h2 - = t(".#{u}") - .btn-group{ role: 'group', 'aria-label': t('.actions') } - - if @policy.invite? - = link_to t('.invite'), - site_usuaries_invite_path(@site, invite_as: u.to_s), - class: 'btn', - data: { toggle: 'tooltip' }, - title: t('.help.invite', invite_as: u.to_s) - - if policy(Collaboration.new(@site)).collaborate? - = link_to t('.public_invite'), - site_collaborate_path(@site), - class: 'btn', - data: { toggle: 'tooltip' }, - title: t('.help.public_invite') - %p= t(".help.#{u}") + %h2.mt-5= t(".#{u}") + .btn-group{ role: 'group', 'aria-label': t('.actions') } + - if @policy.invite? + = link_to t('.invite'), + site_usuaries_invite_path(@site, invite_as: u.to_s), + class: 'btn', + data: { toggle: 'tooltip' }, + title: t('.help.invite', invite_as: u.to_s) + - if policy(Collaboration.new(@site)).collaborate? + = link_to t('.public_invite'), + site_collaborate_path(@site), + class: 'btn', + data: { toggle: 'tooltip' }, + title: t('.help.public_invite') + %p.lead= t(".help.#{u}") %table.table.table-condensed %tbody - @site.send(u).each do |cuenta| diff --git a/app/views/usuaries/invite.haml b/app/views/usuaries/invite.haml index 919ae92b..26eb5039 100644 --- a/app/views/usuaries/invite.haml +++ b/app/views/usuaries/invite.haml @@ -1,11 +1,9 @@ - invite_as = t("usuaries.invite_as.#{params[:invite_as]}") -.row - .col +.row.justify-content-center + .col.col-md-8 %h1= t('.title', invite_as: invite_as) -.row - .col = form_with url: site_usuaries_invite_path(@site), local: true do |f| = f.hidden_field :invited_as, value: params[:invite_as].singularize .form-group From 10580d2b4f169be9643f13c2335d44e3f95a9ef6 Mon Sep 17 00:00:00 2001 From: f Date: Mon, 10 May 2021 10:56:08 -0300 Subject: [PATCH 072/234] compilar el sitio de a uno por vez MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit estamos deshabilitando el botón en el panel, pero gracias a la caché (o si le usuarie tiene el botón ya abierto y habilitado) es posible correr dos o más tareas de compilación a la vez, lo que produce errores. por ejemplo, que una compilación falle porque quiere comprimir archivos que ya no están. con este cambio, si un sitio está compilando, la tarea se aplaza un minuto. si hay muchas tareas, eventualmente se harán todas pero no se pisan entre sí. quizás lo correcto sería hacerlo cuando se lanza la compilación también. --- app/jobs/deploy_job.rb | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/app/jobs/deploy_job.rb b/app/jobs/deploy_job.rb index 98e474ac..e5e051c7 100644 --- a/app/jobs/deploy_job.rb +++ b/app/jobs/deploy_job.rb @@ -8,13 +8,20 @@ class DeployJob < ApplicationJob def perform(site, notify = true) ActiveRecord::Base.connection_pool.with_connection do @site = Site.find(site) - @site.update_attribute :status, 'building' + + # Si ya hay una tarea corriendo, aplazar esta + if @site.status == 'building' + DeployJob.perform_in(60, site, notify) + return + end + + @site.update status: 'building' # Asegurarse que DeployLocal sea el primero! @deployed = { deploy_local: deploy_locally } # No es opcional unless @deployed[:deploy_local] - @site.update_attribute :status, 'waiting' + @site.update status: 'waiting' notify_usuaries if notify # Hacer fallar la tarea @@ -23,7 +30,7 @@ class DeployJob < ApplicationJob deploy_others notify_usuaries if notify - @site.update_attribute :status, 'waiting' + @site.update status: 'waiting' end end # rubocop:enable Metrics/MethodLength From 39eb584a97ebca66c83e591d29e52fd4c6d9c7ad Mon Sep 17 00:00:00 2001 From: f Date: Mon, 10 May 2021 13:14:02 -0300 Subject: [PATCH 073/234] no compilar varias veces desde el compilador MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit esto evita que se espamee el botón de publicar --- app/jobs/deploy_job.rb | 2 +- app/models/site.rb | 13 ++++++++++++- test/controllers/sites_controller_test.rb | 7 +++++++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/app/jobs/deploy_job.rb b/app/jobs/deploy_job.rb index e5e051c7..f1ceca9e 100644 --- a/app/jobs/deploy_job.rb +++ b/app/jobs/deploy_job.rb @@ -10,7 +10,7 @@ class DeployJob < ApplicationJob @site = Site.find(site) # Si ya hay una tarea corriendo, aplazar esta - if @site.status == 'building' + if @site.building? DeployJob.perform_in(60, site, notify) return end diff --git a/app/models/site.rb b/app/models/site.rb index 7a8b4b4b..7aeb8a9b 100644 --- a/app/models/site.rb +++ b/app/models/site.rb @@ -315,14 +315,25 @@ class Site < ApplicationRecord # Poner en la cola de compilación def enqueue! - !enqueued? && update_attribute(:status, 'enqueued') + waiting? && update(status: 'enqueued') end # Está en la cola de compilación? + # + # TODO: definir todos estos métodos dinámicamente, aunque todavía no + # tenemos una máquina de estados propiamente dicha. def enqueued? status == 'enqueued' end + def waiting? + status == 'waiting' + end + + def building? + status == 'building' + end + # Cargar el sitio Jekyll # # TODO: En lugar de leer todo junto de una vez, extraer la carga de diff --git a/test/controllers/sites_controller_test.rb b/test/controllers/sites_controller_test.rb index 5f67092a..a7e2f68b 100644 --- a/test/controllers/sites_controller_test.rb +++ b/test/controllers/sites_controller_test.rb @@ -102,6 +102,13 @@ class SitesControllerTest < ActionDispatch::IntegrationTest 'index.html')) end + test 'no se pueden encolar varias veces seguidas' do + assert_enqueued_jobs 2 do + post site_enqueue_url(@site), headers: @authorization + post site_enqueue_url(@site), headers: @authorization + end + end + test 'se pueden actualizar' do name = SecureRandom.hex design = Design.all.where.not(id: @site.design_id).sample From 8d7e4d2b6406ee9a34204e94454ff4c4a84e7ad8 Mon Sep 17 00:00:00 2001 From: f Date: Mon, 10 May 2021 13:55:36 -0300 Subject: [PATCH 074/234] solo poner en cola una sola vez MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit el cambio anterior no permitía que se encolen varias compilaciones y la versión actual pone el sitio en cola mientras se está compilando. con este cambio el sitio se puede encolar varias veces pero el estado se cambia una sola vez, para no abrirle la puerta a un loop infinito de compilaciones. lo correcto sería generar un modelo de cola con su propio estado (y probablemente a partir de cuál commit se está compilando). --- app/controllers/sites_controller.rb | 3 ++- app/models/site.rb | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/controllers/sites_controller.rb b/app/controllers/sites_controller.rb index d7d2f9f6..22429f35 100644 --- a/app/controllers/sites_controller.rb +++ b/app/controllers/sites_controller.rb @@ -63,7 +63,8 @@ class SitesController < ApplicationController authorize site # XXX: Convertir en una máquina de estados? - DeployJob.perform_async site.id if site.enqueue! + site.enqueue! + DeployJob.perform_async site.id redirect_to site_posts_path(site) end diff --git a/app/models/site.rb b/app/models/site.rb index 7aeb8a9b..683541e4 100644 --- a/app/models/site.rb +++ b/app/models/site.rb @@ -315,7 +315,7 @@ class Site < ApplicationRecord # Poner en la cola de compilación def enqueue! - waiting? && update(status: 'enqueued') + update(status: 'enqueued') if waiting? end # Está en la cola de compilación? From e0eeaf7fc6601b995c6c375102284de12c4e4454 Mon Sep 17 00:00:00 2001 From: f Date: Mon, 10 May 2021 14:27:47 -0300 Subject: [PATCH 075/234] tomar en cuenta el idioma actual al cachear para que al cambiar de idioma no se vean partes en el anterior --- app/controllers/posts_controller.rb | 2 +- app/views/posts/_form.haml | 2 +- app/views/posts/index.haml | 5 +---- app/views/posts/show.haml | 4 ++-- app/views/sites/index.haml | 2 +- 5 files changed, 6 insertions(+), 9 deletions(-) diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index e8ceeebf..5b8f1a16 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -27,7 +27,7 @@ class PostsController < ApplicationController # XXX: Cada vez que cambiamos un Post tocamos el sitio con lo que es # más simple saber si hubo cambios. - if @category || @layout || stale?(@site) + if @category || @layout || stale?([current_usuarie, @site]) @posts = @site.posts(lang: locale) @posts = @posts.where(categories: @category) if @category @posts = @posts.where(layout: @layout) if @layout diff --git a/app/views/posts/_form.haml b/app/views/posts/_form.haml index a9819a1b..e46b2eda 100644 --- a/app/views/posts/_form.haml +++ b/app/views/posts/_form.haml @@ -43,7 +43,7 @@ - metadata = post[attribute] - type = metadata.type - - cache metadata do + - cache [metadata, I18n.locale] do = render("posts/attributes/#{type}", base: 'post', post: post, attribute: attribute, metadata: metadata, site: site, diff --git a/app/views/posts/index.haml b/app/views/posts/index.haml index 291945ae..8d54069c 100644 --- a/app/views/posts/index.haml +++ b/app/views/posts/index.haml @@ -73,10 +73,7 @@ -# TODO: Solo les usuaries cachean porque tenemos que separar les botones por permisos. - - TODO: Verificar qué pasa cuando se gestiona el sitio en - distintos idiomas a la vez - - cache_if @usuarie, post do + - cache_if @usuarie, [post, I18n.locale] do - checkbox_id = "checkbox-#{post.uuid.value}" %tr{ id: post.uuid.value, data: { target: 'reorder.row' } } %td diff --git a/app/views/posts/show.haml b/app/views/posts/show.haml index 9d6f37cd..da6ac9db 100644 --- a/app/views/posts/show.haml +++ b/app/views/posts/show.haml @@ -22,7 +22,7 @@ - metadata = @post[attr] - next unless metadata.front_matter? - - cache metadata do + - cache [metadata, I18n.locale] do = render("posts/attribute_ro/#{metadata.type}", post: @post, attribute: attr, metadata: metadata, @@ -36,6 +36,6 @@ - metadata = @post[attr] - next if metadata.front_matter? - - cache metadata do + - cache [metadata, I18n.locale] do %section.editor{ id: attr, dir: dir } = @post.public_send(attr).to_s.html_safe diff --git a/app/views/sites/index.haml b/app/views/sites/index.haml index dfcc2203..9d831584 100644 --- a/app/views/sites/index.haml +++ b/app/views/sites/index.haml @@ -18,7 +18,7 @@ -# TODO: Solo les usuaries cachean porque tenemos que separar les botones por permisos. - - cache_if (rol.usuarie? && !rol.temporal), site do + - cache_if (rol.usuarie? && !rol.temporal), [site, I18n.locale] do %tr %td %h2 From f417d45a6b732a3b06a127e28f50860dd1ac97b3 Mon Sep 17 00:00:00 2001 From: Maki Date: Mon, 10 May 2021 14:37:41 -0300 Subject: [PATCH 076/234] =?UTF-8?q?bot=C3=B3n=20crear=20cuenta?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/locales/devise.views.es.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/config/locales/devise.views.es.yml b/config/locales/devise.views.es.yml index d0f57934..73166afc 100644 --- a/config/locales/devise.views.es.yml +++ b/config/locales/devise.views.es.yml @@ -102,7 +102,7 @@ es: update: Actualizar mi perfil we_need_your_current_password_to_confirm_your_changes: Necesitamos tu contraseña actual para confirmar los cambios. new: - sign_up: Registrarme por primera vez + sign_up: Registrarme help: Para registrarte solo pedimos una dirección de correo y una contraseña. La contraseña se almacena de forma segura, ¡nadie más que vos la sabe! Recibirás un correo de confirmación de cuenta. signed_up: Bienvenide. Tu cuenta fue creada. signed_up_but_inactive: Tu cuenta ha sido creada correctamente. Sin embargo, no hemos podido iniciar la sesión porque tu cuenta aún no está activada. @@ -124,8 +124,7 @@ es: forgot_your_password: "¿Has olvidado tu contraseña?" sign_in: Iniciar sesión sign_in_with_provider: Iniciar sesión con %{provider} - sign_up: Registrarme por primera vez - i_dont_have_account: ¿Nunca te registraste en LUNAR? + sign_up: Crear cuenta i_have_account: ¿Ya tenés cuenta? minimum_password_length: one: "%{count} caracter como mínimo." From 9faecbc2892eedfcbb8d682772c68cea3d618251 Mon Sep 17 00:00:00 2001 From: f Date: Mon, 10 May 2021 14:48:48 -0300 Subject: [PATCH 077/234] =?UTF-8?q?centralizar=20la=20detecci=C3=B3n=20del?= =?UTF-8?q?=20idioma?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/application_controller.rb | 12 +++++++++++- app/controllers/posts_controller.rb | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index bc375dcf..acd0134d 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -40,11 +40,21 @@ class ApplicationController < ActionController::Base site end + # Devuelve el idioma actual y si no lo encuentra obtiene uno por + # defecto. + # + # Esto se refiere al idioma de la interfaz, no de los artículos. + def current_locale(include_params: true, site: nil) + return params[:locale] if include_params && params[:locale].present? + + current_usuarie&.lang || I18n.locale + end + # El idioma es el preferido por le usuarie, pero no necesariamente se # corresponde con el idioma de los artículos, porque puede querer # traducirlos. def set_locale(&action) - I18n.with_locale(current_usuarie&.lang || I18n.default_locale, &action) + I18n.with_locale(current_locale(include_params: false), &action) end # Muestra una página 404 diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index 5b8f1a16..34055faf 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -14,7 +14,7 @@ class PostsController < ApplicationController # Las URLs siempre llevan el idioma actual o el de le usuarie def default_url_options - { locale: params[:locale] || current_usuarie&.lang || I18n.locale } + { locale: current_locale } end def index From 84e77c1232812def7bb55d6ac29dc6d5954f5a08 Mon Sep 17 00:00:00 2001 From: f Date: Mon, 10 May 2021 14:49:01 -0300 Subject: [PATCH 078/234] si el idioma actual no existe en el sitio mostrar el idioma por defecto --- app/controllers/posts_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index 34055faf..225b0c36 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -144,7 +144,7 @@ class PostsController < ApplicationController # solicite a le usuarie crear el nuevo idioma y que esto lo agregue al # _config.yml del sitio en lugar de mezclar idiomas. def locale - @locale ||= site&.locales&.find(-> { I18n.locale }) do |l| + @locale ||= site&.locales&.find(-> { site&.default_locale }) do |l| l.to_s == params[:locale] end end From cf01fb700ceaa294b2070f77d1af8e6ed2db24b3 Mon Sep 17 00:00:00 2001 From: f Date: Mon, 10 May 2021 14:49:18 -0300 Subject: [PATCH 079/234] siempre empezar por el idioma por defecto --- app/views/sites/index.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/sites/index.haml b/app/views/sites/index.haml index 9d831584..d7eefb95 100644 --- a/app/views/sites/index.haml +++ b/app/views/sites/index.haml @@ -23,7 +23,7 @@ %td %h2 - if policy(site).show? - = link_to site.title, site_path(site) + = link_to site.title, site_posts_path(site, locale: site.default_locale) - else = site.title %p.lead= site.description From 9d91323f496f31599696656a84cddda15c2c1ff0 Mon Sep 17 00:00:00 2001 From: f Date: Mon, 10 May 2021 15:18:04 -0300 Subject: [PATCH 080/234] redirigir a la lista con idioma --- app/controllers/posts_controller.rb | 4 ++-- app/controllers/sites_controller.rb | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index 225b0c36..c578e944 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -120,7 +120,7 @@ class PostsController < ApplicationController # TODO: Notificar si se pudo o no service.destroy site.touch - redirect_to site_posts_path(site) + redirect_to site_posts_path(site, locale: post.locale.value) end # Reordenar los artículos @@ -133,7 +133,7 @@ class PostsController < ApplicationController service.reorder site.touch - redirect_to site_posts_path(site) + redirect_to site_posts_path(site, locale: site.default_locale) end # Devuelve el idioma solicitado a través de un parámetro, validando diff --git a/app/controllers/sites_controller.rb b/app/controllers/sites_controller.rb index f3114d9a..d221628e 100644 --- a/app/controllers/sites_controller.rb +++ b/app/controllers/sites_controller.rb @@ -23,7 +23,7 @@ class SitesController < ApplicationController def show authorize site - redirect_to site_posts_path(site) + redirect_to site_posts_path(site, locale: site.default_locale) end def new @@ -40,7 +40,7 @@ class SitesController < ApplicationController params: site_params) if (@site = service.create).persisted? - redirect_to site_posts_path(@site) + redirect_to site_posts_path(@site, locale: @site.default_locale) else render 'new' end @@ -49,7 +49,7 @@ class SitesController < ApplicationController def edit authorize site - breadcrumb site.title, site_posts_path(site), match: :exact + breadcrumb site.title, site_posts_path(site, locale: site.default_locale), match: :exact breadcrumb 'sites.edit', site_path(site) SiteService.new(site: site).build_deploys @@ -62,7 +62,7 @@ class SitesController < ApplicationController usuarie: current_usuarie) if service.update.valid? - redirect_to site_posts_path(site) + redirect_to site_posts_path(site, locale: site.default_locale) else render 'edit' end @@ -74,7 +74,7 @@ class SitesController < ApplicationController # XXX: Convertir en una máquina de estados? DeployJob.perform_async site.id if site.enqueue! - redirect_to site_posts_path(site) + redirect_to site_posts_path(site, locale: site.default_locale) end def reorder_posts @@ -94,7 +94,7 @@ class SitesController < ApplicationController flash[:danger] = I18n.t('errors.posts.reorder') end - redirect_to site_posts_path(site) + redirect_to site_posts_path(site, locale: site.default_locale) end def fetch From 65a049c0270f35aad6272d961a328fc2c8f68167 Mon Sep 17 00:00:00 2001 From: f Date: Mon, 10 May 2021 15:18:24 -0300 Subject: [PATCH 081/234] traducciones faltantes --- config/locales/en.yml | 56 +++++++++++++++---------------------------- config/locales/es.yml | 40 ------------------------------- 2 files changed, 19 insertions(+), 77 deletions(-) diff --git a/config/locales/en.yml b/config/locales/en.yml index 1ad6cbd3..f1f9b7cb 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -84,10 +84,6 @@ en: title: Link to www success: Success! error: Error - deploy_private: - title: Private version - success: Success! - error: Error deploy_zip: title: Build ZIP file success: Available for download @@ -96,6 +92,10 @@ en: title: Host as Tor Hidden Service success: Success! error: Error + deploy_private: + title: Private version + success: Success! + error: Error deploy_alternative_domain: title: Alternative domain name success: Success! @@ -123,6 +123,7 @@ en: models: usuarie: User licencia: License + design: Design attributes: usuarie: email: 'E-mail address' @@ -130,27 +131,22 @@ en: password_confirmation: 'Password confirmation' current_password: 'Current password' lang: 'Main language' + remember_me: Remember me site: name: 'Name' title: 'Title' description: 'Description' - colaboracion_anonima: Enable anonymous collaboration acepta_invitades: Enable collaboration + colaboracion_anonima: Enable anonymous collaboration contact: Enable contact forms + tienda_url: Store URL + tienda_api_key: Store access key errors: models: site: attributes: deploys: deploy_local_presence: 'We need to be build the site!' - invitadx: - attributes: - email: - taken: 'This e-mail address is already taken, please choose a different one' - password_confirmation: - confirmation: "The passwords don't match" - acepta_politicas_de_privacidad: - no_acepta_politicas_de_privacidad: "Please read and accept the privacy policy" design_id: layout_incompatible: error: "Design can't be changed because there are posts with incompatible layouts" @@ -182,30 +178,6 @@ en: usuarie: edit: Edit my profile category: 'Category' - i18n: - top: 'Back to top' - index: "Here is where you edit the text on your site that doesn't belong to a post, such as its description, sections, buttons... If you change languages up there in the title to be the same, you can edit them. If they're different, you can translate from one into the other." - count: 'This is the amount of texts.' - toc: 'Jump to this section' - meta: 'Metadata' - navegacion: 'Navigation' - inicio: 'Home' - volver: 'Back' - entrar: 'Enter' - cerrar: 'Close' - anchor: 'Internal links' - nav: 'Menu' - nav-lang: 'Language menu' - modulos: 'Modules' - header: 'Header' - sobre: 'About' - metodologia: 'Methodology' - planeando_recursos: 'Planning resources' - rutas: 'Agendas' - complementarios: 'Materials' - recursos: 'Resources' - contacta: 'Contact us' - agradecimientos: 'Acknowledgments' sites: index: 'This is the list of sites you can edit.' enqueued: "The site is on queue to be generated. Once this @@ -217,6 +189,7 @@ en: invitations: accept: "Someone invited you to collaborate on their site. If you accept the invitation, you can access the site's edit mode." reject: "If you decline, you won't have access." + pull: 'You have pending upgrades!' close: 'Close help' deploys: deploy_local: @@ -321,15 +294,19 @@ en: new: title: 'Create site' submit: 'Create site' + help: 'You can edit any of these options after site creation.' edit: title: 'Edit %{site}' submit: 'Save changes' + btn: 'Configuration' form: errors: title: There were errors and we couldn't save your changes :( help: Please, look for the invalid fields to fix them help: name: "The name of your site. It can only include numbers and letters." + title: 'The title can be anything you want' + description: 'You site description that appears in search engines. Between 50 and 160 characters.' design: 'Select the design for your site. You can change it later. We add more designs from time to time!' licencia: 'Everything we publish has automatic copyright. This means nobody can use our works without explicit permission. By @@ -399,6 +376,7 @@ en: en: 'English' ar: 'Arabic' posts: + caption: Post list attribute_ro: file: download: Download file @@ -480,6 +458,10 @@ en: blank: Nothing destroy: Delete confirm_destroy: Are you sure? + form: + errors: + title: There are some errors on the form + help: Please, verify that all values are correct. usuaries: invite_as: usuaries: users diff --git a/config/locales/es.yml b/config/locales/es.yml index c9c42c60..1ce50b09 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -178,45 +178,8 @@ es: usuarie: edit: Editar mi perfil category: 'Categoría' - i18n: - top: 'Volver al principio' - index: 'Aquí puedes editar todos los textos del sitio que no se - corresponden con artículos, como la descripción, secciones, textos - de botones... Si cambias los idiomas arriba para que coincidan, - puedes editar los textos en el mismo idioma. Si los idiomas no - coinciden, puedes traducirlos de uno a otro.' - count: 'Esta es la cantidad de textos.' - toc: 'Saltar hasta esta sección' - meta: 'Metadata' - navegacion: 'Navegación' - inicio: 'Inicio' - volver: 'Volver' - entrar: 'Entrar' - cerrar: 'Cerrar' - anchor: 'Links internos' - nav: 'Menú' - nav-lang: 'Menú de idiomas' - modulos: 'Módulos' - header: 'Portada' - sobre: 'Acerca' - metodologia: 'Metodología' - planeando_recursos: 'Planeando recursos' - rutas: 'Rutas' - complementarios: 'Materiales complementarios' - recursos: 'Recursos' - contacta: 'Contacta' - agradecimientos: 'Agradecimientos' - sesion: 'Sesiones' - sesiones: 'Sesiones' - anexo: 'Anexo' - simple: 'Simple' sites: index: 'Este es el listado de sitios que puedes editar.' - edit_translations: 'Puedes editar los textos que salen en tu sitio - que no corresponden a artículos aquí, además de traducirlos a - otros idiomas.' - edit_posts: 'Aquí verás el listado de todos los artículos y podrás - editarlos o crear nuevos' enqueued: 'El sitio está en la cola de espera para ser generado. Una vez que este proceso termine, recibirás un correo indicando el estado y si todo fue bien, se publicarán los cambios en tu sitio @@ -224,9 +187,6 @@ es: enqueue: 'Cuando termines de hacer cambios en tu sitio, puedes publicarlos con esta acción. Al finalizar recibirás un correo avisándote cómo fue todo.' - build_log: 'Este es el registro de lo que sucedió mientras se - generaba el sitio. Si hubo algún problema, saldrá aquí.' - invitade: 'Les invitades a un sitio solo pueden crear y modificar entradas propias y no pueden publicar sin la revisión de une usuarie' invitations: accept: 'Alguien te invitó a colaborar en su sitio. Si aceptas la invitación, tendrás acceso a este sitio.' reject: 'Si rechazas la invitación, no tendrás acceso.' From 9e935a2c49ce62d32ad716157d817ad759d940f5 Mon Sep 17 00:00:00 2001 From: f Date: Mon, 10 May 2021 15:28:04 -0300 Subject: [PATCH 082/234] poder ver las categorias que son articulos --- app/views/posts/index.haml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/views/posts/index.haml b/app/views/posts/index.haml index 291945ae..35356349 100644 --- a/app/views/posts/index.haml +++ b/app/views/posts/index.haml @@ -99,9 +99,13 @@ - unless post.categories.value.empty? %br %small - - (post.categories.respond_to?(:belongs_to) ? post.categories.belongs_to : post.categories.value).each do |c| + - categories = post.categories.respond_to?(:has_many) ? post.categories.has_many : post.categories.value + - categories.each do |c| + - c.read = link_to site_posts_path(@site, category: (c.respond_to?(:uuid) ? c.uuid.value : c)) do %span{ lang: post.lang.value, dir: dir }= (c.respond_to?(:title) ? c.title.value : c) + - unless categories.last == c + = '/' %td = post.date.value.strftime('%F') From ea12caef9b4ef9d3b7ef63cb1f81dd576552af9a Mon Sep 17 00:00:00 2001 From: f Date: Mon, 10 May 2021 15:28:53 -0300 Subject: [PATCH 083/234] actualizar el recursero para ingles --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index e80d3ccf..73c57a26 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -460,7 +460,7 @@ GEM rb-fsevent (0.10.4) rb-inotify (0.10.1) ffi (~> 1.0) - recursero-jekyll-theme (0.1.2) + recursero-jekyll-theme (0.1.3) jekyll (~> 4.0) jekyll-data (~> 1.1) jekyll-feed (~> 0.9) From 404436e294b4afb8de6cb736e97c9d48b9580795 Mon Sep 17 00:00:00 2001 From: f Date: Tue, 11 May 2021 15:27:54 -0300 Subject: [PATCH 084/234] algunas categorias son de texto fixes #1537 fixes #1538 fixes #1539 fixes #1541 --- app/views/posts/index.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/posts/index.haml b/app/views/posts/index.haml index 67f909f0..33bb5a7c 100644 --- a/app/views/posts/index.haml +++ b/app/views/posts/index.haml @@ -98,7 +98,7 @@ %small - categories = post.categories.respond_to?(:has_many) ? post.categories.has_many : post.categories.value - categories.each do |c| - - c.read + - c.read if c.respond_to? :read = link_to site_posts_path(@site, category: (c.respond_to?(:uuid) ? c.uuid.value : c)) do %span{ lang: post.lang.value, dir: dir }= (c.respond_to?(:title) ? c.title.value : c) - unless categories.last == c From c1d631646225bd32772a82753ebf14a11c827c08 Mon Sep 17 00:00:00 2001 From: f Date: Tue, 11 May 2021 17:59:30 -0300 Subject: [PATCH 085/234] los posts solo tienen lang fixes #1551 fixes #1550 fixes #1549 fixes #1548 --- app/controllers/posts_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index c578e944..da0b28aa 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -120,7 +120,7 @@ class PostsController < ApplicationController # TODO: Notificar si se pudo o no service.destroy site.touch - redirect_to site_posts_path(site, locale: post.locale.value) + redirect_to site_posts_path(site, locale: post.lang.value) end # Reordenar los artículos From 5d248342348ac14d4fb085f81c4859d07b1099b2 Mon Sep 17 00:00:00 2001 From: f Date: Tue, 11 May 2021 18:16:34 -0300 Subject: [PATCH 086/234] =?UTF-8?q?los=20sitios=20pueden=20ser=20m=C3=A1s?= =?UTF-8?q?=20grandes=20que=202GB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fixes #1552 fixes #1553 fixes #1554 fixes #1555 --- db/migrate/20210511211357_change_bytes_to_big_int.rb | 5 +++++ db/schema.rb | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 db/migrate/20210511211357_change_bytes_to_big_int.rb diff --git a/db/migrate/20210511211357_change_bytes_to_big_int.rb b/db/migrate/20210511211357_change_bytes_to_big_int.rb new file mode 100644 index 00000000..4db65308 --- /dev/null +++ b/db/migrate/20210511211357_change_bytes_to_big_int.rb @@ -0,0 +1,5 @@ +class ChangeBytesToBigInt < ActiveRecord::Migration[6.1] + def change + change_column :build_stats, :bytes, :bigint + end +end diff --git a/db/schema.rb b/db/schema.rb index 2a93c5f1..eeb90ac6 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2021_04_14_152728) do +ActiveRecord::Schema.define(version: 2021_05_11_211357) do # Could not dump table "access_logs" because of following StandardError # Unknown type '' for column 'id' @@ -57,7 +57,7 @@ ActiveRecord::Schema.define(version: 2021_04_14_152728) do t.datetime "created_at", null: false t.datetime "updated_at", null: false t.integer "deploy_id" - t.integer "bytes" + t.bigint "bytes" t.float "seconds" t.string "action", null: false t.text "log" From 8a5e965e617142250debe2a0ae3bf04a223e54e8 Mon Sep 17 00:00:00 2001 From: f Date: Thu, 13 May 2021 11:44:09 -0300 Subject: [PATCH 087/234] validar la fecha MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit una fecha en el futuro como 20213-01-01 es válida para ruby, pero jekyll valida las fechas con cuatro digitos nada mas (issue pendiente :P), así que tomaba el archivo con fecha en el futurísimo como un archivo sin fecha. fixes #1573 fixes #1572 --- app/models/metadata_document_date.rb | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/app/models/metadata_document_date.rb b/app/models/metadata_document_date.rb index 39e68735..f6ad7d0d 100644 --- a/app/models/metadata_document_date.rb +++ b/app/models/metadata_document_date.rb @@ -13,11 +13,25 @@ class MetadataDocumentDate < MetadataTemplate # El valor puede ser un Date, Time o una String en el formato # "yyyy-mm-dd" + # + # XXX: Date.iso8601 acepta fechas en el futuro lejano, como 20000, + # pero Jekyll las limita a cuatro cifras, así que vamos a mantener + # eso. + # + # @see {https://github.com/jekyll/jekyll/blob/master/lib/jekyll/document.rb#L15} def value - return (self[:value] = value_from_document || default_value) if self[:value].nil? + self[:value] = + case self[:value] + when String + begin + raise Date::Error unless /\A\d{2,4}-\d{1,2}-\d{1,2}\z/ ~= self[:value] - self[:value] = Date.iso8601(self[:value]).to_time if self[:value].is_a? String - - self[:value] + Date.iso8601(self[:value]).to_time + rescue Date::Error + value_from_document || default_value + end + else + value_from_document || default_value + end end end From f5834b5c1bbd647530b7ca160343832aeeedcb4e Mon Sep 17 00:00:00 2001 From: f Date: Thu, 13 May 2021 12:52:49 -0300 Subject: [PATCH 088/234] en lugar de asumir un valor, informar el error --- app/models/metadata_document_date.rb | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/app/models/metadata_document_date.rb b/app/models/metadata_document_date.rb index f6ad7d0d..82126ba3 100644 --- a/app/models/metadata_document_date.rb +++ b/app/models/metadata_document_date.rb @@ -11,6 +11,19 @@ class MetadataDocumentDate < MetadataTemplate document.date end + # Siempre es obligatorio + def required + true + end + + def validate + super + + errors << I18n.t('metadata.date.invalid_format') unless valid_format? + + errors.empty? + end + # El valor puede ser un Date, Time o una String en el formato # "yyyy-mm-dd" # @@ -24,8 +37,6 @@ class MetadataDocumentDate < MetadataTemplate case self[:value] when String begin - raise Date::Error unless /\A\d{2,4}-\d{1,2}-\d{1,2}\z/ ~= self[:value] - Date.iso8601(self[:value]).to_time rescue Date::Error value_from_document || default_value @@ -34,4 +45,13 @@ class MetadataDocumentDate < MetadataTemplate value_from_document || default_value end end + + private + + def valid_format? + return true if self[:value].is_a?(Time) + + @valid_format_re ||= /\A\d{2,4}-\d{1,2}-\d{1,2}\z/ + @valid_format_re =~ self[:value].to_s + end end From 42923c4e04a3701468c97caa22bd459205b07694 Mon Sep 17 00:00:00 2001 From: f Date: Thu, 13 May 2021 19:41:39 -0300 Subject: [PATCH 089/234] sitios de testeo --- .../site_with_relationships/README.md | 2 ++ .../site_with_relationships/_config.yml | 2 ++ .../_data/layouts/author.yml | 9 ++++++++ .../_data/layouts/post.yml | 23 +++++++++++++++++++ .../site_with_relationships/_en/.keep | 0 test/fixtures/site_with_relationships/_posts | 1 + test/models/metadata_test.rb | 19 +++++++++++++++ 7 files changed, 56 insertions(+) create mode 100644 test/fixtures/site_with_relationships/README.md create mode 100644 test/fixtures/site_with_relationships/_config.yml create mode 100644 test/fixtures/site_with_relationships/_data/layouts/author.yml create mode 100644 test/fixtures/site_with_relationships/_data/layouts/post.yml create mode 100644 test/fixtures/site_with_relationships/_en/.keep create mode 120000 test/fixtures/site_with_relationships/_posts create mode 100644 test/models/metadata_test.rb diff --git a/test/fixtures/site_with_relationships/README.md b/test/fixtures/site_with_relationships/README.md new file mode 100644 index 00000000..89b5b892 --- /dev/null +++ b/test/fixtures/site_with_relationships/README.md @@ -0,0 +1,2 @@ +This is site where posts can have many authors and viceversa and posts +can be replies to others. diff --git a/test/fixtures/site_with_relationships/_config.yml b/test/fixtures/site_with_relationships/_config.yml new file mode 100644 index 00000000..da2d25c8 --- /dev/null +++ b/test/fixtures/site_with_relationships/_config.yml @@ -0,0 +1,2 @@ +locales: +- en diff --git a/test/fixtures/site_with_relationships/_data/layouts/author.yml b/test/fixtures/site_with_relationships/_data/layouts/author.yml new file mode 100644 index 00000000..afe620e2 --- /dev/null +++ b/test/fixtures/site_with_relationships/_data/layouts/author.yml @@ -0,0 +1,9 @@ +--- +title: + type: 'string' + required: true +posts: + type: 'has_and_belongs_to_many' + inverse: 'authors' + filter: + layout: 'post' diff --git a/test/fixtures/site_with_relationships/_data/layouts/post.yml b/test/fixtures/site_with_relationships/_data/layouts/post.yml new file mode 100644 index 00000000..c98baf7d --- /dev/null +++ b/test/fixtures/site_with_relationships/_data/layouts/post.yml @@ -0,0 +1,23 @@ +--- +title: + type: 'string' + required: true +authors: + type: 'has_and_belongs_to_many' + inverse: 'posts' + filter: + layout: 'author' +posts: + type: 'has_many' + inverse: 'in_reply_to' + filter: + layout: 'post' +in_reply_to: + type: 'belongs_to' + inverse: 'posts' + filter: + layout: 'post' +recommended_posts: + type: 'related_posts' + filter: + layout: 'post' diff --git a/test/fixtures/site_with_relationships/_en/.keep b/test/fixtures/site_with_relationships/_en/.keep new file mode 100644 index 00000000..e69de29b diff --git a/test/fixtures/site_with_relationships/_posts b/test/fixtures/site_with_relationships/_posts new file mode 120000 index 00000000..3da1d67b --- /dev/null +++ b/test/fixtures/site_with_relationships/_posts @@ -0,0 +1 @@ +_en \ No newline at end of file diff --git a/test/models/metadata_test.rb b/test/models/metadata_test.rb new file mode 100644 index 00000000..24d955ae --- /dev/null +++ b/test/models/metadata_test.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module MetadataTest + extend ActiveSupport::Concern + + included do + setup do + name = SecureRandom.hex + # TODO: Poder cambiar el nombre + FileUtils.cp_r(Rails.root.join('test', 'fixtures', 'site_with_relationships'), Rails.root.join('_sites', name)) + + @site = create :site, name: name + end + + teardown do + @site&.destroy + end + end +end From 38c5cdef813c9989b3c5d05ca1e7cae0a1414c6d Mon Sep 17 00:00:00 2001 From: f Date: Thu, 13 May 2021 19:44:21 -0300 Subject: [PATCH 090/234] =?UTF-8?q?testear=20la=20relaci=C3=B3n=20belongs?= =?UTF-8?q?=5Fto?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit belongs_to indica que un artículo pertenece a otro, la relación inversa es un has_many. en este caso nos fuimos dando cuenta que las memoizaciones nos juegan en contra, así que las vamos eliminando del código cuando no hacen falta. utilizamos `value = value.filtrado` para aprovechar `MetadataTemplate#value=` que guarda el valor anterior en `MetadataTemplate#value_was` y nos permite comparar entre el valor anterior y el actual. --- app/models/metadata_belongs_to.rb | 26 +++++--------- test/models/metadata_belongs_to_test.rb | 45 +++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 18 deletions(-) create mode 100644 test/models/metadata_belongs_to_test.rb diff --git a/app/models/metadata_belongs_to.rb b/app/models/metadata_belongs_to.rb index 0626ba0c..ee182a50 100644 --- a/app/models/metadata_belongs_to.rb +++ b/app/models/metadata_belongs_to.rb @@ -3,13 +3,6 @@ # Almacena el UUID de otro Post y actualiza el valor en el Post # relacionado. class MetadataBelongsTo < MetadataRelatedPosts - def value_was=(new_value) - @belongs_to = nil - @belonged_to = nil - - super(new_value) - end - # TODO: Convertir algunos tipos de valores en módulos para poder # implementar varios tipos de campo sin repetir código # @@ -39,10 +32,14 @@ class MetadataBelongsTo < MetadataRelatedPosts # Si estamos cambiando la relación, tenemos que eliminar la relación # anterior - belonged_to[inverse].value.delete post.uuid.value if changed? && belonged_to.present? + if belonged_to.present? + belonged_to[inverse].value = belonged_to[inverse].value.reject do |rej| + rej == post.uuid.value + end + end # No duplicar las relaciones - belongs_to[inverse].value << post.uuid.value unless belongs_to.blank? || included? + belongs_to[inverse].value = (belongs_to[inverse].value.dup << post.uuid.value) unless belongs_to.blank? || included? true end @@ -63,20 +60,13 @@ class MetadataBelongsTo < MetadataRelatedPosts end # El Post relacionado con este artículo - # - # XXX: Memoizamos usando el valor para tener el valor siempre - # actualizado. def belongs_to - return if value.blank? - - @belongs_to ||= posts.find(value, uuid: true) + posts.find(value, uuid: true) if value.present? end # El artículo relacionado anterior def belonged_to - return if value_was.blank? - - @belonged_to ||= posts.find(value_was, uuid: true) + posts.find(value_was, uuid: true) if value_was.present? end def related_posts? diff --git a/test/models/metadata_belongs_to_test.rb b/test/models/metadata_belongs_to_test.rb new file mode 100644 index 00000000..bf3f7b67 --- /dev/null +++ b/test/models/metadata_belongs_to_test.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +require 'test_helper' +require_relative 'metadata_test' + +class MetadataBelongsToTest < ActiveSupport::TestCase + include MetadataTest + + test 'se pueden relacionar artículos' do + post = @site.posts.create(layout: :post, title: SecureRandom.hex) + reply = @site.posts.create(layout: :post, title: SecureRandom.hex, in_reply_to: post.uuid.value) + + assert_equal post, reply.in_reply_to.belongs_to + assert_includes post.posts.has_many, reply + end + + test 'se puede eliminar la relación' do + post = @site.posts.create(layout: :post, title: SecureRandom.hex) + reply = @site.posts.create(layout: :post, title: SecureRandom.hex, in_reply_to: post.uuid.value) + + reply.in_reply_to.value = '' + reply.save + + assert_not_equal post, reply.in_reply_to.belongs_to + assert_equal post, reply.in_reply_to.belonged_to + assert_nil reply.in_reply_to.belongs_to + assert_not_includes post.posts.has_many, reply + end + + test 'se puede cambiar la relación' do + post1 = @site.posts.create(layout: :post, title: SecureRandom.hex) + post2 = @site.posts.create(layout: :post, title: SecureRandom.hex) + reply = @site.posts.create(layout: :post, title: SecureRandom.hex, in_reply_to: post1.uuid.value) + + reply.in_reply_to.value = post2.uuid.value + reply.save + + assert_not_equal post1, reply.in_reply_to.belongs_to + assert_equal post1, reply.in_reply_to.belonged_to + assert_not_includes post1.posts.has_many, reply + + assert_equal post2, reply.in_reply_to.belongs_to + assert_includes post2.posts.has_many, reply + end +end From bda56e3c1075554b0272f84b1a4b9a8a83bad593 Mon Sep 17 00:00:00 2001 From: f Date: Thu, 13 May 2021 19:47:00 -0300 Subject: [PATCH 091/234] =?UTF-8?q?eliminar=20memoizaci=C3=B3n=20en=20las?= =?UTF-8?q?=20b=C3=BAsquedas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/metadata_related_posts.rb | 2 +- app/models/post_relation.rb | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/models/metadata_related_posts.rb b/app/models/metadata_related_posts.rb index 9a73dea9..855ea73d 100644 --- a/app/models/metadata_related_posts.rb +++ b/app/models/metadata_related_posts.rb @@ -22,7 +22,7 @@ class MetadataRelatedPosts < MetadataArray # Obtiene todos los posts y opcionalmente los filtra def posts - @posts ||= site.posts(lang: lang).where(**filter) + site.posts(lang: lang).where(**filter) end def title(post) diff --git a/app/models/post_relation.rb b/app/models/post_relation.rb index 850a83dc..531d3cc4 100644 --- a/app/models/post_relation.rb +++ b/app/models/post_relation.rb @@ -93,8 +93,7 @@ class PostRelation < Array def where(**args) return self if args.empty? - @where ||= {} - @where[args.hash.to_s] ||= begin + begin PostRelation.new(site: site, lang: lang).concat(select do |post| result = args.map do |attr, value| next unless post.attribute?(attr) From 817d5650f846e4ce5f965686067d667d08e8b20e Mon Sep 17 00:00:00 2001 From: f Date: Thu, 13 May 2021 19:48:41 -0300 Subject: [PATCH 092/234] =?UTF-8?q?testear=20la=20relaci=C3=B3n=20has=5Fma?= =?UTF-8?q?ny?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit un artículo puede tener varios artículos, es la inversa de belongs_to también eliminamos la memoización --- app/models/metadata_has_many.rb | 16 +++------- test/models/metadata_has_many_test.rb | 45 +++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 11 deletions(-) create mode 100644 test/models/metadata_has_many_test.rb diff --git a/app/models/metadata_has_many.rb b/app/models/metadata_has_many.rb index 61354011..a24a5f1b 100644 --- a/app/models/metadata_has_many.rb +++ b/app/models/metadata_has_many.rb @@ -6,14 +6,6 @@ # Localmente tenemos un Array de UUIDs. Remotamente tenemos una String # apuntando a un Post, que se mantiene actualizado como el actual. class MetadataHasMany < MetadataRelatedPosts - # Invalidar la relación anterior - def value_was=(new_value) - @had_many = nil - @has_many = nil - - super(new_value) - end - def validate super @@ -24,14 +16,16 @@ class MetadataHasMany < MetadataRelatedPosts # Todos los Post relacionados def has_many - @has_many ||= posts.where(uuid: value) + return default_value if value.blank? + + posts.where(uuid: value) end # La relación anterior def had_many - return [] if value_was.blank? + return default_value if value_was.blank? - @had_many ||= posts.where(uuid: value_was) + posts.where(uuid: value_was) end def inverse? diff --git a/test/models/metadata_has_many_test.rb b/test/models/metadata_has_many_test.rb new file mode 100644 index 00000000..637c9425 --- /dev/null +++ b/test/models/metadata_has_many_test.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +require 'test_helper' +require_relative 'metadata_test' + +class MetadataHasManyTest < ActiveSupport::TestCase + include MetadataTest + + test 'se pueden relacionar artículos' do + reply = @site.posts.create(layout: :post, title: SecureRandom.hex) + post = @site.posts.create(layout: :post, title: SecureRandom.hex, posts: [reply.uuid.value]) + + assert_equal post, reply.in_reply_to.belongs_to + assert_includes post.posts.has_many, reply + end + + test 'se puede eliminar la relación' do + reply = @site.posts.create(layout: :post, title: SecureRandom.hex) + post = @site.posts.create(layout: :post, title: SecureRandom.hex, posts: [reply.uuid.value]) + + post.posts.value = [] + post.save + + assert_not_equal post, reply.in_reply_to.belongs_to + assert_equal post, reply.in_reply_to.belonged_to + assert_nil reply.in_reply_to.belongs_to + assert_not_includes post.posts.has_many, reply + end + + test 'se puede cambiar la relación' do + reply = @site.posts.create(layout: :post, title: SecureRandom.hex) + post1 = @site.posts.create(layout: :post, title: SecureRandom.hex, posts: [reply.uuid.value]) + post2 = @site.posts.create(layout: :post, title: SecureRandom.hex) + + reply.in_reply_to.value = post2.uuid.value + reply.save + + assert_not_equal post1, reply.in_reply_to.belongs_to + assert_equal post1, reply.in_reply_to.belonged_to + assert_not_includes post1.posts.has_many, reply + + assert_equal post2, reply.in_reply_to.belongs_to + assert_includes post2.posts.has_many, reply + end +end From fc6e7da5d698c95f10ad10749e9c703a4c6f86ea Mon Sep 17 00:00:00 2001 From: f Date: Thu, 13 May 2021 19:50:41 -0300 Subject: [PATCH 093/234] =?UTF-8?q?testear=20relaci=C3=B3n=20has=5Fand=5Fb?= =?UTF-8?q?elongs=5Fto=5Fmany?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit un artículo puede pertenecer y tener muchos artículos (una relación poliamorosa de muchos a muchos) también utilizamos asignación para aprovechar `value=`. --- .../metadata_has_and_belongs_to_many.rb | 19 +++--- .../metadata_has_and_belongs_to_many_test.rb | 58 +++++++++++++++++++ 2 files changed, 65 insertions(+), 12 deletions(-) create mode 100644 test/models/metadata_has_and_belongs_to_many_test.rb diff --git a/app/models/metadata_has_and_belongs_to_many.rb b/app/models/metadata_has_and_belongs_to_many.rb index f14827ec..2c4f3d43 100644 --- a/app/models/metadata_has_and_belongs_to_many.rb +++ b/app/models/metadata_has_and_belongs_to_many.rb @@ -18,6 +18,7 @@ class MetadataHasAndBelongsToMany < MetadataHasMany # # Buscamos en belongs_to la relación local, si se eliminó hay que # quitarla de la relación remota, sino hay que agregarla. + # def save # XXX: No usamos super self[:value] = sanitize value @@ -25,27 +26,21 @@ class MetadataHasAndBelongsToMany < MetadataHasMany return true unless changed? return true unless inverse? + # XXX: Usamos asignación para aprovechar value= que setea el valor + # anterior en @value_was (had_many - has_many).each do |remove| - remove[inverse]&.value&.delete post.uuid.value + remove[inverse].value = remove[inverse].value.reject do |rej| + rej == post.uuid.value + end end (has_many - had_many).each do |add| next unless add[inverse] next if add[inverse].value.include? post.uuid.value - add[inverse].value << post.uuid.value + add[inverse].value = (add[inverse].value.dup << post.uuid.value) end true end - - private - - # Igual que en MetadataRelatedPosts - # TODO: Mover a un módulo - def sanitize(uuid) - super(uuid.map do |u| - u.to_s.gsub(/[^a-f0-9\-]/i, '') - end) - end end diff --git a/test/models/metadata_has_and_belongs_to_many_test.rb b/test/models/metadata_has_and_belongs_to_many_test.rb new file mode 100644 index 00000000..1404e145 --- /dev/null +++ b/test/models/metadata_has_and_belongs_to_many_test.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +require 'test_helper' +require_relative 'metadata_test' + +class MetadataHasAndBelongsManyTest < ActiveSupport::TestCase + include MetadataTest + + test 'se pueden relacionar artículos' do + author = @site.posts.create(layout: :author, title: SecureRandom.hex) + post = @site.posts.create(layout: :post, title: SecureRandom.hex) + + post.authors.value = [author.uuid.value] + assert post.save + + assert_includes author.posts.has_many, post + assert_includes post.authors.has_many, author + end + + test 'se puede eliminar la relación' do + author = @site.posts.create(layout: :author, title: SecureRandom.hex) + post = @site.posts.create(layout: :post, title: SecureRandom.hex, authors: [author.uuid.value]) + + assert_includes post.authors.value, author.uuid.value + assert_includes author.posts.value, post.uuid.value + + post.authors.value = [] + ENV['HOLA'] = 'hola' + assert post.save + + assert_not_includes author.posts.has_many, post + assert_not_includes post.authors.has_many, author + + assert_includes author.posts.had_many, post + assert_includes post.authors.had_many, author + end + + test 'se puede cambiar la relación' do + author = @site.posts.create(layout: :author, title: SecureRandom.hex) + post1 = @site.posts.create(layout: :post, title: SecureRandom.hex, authors: [author.uuid.value]) + post2 = @site.posts.create(layout: :post, title: SecureRandom.hex) + + author.posts.value = [post2.uuid.value] + assert author.save + + assert_not_includes author.posts.has_many, post1 + assert_not_includes post1.authors.has_many, author + + assert_includes author.posts.had_many, post1 + assert_includes post1.authors.had_many, author + + assert_not_includes author.posts.had_many, post2 + assert_not_includes post2.authors.had_many, author + + assert_includes author.posts.has_many, post2 + assert_includes post2.authors.has_many, author + end +end From 3dacdf82bf528fbb380ad623e00c9c4e470cd6b4 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 14 May 2021 11:15:32 -0300 Subject: [PATCH 094/234] testear que lo que diga sutty termine en jekyll MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit agregué el método `Jekyll::Document#reset` para que no queden datos de la versión anterior, porque jekyll mergea la información. además, para evitar bugs en las plantillas, se mantienen los arrays vacíos en el front matter para que se puedan seguir usando métodos de arrays, como each y sort. --- app/models/post.rb | 7 +++++-- config/initializers/core_extensions.rb | 11 +++++++++- test/models/metadata_belongs_to_test.rb | 21 +++++++++++++++++-- .../metadata_has_and_belongs_to_many_test.rb | 20 +++++++++++++++++- test/models/metadata_has_many_test.rb | 21 +++++++++++++++++-- 5 files changed, 72 insertions(+), 8 deletions(-) diff --git a/app/models/post.rb b/app/models/post.rb index 461733f9..d5f77092 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -230,12 +230,14 @@ class Post template = public_send attr unless template.front_matter? - body += "\n\n" + body += "\n\n" if body.present? body += template.value next end - next if template.empty? + # Queremos mantener los Array en el resultado final para que + # siempre respondan a {% for %} en Liquid. + next if template.empty? && !template.value.is_a?(Array) [attr.to_s, template.value] end.compact.to_h @@ -285,6 +287,7 @@ class Post return false unless write # Vuelve a leer el post para tomar los cambios + document.reset read written? diff --git a/config/initializers/core_extensions.rb b/config/initializers/core_extensions.rb index 8bbcbe71..d43a5116 100644 --- a/config/initializers/core_extensions.rb +++ b/config/initializers/core_extensions.rb @@ -52,7 +52,7 @@ module Jekyll # Solo lee los datos def read_data - @site.data = DataReader.new(site).read(site.config["data_dir"]) + @site.data = DataReader.new(site).read(site.config['data_dir']) end # Lee todos los artículos del sitio @@ -73,6 +73,15 @@ module Jekyll Document.class_eval do alias_method :read!, :read def read; end + + # Permitir restablecer el documento sin crear uno nuevo + def reset + @path = @extname = @has_yaml_header = @relative_path = nil + @basename_without_ext = @data = @basename = nil + @renderer = @url_placeholders = @url = nil + @to_liquid = @write_p = @excerpt_separator = @id = nil + @related_posts = @cleaned_relative_path = self.content = nil + end end # Prevenir la instanciación de Time diff --git a/test/models/metadata_belongs_to_test.rb b/test/models/metadata_belongs_to_test.rb index bf3f7b67..09b9714f 100644 --- a/test/models/metadata_belongs_to_test.rb +++ b/test/models/metadata_belongs_to_test.rb @@ -12,6 +12,11 @@ class MetadataBelongsToTest < ActiveSupport::TestCase assert_equal post, reply.in_reply_to.belongs_to assert_includes post.posts.has_many, reply + + assert post.save + + assert_equal reply.document.data['in_reply_to'], post.document.data['uuid'] + assert_includes post.document.data['posts'], reply.document.data['uuid'] end test 'se puede eliminar la relación' do @@ -19,12 +24,17 @@ class MetadataBelongsToTest < ActiveSupport::TestCase reply = @site.posts.create(layout: :post, title: SecureRandom.hex, in_reply_to: post.uuid.value) reply.in_reply_to.value = '' - reply.save + assert reply.save assert_not_equal post, reply.in_reply_to.belongs_to assert_equal post, reply.in_reply_to.belonged_to assert_nil reply.in_reply_to.belongs_to assert_not_includes post.posts.has_many, reply + + assert post.save + + assert_nil reply.document.data['in_reply_to'] + assert_not_includes post.document.data['posts'], reply.document.data['uuid'] end test 'se puede cambiar la relación' do @@ -33,7 +43,7 @@ class MetadataBelongsToTest < ActiveSupport::TestCase reply = @site.posts.create(layout: :post, title: SecureRandom.hex, in_reply_to: post1.uuid.value) reply.in_reply_to.value = post2.uuid.value - reply.save + assert reply.save assert_not_equal post1, reply.in_reply_to.belongs_to assert_equal post1, reply.in_reply_to.belonged_to @@ -41,5 +51,12 @@ class MetadataBelongsToTest < ActiveSupport::TestCase assert_equal post2, reply.in_reply_to.belongs_to assert_includes post2.posts.has_many, reply + + assert post1.save + assert post2.save + + assert_equal post2.document.data['uuid'], reply.document.data['in_reply_to'] + assert_includes post2.document.data['posts'], reply.document.data['uuid'] + assert_not_includes post1.document.data['posts'], reply.document.data['uuid'] end end diff --git a/test/models/metadata_has_and_belongs_to_many_test.rb b/test/models/metadata_has_and_belongs_to_many_test.rb index 1404e145..4887a96e 100644 --- a/test/models/metadata_has_and_belongs_to_many_test.rb +++ b/test/models/metadata_has_and_belongs_to_many_test.rb @@ -15,6 +15,11 @@ class MetadataHasAndBelongsManyTest < ActiveSupport::TestCase assert_includes author.posts.has_many, post assert_includes post.authors.has_many, author + + assert author.save + + assert_includes author.document.data['posts'], post.document.data['uuid'] + assert_includes post.document.data['authors'], author.document.data['uuid'] end test 'se puede eliminar la relación' do @@ -25,7 +30,6 @@ class MetadataHasAndBelongsManyTest < ActiveSupport::TestCase assert_includes author.posts.value, post.uuid.value post.authors.value = [] - ENV['HOLA'] = 'hola' assert post.save assert_not_includes author.posts.has_many, post @@ -33,6 +37,11 @@ class MetadataHasAndBelongsManyTest < ActiveSupport::TestCase assert_includes author.posts.had_many, post assert_includes post.authors.had_many, author + + assert author.save + + assert_not_includes author.document.data['posts'], post.document.data['uuid'] + assert_not_includes post.document.data['authors'], author.document.data['uuid'] end test 'se puede cambiar la relación' do @@ -54,5 +63,14 @@ class MetadataHasAndBelongsManyTest < ActiveSupport::TestCase assert_includes author.posts.has_many, post2 assert_includes post2.authors.has_many, author + + assert post1.save + assert post2.save + + assert_not_includes author.document.data['posts'], post1.document.data['uuid'] + assert_not_includes post1.document.data['authors'], author.document.data['uuid'] + + assert_includes author.document.data['posts'], post2.document.data['uuid'] + assert_includes post2.document.data['authors'], author.document.data['uuid'] end end diff --git a/test/models/metadata_has_many_test.rb b/test/models/metadata_has_many_test.rb index 637c9425..38b4d46a 100644 --- a/test/models/metadata_has_many_test.rb +++ b/test/models/metadata_has_many_test.rb @@ -12,6 +12,11 @@ class MetadataHasManyTest < ActiveSupport::TestCase assert_equal post, reply.in_reply_to.belongs_to assert_includes post.posts.has_many, reply + + assert reply.save + + assert_equal reply.document.data['in_reply_to'], post.document.data['uuid'] + assert_includes post.document.data['posts'], reply.document.data['uuid'] end test 'se puede eliminar la relación' do @@ -19,12 +24,17 @@ class MetadataHasManyTest < ActiveSupport::TestCase post = @site.posts.create(layout: :post, title: SecureRandom.hex, posts: [reply.uuid.value]) post.posts.value = [] - post.save + assert post.save assert_not_equal post, reply.in_reply_to.belongs_to assert_equal post, reply.in_reply_to.belonged_to assert_nil reply.in_reply_to.belongs_to assert_not_includes post.posts.has_many, reply + + assert reply.save + + assert_nil reply.document.data['in_reply_to'] + assert_not_includes post.document.data['posts'], reply.document.data['uuid'] end test 'se puede cambiar la relación' do @@ -33,7 +43,7 @@ class MetadataHasManyTest < ActiveSupport::TestCase post2 = @site.posts.create(layout: :post, title: SecureRandom.hex) reply.in_reply_to.value = post2.uuid.value - reply.save + assert reply.save assert_not_equal post1, reply.in_reply_to.belongs_to assert_equal post1, reply.in_reply_to.belonged_to @@ -41,5 +51,12 @@ class MetadataHasManyTest < ActiveSupport::TestCase assert_equal post2, reply.in_reply_to.belongs_to assert_includes post2.posts.has_many, reply + + assert post1.save + assert post2.save + + assert_equal post2.document.data['uuid'], reply.document.data['in_reply_to'] + assert_includes post2.document.data['posts'], reply.document.data['uuid'] + assert_not_includes post1.document.data['posts'], reply.document.data['uuid'] end end From 219a9985f555ac34f84b61daf31e417270853686 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 14 May 2021 16:59:47 -0300 Subject: [PATCH 095/234] testear el buscador --- app/models/indexed_post.rb | 36 ++++++++++++------------ app/models/post/indexable.rb | 17 ++++------- app/models/site.rb | 5 ++-- test/models/indexed_post_test.rb | 35 +++++++++++++++++++++++ test/models/post/indexable_test.rb | 45 ++++++++++++++++++++++++++++++ 5 files changed, 107 insertions(+), 31 deletions(-) create mode 100644 test/models/indexed_post_test.rb create mode 100644 test/models/post/indexable_test.rb diff --git a/app/models/indexed_post.rb b/app/models/indexed_post.rb index 16858a66..7f6865f6 100644 --- a/app/models/indexed_post.rb +++ b/app/models/indexed_post.rb @@ -13,26 +13,26 @@ class IndexedPost < ApplicationRecord # TODO: Los indexed posts tienen que estar scopeados al idioma actual, # no buscar sobre todos pg_search_scope :search, - lambda { |locale, query| - { - against: :content, - query: query, - using: { - tsearch: { - dictionary: dictionary, - tsvector_column: 'indexed_content' - }, - trigram: { - word_similarity: true - } - } - } - } + lambda { |locale, query| + { + against: :content, + query: query, + using: { + tsearch: { + dictionary: IndexedPost.to_dictionary(locale: locale), + tsvector_column: 'indexed_content' + }, + trigram: { + word_similarity: true + } + } + } + } # Trae los IndexedPost en el orden en que van a terminar en el sitio. - default_scope lambda { order(order: :desc, created_at: :desc) } - scope :in_category, lambda { |category| where("front_matter->'categories' ? :category", category: category.to_s) } - scope :by_usuarie, lambda { |usuarie| where("front_matter->'usuaries' @> :usuarie::jsonb", usuarie: usuarie.to_s) } + default_scope -> { order(order: :desc, created_at: :desc) } + scope :in_category, ->(category) { where("front_matter->'categories' ? :category", category: category.to_s) } + scope :by_usuarie, ->(usuarie) { where("front_matter->'usuaries' @> :usuarie::jsonb", usuarie: usuarie.to_s) } belongs_to :site diff --git a/app/models/post/indexable.rb b/app/models/post/indexable.rb index b1d62504..7757e7f7 100644 --- a/app/models/post/indexable.rb +++ b/app/models/post/indexable.rb @@ -47,15 +47,10 @@ class Post # # @return [Hash] def indexable_front_matter - {}.tap do |indexable_front_matter| - indexable_front_matter = { - usuaries: usuaries.map(&:id), - draft: attribute?(:draft) ? draft.value : false - } - - if attribute? :categories - indexable_front_matter[:categories] = categories.indexable_values - end + {}.tap do |ifm| + ifm[:usuaries] = usuaries.map(&:id) + ifm[:draft] = attribute?(:draft) ? draft.value : false + ifm[:categories] = categories.indexable_values if attribute? :categories end end @@ -73,8 +68,8 @@ class Post def indexable_attributes @indexable_attributes ||= attributes.select do |attr| - self[attr].indexable? - end + self[attr].indexable? + end end end end diff --git a/app/models/site.rb b/app/models/site.rb index 4bfbed4d..b3cae93e 100644 --- a/app/models/site.rb +++ b/app/models/site.rb @@ -7,7 +7,6 @@ class Site < ApplicationRecord include Site::Forms include Site::FindAndReplace include Site::Api - include Site::Index include Tienda # Cifrar la llave privada que cifra y decifra campos ocultos. Sutty @@ -38,7 +37,6 @@ class Site < ApplicationRecord belongs_to :design belongs_to :licencia - has_many :indexed_posts, dependent: :destroy has_many :log_entries, dependent: :destroy has_many :deploys, dependent: :destroy has_many :build_stats, through: :deploys @@ -70,6 +68,9 @@ class Site < ApplicationRecord # El sitio en Jekyll attr_reader :jekyll + # XXX: Es importante incluir luego de los callbacks de :load_jekyll + include Site::Index + # No permitir HTML en estos atributos def title=(title) super(title.strip_tags) diff --git a/test/models/indexed_post_test.rb b/test/models/indexed_post_test.rb new file mode 100644 index 00000000..27d4e29e --- /dev/null +++ b/test/models/indexed_post_test.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require 'test_helper' + +class IndexedPostTest < ActiveSupport::TestCase + def site + @site ||= create :site + end + + teardown do + @site&.destroy + end + + test 'se pueden convertir los diccionarios' do + IndexedPost::DICTIONARIES.each do |locale, dict| + assert_equal dict, IndexedPost.to_dictionary(locale: locale) + end + end + + test 'se pueden buscar por categoría' do + assert(post = site.posts.create(title: SecureRandom.hex, description: SecureRandom.hex, + categories: [SecureRandom.hex, SecureRandom.hex])) + assert_not_empty site.indexed_posts.in_category(post.categories.value.sample) + end + + test 'se pueden encontrar por usuarie' do + usuarie = create :usuarie + assert(post = site.posts.create(title: SecureRandom.hex, description: SecureRandom.hex)) + + post.usuaries << usuarie + post.save + + assert_not_empty site.indexed_posts.by_usuarie(usuarie.id) + end +end diff --git a/test/models/post/indexable_test.rb b/test/models/post/indexable_test.rb new file mode 100644 index 00000000..6110bcf0 --- /dev/null +++ b/test/models/post/indexable_test.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +require 'test_helper' + +class Post::IndexableTest < ActiveSupport::TestCase + setup do + @site = create :site + end + + teardown do + @site&.destroy + end + + test 'los posts se indexan apenas se crean' do + post = @site.posts.create(title: SecureRandom.hex, description: SecureRandom.hex) + indexed_post = @site.indexed_posts.find_by_title post.title.value + + assert indexed_post + assert_equal post.locale.value.to_s, indexed_post.locale + assert_equal post.order.value, indexed_post.order + assert_equal post.path.basename, indexed_post.path + assert_equal post.layout.name.to_s, indexed_post.layout + end + + test 'se pueden encontrar posts' do + post = @site.posts.sample + + assert @site.indexed_posts.where(locale: post.lang.value).search(post.lang.value, post.title.value) + assert @site.indexed_posts.where(locale: post.lang.value).search(post.lang.value, post.description.value) + end + + test 'se pueden actualizar posts' do + post = @site.posts.sample + post.description.value = SecureRandom.hex + + assert post.save + assert @site.indexed_posts.where(locale: post.lang.value).search(post.lang.value, post.description.value) + end + + test 'al borrar el post se borra el indice' do + post = @site.posts.sample + assert post.destroy + assert_not @site.indexed_posts.find_by_id(post.uuid.value) + end +end From 189b94e074cfb63bc14408584e7098eb48149b79 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 14 May 2021 17:19:03 -0300 Subject: [PATCH 096/234] =?UTF-8?q?aplicar=20cach=C3=A9=20a=20los=20par?= =?UTF-8?q?=C3=A1metros=20de=20b=C3=BAsqueda=20tambi=C3=A9n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/posts_controller.rb | 9 +++------ db/schema.rb | 2 +- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index e1971400..3ef26720 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -20,14 +20,11 @@ class PostsController < ApplicationController def index authorize Post - @site = find_site - @locale = locale - # XXX: Cada vez que cambiamos un Post tocamos el sitio con lo que es # más simple saber si hubo cambios. - if filter_params.present? || stale?([current_usuarie, @site]) + if stale?([current_usuarie, site, filter_params]) # Todos los artículos de este sitio para el idioma actual - @posts = @site.indexed_posts.where(locale: locale) + @posts = site.indexed_posts.where(locale: locale) # De este tipo @posts = @posts.where(layout: filter_params[:layout]) if filter_params[:layout] # Que estén dentro de la categoría @@ -38,7 +35,7 @@ class PostsController < ApplicationController @posts = PostPolicy::Scope.new(current_usuarie, @posts).resolve # Filtrar los posts que les invitades no pueden ver - @usuarie = @site.usuarie? current_usuarie + @usuarie = site.usuarie? current_usuarie end end diff --git a/db/schema.rb b/db/schema.rb index 86354240..107e7be7 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2021_05_11_211357) do +ActiveRecord::Schema.define(version: 2021_05_14_165639) do # These are extensions that must be enabled in order to support this database enable_extension "pg_trgm" From 64b59bfc4dc996f9a0e2391dc328cba9a13a9631 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 14 May 2021 17:41:44 -0300 Subject: [PATCH 097/234] si el post ya tiene fecha, no cambiarla --- app/models/metadata_document_date.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/models/metadata_document_date.rb b/app/models/metadata_document_date.rb index 82126ba3..312bb3af 100644 --- a/app/models/metadata_document_date.rb +++ b/app/models/metadata_document_date.rb @@ -8,6 +8,8 @@ class MetadataDocumentDate < MetadataTemplate end def value_from_document + return nil unless document.path + document.date end @@ -42,7 +44,7 @@ class MetadataDocumentDate < MetadataTemplate value_from_document || default_value end else - value_from_document || default_value + self[:value] || value_from_document || default_value end end From e19713f5df355150a99b07c2d20f3a5be2693815 Mon Sep 17 00:00:00 2001 From: f Date: Mon, 17 May 2021 12:43:23 -0300 Subject: [PATCH 098/234] =?UTF-8?q?usar=20Post#new=3F=20que=20ya=20sabe=20?= =?UTF-8?q?cu=C3=A1ndo=20el=20documento=20no=20existe=20aun?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/metadata_document_date.rb | 2 +- app/models/post.rb | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/app/models/metadata_document_date.rb b/app/models/metadata_document_date.rb index 312bb3af..7512cbfb 100644 --- a/app/models/metadata_document_date.rb +++ b/app/models/metadata_document_date.rb @@ -8,7 +8,7 @@ class MetadataDocumentDate < MetadataTemplate end def value_from_document - return nil unless document.path + return nil if post.new? document.date end diff --git a/app/models/post.rb b/app/models/post.rb index 461733f9..13e8a919 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -49,9 +49,7 @@ class Post public_send(attr)&.value = args[attr] if args.key?(attr) end - # XXX: No usamos Post#read porque a esta altura todavía no sabemos - # nada del Document - document.read! if File.exist? document.path + document.read! unless new? end def inspect From a112233e2a6d0cecd624e13630bab47e5146218f Mon Sep 17 00:00:00 2001 From: f Date: Mon, 17 May 2021 13:14:57 -0300 Subject: [PATCH 099/234] soportar `make bundle` --- Makefile | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index e2e320d6..af466482 100644 --- a/Makefile +++ b/Makefile @@ -29,10 +29,13 @@ serve: /etc/hosts # make rails args="db:migrate" rails: - $(hain) 'cd /Sutty/sutty; bundle exec rails $(args)' + $(MAKE) bundle args="exec rails $(args)" rake: - $(hain) 'cd /Sutty/sutty; bundle exec rake $(args)' + $(MAKE) bundle args="exec rake $(args)" + +bundle: + $(hain) 'cd /Sutty/sutty; bundle $(args)' # Servir JS con el dev server. # Esto acelera la compilación del javascript, tiene que correrse por separado From 7257e02cbd467c7084223daeffbbb1f2d4111933 Mon Sep 17 00:00:00 2001 From: Maki Date: Mon, 17 May 2021 13:31:04 -0300 Subject: [PATCH 100/234] =?UTF-8?q?vendor=20vac=C3=ADo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vendor/.keep | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 vendor/.keep diff --git a/vendor/.keep b/vendor/.keep new file mode 100644 index 00000000..e69de29b From df425bab5aa2c04ce9a79d951e066e58d8fb9ab1 Mon Sep 17 00:00:00 2001 From: f Date: Mon, 17 May 2021 15:33:46 -0300 Subject: [PATCH 101/234] poder indexar los sitios! MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit me había olvidado de este archivo --- app/models/site/index.rb | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 app/models/site/index.rb diff --git a/app/models/site/index.rb b/app/models/site/index.rb new file mode 100644 index 00000000..e10fa523 --- /dev/null +++ b/app/models/site/index.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +# Indexa todos los artículos de un sitio +# +# TODO: Hacer opcional +class Site + module Index + extend ActiveSupport::Concern + + included do + # TODO: Debería ser un Job? + after_create :index_posts! + has_many :indexed_posts, dependent: :destroy + + def index_posts! + Site.transaction do + docs.each do |post| + post.to_index.save + end + end + end + end + end +end From 43b85a6e9b7348e1fee490d88219b36df348c46f Mon Sep 17 00:00:00 2001 From: f Date: Mon, 17 May 2021 15:51:20 -0300 Subject: [PATCH 102/234] =?UTF-8?q?poder=20cambiar=20la=20base=20de=20dato?= =?UTF-8?q?s=20de=20producci=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 1 + config/database.yml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.env.example b/.env.example index 56075093..2ba46219 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,4 @@ +DATABASE= RAILS_ENV= IMAP_SERVER= DEFAULT_FROM= diff --git a/config/database.yml b/config/database.yml index 28e195b8..7989a013 100644 --- a/config/database.yml +++ b/config/database.yml @@ -23,7 +23,7 @@ test: production: adapter: postgresql pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> - database: sutty + database: <%= ENV.fetch('DATABASE') { 'sutty' } %> user: sutty host: postgresql encoding: unicode From 553b4f15f7719e8c663b927862689ce32f741bbe Mon Sep 17 00:00:00 2001 From: f Date: Mon, 17 May 2021 15:58:37 -0300 Subject: [PATCH 103/234] mostrar si es borrador sin acudir al sitio --- app/views/posts/index.haml | 3 +-- config/locales/en.yml | 2 ++ config/locales/es.yml | 2 ++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/app/views/posts/index.haml b/app/views/posts/index.haml index 55ebcce1..06287ba5 100644 --- a/app/views/posts/index.haml +++ b/app/views/posts/index.haml @@ -103,8 +103,7 @@ = link_to site_post_path(@site, post.path) do %span{ lang: post.locale, dir: dir }= post.title - if post.front_matter['draft'].present? - %span.badge.badge-primary - = post_label_t(:draft, post: post) + %span.badge.badge-primary= I18n.t('posts.attributes.draft.label') - if post.front_matter['categories'].present? %br %small diff --git a/config/locales/en.yml b/config/locales/en.yml index 2705ad94..fc194eab 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -413,6 +413,8 @@ en: destroy: Remove image belongs_to: empty: "(Empty)" + draft: + label: Draft reorder: submit: 'Save order' select: 'Select this post' diff --git a/config/locales/es.yml b/config/locales/es.yml index fe1e3180..eca7cee8 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -420,6 +420,8 @@ es: destroy: 'Eliminar imagen' belongs_to: empty: "(Vacío)" + draft: + label: Borrador reorder: submit: 'Guardar orden' select: 'Seleccionar este artículo' From 0a23fe1edd475f11121d05e265e87be9d2472494 Mon Sep 17 00:00:00 2001 From: f Date: Mon, 17 May 2021 17:27:01 -0300 Subject: [PATCH 104/234] lo que se indexa son los valores actuales, no todos los valores posibles --- app/models/metadata_belongs_to.rb | 4 ++++ app/models/metadata_related_posts.rb | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/app/models/metadata_belongs_to.rb b/app/models/metadata_belongs_to.rb index ee182a50..1438c8db 100644 --- a/app/models/metadata_belongs_to.rb +++ b/app/models/metadata_belongs_to.rb @@ -77,6 +77,10 @@ class MetadataBelongsTo < MetadataRelatedPosts @related_methods ||= %i[belongs_to belonged_to].freeze end + def indexable_values + belongs_to&.title&.value + end + private def post_exists? diff --git a/app/models/metadata_related_posts.rb b/app/models/metadata_related_posts.rb index af91c28b..092f219a 100644 --- a/app/models/metadata_related_posts.rb +++ b/app/models/metadata_related_posts.rb @@ -23,7 +23,7 @@ class MetadataRelatedPosts < MetadataArray end def indexable_values - values.keys + posts.where(uuid: value).map(&:title).map(&:value) end private From 7f75f5cd8a08c1891e25fc20cb1e2619a532d394 Mon Sep 17 00:00:00 2001 From: f Date: Mon, 17 May 2021 17:54:21 -0300 Subject: [PATCH 105/234] si los archivos del sitio no existen, saltearlo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit lo correcto sería mostrar un error. esto nos ayuda a tener pocos sitios en staging en lugar de todos y que se puedan mostrar en el panel. --- app/views/sites/index.haml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/views/sites/index.haml b/app/views/sites/index.haml index d7eefb95..d69dbeac 100644 --- a/app/views/sites/index.haml +++ b/app/views/sites/index.haml @@ -14,6 +14,7 @@ %table.table.table-condensed %tbody - @sites.each do |site| + - next unless site.jekyll - rol = current_usuarie.rol_for_site(site) -# TODO: Solo les usuaries cachean porque tenemos que separar From 2612f449654d9611fd544478a64f3128d5abe589 Mon Sep 17 00:00:00 2001 From: f Date: Mon, 17 May 2021 17:56:56 -0300 Subject: [PATCH 106/234] =?UTF-8?q?validar=20de=20m=C3=A1s=20has=5Fmany?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit esta validación de has_many no permite guardar el post si no coinciden la cantidad de valores con los posts asociados, lo que funciona en casos muy ideales y no deja que se pueda guardar el post desde el formulario. el formulario envía un valor vacío para poder indicar que se quieren borrar todos los artículos por limitaciones de http, pero también suma un ítem vacío a la lista, con lo que la cantidad de posts encontrados siempre es 1 menor. de la misma forma, si la relación está rota porque uno de los posts asociados ya no existe, no permite guardar sin dar mayor feedback. así que por ahora dejamos que se puedan guardar. --- app/models/metadata_has_many.rb | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/app/models/metadata_has_many.rb b/app/models/metadata_has_many.rb index a24a5f1b..13f0dcf5 100644 --- a/app/models/metadata_has_many.rb +++ b/app/models/metadata_has_many.rb @@ -6,14 +6,6 @@ # Localmente tenemos un Array de UUIDs. Remotamente tenemos una String # apuntando a un Post, que se mantiene actualizado como el actual. class MetadataHasMany < MetadataRelatedPosts - def validate - super - - errors << I18n.t('metadata.has_many.missing_posts') unless posts_exist? - - errors.empty? - end - # Todos los Post relacionados def has_many return default_value if value.blank? @@ -65,8 +57,4 @@ class MetadataHasMany < MetadataRelatedPosts def related_methods @related_methods ||= %i[has_many had_many].freeze end - - def posts_exist? - has_many.size == sanitize(value).size - end end From 4609ab21b263c1f626fef071463f58bccf22c8bd Mon Sep 17 00:00:00 2001 From: f Date: Tue, 18 May 2021 11:22:09 -0300 Subject: [PATCH 107/234] =?UTF-8?q?separar=20las=20categor=C3=ADas=20con?= =?UTF-8?q?=20barras?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/posts/index.haml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/views/posts/index.haml b/app/views/posts/index.haml index 06287ba5..8b776590 100644 --- a/app/views/posts/index.haml +++ b/app/views/posts/index.haml @@ -110,6 +110,7 @@ - post.front_matter['categories'].each do |category| = link_to site_posts_path(@site, **@filter_params.merge(category: category)) do %span{ lang: post.locale, dir: dir }= category + = '/' unless post.front_matter['categories'].last == category %td = post.created_at.strftime('%F') From e4754b342b2725181b91ba8eb63cfe094bffa0bc Mon Sep 17 00:00:00 2001 From: f Date: Wed, 19 May 2021 17:55:55 -0300 Subject: [PATCH 108/234] =?UTF-8?q?trabajar=20con=20un=20entorno=20de=20pr?= =?UTF-8?q?uebas=20que=20es=20igual=20a=20producci=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit al correr `make ota build save` se genera un entorno de staging en panel.staging.sutty.nl con los datos de los sitios y una copia de la base de datos. no copiamos los sitios porque les usuaries van a entrar a hacer pruebas en staging y esperar encontrar los cambios luego en producción. además usaría mucho almacenamiento hacer copias. al correr `make env=production ota build save` se realiza el mismo proceso para producción. los assets se alojan por fuera para que podamos actualizarlos sin tocar el contenedor y separar los de production de los de staging. estos cambios van en línea con ansible-sutty-deploy. --- Dockerfile | 10 ++++------ Makefile | 27 +++++++++++++++++++++------ monit.conf | 4 ---- sync_assets.sh | 5 ----- 4 files changed, 25 insertions(+), 21 deletions(-) delete mode 100644 sync_assets.sh diff --git a/Dockerfile b/Dockerfile index 1aa97b16..3781772b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,8 +6,10 @@ FROM alpine:3.13.5 AS build MAINTAINER "f " ARG RAILS_MASTER_KEY +ARG BRANCH # Un entorno base +ENV BRANCH=$BRANCH ENV SECRET_KEY_BASE solo_es_necesaria_para_correr_rake ENV RAILS_ENV production ENV RAILS_MASTER_KEY=$RAILS_MASTER_KEY @@ -47,17 +49,16 @@ COPY --chown=app:www-data ./.git/ ./.git/ # Hacer un clon limpio del repositorio en lugar de copiar todos los # archivos RUN cd .. && git clone sutty checkout +RUN cd ../checkout && git checkout $BRANCH WORKDIR /home/app/checkout # Traer las gemas: +RUN rm -r ./vendor RUN mv ../sutty/vendor ./vendor RUN mv ../sutty/.bundle ./.bundle # Instalar secretos COPY --chown=app:root ./config/credentials.yml.enc ./config/ -# Traer los assets pre-compilados -COPY --chown=app:www-data ./public/assets ./public/assets -COPY --chown=app:www-data ./public/packs ./public/packs # Eliminar la necesidad de un runtime JS en producción, porque los # assets ya están pre-compilados. @@ -110,13 +111,10 @@ RUN rm -rf /srv/http/_sites /srv/http/_deploy RUN ln -s data/_storage /srv/http/_storage RUN ln -s data/_sites /srv/http/_sites RUN ln -s data/_deploy /srv/http/_deploy -RUN ln -s data/_public /srv/http/_public RUN ln -s data/_private /srv/http/_private # Volver a root para cerrar la compilación USER root -# Sincronizar los assets a un directorio compartido -RUN install -m 755 /srv/http/sync_assets.sh /usr/local/bin/sync_assets # Instalar la configuración de monit RUN install -m 640 -o root -g root /srv/http/monit.conf /etc/monit.d/sutty.conf RUN apk add --no-cache daemonize ruby-webrick diff --git a/Makefile b/Makefile index af466482..c7f4fd39 100644 --- a/Makefile +++ b/Makefile @@ -11,6 +11,20 @@ assets := package.json yarn.lock $(shell find app/assets/ app/javascript/ -type alpine_version := 3.13 hain ?= ../haini.sh/haini.sh +env ?= staging + +ifeq ($(env),production) +container ?= sutty +branch ?= rails +public ?= public +endif + +ifeq ($(env),staging) +container := staging +branch := staging +public := staging +endif + export public/packs/manifest.json.br: $(assets) @@ -49,12 +63,12 @@ clean: # Generar la imagen Docker build: assets - time docker build --build-arg="RAILS_MASTER_KEY=`cat config/master.key`" -t sutty/sutty . - docker tag sutty/sutty:latest sutty:keep + time docker build --build-arg="BRANCH=$(branch)" --build-arg="RAILS_MASTER_KEY=`cat config/master.key`" -t sutty/$(container) . + docker tag sutty/$(container):latest sutty:keep save: - time docker save sutty/sutty:latest | ssh root@$(delegate).sutty.nl docker load - date +%F | xargs git tag -f + time docker save sutty/$(container):latest | ssh root@$(delegate).sutty.nl docker load + date +%F | xargs -I {} git tag -f $(container)-{} @echo -e "\a" # proyectos. @@ -100,13 +114,14 @@ $(dirs): ota: assets sudo chgrp -R 82 public/ - rsync -avi --delete-after public/ athshe:/srv/sutty/srv/http/data/_public/ + rsync -avi --delete-after public/ $(delegate):/srv/sutty/srv/http/data/_$(public)/ + ssh $(delegate) docker exec $(container) sh -c "cat /srv/http/tmp/puma.pid | xargs -r kill -USR2" # Hotfixes commit ?= origin/rails ota-rb: umask 022; git format-patch $(commit) - scp ./0*.patch root@athshe.sutty.nl:/tmp/ + scp ./0*.patch $(delegate):/tmp/ rm ./0*.patch /etc/hosts: always diff --git a/monit.conf b/monit.conf index b8c9e442..f574c56d 100644 --- a/monit.conf +++ b/monit.conf @@ -6,10 +6,6 @@ check process prometheus with pidfile /tmp/prometheus.pid start program = "/usr/local/bin/sutty prometheus" stop program = "/bin/sh -c 'cat /tmp/prometheus.pid | xargs kill'" -check program sync_assets - with path /usr/local/bin/sync_assets - if status = 0 then unmonitor - check program blazer_5m with path "/bin/sh -c 'cd /srv/http && foreman start blazer_5m'" as uid "app" and gid "www-data" diff --git a/sync_assets.sh b/sync_assets.sh deleted file mode 100644 index 1c1a6ca1..00000000 --- a/sync_assets.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/sh -# Sincronizar assets desde public a _public para que estén disponibles -# en el contenedor web. - -rsync -a --delete-after /srv/http/public/ /srv/http/_public/ From 691f064a24983ad346260dde0142c45f6c3fd812 Mon Sep 17 00:00:00 2001 From: f Date: Thu, 20 May 2021 20:32:53 -0300 Subject: [PATCH 109/234] =?UTF-8?q?actualizaci=C3=B3n=20de=20cuidados?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Gemfile.lock | 206 +++++++++++++++++++++------------------------------ 1 file changed, 85 insertions(+), 121 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 73c57a26..942d7d6f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -18,60 +18,60 @@ GIT GEM remote: https://gems.sutty.nl/ specs: - actioncable (6.1.3.1) - actionpack (= 6.1.3.1) - activesupport (= 6.1.3.1) + actioncable (6.1.3.2) + actionpack (= 6.1.3.2) + activesupport (= 6.1.3.2) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (6.1.3.1) - actionpack (= 6.1.3.1) - activejob (= 6.1.3.1) - activerecord (= 6.1.3.1) - activestorage (= 6.1.3.1) - activesupport (= 6.1.3.1) + actionmailbox (6.1.3.2) + actionpack (= 6.1.3.2) + activejob (= 6.1.3.2) + activerecord (= 6.1.3.2) + activestorage (= 6.1.3.2) + activesupport (= 6.1.3.2) mail (>= 2.7.1) - actionmailer (6.1.3.1) - actionpack (= 6.1.3.1) - actionview (= 6.1.3.1) - activejob (= 6.1.3.1) - activesupport (= 6.1.3.1) + actionmailer (6.1.3.2) + actionpack (= 6.1.3.2) + actionview (= 6.1.3.2) + activejob (= 6.1.3.2) + activesupport (= 6.1.3.2) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (6.1.3.1) - actionview (= 6.1.3.1) - activesupport (= 6.1.3.1) + actionpack (6.1.3.2) + actionview (= 6.1.3.2) + activesupport (= 6.1.3.2) rack (~> 2.0, >= 2.0.9) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (6.1.3.1) - actionpack (= 6.1.3.1) - activerecord (= 6.1.3.1) - activestorage (= 6.1.3.1) - activesupport (= 6.1.3.1) + actiontext (6.1.3.2) + actionpack (= 6.1.3.2) + activerecord (= 6.1.3.2) + activestorage (= 6.1.3.2) + activesupport (= 6.1.3.2) nokogiri (>= 1.8.5) - actionview (6.1.3.1) - activesupport (= 6.1.3.1) + actionview (6.1.3.2) + activesupport (= 6.1.3.2) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.1, >= 1.2.0) - activejob (6.1.3.1) - activesupport (= 6.1.3.1) + activejob (6.1.3.2) + activesupport (= 6.1.3.2) globalid (>= 0.3.6) - activemodel (6.1.3.1) - activesupport (= 6.1.3.1) - activerecord (6.1.3.1) - activemodel (= 6.1.3.1) - activesupport (= 6.1.3.1) - activestorage (6.1.3.1) - actionpack (= 6.1.3.1) - activejob (= 6.1.3.1) - activerecord (= 6.1.3.1) - activesupport (= 6.1.3.1) + activemodel (6.1.3.2) + activesupport (= 6.1.3.2) + activerecord (6.1.3.2) + activemodel (= 6.1.3.2) + activesupport (= 6.1.3.2) + activestorage (6.1.3.2) + actionpack (= 6.1.3.2) + activejob (= 6.1.3.2) + activerecord (= 6.1.3.2) + activesupport (= 6.1.3.2) marcel (~> 1.0.0) mini_mime (~> 1.0.2) - activesupport (6.1.3.1) + activesupport (6.1.3.2) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) @@ -89,14 +89,11 @@ GEM jekyll-relative-urls (~> 0.0) jekyll-seo-tag (~> 2.1) ast (2.4.2) - autoprefixer-rails (10.2.4.0) - execjs - bcrypt (3.1.16) + autoprefixer-rails (10.2.5.0) + execjs (< 2.8.0) bcrypt (3.1.16-x86_64-linux-musl) - bcrypt_pbkdf (1.1.0) bcrypt_pbkdf (1.1.0-x86_64-linux-musl) benchmark-ips (2.8.4) - bindex (0.8.1) bindex (0.8.1-x86_64-linux-musl) blazer (2.4.2) activerecord (>= 5) @@ -107,7 +104,7 @@ GEM autoprefixer-rails (>= 9.1.0) popper_js (>= 1.14.3, < 2) sassc-rails (>= 2.0.0) - brakeman (5.0.0) + brakeman (5.0.1) builder (3.2.4) capybara (2.18.0) addressable @@ -116,28 +113,24 @@ GEM rack (>= 1.0.0) rack-test (>= 0.5.4) xpath (>= 2.0, < 4.0) - chartkick (4.0.3) + chartkick (4.0.4) childprocess (3.0.0) coderay (1.1.3) colorator (1.1.0) - commonmarker (0.21.2) - ruby-enum (~> 0.5) commonmarker (0.21.2-x86_64-linux-musl) ruby-enum (~> 0.5) concurrent-ruby (1.1.8) - concurrent-ruby-ext (1.1.8) - concurrent-ruby (= 1.1.8) concurrent-ruby-ext (1.1.8-x86_64-linux-musl) concurrent-ruby (= 1.1.8) crass (1.0.6) database_cleaner (2.0.1) database_cleaner-active_record (~> 2.0.0) - database_cleaner-active_record (2.0.0) + database_cleaner-active_record (2.0.1) activerecord (>= 5.a) database_cleaner-core (~> 2.0.0) database_cleaner-core (2.0.1) - dead_end (1.1.6) - derailed_benchmarks (2.0.1) + dead_end (1.1.7) + derailed_benchmarks (2.1.0) benchmark-ips (~> 2) dead_end get_process_mem (~> 0) @@ -149,26 +142,25 @@ GEM rake (> 10, < 14) ruby-statistics (>= 2.1) thor (>= 0.19, < 2) - devise (4.7.3) + devise (4.8.0) bcrypt (~> 3.0) orm_adapter (~> 0.1) railties (>= 4.1.0) responders warden (~> 1.2.3) - devise-i18n (1.9.3) + devise-i18n (1.9.4) devise (>= 4.7.1) - devise_invitable (2.0.4) + devise_invitable (2.0.5) actionmailer (>= 5.0) devise (>= 4.6) dotenv (2.7.6) dotenv-rails (2.7.6) dotenv (= 2.7.6) railties (>= 3.2) - down (5.2.0) + down (5.2.1) addressable (~> 2.5) - ed25519 (1.2.4) ed25519 (1.2.4-x86_64-linux-musl) - editorial-autogestiva-jekyll-theme (0.3.0) + editorial-autogestiva-jekyll-theme (0.3.4) jekyll (~> 4) jekyll-commonmark (~> 1.3) jekyll-data (~> 1.1) @@ -192,22 +184,18 @@ GEM http_parser.rb (~> 0.6.0) errbase (0.2.1) erubi (1.10.0) - eventmachine (1.2.7) eventmachine (1.2.7-x86_64-linux-musl) exception_notification (4.4.3) actionmailer (>= 4.0, < 7) activesupport (>= 4.0, < 7) execjs (2.7.0) - factory_bot (6.1.0) + factory_bot (6.2.0) activesupport (>= 5.0.0) - factory_bot_rails (6.1.0) - factory_bot (~> 6.1.0) + factory_bot_rails (6.2.0) + factory_bot (~> 6.2.0) railties (>= 5.0.0) - fast_blank (1.0.0) fast_blank (1.0.0-x86_64-linux-musl) - fast_jsonparser (0.5.0) fast_jsonparser (0.5.0-x86_64-linux-musl) - ffi (1.15.0) ffi (1.15.0-x86_64-linux-musl) flamegraph (0.9.5) forwardable-extended (2.6.0) @@ -228,10 +216,6 @@ GEM rainbow rubocop (>= 0.50.0) sysexits (~> 1.1) - hamlit (2.15.0) - temple (>= 0.8.2) - thor - tilt hamlit (2.15.0-x86_64-linux-musl) temple (>= 0.8.2) thor @@ -243,9 +227,7 @@ GEM railties (>= 4.0.1) heapy (0.2.0) thor - hiredis (0.6.3) hiredis (0.6.3-x86_64-linux-musl) - http_parser.rb (0.6.0) http_parser.rb (0.6.0-x86_64-linux-musl) httparty (0.18.1) mime-types (~> 3.0) @@ -312,7 +294,7 @@ GEM sassc (> 2.0.1, < 3.0) jekyll-seo-tag (2.7.1) jekyll (>= 3.8, < 5.0) - jekyll-spree-client (0.1.12) + jekyll-spree-client (0.1.14) fast_blank (~> 1) spree-api-client (~> 0.2) jekyll-turbolinks (0.0.5) @@ -361,33 +343,28 @@ GEM mini_histogram (0.3.1) mini_magick (4.11.0) mini_mime (1.0.3) - mini_portile2 (2.5.0) + mini_portile2 (2.5.1) minima (2.5.1) jekyll (>= 3.5, < 5.0) jekyll-feed (~> 0.9) jekyll-seo-tag (~> 2.1) minitest (5.14.4) - mobility (1.1.1) + mobility (1.1.2) i18n (>= 0.6.10, < 2) request_store (~> 1.0) multi_xml (0.6.0) net-ssh (6.1.0) netaddr (2.0.4) - nio4r (2.5.7) nio4r (2.5.7-x86_64-linux-musl) - nokogiri (1.11.3) - mini_portile2 (~> 2.5.0) - racc (~> 1.4) - nokogiri (1.11.3-x86_64-linux-musl) + nokogiri (1.11.5-x86_64-linux-musl) mini_portile2 (~> 2.5.0) racc (~> 1.4) orm_adapter (0.5.0) parallel (1.20.1) - parser (3.0.1.0) + parser (3.0.1.1) ast (~> 2.4.1) pathutil (0.16.2) forwardable-extended (~> 2.6) - pg (1.2.3) pg (1.2.3-x86_64-linux-musl) popper_js (1.16.0) prometheus_exporter (0.7.0) @@ -396,18 +373,15 @@ GEM coderay (~> 1.1) method_source (~> 1.0) public_suffix (4.0.6) - puma (5.2.2) - nio4r (~> 2.0) - puma (5.2.2-x86_64-linux-musl) + puma (5.3.1-x86_64-linux-musl) nio4r (~> 2.0) pundit (2.1.0) activesupport (>= 3.0.0) - racc (1.5.2) racc (1.5.2-x86_64-linux-musl) rack (2.2.3) rack-cors (1.1.1) rack (>= 2.0.0) - rack-mini-profiler (2.3.1) + rack-mini-profiler (2.3.2) rack (>= 1.2.0) rack-proxy (0.6.5) rack @@ -424,20 +398,20 @@ GEM jekyll-relative-urls (~> 0.0) jekyll-seo-tag (~> 2.1) jekyll-turbolinks (~> 0) - rails (6.1.3.1) - actioncable (= 6.1.3.1) - actionmailbox (= 6.1.3.1) - actionmailer (= 6.1.3.1) - actionpack (= 6.1.3.1) - actiontext (= 6.1.3.1) - actionview (= 6.1.3.1) - activejob (= 6.1.3.1) - activemodel (= 6.1.3.1) - activerecord (= 6.1.3.1) - activestorage (= 6.1.3.1) - activesupport (= 6.1.3.1) + rails (6.1.3.2) + actioncable (= 6.1.3.2) + actionmailbox (= 6.1.3.2) + actionmailer (= 6.1.3.2) + actionpack (= 6.1.3.2) + actiontext (= 6.1.3.2) + actionview (= 6.1.3.2) + activejob (= 6.1.3.2) + activemodel (= 6.1.3.2) + activerecord (= 6.1.3.2) + activestorage (= 6.1.3.2) + activesupport (= 6.1.3.2) bundler (>= 1.15.0) - railties (= 6.1.3.1) + railties (= 6.1.3.2) sprockets-rails (>= 2.0.0) rails-dom-testing (2.0.3) activesupport (>= 4.2.0) @@ -449,15 +423,15 @@ GEM railties (>= 6.0.0, < 7) rails_warden (0.6.0) warden (>= 1.2.0) - railties (6.1.3.1) - actionpack (= 6.1.3.1) - activesupport (= 6.1.3.1) + railties (6.1.3.2) + actionpack (= 6.1.3.2) + activesupport (= 6.1.3.2) method_source rake (>= 0.8.7) thor (~> 1.0) rainbow (3.0.0) rake (13.0.3) - rb-fsevent (0.10.4) + rb-fsevent (0.11.0) rb-inotify (0.10.1) ffi (~> 1.0) recursero-jekyll-theme (0.1.3) @@ -502,38 +476,34 @@ GEM railties (>= 5.0) rexml (3.2.5) rouge (3.26.0) - rubocop (1.12.1) + rubocop (1.15.0) parallel (~> 1.10) parser (>= 3.0.0.0) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 1.8, < 3.0) rexml - rubocop-ast (>= 1.2.0, < 2.0) + rubocop-ast (>= 1.5.0, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 1.4.0, < 3.0) - rubocop-ast (1.4.1) - parser (>= 2.7.1.5) - rubocop-rails (2.9.1) + rubocop-ast (1.5.0) + parser (>= 3.0.1.1) + rubocop-rails (2.10.1) activesupport (>= 4.2.0) rack (>= 1.1) - rubocop (>= 0.90.0, < 2.0) + rubocop (>= 1.7.0, < 2.0) ruby-enum (0.9.0) i18n - ruby-filemagic (0.7.2) ruby-filemagic (0.7.2-x86_64-linux-musl) ruby-progressbar (1.11.0) ruby-statistics (2.1.3) - ruby-vips (2.1.0) + ruby-vips (2.1.2) ffi (~> 1.12) ruby_dep (1.5.0) rubyzip (2.3.0) - rugged (1.1.0) rugged (1.1.0-x86_64-linux-musl) safe_yaml (1.0.6) safely_block (0.3.0) errbase (>= 0.1.1) - sassc (2.4.0) - ffi (~> 1.9) sassc (2.4.0-x86_64-linux-musl) ffi (~> 1.9) sassc-rails (2.1.2) @@ -571,10 +541,8 @@ GEM actionpack (>= 4.0) activesupport (>= 4.0) sprockets (>= 3.0.0) - sqlite3 (1.4.2) sqlite3 (1.4.2-x86_64-linux-musl) - stackprof (0.2.16) - stackprof (0.2.16-x86_64-linux-musl) + stackprof (0.2.17-x86_64-linux-musl) sucker_punch (3.0.1) concurrent-ruby (~> 1.0) sutty-archives (2.5.4) @@ -603,7 +571,6 @@ GEM jekyll (>= 3.5, < 5.0) jekyll-feed (~> 0.9) jekyll-seo-tag (~> 2.1) - symbol-fstring (1.0.0) symbol-fstring (1.0.0-x86_64-linux-musl) sysexits (1.2.0) temple (0.8.2) @@ -621,7 +588,6 @@ GEM execjs (>= 0.3.0, < 3) unf (0.1.4) unf_ext - unf_ext (0.0.7.7) unf_ext (0.0.7.7-x86_64-linux-musl) unicode-display_width (1.7.0) validates_hostname (1.0.11) @@ -634,14 +600,12 @@ GEM activemodel (>= 6.0.0) bindex (>= 0.4.0) railties (>= 6.0.0) - webpacker (5.2.1) + webpacker (5.4.0) activesupport (>= 5.2) rack-proxy (>= 0.6.1) railties (>= 5.2) semantic_range (>= 2.3.0) webrick (1.7.0) - websocket-driver (0.7.3) - websocket-extensions (>= 0.1.0) websocket-driver (0.7.3-x86_64-linux-musl) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) From 160558470b7c783c5fba6247fdbff834169eeb93 Mon Sep 17 00:00:00 2001 From: f Date: Mon, 24 May 2021 10:49:35 -0300 Subject: [PATCH 110/234] =?UTF-8?q?a=20veces=20el=20decifrado=20falla=20po?= =?UTF-8?q?rque=20el=20valor=20todav=C3=ADa=20no=20estaba=20cifrado?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit buscamos un espacio para saber si se trata de una oración o un mensaje que realmente no se pudo decifrar. fixes #1704 fixes #1706 fixes #1708 fixes #1709 fixes #1710 fixes #1712 fixes #1713 fixes #1714 fixes #1717 fixes #1718 fixes #1719 fixes #1721 fixes #1723 fixes #1724 fixes #1725 fixes #1728 fixes #1779 fixes #1780 fixes #1785 fixes #1799 fixes #1800 fixes #1802 fixes #1803 fixes #1804 fixes #1805 fixes #1807 fixes #1809 fixes #1810 fixes #1811 fixes #1813 --- app/models/metadata_template.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/models/metadata_template.rb b/app/models/metadata_template.rb index 23df8803..cea4d009 100644 --- a/app/models/metadata_template.rb +++ b/app/models/metadata_template.rb @@ -211,9 +211,13 @@ MetadataTemplate = Struct.new(:site, :document, :name, :label, :type, box.decrypt_str value.to_s rescue Lockbox::DecryptionError => e - ExceptionNotifier.notify_exception(e, data: { site: site.name, post: post.path.absolute, name: name }) + if value.to_s.include? ' ' + value + else + ExceptionNotifier.notify_exception(e, data: { site: site.name, post: post.path.absolute, name: name }) - I18n.t('lockbox.help.decryption_error') + I18n.t('lockbox.help.decryption_error') + end end # Cifra el valor. From fc7c2f31ddcc368d29ba3a23107c1a758a915d68 Mon Sep 17 00:00:00 2001 From: f Date: Sat, 29 May 2021 17:42:45 -0300 Subject: [PATCH 111/234] enviar reportes a gitlab usando la api en lugar del correo --- Gemfile | 1 + Gemfile.lock | 1 + app/jobs/backtrace_job.rb | 2 +- app/jobs/gitlab_notifier_job.rb | 142 ++++++++++++++++++ app/lib/exception_notifier/gitlab_notifier.rb | 17 +++ app/lib/gitlab_api_client.rb | 61 ++++++++ .../exception_notifier/_backtrace.text.erb | 2 +- app/views/exception_notifier/_data.text.erb | 2 +- config/environments/production.rb | 9 +- 9 files changed, 226 insertions(+), 11 deletions(-) create mode 100644 app/jobs/gitlab_notifier_job.rb create mode 100644 app/lib/exception_notifier/gitlab_notifier.rb create mode 100644 app/lib/gitlab_api_client.rb diff --git a/Gemfile b/Gemfile index 3f100e6b..5f1cdc4f 100644 --- a/Gemfile +++ b/Gemfile @@ -41,6 +41,7 @@ gem 'hiredis' gem 'image_processing' gem 'icalendar' gem 'inline_svg' +gem 'httparty' gem 'safe_yaml', source: 'https://gems.sutty.nl' gem 'jekyll', '~> 4.2' gem 'jekyll-data', source: 'https://gems.sutty.nl' diff --git a/Gemfile.lock b/Gemfile.lock index 942d7d6f..5484ad88 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -646,6 +646,7 @@ DEPENDENCIES haml-lint hamlit-rails hiredis + httparty icalendar image_processing inline_svg diff --git a/app/jobs/backtrace_job.rb b/app/jobs/backtrace_job.rb index eab9f226..86a9b2a6 100644 --- a/app/jobs/backtrace_job.rb +++ b/app/jobs/backtrace_job.rb @@ -40,7 +40,7 @@ class BacktraceJob < ApplicationJob begin raise BacktraceException, "#{origin}: #{message}" rescue BacktraceException => e - ExceptionNotifier.notify_exception(e, data: { site: site.name, params: params, _backtrace: true }) + ExceptionNotifier.notify_exception(e, data: { site: site.name, params: params, javascript_backtrace: true }) end end diff --git a/app/jobs/gitlab_notifier_job.rb b/app/jobs/gitlab_notifier_job.rb new file mode 100644 index 00000000..4d676b8d --- /dev/null +++ b/app/jobs/gitlab_notifier_job.rb @@ -0,0 +1,142 @@ +# frozen_string_literal: true + +class GitlabNotifierJob < ApplicationJob + include ExceptionNotifier::BacktraceCleaner + + attr_reader :exception, :options + + queue_as :low_priority + + def perform(exception, **options) + @exception = exception + @options = options + + Rails.logger.info 'Enviando reporte a Gitlab' + + i = client.new_issue confidential: true, title: title, description: description + + Rails.logger.info "Enviado reporte a Gitlab: #{i['iid']}" + rescue Exception => e + Rails.logger.info 'No entrar en loop' + end + + private + + # Define si es una excepción de javascript o local + # + # @see BacktraceJob + def javascript? + @javascript ||= options.dig(:data, :javascript_backtrace).present? + end + + # @return [String] + def title + @title ||= ''.dup.tap do |t| + t << "[#{exception.class}] " unless javascript? + t << exception.message + end + end + + # @return [String] + def description + @description ||= ''.dup.tap do |d| + d << request_section + d << javascript_section + d << javascript_footer + d << backtrace_section + d << data_section + end + end + + # @return [String,Nil] + def backtrace + @backtrace ||= exception.backtrace ? clean_backtrace(exception) : nil + end + + def env + options[:env] + end + + def request + @request ||= ActionDispatch::Request.new(env) if env.present? + end + + # @return [GitlabApiClient] + def client + @client ||= GitlabApiClient.new + end + + def request_section + return '' unless request + + <<~REQUEST + + # Request + + ``` + #{request.request_method} #{request.url}#{' '} + + #{pp request.filtered_parameters} + ``` + + REQUEST + end + + def javascript_section + return '' unless javascript? + + options.dig(:data, :params, 'errors')&.map do |error| + <<~JAVASCRIPT + + ## #{error['type'] || 'NoError'}: #{error['message']} + + + ``` + #{Terminal::Table.new headings: error['backtrace'].first.keys, rows: error['backtrace'].map(&:values)} + ``` + + JAVASCRIPT + end&.join + end + + def javascript_footer + return '' unless javascript? + + <<~JAVASCRIPT + + #{options.dig(:data, :params, 'context', 'userAgent')} + + <#{options.dig(:data, :params, 'context', 'url')}> + + JAVASCRIPT + end + + def backtrace_section + return '' if javascript? + return '' unless backtrace + + <<~BACKTRACE + + ## Backtrace + + ``` + #{backtrace.join("\n")} + ``` + + BACKTRACE + end + + def data_section + return '' unless options[:data] + + <<~DATA + + ## Data + + ``` + #{pp options[:data]} + ``` + + DATA + end +end diff --git a/app/lib/exception_notifier/gitlab_notifier.rb b/app/lib/exception_notifier/gitlab_notifier.rb new file mode 100644 index 00000000..18bfc6d4 --- /dev/null +++ b/app/lib/exception_notifier/gitlab_notifier.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module ExceptionNotifier + # Notifica las excepciones como incidencias en Gitlab + class GitlabNotifier + def initialize(_); end + + # Recibe la excepción y empieza la tarea de notificación en segundo + # plano. + # + # @param [Exception] + # @param [Hash] + def call(exception, **options) + GitlabNotifierJob.perform_async(exception, **options) + end + end +end diff --git a/app/lib/gitlab_api_client.rb b/app/lib/gitlab_api_client.rb new file mode 100644 index 00000000..c8157b47 --- /dev/null +++ b/app/lib/gitlab_api_client.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +require 'httparty' + +class GitlabApiClient + include HTTParty + + # TODO: Hacer configurable por sitio + base_uri ENV.fetch('GITLAB_URI', 'https://0xacab.org') + no_follow true + + # Trae todos los proyectos. Como estamos usando un Project Token, + # siempre va a traer uno solo. + # + # @return [HTTParty::Response] + def projects + self.class.get('/api/v4/projects', { query: params(membership: true) }) + end + + # Obtiene el identificador del proyecto + # + # @return [Integer] + def project_id + @project_id ||= ENV['GITLAB_PROJECT'] || projects&.first&.dig('id') + end + + # Crea un issue + # + # @see https://docs.gitlab.com/ee/api/issues.html#new-issue + # @return [HTTParty::Response] + def new_issue(**args) + self.class.post("/api/v4/projects/#{project_id}/issues", { query: params(**args) }) + end + + # Modifica un issue + # + # @see https://docs.gitlab.com/ee/api/issues.html#edit-issue + # @return [HTTParty::Response] + def edit_issue(**args) + self.class.put("/api/v4/projects/#{project_id}/issues", { query: params(**args) }) + end + + # Crea un comentario + # + # @see https://docs.gitlab.com/ee/api/notes.html#create-new-issue-note + # @return [HTTParty::Response] + def new_note(iid:, **args) + self.class.post("/api/v4/projects/#{project_id}/issues/#{iid}/notes", { query: params(**args) }) + end + + private + + def params(**args) + default_params.merge(args) + end + + # TODO: Que cada sitio tenga su propio token y uri + def default_params + { private_token: ENV['GITLAB_TOKEN'] } + end +end diff --git a/app/views/exception_notifier/_backtrace.text.erb b/app/views/exception_notifier/_backtrace.text.erb index d62b5719..aed7adbe 100644 --- a/app/views/exception_notifier/_backtrace.text.erb +++ b/app/views/exception_notifier/_backtrace.text.erb @@ -1,4 +1,4 @@ -<% unless @data[:_backtrace] %> +<% unless @data[:javascript_backtrace] %> ``` <%= raw @backtrace.join("\n") %> ``` diff --git a/app/views/exception_notifier/_data.text.erb b/app/views/exception_notifier/_data.text.erb index 09313f4c..acb94b89 100644 --- a/app/views/exception_notifier/_data.text.erb +++ b/app/views/exception_notifier/_data.text.erb @@ -1,4 +1,4 @@ -<% if @data[:_backtrace] %> +<% if @data[:javascript_backtrace] %> <% @data.dig(:params, 'errors')&.each do |error| %> # <%= error['type'] %>: <%= error['message'] %> diff --git a/config/environments/production.rb b/config/environments/production.rb index c1269fb2..d121bdbd 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -147,14 +147,7 @@ Rails.application.configure do } config.action_mailer.default_options = { from: ENV.fetch('DEFAULT_FROM', "noreply@sutty.nl") } - config.middleware.use ExceptionNotification::Rack, - error_grouping: true, - email: { - email_prefix: '', - sender_address: ENV.fetch('DEFAULT_FROM', "noreply@sutty.nl"), - exception_recipients: ENV.fetch('EXCEPTION_TO', "errors@sutty.nl"), - normalize_subject: true - } + config.middleware.use ExceptionNotification::Rack, gitlab: {} Rails.application.routes.default_url_options[:host] = "panel.#{ENV.fetch('SUTTY', 'sutty.nl')}" Rails.application.routes.default_url_options[:protocol] = 'https' From 5468a118851b92271e1d081ca386438970101d31 Mon Sep 17 00:00:00 2001 From: f Date: Mon, 31 May 2021 12:08:19 -0300 Subject: [PATCH 112/234] reportar el issue una sola vez y luego actualizarlo --- app/jobs/gitlab_notifier_job.rb | 130 +++++++++++++++++++++++++++++--- app/lib/gitlab_api_client.rb | 22 +++--- 2 files changed, 130 insertions(+), 22 deletions(-) diff --git a/app/jobs/gitlab_notifier_job.rb b/app/jobs/gitlab_notifier_job.rb index 4d676b8d..ac712ab2 100644 --- a/app/jobs/gitlab_notifier_job.rb +++ b/app/jobs/gitlab_notifier_job.rb @@ -1,42 +1,98 @@ # frozen_string_literal: true +# Notifica excepciones a una instancia de Gitlab, como incidencias +# nuevas o como comentarios a las incidencias pre-existentes. class GitlabNotifierJob < ApplicationJob include ExceptionNotifier::BacktraceCleaner - attr_reader :exception, :options + # Variables que vamos a acceder luego + attr_reader :exception, :options, :issue_data, :cached queue_as :low_priority + # @param [Exception] la excepción lanzada + # @param [Hash] opciones de ExceptionNotifier def perform(exception, **options) @exception = exception @options = options + @issue_data = { count: 1 } + # Necesitamos saber si el issue ya existía + @cached = false - Rails.logger.info 'Enviando reporte a Gitlab' + # Traemos los datos desde la caché si existen, sino generamos un + # issue nuevo e inicializamos la caché + @issue_data = Rails.cache.fetch(cache_key) do + issue = client.new_issue confidential: true, title: title, description: description, issue_type: 'incident' + @cached = true - i = client.new_issue confidential: true, title: title, description: description + { + count: 1, + issue: issue['iid'], + user_agents: [user_agent].compact, + params: [request&.filtered_parameters].compact, + urls: [url].compact + } + end - Rails.logger.info "Enviado reporte a Gitlab: #{i['iid']}" + # No seguimos actualizando si acabamos de generar el issue + return if cached + + # Incrementar la cuenta de veces que ocurrió + issue_data[:count] += 1 + # Guardar información útil + issue_data[:urls] << url unless issue_data[:urls].include? url + issue_data[:user_agents] << user_agent unless issue_data[:user_agents].include? user_agent + + # Editar el título para que incluya la cuenta de eventos + client.edit_issue(iid: issue_data[:issue], title: title, state_event: 'reopen') + + # Agregar un comentario con la información posiblemente nueva + client.new_note(iid: issue_data[:issue], body: body) + + # Guardar para después + Rails.cache.write(cache_key, issue_data) + # Si este trabajo genera una excepción va a entrar en un loop + # TODO: Notificarnos por otros medios (mail) rescue Exception => e Rails.logger.info 'No entrar en loop' end private + # La llave en la cache tiene en cuenta la excepción, el mensaje, la + # ruta del backtrace y los errores de JS + # + # @return [String] + def cache_key + @cache_key ||= [ + exception.class.name, + Digest::SHA1.hexdigest(exception.message), + Digest::SHA1.hexdigest(backtrace&.first.to_s), + Digest::SHA1.hexdigest(options.dig(:data, :params, 'errors').to_s) + ].join('/') + end + # Define si es una excepción de javascript o local # # @see BacktraceJob + # @return [Boolean] def javascript? @javascript ||= options.dig(:data, :javascript_backtrace).present? end + # Título + # # @return [String] def title @title ||= ''.dup.tap do |t| t << "[#{exception.class}] " unless javascript? t << exception.message + t << " [#{issue_data[:count]}]" end end + # Descripción + # # @return [String] def description @description ||= ''.dup.tap do |d| @@ -48,24 +104,48 @@ class GitlabNotifierJob < ApplicationJob end end - # @return [String,Nil] + # Comentario + # + # @return [String] + def body + @body ||= ''.dup.tap do |b| + b << request_section + b << javascript_footer + b << data_section + end + end + + # Cadena de archivos donde se produjo el error + # + # @return [Array,Nil] def backtrace @backtrace ||= exception.backtrace ? clean_backtrace(exception) : nil end + # Entorno del error + # + # @return [Hash] def env options[:env] end + # Genera una petición a partir del entorno + # + # @return [ActionDispatch::Request] def request @request ||= ActionDispatch::Request.new(env) if env.present? end + # Cliente de la API de Gitlab + # # @return [GitlabApiClient] def client @client ||= GitlabApiClient.new end + # Muestra información de la petición + # + # @return [String] def request_section return '' unless request @@ -74,7 +154,7 @@ class GitlabNotifierJob < ApplicationJob # Request ``` - #{request.request_method} #{request.url}#{' '} + #{request.request_method} #{url} #{pp request.filtered_parameters} ``` @@ -82,14 +162,19 @@ class GitlabNotifierJob < ApplicationJob REQUEST end + # Muestra información de JavaScript + # + # @return [String] def javascript_section return '' unless javascript? options.dig(:data, :params, 'errors')&.map do |error| + # Algunos errores no son excepciones (?) + error['type'] = 'undefined' if error['type'].blank? + <<~JAVASCRIPT - ## #{error['type'] || 'NoError'}: #{error['message']} - + ## #{error['type']}: #{error['message']} ``` #{Terminal::Table.new headings: error['backtrace'].first.keys, rows: error['backtrace'].map(&:values)} @@ -99,18 +184,24 @@ class GitlabNotifierJob < ApplicationJob end&.join end + # Muestra información de la visita que generó el error en JS + # + # @return [String] def javascript_footer return '' unless javascript? <<~JAVASCRIPT - #{options.dig(:data, :params, 'context', 'userAgent')} + #{user_agent} - <#{options.dig(:data, :params, 'context', 'url')}> + <#{url}> JAVASCRIPT end + # Muestra el historial del error en Ruby + # + # @return [String] def backtrace_section return '' if javascript? return '' unless backtrace @@ -126,6 +217,9 @@ class GitlabNotifierJob < ApplicationJob BACKTRACE end + # Muestra datos extra de la visita + # + # @return [String] def data_section return '' unless options[:data] @@ -139,4 +233,20 @@ class GitlabNotifierJob < ApplicationJob DATA end + + # Obtiene el UA de este error + # + # @return [String] + def user_agent + @user_agent ||= options.dig(:data, :params, 'context', 'userAgent') if javascript? + @user_agent ||= request.headers['user-agent'] if request + @user_agent + end + + # Obtiene la URL actual + # + # @return [String] + def url + @url ||= request&.url || options.dig(:data, :params, 'context', 'url') + end end diff --git a/app/lib/gitlab_api_client.rb b/app/lib/gitlab_api_client.rb index c8157b47..5b1287d6 100644 --- a/app/lib/gitlab_api_client.rb +++ b/app/lib/gitlab_api_client.rb @@ -7,6 +7,9 @@ class GitlabApiClient # TODO: Hacer configurable por sitio base_uri ENV.fetch('GITLAB_URI', 'https://0xacab.org') + # No seguir redirecciones. Si nos olvidamos https:// en la dirección, + # las redirecciones nos pueden llevar a cualquier lado y obtener + # resultados diferentes. no_follow true # Trae todos los proyectos. Como estamos usando un Project Token, @@ -14,7 +17,7 @@ class GitlabApiClient # # @return [HTTParty::Response] def projects - self.class.get('/api/v4/projects', { query: params(membership: true) }) + self.class.get('/api/v4/projects', { query: { membership: true }, headers: headers }) end # Obtiene el identificador del proyecto @@ -29,15 +32,15 @@ class GitlabApiClient # @see https://docs.gitlab.com/ee/api/issues.html#new-issue # @return [HTTParty::Response] def new_issue(**args) - self.class.post("/api/v4/projects/#{project_id}/issues", { query: params(**args) }) + self.class.post("/api/v4/projects/#{project_id}/issues", { body: args, headers: headers }) end # Modifica un issue # # @see https://docs.gitlab.com/ee/api/issues.html#edit-issue # @return [HTTParty::Response] - def edit_issue(**args) - self.class.put("/api/v4/projects/#{project_id}/issues", { query: params(**args) }) + def edit_issue(iid:, **args) + self.class.put("/api/v4/projects/#{project_id}/issues/#{iid}", { body: args, headers: headers }) end # Crea un comentario @@ -45,17 +48,12 @@ class GitlabApiClient # @see https://docs.gitlab.com/ee/api/notes.html#create-new-issue-note # @return [HTTParty::Response] def new_note(iid:, **args) - self.class.post("/api/v4/projects/#{project_id}/issues/#{iid}/notes", { query: params(**args) }) + self.class.post("/api/v4/projects/#{project_id}/issues/#{iid}/notes", { body: args, headers: headers }) end private - def params(**args) - default_params.merge(args) - end - - # TODO: Que cada sitio tenga su propio token y uri - def default_params - { private_token: ENV['GITLAB_TOKEN'] } + def headers(extra = {}) + { 'Authorization' => "Bearer #{ENV['GITLAB_TOKEN']}" }.merge(extra) end end From 4540ea73ad6f3353f63deae5506e1e47991bb8e3 Mon Sep 17 00:00:00 2001 From: f Date: Mon, 31 May 2021 12:18:06 -0300 Subject: [PATCH 113/234] variables de entorno nuevas --- .env.example | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.env.example b/.env.example index 2ba46219..a1348593 100644 --- a/.env.example +++ b/.env.example @@ -29,3 +29,6 @@ TIENDA=tienda.sutty.local PANEL_URL=https://panel.sutty.nl AIRBRAKE_SITE_ID=1 AIRBRAKE_API_KEY= +GITLAB_URI=https://0xacab.org +GITLAB_PROJECT= +GITLAB_TOKEN= From c6bcf95f345fccc3d7519b8ec98699b33fb9cc42 Mon Sep 17 00:00:00 2001 From: f Date: Mon, 31 May 2021 12:18:23 -0300 Subject: [PATCH 114/234] =?UTF-8?q?si=20la=20notificaci=C3=B3n=20produce?= =?UTF-8?q?=20una=20excepci=C3=B3n,=20capturarla=20por=20correo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/jobs/gitlab_notifier_job.rb | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/app/jobs/gitlab_notifier_job.rb b/app/jobs/gitlab_notifier_job.rb index ac712ab2..7218f68a 100644 --- a/app/jobs/gitlab_notifier_job.rb +++ b/app/jobs/gitlab_notifier_job.rb @@ -51,14 +51,22 @@ class GitlabNotifierJob < ApplicationJob # Guardar para después Rails.cache.write(cache_key, issue_data) - # Si este trabajo genera una excepción va a entrar en un loop - # TODO: Notificarnos por otros medios (mail) + # Si este trabajo genera una excepción va a entrar en un loop, así que + # la notificamos por correo rescue Exception => e - Rails.logger.info 'No entrar en loop' + email_notification.call(e) + email_notification.call(exception, options) end private + # Notificar por correo + # + # @return [ExceptionNotifier::EmailNotifier] + def email_notification + @email_notification ||= ExceptionNotifier::EmailNotifier.new(email_prefix: '[ERROR] ', sender_address: ENV['DEFAULT_FROM'], exception_recipients: ENV['EXCEPTION_TO']) + end + # La llave en la cache tiene en cuenta la excepción, el mensaje, la # ruta del backtrace y los errores de JS # From 7191baff4abd7d6358769a5aebdd032e84c80aba Mon Sep 17 00:00:00 2001 From: f Date: Mon, 31 May 2021 13:23:40 -0300 Subject: [PATCH 115/234] =?UTF-8?q?reindexar=20despu=C3=A9s=20de=20mergear?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/sites_controller.rb | 2 +- app/services/site_service.rb | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/app/controllers/sites_controller.rb b/app/controllers/sites_controller.rb index 20ce5bad..bdaa9011 100644 --- a/app/controllers/sites_controller.rb +++ b/app/controllers/sites_controller.rb @@ -107,7 +107,7 @@ class SitesController < ApplicationController def merge authorize site - if site.repository.merge(current_usuarie) + if SiteService.new(site: site, usuarie: current_usuarie).merge flash[:success] = I18n.t('sites.fetch.merge.success') else flash[:error] = I18n.t('sites.fetch.merge.error') diff --git a/app/services/site_service.rb b/app/services/site_service.rb index 4f3905a5..389549c3 100644 --- a/app/services/site_service.rb +++ b/app/services/site_service.rb @@ -61,6 +61,18 @@ SiteService = Struct.new(:site, :usuarie, :params, keyword_init: true) do commit_config(action: :tor) end + # Trae cambios desde la rama remota y reindexa los artículos. + # + # @return [Boolean] + def merge + result = site.repository.merge(usuarie) + + # TODO: Implementar callbacks + site.try(:index_posts!) if result + + result.present? + end + private # Guarda los cambios de la configuración en el repositorio git From 39ffe9e926b2f2722d1e5bb83661ba65d3b77b66 Mon Sep 17 00:00:00 2001 From: Maki Date: Thu, 3 Jun 2021 16:09:26 -0300 Subject: [PATCH 116/234] edit_posts recuperado #1823 --- config/locales/es.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/config/locales/es.yml b/config/locales/es.yml index 1ce50b09..4361f790 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -180,6 +180,8 @@ es: category: 'Categoría' sites: index: 'Este es el listado de sitios que puedes editar.' + edit_posts: 'Aquí verás el listado de todos los artículos y podrás +- editarlos o crear nuevos' enqueued: 'El sitio está en la cola de espera para ser generado. Una vez que este proceso termine, recibirás un correo indicando el estado y si todo fue bien, se publicarán los cambios en tu sitio From 9ea199876d49a74659648f3e86a3a665c5f3eaa9 Mon Sep 17 00:00:00 2001 From: f Date: Mon, 7 Jun 2021 13:37:18 -0300 Subject: [PATCH 117/234] =?UTF-8?q?cargar=20los=20layouts=20para=20poder?= =?UTF-8?q?=20acceder=20a=20la=20versi=C3=B3n=20preliminar?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cuando separamos la lectura de datos de jekyll había faltado leer los layouts para poder aplicarlos a la version preliminar. con este cambio solo se leen cuando se los va a usar. fixes #2006 --- app/models/post.rb | 3 +++ app/models/site.rb | 7 +++++++ config/initializers/core_extensions.rb | 5 +++++ 3 files changed, 15 insertions(+) diff --git a/app/models/post.rb b/app/models/post.rb index 31a269e7..d6baee84 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -69,6 +69,9 @@ class Post # TODO: Cambiar el locale en otro lado l = lang.value.to_s site.jekyll.config['locale'] = site.jekyll.config['lang'] = l + # XXX: Es necesario leer los layouts para poder renderizar el + # sitio + site.theme_layouts # Payload básico con traducciones. document.renderer.payload = { diff --git a/app/models/site.rb b/app/models/site.rb index a4349f57..6466384a 100644 --- a/app/models/site.rb +++ b/app/models/site.rb @@ -283,6 +283,13 @@ class Site < ApplicationRecord layout_keys.include? layout.to_sym end + # Lee los layouts en HTML desde el sitio + # + # @return [Hash] + def theme_layouts + @jekyll.reader.read_layouts + end + # Trae todos los valores disponibles para un campo # # TODO: Traer recursivamente, si el campo contiene Hash diff --git a/config/initializers/core_extensions.rb b/config/initializers/core_extensions.rb index d43a5116..c1e8d652 100644 --- a/config/initializers/core_extensions.rb +++ b/config/initializers/core_extensions.rb @@ -55,6 +55,11 @@ module Jekyll @site.data = DataReader.new(site).read(site.config['data_dir']) end + # Lee los layouts + def read_layouts + @site.layouts = LayoutReader.new(site).read unless @site.layouts.present? + end + # Lee todos los artículos del sitio def read_collections read_directories From 57e86f3cfbb2fa2a9719be47e42c9cd3244bc87a Mon Sep 17 00:00:00 2001 From: f Date: Tue, 8 Jun 2021 14:36:48 -0300 Subject: [PATCH 118/234] usar postgresql en desarrollo --- Makefile | 8 ++++++-- config/database.yml | 14 +++++++++----- config/puma.rb | 11 +++++++++-- 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/Makefile b/Makefile index c7f4fd39..e0da9ddc 100644 --- a/Makefile +++ b/Makefile @@ -38,8 +38,11 @@ test/%_test.rb: always test: always $(hain) 'cd /Sutty/sutty; RAILS_ENV=test bundle exec rake test' -serve: /etc/hosts - $(hain) 'cd /Sutty/sutty; bundle exec rails s -b "ssl://0.0.0.0:3000?key=../sutty.local/domain/$(SUTTY).key&cert=../sutty.local/domain/$(SUTTY).crt"' +postgresql: /etc/hosts + pgrep postgres >/dev/null || $(hain) postgresql + +serve: /etc/hosts postgresql + $(MAKE) rails args=server # make rails args="db:migrate" rails: @@ -129,5 +132,6 @@ ota-rb: @grep -q " $(SUTTY)$$" $@ || echo -e "127.0.0.1 $(SUTTY)\n::1 $(SUTTY)" | sudo tee -a $@ @grep -q " api.$(SUTTY)$$" $@ || echo -e "127.0.0.1 api.$(SUTTY)\n::1 api.$(SUTTY)" | sudo tee -a $@ @grep -q " panel.$(SUTTY)$$" $@ || echo -e "127.0.0.1 panel.$(SUTTY)\n::1 panel.$(SUTTY)" | sudo tee -a $@ + @grep -q " postgresql.$(SUTTY)$$" $@ || echo -e "127.0.0.1 postgresql.$(SUTTY)\n::1 postgresql.$(SUTTY)" | sudo tee -a $@ .PHONY: always diff --git a/config/database.yml b/config/database.yml index 7989a013..cd599a24 100644 --- a/config/database.yml +++ b/config/database.yml @@ -5,20 +5,25 @@ # gem 'sqlite3' # default: &default - adapter: sqlite3 - pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> timeout: 5000 + adapter: postgresql + pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> + encoding: unicode development: <<: *default - database: db/development.sqlite3 + database: 'sutty' + host: 'postgresql.sutty.local' + user: <%= ENV['USER'] %> # Warning: The database defined as "test" will be erased and # re-generated from your development database when you run "rake". # Do not set this db to the same as development or production. test: <<: *default - database: db/test.sqlite3 + database: 'sutty_test' + host: 'postgresql.sutty.local' + user: <%= ENV['USER'] %> production: adapter: postgresql @@ -26,4 +31,3 @@ production: database: <%= ENV.fetch('DATABASE') { 'sutty' } %> user: sutty host: postgresql - encoding: unicode diff --git a/config/puma.rb b/config/puma.rb index 48ff0e62..60ee5ecc 100644 --- a/config/puma.rb +++ b/config/puma.rb @@ -19,8 +19,15 @@ worker_timeout 3600 if ENV.fetch('RAILS_ENV', 'development') == 'development' # Specifies the `port` that Puma will listen on to receive requests; # default is 3000. # -# port ENV.fetch('PORT') { 3000 } -bind 'tcp://[::]:3100' +# XXX: Por alguna razón el puerto en el contenedor es históricamente +# 3100, aunque en desarrollo es 3000. En algún momento deberíamos +# establecer un solo puerto. +if ENV['RAILS_ENV'] == 'production' + bind 'tcp://[::]:3100' +else + sutty = ENV.fetch('SUTTY', 'sutty.local') + bind "ssl://[::]:3000?key=../sutty.local/domain/#{sutty}.key&cert=../sutty.local/domain/#{sutty}.crt" +end # Specifies the `environment` that Puma will run in. # From e014ab166aad9afbeb49c3d6390a53681271a7f2 Mon Sep 17 00:00:00 2001 From: fauno Date: Tue, 8 Jun 2021 17:52:41 +0000 Subject: [PATCH 119/234] poder traer actualizaciones desde repositorios remotos --- app/models/site/repository.rb | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/app/models/site/repository.rb b/app/models/site/repository.rb index d9695227..74db2549 100644 --- a/app/models/site/repository.rb +++ b/app/models/site/repository.rb @@ -44,8 +44,8 @@ class Site # # @return [Integer] def fetch - if origin.check_connection :fetch - rugged.fetch(origin)[:received_objects] + if origin.check_connection(:fetch, credentials: credentials) + rugged.fetch(origin, credentials: credentials)[:received_objects] else 0 end @@ -149,6 +149,26 @@ class Site private + # Si Sutty tiene una llave privada de tipo ED25519, devuelve las + # credenciales necesarias para trabajar con repositorios remotos. + # + # @return [Nil, Rugged::Credentials::SshKey] + def credentials + return unless File.exist? private_key + + @credentials ||= Rugged::Credentials::SshKey.new username: 'git', publickey: public_key, privatekey: private_key + end + + # @return [String] + def public_key + @public_key ||= Rails.root.join('.ssh', 'id_ed25519.pub').to_s + end + + # @return [String] + def private_key + @private_key ||= Rails.root.join('.ssh', 'id_ed25519').to_s + end + def relativize(file) Pathname.new(file).relative_path_from(Pathname.new(path)).to_s end From a2669e0aa44a3bcd568535d937a143abe47b26ed Mon Sep 17 00:00:00 2001 From: f Date: Tue, 15 Jun 2021 17:07:52 -0300 Subject: [PATCH 120/234] =?UTF-8?q?actualizaci=C3=B3n=20de=20cuidados?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Gemfile.lock | 69 +++++++++++++++++++++++++--------------------------- 1 file changed, 33 insertions(+), 36 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 942d7d6f..e0c19860 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -89,11 +89,11 @@ GEM jekyll-relative-urls (~> 0.0) jekyll-seo-tag (~> 2.1) ast (2.4.2) - autoprefixer-rails (10.2.5.0) - execjs (< 2.8.0) + autoprefixer-rails (10.2.5.1) + execjs (> 0) bcrypt (3.1.16-x86_64-linux-musl) bcrypt_pbkdf (1.1.0-x86_64-linux-musl) - benchmark-ips (2.8.4) + benchmark-ips (2.9.1) bindex (0.8.1-x86_64-linux-musl) blazer (2.4.2) activerecord (>= 5) @@ -104,7 +104,7 @@ GEM autoprefixer-rails (>= 9.1.0) popper_js (>= 1.14.3, < 2) sassc-rails (>= 2.0.0) - brakeman (5.0.1) + brakeman (5.0.4) builder (3.2.4) capybara (2.18.0) addressable @@ -119,9 +119,9 @@ GEM colorator (1.1.0) commonmarker (0.21.2-x86_64-linux-musl) ruby-enum (~> 0.5) - concurrent-ruby (1.1.8) - concurrent-ruby-ext (1.1.8-x86_64-linux-musl) - concurrent-ruby (= 1.1.8) + concurrent-ruby (1.1.9) + concurrent-ruby-ext (1.1.9-x86_64-linux-musl) + concurrent-ruby (= 1.1.9) crass (1.0.6) database_cleaner (2.0.1) database_cleaner-active_record (~> 2.0.0) @@ -157,7 +157,7 @@ GEM dotenv-rails (2.7.6) dotenv (= 2.7.6) railties (>= 3.2) - down (5.2.1) + down (5.2.2) addressable (~> 2.5) ed25519 (1.2.4-x86_64-linux-musl) editorial-autogestiva-jekyll-theme (0.3.4) @@ -188,7 +188,7 @@ GEM exception_notification (4.4.3) actionmailer (>= 4.0, < 7) activesupport (>= 4.0, < 7) - execjs (2.7.0) + execjs (2.8.1) factory_bot (6.2.0) activesupport (>= 5.0.0) factory_bot_rails (6.2.0) @@ -196,7 +196,7 @@ GEM railties (>= 5.0.0) fast_blank (1.0.0-x86_64-linux-musl) fast_jsonparser (0.5.0-x86_64-linux-musl) - ffi (1.15.0-x86_64-linux-musl) + ffi (1.15.1-x86_64-linux-musl) flamegraph (0.9.5) forwardable-extended (2.6.0) friendly_id (5.4.2) @@ -285,8 +285,6 @@ GEM jekyll-locales (0.1.12) jekyll-lunr (0.2.0) loofah (~> 2.4) - jekyll-node-modules (0.1.0) - jekyll (~> 4) jekyll-order (0.1.4) jekyll-relative-urls (0.0.6) jekyll (~> 4) @@ -328,7 +326,7 @@ GEM activesupport (>= 4) railties (>= 4) request_store (~> 1.0) - loofah (2.9.1) + loofah (2.10.0) crass (~> 1.0.2) nokogiri (>= 1.5.9) mail (2.7.1) @@ -343,7 +341,6 @@ GEM mini_histogram (0.3.1) mini_magick (4.11.0) mini_mime (1.0.3) - mini_portile2 (2.5.1) minima (2.5.1) jekyll (>= 3.5, < 5.0) jekyll-feed (~> 0.9) @@ -356,8 +353,7 @@ GEM net-ssh (6.1.0) netaddr (2.0.4) nio4r (2.5.7-x86_64-linux-musl) - nokogiri (1.11.5-x86_64-linux-musl) - mini_portile2 (~> 2.5.0) + nokogiri (1.11.7-x86_64-linux) racc (~> 1.4) orm_adapter (0.5.0) parallel (1.20.1) @@ -373,7 +369,7 @@ GEM coderay (~> 1.1) method_source (~> 1.0) public_suffix (4.0.6) - puma (5.3.1-x86_64-linux-musl) + puma (5.3.2-x86_64-linux-musl) nio4r (~> 2.0) pundit (2.1.0) activesupport (>= 3.0.0) @@ -383,11 +379,11 @@ GEM rack (>= 2.0.0) rack-mini-profiler (2.3.2) rack (>= 1.2.0) - rack-proxy (0.6.5) + rack-proxy (0.7.0) rack rack-test (1.1.0) rack (>= 1.0, < 3) - radios-comunitarias-jekyll-theme (0.1.4) + radios-comunitarias-jekyll-theme (0.1.5) jekyll (~> 4.0) jekyll-data (~> 1.1) jekyll-feed (~> 0.9) @@ -434,29 +430,30 @@ GEM rb-fsevent (0.11.0) rb-inotify (0.10.1) ffi (~> 1.0) - recursero-jekyll-theme (0.1.3) - jekyll (~> 4.0) + recursero-jekyll-theme (0.2.0) + jekyll (~> 4) + jekyll-commonmark (~> 1.3) jekyll-data (~> 1.1) - jekyll-feed (~> 0.9) + jekyll-dotenv (>= 0.2) + jekyll-feed (~> 0.15) + jekyll-ignore-layouts (~> 0) jekyll-images (~> 0.2) jekyll-include-cache (~> 0) - jekyll-linked-posts (~> 0.2) + jekyll-linked-posts (~> 0) jekyll-locales (~> 0.1) jekyll-lunr (~> 0.1) - jekyll-node-modules (~> 0.1) - jekyll-order (~> 0.1) - jekyll-relative-urls (~> 0.0) - jekyll-seo-tag (~> 2.1) - jekyll-turbolinks (~> 0) + jekyll-order (~> 0) + jekyll-relative-urls (~> 0) + jekyll-seo-tag (~> 2) jekyll-unique-urls (~> 0.1) sutty-archives (~> 2.2) - sutty-liquid (~> 0.1) - redis (4.2.5) + sutty-liquid (~> 0) + redis (4.3.1) redis-actionpack (5.2.0) actionpack (>= 5, < 7) redis-rack (>= 2.1.0, < 3) redis-store (>= 1.1.0, < 2) - redis-activesupport (5.2.0) + redis-activesupport (5.2.1) activesupport (>= 3, < 7) redis-store (>= 1.3, < 2) redis-rack (2.1.3) @@ -476,16 +473,16 @@ GEM railties (>= 5.0) rexml (3.2.5) rouge (3.26.0) - rubocop (1.15.0) + rubocop (1.17.0) parallel (~> 1.10) parser (>= 3.0.0.0) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 1.8, < 3.0) rexml - rubocop-ast (>= 1.5.0, < 2.0) + rubocop-ast (>= 1.7.0, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 1.4.0, < 3.0) - rubocop-ast (1.5.0) + rubocop-ast (1.7.0) parser (>= 3.0.1.1) rubocop-rails (2.10.1) activesupport (>= 4.2.0) @@ -571,7 +568,7 @@ GEM jekyll (>= 3.5, < 5.0) jekyll-feed (~> 0.9) jekyll-seo-tag (~> 2.1) - symbol-fstring (1.0.0-x86_64-linux-musl) + symbol-fstring (1.0.2-x86_64-linux-musl) sysexits (1.2.0) temple (0.8.2) terminal-table (2.0.0) @@ -606,7 +603,7 @@ GEM railties (>= 5.2) semantic_range (>= 2.3.0) webrick (1.7.0) - websocket-driver (0.7.3-x86_64-linux-musl) + websocket-driver (0.7.5-x86_64-linux-musl) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) xpath (3.2.0) From 92a292c42b856935fef87f51048f1716e111500e Mon Sep 17 00:00:00 2001 From: f Date: Tue, 15 Jun 2021 19:04:00 -0300 Subject: [PATCH 121/234] no fallar el container si vendor no existe --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 3781772b..0b3253b4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -53,7 +53,7 @@ RUN cd ../checkout && git checkout $BRANCH WORKDIR /home/app/checkout # Traer las gemas: -RUN rm -r ./vendor +RUN rm -rf ./vendor RUN mv ../sutty/vendor ./vendor RUN mv ../sutty/.bundle ./.bundle From 7b5b24ab2ba8e4ad66b18672cda4a25b2fe82a50 Mon Sep 17 00:00:00 2001 From: f Date: Tue, 15 Jun 2021 20:40:18 -0300 Subject: [PATCH 122/234] actualizar sutty-liquid MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit las versiones anteriores no verifican correctamente la fecha en el filtro `date_local` y fallan en fechas incorrectas, especialmente en la vista previa de los artículos. fixes #2100 fixes #2092 --- Gemfile | 2 +- Gemfile.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile b/Gemfile index 5f1cdc4f..aa2d841e 100644 --- a/Gemfile +++ b/Gemfile @@ -48,7 +48,7 @@ gem 'jekyll-data', source: 'https://gems.sutty.nl' gem 'jekyll-commonmark' gem 'jekyll-images' gem 'jekyll-include-cache' -gem 'sutty-liquid' +gem 'sutty-liquid', '>= 0.7.3' gem 'loaf' gem 'lockbox' gem 'mini_magick' diff --git a/Gemfile.lock b/Gemfile.lock index 5484ad88..d96f1fe9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -564,7 +564,7 @@ GEM jekyll-include-cache (~> 0) jekyll-relative-urls (~> 0.0) jekyll-seo-tag (~> 2.1) - sutty-liquid (0.7.2) + sutty-liquid (0.7.3) fast_blank (~> 1.0) jekyll (~> 4) sutty-minima (2.5.0) From 810c5e32da4157813b905c2320ee4c25ebe1f848 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 16 Jun 2021 09:11:13 -0300 Subject: [PATCH 123/234] convertir valores en arrays por retrocompatibilidad MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit esto permite indexar sitios antiguos y cargar artículos sin generar errores. --- app/models/metadata_array.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/models/metadata_array.rb b/app/models/metadata_array.rb index 9f5a84b6..96d3be3c 100644 --- a/app/models/metadata_array.rb +++ b/app/models/metadata_array.rb @@ -23,6 +23,14 @@ class MetadataArray < MetadataTemplate value.join(', ') end + # Obtiene el valor desde el documento, convirtiéndolo a Array si no lo + # era ya, por retrocompabilidad. + # + # @return [Array] + def document_value + [super].flatten(1) + end + alias indexable_values values private From 78a3cae077fae9bf5415e24db782db34fc5c7477 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 16 Jun 2021 09:13:40 -0300 Subject: [PATCH 124/234] solo indexar los valores actuales MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit sin este cambio los artículos se indexaban por todas las categorías posibles! --- app/models/metadata_array.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/metadata_array.rb b/app/models/metadata_array.rb index 96d3be3c..0527ccb8 100644 --- a/app/models/metadata_array.rb +++ b/app/models/metadata_array.rb @@ -31,7 +31,7 @@ class MetadataArray < MetadataTemplate [super].flatten(1) end - alias indexable_values values + alias indexable_values value private From c3a8b2401ceb249599e7dc39b16a6d0ec9d42b6f Mon Sep 17 00:00:00 2001 From: f Date: Wed, 16 Jun 2021 11:35:37 -0300 Subject: [PATCH 125/234] estandarizar la forma de obtener el valor de los documentos MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit teníamos dos métodos que hacían lo mismo y generaban conflictos al obtener el valor por defecto de los arrays cuando no eran arrays. --- app/models/metadata_document_date.rb | 7 ++++--- app/models/metadata_lang.rb | 5 +++-- app/models/metadata_markdown_content.rb | 5 +++-- app/models/metadata_path.rb | 8 ++++++-- app/models/metadata_template.rb | 8 ++------ 5 files changed, 18 insertions(+), 15 deletions(-) diff --git a/app/models/metadata_document_date.rb b/app/models/metadata_document_date.rb index 34cbe1c5..324e4be8 100644 --- a/app/models/metadata_document_date.rb +++ b/app/models/metadata_document_date.rb @@ -7,7 +7,8 @@ class MetadataDocumentDate < MetadataTemplate Date.today.to_time end - def value_from_document + # @return [Time] + def document_value return nil if post.new? document.date @@ -45,10 +46,10 @@ class MetadataDocumentDate < MetadataTemplate begin Date.iso8601(self[:value]).to_time rescue Date::Error - value_from_document || default_value + document_value || default_value end else - self[:value] || value_from_document || default_value + self[:value] || document_value || default_value end end diff --git a/app/models/metadata_lang.rb b/app/models/metadata_lang.rb index 5f31ee9d..ff6c08e6 100644 --- a/app/models/metadata_lang.rb +++ b/app/models/metadata_lang.rb @@ -6,12 +6,13 @@ class MetadataLang < MetadataTemplate super || I18n.locale end - def value_from_document + # @return [Symbol] + def document_value document.collection.label.to_sym end def value - self[:value] ||= value_from_document || default_value + self[:value] ||= document_value || default_value end def values diff --git a/app/models/metadata_markdown_content.rb b/app/models/metadata_markdown_content.rb index d3cc6dec..92a1ab21 100644 --- a/app/models/metadata_markdown_content.rb +++ b/app/models/metadata_markdown_content.rb @@ -9,14 +9,15 @@ class MetadataMarkdownContent < MetadataText end def value - self[:value] || value_from_document || default_value + self[:value] || document_value || default_value end def front_matter? false end - def value_from_document + # @return [String] + def document_value document.content end diff --git a/app/models/metadata_path.rb b/app/models/metadata_path.rb index 3c93cca6..95fc7dbb 100644 --- a/app/models/metadata_path.rb +++ b/app/models/metadata_path.rb @@ -3,12 +3,16 @@ # Este campo representa el archivo donde se almacenan los datos class MetadataPath < MetadataTemplate # :label en este caso es el idioma/colección + # + # @return [String] def default_value File.join(site.path, "_#{lang}", "#{date}-#{slug}#{ext}") end - # El valor no vuelve desde el documento - def value_from_document + # La ruta del archivo según Jekyll + # + # @return [String] + def document_value document.path end diff --git a/app/models/metadata_template.rb b/app/models/metadata_template.rb index 638bdb8e..76c3e7b5 100644 --- a/app/models/metadata_template.rb +++ b/app/models/metadata_template.rb @@ -49,11 +49,7 @@ MetadataTemplate = Struct.new(:site, :document, :name, :label, :type, def value_was return @value_was if instance_variable_defined? '@value_was' - @value_was = value_from_document - end - - def value_from_document - @value_from_document ||= document.data[name.to_s] + @value_was = document_value end def changed? @@ -85,7 +81,7 @@ MetadataTemplate = Struct.new(:site, :document, :name, :label, :type, # Valor actual o por defecto. Al memoizarlo podemos modificarlo # usando otros métodos que el de asignación. def value - self[:value] ||= if (data = value_from_document).present? + self[:value] ||= if (data = document_value).present? private? ? decrypt(data) : data else default_value From 96a16379bdbd2f3afccc03e75da9219cf354e567 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 16 Jun 2021 12:08:05 -0300 Subject: [PATCH 126/234] correr yarn en hain --- Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Makefile b/Makefile index e0da9ddc..ab630b9b 100644 --- a/Makefile +++ b/Makefile @@ -54,6 +54,9 @@ rake: bundle: $(hain) 'cd /Sutty/sutty; bundle $(args)' +yarn: + $(hain) 'yarn $(args)' + # Servir JS con el dev server. # Esto acelera la compilación del javascript, tiene que correrse por separado # de serve. From bd3b117282bd52cc5d3fc1e663dea34bcbf83fd3 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 16 Jun 2021 12:08:21 -0300 Subject: [PATCH 127/234] aplicar parches en el servidor --- Makefile | 9 +++++++++ ota.sh | 10 ++++++++++ 2 files changed, 19 insertions(+) create mode 100755 ota.sh diff --git a/Makefile b/Makefile index ab630b9b..f3a74f57 100644 --- a/Makefile +++ b/Makefile @@ -124,10 +124,19 @@ ota: assets ssh $(delegate) docker exec $(container) sh -c "cat /srv/http/tmp/puma.pid | xargs -r kill -USR2" # Hotfixes +# +# TODO: Reemplazar esto por git pull en el contenedor commit ?= origin/rails ota-rb: umask 022; git format-patch $(commit) scp ./0*.patch $(delegate):/tmp/ + ssh $(delegate) mkdir -p /tmp/patches-$(commit)/ + scp ./0*.patch $(delegate):/tmp/patches-$(commit)/ + scp ./ota.sh $(delegate):/tmp/ + ssh $(delegate) docker cp /tmp/patches-$(commit) $(container):/tmp/ + ssh $(delegate) docker cp /tmp/ota.sh $(container):/usr/local/bin/ota + ssh $(delegate) docker exec $(container) apk add --no-cache patch + ssh $(delegate) docker exec $(container) ota $(commit) rm ./0*.patch /etc/hosts: always diff --git a/ota.sh b/ota.sh new file mode 100755 index 00000000..68a0642f --- /dev/null +++ b/ota.sh @@ -0,0 +1,10 @@ +#!/bin/sh +set -e + +cd /srv/http + +for patch in /tmp/patches-${1}/*.patch; do + su -c "patch -Np 1 -i ${patch}" app && rm $patch +done + +cat tmp/puma.pid | xargs -r kill -USR2 From a4716a0ad1af8f4bd49e263ba5a152ccac4f16ef Mon Sep 17 00:00:00 2001 From: void Date: Fri, 18 Jun 2021 22:47:42 +0000 Subject: [PATCH 128/234] usar certificados de haini.sh para dev en vez de sutty.local, ver https://0xacab.org/sutty/haini.sh/-/merge_requests/18 --- config/puma.rb | 2 +- config/webpacker.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/config/puma.rb b/config/puma.rb index 60ee5ecc..414507ed 100644 --- a/config/puma.rb +++ b/config/puma.rb @@ -26,7 +26,7 @@ if ENV['RAILS_ENV'] == 'production' bind 'tcp://[::]:3100' else sutty = ENV.fetch('SUTTY', 'sutty.local') - bind "ssl://[::]:3000?key=../sutty.local/domain/#{sutty}.key&cert=../sutty.local/domain/#{sutty}.crt" + bind "ssl://[::]:3000?key=/etc/ssl/private/#{sutty}.key&cert=/etc/ssl/certs/#{sutty}.crt" end # Specifies the `environment` that Puma will run in. diff --git a/config/webpacker.yml b/config/webpacker.yml index 25519907..d555770d 100644 --- a/config/webpacker.yml +++ b/config/webpacker.yml @@ -56,8 +56,8 @@ development: # Reference: https://webpack.js.org/configuration/dev-server/ dev_server: https: - key: '../sutty.local/domain/sutty.local.key' - cert: '../sutty.local/domain/sutty.local.crt' + key: '/etc/ssl/private/sutty.local.key' + cert: '/etc/ssl/certs/sutty.local.crt' # XXX: esto está hardcodeado, debería conseguirlo del ENV host: sutty.local port: 3035 From c7b7d660e1d3eaf5a2eb1e41e99a4ef215c30d59 Mon Sep 17 00:00:00 2001 From: void Date: Sat, 19 Jun 2021 14:59:37 +0000 Subject: [PATCH 129/234] arreglar el atributo `markdown` es igual a markdown_content, pero como estaba antes causaba #2102 por que le faltaba .markdown-editor. fixes #2102 --- app/views/posts/attributes/_markdown.haml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/views/posts/attributes/_markdown.haml b/app/views/posts/attributes/_markdown.haml index 325beb5c..8042009f 100644 --- a/app/views/posts/attributes/_markdown.haml +++ b/app/views/posts/attributes/_markdown.haml @@ -1,8 +1,8 @@ .form-group.markdown-content = label_tag "#{base}_#{attribute}", post_label_t(attribute, post: post) + = render 'posts/attribute_feedback', + post: post, attribute: attribute, metadata: metadata = text_area_tag "#{base}[#{attribute}]", metadata.value, dir: dir, lang: locale, **field_options(attribute, metadata, class: 'content') - .editor.mt-1 - = render 'posts/attribute_feedback', - post: post, attribute: attribute, metadata: metadata + .markdown-editor.mt-1 From 06e958710faf9a03d90ee585b1c1e76fbca15cab Mon Sep 17 00:00:00 2001 From: f Date: Sat, 26 Jun 2021 20:34:13 -0300 Subject: [PATCH 130/234] no guardar valores nulos --- app/models/metadata_array.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/metadata_array.rb b/app/models/metadata_array.rb index 0527ccb8..368aa546 100644 --- a/app/models/metadata_array.rb +++ b/app/models/metadata_array.rb @@ -28,7 +28,7 @@ class MetadataArray < MetadataTemplate # # @return [Array] def document_value - [super].flatten(1) + [super].flatten(1).compact end alias indexable_values value From 66d1846cea03be25fbb3337c711b97edd57fdb8d Mon Sep 17 00:00:00 2001 From: f Date: Sat, 26 Jun 2021 20:34:33 -0300 Subject: [PATCH 131/234] =?UTF-8?q?la=20fecha=20cambi=C3=B3=20si=20se=20ac?= =?UTF-8?q?aba=20de=20inicializar=20el=20post?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/models/post_test.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/test/models/post_test.rb b/test/models/post_test.rb index f98d7af3..52e59a8e 100644 --- a/test/models/post_test.rb +++ b/test/models/post_test.rb @@ -110,7 +110,6 @@ class PostTest < ActiveSupport::TestCase end test 'se puede cambiar la fecha' do - assert_not @post.date.changed? assert @post.date.valid? ex_date = @post.date.value From fd9350cd10af43659befb4a26b81635927f0033f Mon Sep 17 00:00:00 2001 From: f Date: Sat, 26 Jun 2021 20:34:53 -0300 Subject: [PATCH 132/234] permitir autocompletar tests en make --- Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index f3a74f57..c083ddf8 100644 --- a/Makefile +++ b/Makefile @@ -32,7 +32,8 @@ public/packs/manifest.json.br: $(assets) assets: public/packs/manifest.json.br -test/%_test.rb: always +tests := $(shell find test/ -name "*_test.rb") +$(tests): always $(hain) 'cd /Sutty/sutty; bundle exec rake test TEST="$@" RAILS_ENV=test' test: always From 69577e6495dca362e379f16ace1320dddf739579 Mon Sep 17 00:00:00 2001 From: f Date: Sat, 26 Jun 2021 20:35:19 -0300 Subject: [PATCH 133/234] sutty-liquid 0.7.3 no falla con algunas fechas --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index d99ed497..8e7669f7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -710,7 +710,7 @@ DEPENDENCIES sucker_punch sutty-donaciones-jekyll-theme sutty-jekyll-theme - sutty-liquid + sutty-liquid (>= 0.7.3) sutty-minima symbol-fstring terminal-table From f19bfe153931d6100b7da2dfe70438e809f33bad Mon Sep 17 00:00:00 2001 From: Maki Date: Mon, 28 Jun 2021 10:18:20 -0300 Subject: [PATCH 134/234] una linea --- config/locales/es.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/config/locales/es.yml b/config/locales/es.yml index 4361f790..0d16f3f9 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -180,8 +180,7 @@ es: category: 'Categoría' sites: index: 'Este es el listado de sitios que puedes editar.' - edit_posts: 'Aquí verás el listado de todos los artículos y podrás -- editarlos o crear nuevos' + edit_posts: 'Aquí verás el listado de todos los artículos y podrás editarlos o crear nuevos' enqueued: 'El sitio está en la cola de espera para ser generado. Una vez que este proceso termine, recibirás un correo indicando el estado y si todo fue bien, se publicarán los cambios en tu sitio From 2ceeeff86c23dcdd32d2c05d3164c11c6e3712fe Mon Sep 17 00:00:00 2001 From: f Date: Sat, 10 Jul 2021 20:03:19 -0300 Subject: [PATCH 135/234] Mejorar el entorno de desarrollo fixes #1190 fixes #732 --- .env.example | 2 + Makefile | 180 +++++++++++++++++++++++++-------------------------- README.md | 21 ++++++ 3 files changed, 112 insertions(+), 91 deletions(-) diff --git a/.env.example b/.env.example index a1348593..a62e2b0a 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,5 @@ +DELEGATE=athshe.sutty.nl +HAINISH=../haini.sh/haini.sh DATABASE= RAILS_ENV= IMAP_SERVER= diff --git a/Makefile b/Makefile index c083ddf8..a4480476 100644 --- a/Makefile +++ b/Makefile @@ -1,134 +1,111 @@ -.SHELL := /bin/bash -# Incluir las variables de entorno -mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST))) -root_dir := $(patsubst %/,%,$(dir $(mkfile_path))) -include $(root_dir)/.env +SHELL := /bin/bash +.DEFAULT_GOAL := help -delegate := athshe +# Copiar el archivo de configuración y avisar cuando hay que +# actualizarlo. +.env: .env.example + @test -f $@ || cp -v $< $@ + @test -f $@ && echo "Revisa $@ para actualizarlo con respecto a $<" + @test -f $@ && diff -auN --color $@ $< -assets := package.json yarn.lock $(shell find app/assets/ app/javascript/ -type f) +include .env -alpine_version := 3.13 -hain ?= ../haini.sh/haini.sh +export -env ?= staging +args ?= ## Argumentos para Hain +commit ?= origin/rails ## Commit desde el que actualizar +env ?= staging ## Entorno del nodo delegado +sutty ?= $(SUTTY) ## Dirección local +delegate ?= $(DELEGATE) ## Cambia el nodo delegado +hain ?= $(HAINISH) ## Ubicación de Hainish +# El nodo delegado tiene dos entornos, production y staging. +# Dependiendo del entorno que elijamos, se van a generar los assets y el +# contenedor y subirse a un servidor u otro. No utilizamos CI/CD (aún). +# +# Production es el entorno de panel.sutty.nl ifeq ($(env),production) container ?= sutty +## TODO: Cambiar a otra cosa branch ?= rails public ?= public endif +# Staging es el entorno de panel.staging.sutty.nl ifeq ($(env),staging) container := staging branch := staging public := staging endif -export +help: always ## Ayuda + @echo -e "Sutty\n" | sed -re "s/^.*/\x1B[38;5;197m&\x1B[0m/" + @echo -e "Servidor: https://panel.$(SUTTY_WITH_PORT)/\n" + @echo -e "Uso: make TAREA args=\"ARGUMENTOS\"\n" + @echo -e "Tareas:\n" + @grep -E "^[a-z\-]+:.*##" Makefile | sed -re "s/(.*):.*##(.*)/\1;\2/" | column -s ";" -t | sed -re "s/^([^ ]+) /\x1B[38;5;197m\1\x1B[0m/" + @echo -e "\nArgumentos:\n" + @grep -E "^[a-z\-]+ \?=.*##" Makefile | sed -re "s/(.*) \?=.*##(.*)/\1;\2/" | column -s ";" -t | sed -re "s/^([^ ]+) /\x1B[38;5;197m\1\x1B[0m/" -public/packs/manifest.json.br: $(assets) - $(hain) 'cd /Sutty/sutty; PANEL_URL=https://panel.sutty.nl RAILS_ENV=production NODE_ENV=production bundle exec rake assets:precompile assets:clean' +assets: node_modules public/packs/manifest.json.br ## Compilar los assets -assets: public/packs/manifest.json.br +test: always ## Ejecutar los tests + $(MAKE) rake args="test RAILS_ENV=test $(args)" -tests := $(shell find test/ -name "*_test.rb") -$(tests): always - $(hain) 'cd /Sutty/sutty; bundle exec rake test TEST="$@" RAILS_ENV=test' - -test: always - $(hain) 'cd /Sutty/sutty; RAILS_ENV=test bundle exec rake test' - -postgresql: /etc/hosts +postgresql: /etc/hosts ## Iniciar la base de datos pgrep postgres >/dev/null || $(hain) postgresql -serve: /etc/hosts postgresql +serve-js: /etc/hosts node_modules ## Iniciar el servidor de desarrollo de Javascript + $(hain) 'bundle exec ./bin/webpack-dev-server' + +serve: /etc/hosts postgresql Gemfile.lock ## Iniciar el servidor de desarrollo de Rails $(MAKE) rails args=server -# make rails args="db:migrate" -rails: +rails: ## Tareas de Rails $(MAKE) bundle args="exec rails $(args)" -rake: +rake: ## Tareas de Rake $(MAKE) bundle args="exec rake $(args)" -bundle: - $(hain) 'cd /Sutty/sutty; bundle $(args)' +bundle: ## Tareas de bundler + $(hain) 'bundle $(args)' -yarn: +rubocop: ## Yutea el código que está por ser commiteado + git status --porcelain \ + | grep -E "^(A|M)" \ + | sed "s/^...//" \ + | grep ".rb$$" \ + | ../haini.sh/haini.sh "xargs -r ./bin/rubocop --auto-correct" + +audit: ## Encuentra dependencias con vulnerabilidades + $(hain) 'gem install bundler-audit' + $(hain) 'bundle audit --update' + +brakeman: ## Busca posibles vulnerabilidades en Sutty + $(MAKE) bundle args='exec brakeman' + +yarn: ## Tareas de yarn $(hain) 'yarn $(args)' -# Servir JS con el dev server. -# Esto acelera la compilación del javascript, tiene que correrse por separado -# de serve. -serve-js: /etc/hosts - $(hain) 'cd /Sutty/sutty; bundle exec ./bin/webpack-dev-server' +clean: ## Limpieza + rm -rf _sites/test-* _deploy/test-* log/*.log tmp/cache tmp/letter_opener tmp/miniprofiler tmp/storage -# Limpiar los archivos de testeo -clean: - rm -rf _sites/test-* _deploy/test-* - -# Generar la imagen Docker -build: assets +build: Gemfile.lock ## Generar la imagen Docker time docker build --build-arg="BRANCH=$(branch)" --build-arg="RAILS_MASTER_KEY=`cat config/master.key`" -t sutty/$(container) . docker tag sutty/$(container):latest sutty:keep + @echo -e "\a" -save: - time docker save sutty/$(container):latest | ssh root@$(delegate).sutty.nl docker load +save: ## Subir la imagen Docker al nodo delegado + time docker save sutty/$(container):latest | ssh root@$(delegate) docker load date +%F | xargs -I {} git tag -f $(container)-{} @echo -e "\a" -# proyectos. -../gems/: - mkdir -p $@ - -# Crear el directorio donde se almacenan las gemas binarias -# TODO: Mover a un proyecto propio, porque lo utilizamos en todos los -gem_dir := $(shell readlink -f ../gems) -gem_cache_dir := $(gem_dir)/cache -gem_binary_dir := $(gem_dir)/$(alpine_version) -ifeq ($(MAKECMDGOALS),build-gems) -gems := $(shell bundle show --paths | xargs -I {} sh -c 'find {}/ext/ -name extconf.rb &>/dev/null && basename {}') -gems := $(patsubst %-x86_64-linux,%,$(gems)) -gems := $(patsubst %,$(gem_cache_dir)/%.gem,$(gems)) -gems_musl := $(patsubst $(gem_cache_dir)/%.gem,$(gem_binary_dir)/%-x86_64-linux-musl.gem,$(gems)) -endif - -$(gem_binary_dir)/%-x86_64-linux-musl.gem: - @docker run \ - -v $(gem_dir):/srv/gems \ - -v `readlink -f ~/.ccache`:/home/builder/.ccache \ - -e HTTP_BASIC_USER=$(HTTP_BASIC_USER) \ - -e HTTP_BASIC_PASSWORD=$(HTTP_BASIC_PASSWORD) \ - -e GEM=`echo $(notdir $*) | sed -re "s/-[^-]+$$//"` \ - -e VERSION=`echo $(notdir $*) | sed -re "s/.*-([^-]+)$$/\1/"` \ - -e JOBS=2 \ - --rm -it \ - sutty/gem-compiler:latest || echo "No se pudo compilar $*" - -# Compilar todas las gemas binarias y subirlas a gems.sutty.nl para que -# al crear el contenedor no tengamos que compilarlas cada vez -build-gems: $(gems_musl) - -cached_gems = $(wildcard $(gem_dir)/cache/*.gem) -rebuild_gems = $(patsubst $(gem_dir)/cache/%.gem,$(gem_dir)/$(alpine_version)/%-x86_64-linux-musl.gem,$(cached_gems)) -rebuild-gems: $(rebuild_gems) - -dirs := $(patsubst %,root/%,data sites deploy public) - -$(dirs): - mkdir -p $@ - -ota: assets +ota-js: assets ## Actualizar Javascript en el nodo delegado sudo chgrp -R 82 public/ - rsync -avi --delete-after public/ $(delegate):/srv/sutty/srv/http/data/_$(public)/ - ssh $(delegate) docker exec $(container) sh -c "cat /srv/http/tmp/puma.pid | xargs -r kill -USR2" + rsync -avi --delete-after public/ root@$(delegate):/srv/sutty/srv/http/data/_$(public)/ + ssh root@$(delegate) docker exec $(container) sh -c "cat /srv/http/tmp/puma.pid | xargs -r kill -USR2" -# Hotfixes -# -# TODO: Reemplazar esto por git pull en el contenedor -commit ?= origin/rails -ota-rb: +ota: ## Actualizar Rails en el nodo delegado umask 022; git format-patch $(commit) scp ./0*.patch $(delegate):/tmp/ ssh $(delegate) mkdir -p /tmp/patches-$(commit)/ @@ -140,6 +117,19 @@ ota-rb: ssh $(delegate) docker exec $(container) ota $(commit) rm ./0*.patch +# Todos los archivos de assets. Si alguno cambia, se van a recompilar +# los assets que luego se suben al nodo delegado. +assets := package.json yarn.lock $(shell find app/assets/ app/javascript/ -type f) +public/packs/manifest.json.br: $(assets) + $(hain) 'PANEL_URL=https://panel.sutty.nl RAILS_ENV=production NODE_ENV=production bundle exec rake assets:precompile assets:clean' + +# Correr un test en particular por ejemplo +# `make test/models/usuarie_test.rb` +tests := $(shell find test/ -name "*_test.rb") +$(tests): always + $(MAKE) test args="TEST=$@" + +# Agrega las direcciones locales al sistema /etc/hosts: always @echo "Chequeando si es necesario agregar el dominio local $(SUTTY)" @grep -q " $(SUTTY)$$" $@ || echo -e "127.0.0.1 $(SUTTY)\n::1 $(SUTTY)" | sudo tee -a $@ @@ -147,4 +137,12 @@ ota-rb: @grep -q " panel.$(SUTTY)$$" $@ || echo -e "127.0.0.1 panel.$(SUTTY)\n::1 panel.$(SUTTY)" | sudo tee -a $@ @grep -q " postgresql.$(SUTTY)$$" $@ || echo -e "127.0.0.1 postgresql.$(SUTTY)\n::1 postgresql.$(SUTTY)" | sudo tee -a $@ +# Instala las dependencias de Javascript +node_modules: package.json + $(MAKE) yarn + +# Instala las dependencias de Rails +Gemfile.lock: Gemfile + $(MAKE) bundle args=install + .PHONY: always diff --git a/README.md b/README.md index ef889f9b..25d0d31c 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,17 @@ Este repositorio es la plataforma _Ruby on Rails_ para alojar el Para más información visita el [sitio de Sutty](https://sutty.nl/). +### Desarrollar + +Todas las tareas se gestionan con `make`, por favor instala GNU Make +antes de comenzar. + +```bash +make help +``` + +[Leer la documentación](https://docs.sutty.nl/) + ## English Sutty is a platform for hosting safer, faster and more resilient @@ -25,3 +36,13 @@ This repository is the Ruby on Rails platform that hosts the self-managed [panel](https://panel.sutty.nl/). For more information, visit [Sutty's website](https://sutty.nl/en/). + +### Development + +Every task is run via `make`, please install GNU Make before developing. + +```bash +make help +``` + +[Read the documentation](https://docs.sutty.nl/en/) From cf67dbb0c3b98106c4540e9eaeabafceff805e0c Mon Sep 17 00:00:00 2001 From: f Date: Mon, 12 Jul 2021 16:52:44 -0300 Subject: [PATCH 136/234] Devolver el tipo de dato correcto MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Como MetadataBelongsTo desciende de MetadataArray, al cambiar métodos de MetadataArray tenemos efectos secundarios. Esto es un hotfix, la solución correcta sería reimplementar MetadataBelongsTo para que deje de ser un Array. --- app/models/metadata_belongs_to.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/models/metadata_belongs_to.rb b/app/models/metadata_belongs_to.rb index 1438c8db..be1fa670 100644 --- a/app/models/metadata_belongs_to.rb +++ b/app/models/metadata_belongs_to.rb @@ -13,6 +13,13 @@ class MetadataBelongsTo < MetadataRelatedPosts '' end + # Obtiene el valor desde el documento. + # + # @return [String] + def document_value + document.data[name.to_s] + end + def validate super From 68bd68a2ee2cac6e990f49f1266b612e771fd4d3 Mon Sep 17 00:00:00 2001 From: f Date: Tue, 13 Jul 2021 17:22:05 -0300 Subject: [PATCH 137/234] =?UTF-8?q?actualizaci=C3=B3n=20de=20cuidados,=20r?= =?UTF-8?q?esuelve=20un=20cve=20en=20addressable?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Gemfile.lock | 162 +++++++++++++++++++++++++-------------------------- 1 file changed, 81 insertions(+), 81 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index aed60b18..33fba3a0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -18,66 +18,66 @@ GIT GEM remote: https://gems.sutty.nl/ specs: - actioncable (6.1.3.2) - actionpack (= 6.1.3.2) - activesupport (= 6.1.3.2) + actioncable (6.1.4) + actionpack (= 6.1.4) + activesupport (= 6.1.4) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (6.1.3.2) - actionpack (= 6.1.3.2) - activejob (= 6.1.3.2) - activerecord (= 6.1.3.2) - activestorage (= 6.1.3.2) - activesupport (= 6.1.3.2) + actionmailbox (6.1.4) + actionpack (= 6.1.4) + activejob (= 6.1.4) + activerecord (= 6.1.4) + activestorage (= 6.1.4) + activesupport (= 6.1.4) mail (>= 2.7.1) - actionmailer (6.1.3.2) - actionpack (= 6.1.3.2) - actionview (= 6.1.3.2) - activejob (= 6.1.3.2) - activesupport (= 6.1.3.2) + actionmailer (6.1.4) + actionpack (= 6.1.4) + actionview (= 6.1.4) + activejob (= 6.1.4) + activesupport (= 6.1.4) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (6.1.3.2) - actionview (= 6.1.3.2) - activesupport (= 6.1.3.2) + actionpack (6.1.4) + actionview (= 6.1.4) + activesupport (= 6.1.4) rack (~> 2.0, >= 2.0.9) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (6.1.3.2) - actionpack (= 6.1.3.2) - activerecord (= 6.1.3.2) - activestorage (= 6.1.3.2) - activesupport (= 6.1.3.2) + actiontext (6.1.4) + actionpack (= 6.1.4) + activerecord (= 6.1.4) + activestorage (= 6.1.4) + activesupport (= 6.1.4) nokogiri (>= 1.8.5) - actionview (6.1.3.2) - activesupport (= 6.1.3.2) + actionview (6.1.4) + activesupport (= 6.1.4) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.1, >= 1.2.0) - activejob (6.1.3.2) - activesupport (= 6.1.3.2) + activejob (6.1.4) + activesupport (= 6.1.4) globalid (>= 0.3.6) - activemodel (6.1.3.2) - activesupport (= 6.1.3.2) - activerecord (6.1.3.2) - activemodel (= 6.1.3.2) - activesupport (= 6.1.3.2) - activestorage (6.1.3.2) - actionpack (= 6.1.3.2) - activejob (= 6.1.3.2) - activerecord (= 6.1.3.2) - activesupport (= 6.1.3.2) + activemodel (6.1.4) + activesupport (= 6.1.4) + activerecord (6.1.4) + activemodel (= 6.1.4) + activesupport (= 6.1.4) + activestorage (6.1.4) + actionpack (= 6.1.4) + activejob (= 6.1.4) + activerecord (= 6.1.4) + activesupport (= 6.1.4) marcel (~> 1.0.0) - mini_mime (~> 1.0.2) - activesupport (6.1.3.2) + mini_mime (>= 1.1.0) + activesupport (6.1.4) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) tzinfo (~> 2.0) zeitwerk (~> 2.3) - addressable (2.7.0) + addressable (2.8.0) public_suffix (>= 2.0.2, < 5.0) adhesiones-jekyll-theme (0.2.1) jekyll (~> 4.0) @@ -113,7 +113,7 @@ GEM rack (>= 1.0.0) rack-test (>= 0.5.4) xpath (>= 2.0, < 4.0) - chartkick (4.0.4) + chartkick (4.0.5) childprocess (3.0.0) coderay (1.1.3) colorator (1.1.0) @@ -130,7 +130,7 @@ GEM database_cleaner-core (~> 2.0.0) database_cleaner-core (2.0.1) dead_end (1.1.7) - derailed_benchmarks (2.1.0) + derailed_benchmarks (2.1.1) benchmark-ips (~> 2) dead_end get_process_mem (~> 0) @@ -148,8 +148,8 @@ GEM railties (>= 4.1.0) responders warden (~> 1.2.3) - devise-i18n (1.9.4) - devise (>= 4.7.1) + devise-i18n (1.10.0) + devise (>= 4.8.0) devise_invitable (2.0.5) actionmailer (>= 5.0) devise (>= 4.6) @@ -196,7 +196,7 @@ GEM railties (>= 5.0.0) fast_blank (1.0.0-x86_64-linux-musl) fast_jsonparser (0.5.0-x86_64-linux-musl) - ffi (1.15.1-x86_64-linux-musl) + ffi (1.15.3-x86_64-linux-musl) flamegraph (0.9.5) forwardable-extended (2.6.0) friendly_id (5.4.2) @@ -214,7 +214,7 @@ GEM tilt haml-lint (0.999.999) haml_lint - haml_lint (0.37.0) + haml_lint (0.37.1) haml (>= 4.0, < 5.3) parallel (~> 1.10) rainbow @@ -264,8 +264,8 @@ GEM rouge (~> 3.0) safe_yaml (~> 1.0) terminal-table (~> 2.0) - jekyll-commonmark (1.3.1) - commonmarker (~> 0.14) + jekyll-commonmark (1.3.2) + commonmarker (~> 0.14, < 0.22) jekyll (>= 3.7, < 5.0) jekyll-data (1.1.2) jekyll (>= 3.3, < 5.0.0) @@ -284,10 +284,10 @@ GEM ruby-vips (~> 2) jekyll-include-cache (0.2.1) jekyll (>= 3.7, < 5.0) - jekyll-linked-posts (0.2.0) + jekyll-linked-posts (0.4.0) jekyll (~> 4) jekyll-locales (0.1.12) - jekyll-lunr (0.2.0) + jekyll-lunr (0.3.0) loofah (~> 2.4) jekyll-order (0.1.4) jekyll-relative-urls (0.0.6) @@ -296,7 +296,7 @@ GEM sassc (> 2.0.1, < 3.0) jekyll-seo-tag (2.7.1) jekyll (>= 3.8, < 5.0) - jekyll-spree-client (0.1.14) + jekyll-spree-client (0.1.15) fast_blank (~> 1) spree-api-client (~> 0.2) jekyll-turbolinks (0.0.5) @@ -324,7 +324,7 @@ GEM ruby_dep (~> 1.2) loaf (0.10.0) railties (>= 3.2) - lockbox (0.6.4) + lockbox (0.6.5) lograge (0.11.2) actionpack (>= 4) activesupport (>= 4) @@ -341,10 +341,10 @@ GEM method_source (1.0.0) mime-types (3.3.1) mime-types-data (~> 3.2015) - mime-types-data (3.2021.0225) + mime-types-data (3.2021.0704) mini_histogram (0.3.1) mini_magick (4.11.0) - mini_mime (1.0.3) + mini_mime (1.1.0) minima (2.5.1) jekyll (>= 3.5, < 5.0) jekyll-feed (~> 0.9) @@ -361,7 +361,7 @@ GEM racc (~> 1.4) orm_adapter (0.5.0) parallel (1.20.1) - parser (3.0.1.1) + parser (3.0.2.0) ast (~> 2.4.1) pathutil (0.16.2) forwardable-extended (~> 2.6) @@ -370,7 +370,7 @@ GEM activerecord (>= 5.2) activesupport (>= 5.2) popper_js (1.16.0) - prometheus_exporter (0.7.0) + prometheus_exporter (0.8.0) webrick pry (0.14.1) coderay (~> 1.1) @@ -401,20 +401,20 @@ GEM jekyll-relative-urls (~> 0.0) jekyll-seo-tag (~> 2.1) jekyll-turbolinks (~> 0) - rails (6.1.3.2) - actioncable (= 6.1.3.2) - actionmailbox (= 6.1.3.2) - actionmailer (= 6.1.3.2) - actionpack (= 6.1.3.2) - actiontext (= 6.1.3.2) - actionview (= 6.1.3.2) - activejob (= 6.1.3.2) - activemodel (= 6.1.3.2) - activerecord (= 6.1.3.2) - activestorage (= 6.1.3.2) - activesupport (= 6.1.3.2) + rails (6.1.4) + actioncable (= 6.1.4) + actionmailbox (= 6.1.4) + actionmailer (= 6.1.4) + actionpack (= 6.1.4) + actiontext (= 6.1.4) + actionview (= 6.1.4) + activejob (= 6.1.4) + activemodel (= 6.1.4) + activerecord (= 6.1.4) + activestorage (= 6.1.4) + activesupport (= 6.1.4) bundler (>= 1.15.0) - railties (= 6.1.3.2) + railties (= 6.1.4) sprockets-rails (>= 2.0.0) rails-dom-testing (2.0.3) activesupport (>= 4.2.0) @@ -426,14 +426,14 @@ GEM railties (>= 6.0.0, < 7) rails_warden (0.6.0) warden (>= 1.2.0) - railties (6.1.3.2) - actionpack (= 6.1.3.2) - activesupport (= 6.1.3.2) + railties (6.1.4) + actionpack (= 6.1.4) + activesupport (= 6.1.4) method_source - rake (>= 0.8.7) + rake (>= 0.13) thor (~> 1.0) rainbow (3.0.0) - rake (13.0.3) + rake (13.0.6) rb-fsevent (0.11.0) rb-inotify (0.10.1) ffi (~> 1.0) @@ -480,7 +480,7 @@ GEM railties (>= 5.0) rexml (3.2.5) rouge (3.26.0) - rubocop (1.17.0) + rubocop (1.18.3) parallel (~> 1.10) parser (>= 3.0.0.0) rainbow (>= 2.2.2, < 4.0) @@ -491,7 +491,7 @@ GEM unicode-display_width (>= 1.4.0, < 3.0) rubocop-ast (1.7.0) parser (>= 3.0.1.1) - rubocop-rails (2.10.1) + rubocop-rails (2.11.3) activesupport (>= 4.2.0) rack (>= 1.1) rubocop (>= 1.7.0, < 2.0) @@ -506,10 +506,10 @@ GEM ruby_parser (~> 3.1) sexp_processor (~> 4.6) ruby_dep (1.5.0) - ruby_parser (3.15.1) - sexp_processor (~> 4.9) - rubyzip (2.3.0) - rugged (1.1.0-x86_64-linux-musl) + ruby_parser (3.16.0) + sexp_processor (~> 4.15, >= 4.15.1) + rubyzip (2.3.2) + rugged (1.1.1-x86_64-linux-musl) safe_yaml (1.0.6) safely_block (0.3.0) errbase (>= 0.1.1) @@ -525,7 +525,7 @@ GEM childprocess (>= 0.5, < 4.0) rubyzip (>= 1.2.2) semantic_range (3.0.0) - sexp_processor (4.15.2) + sexp_processor (4.15.3) share-to-fediverse-jekyll-theme (0.1.4) jekyll (~> 4.0) jekyll-data (~> 1.1) @@ -537,7 +537,7 @@ GEM simpleidn (0.2.1) unf (~> 0.1.4) sourcemap (0.1.1) - spree-api-client (0.2.1) + spree-api-client (0.2.2) fast_blank (~> 1) httparty (~> 0.18.0) spring (2.1.1) From c9de4f71984ba3f357b23bc6ac008b2a1911410c Mon Sep 17 00:00:00 2001 From: f Date: Tue, 13 Jul 2021 17:24:09 -0300 Subject: [PATCH 138/234] si estamos actualizando desde origin/rails hay que ignorar el directorio --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index c083ddf8..6f04c74f 100644 --- a/Makefile +++ b/Makefile @@ -134,7 +134,7 @@ ota-rb: ssh $(delegate) mkdir -p /tmp/patches-$(commit)/ scp ./0*.patch $(delegate):/tmp/patches-$(commit)/ scp ./ota.sh $(delegate):/tmp/ - ssh $(delegate) docker cp /tmp/patches-$(commit) $(container):/tmp/ + ssh $(delegate) docker cp /tmp/patches-$(shell echo $(commit) | cut -d / -f 1) $(container):/tmp/ ssh $(delegate) docker cp /tmp/ota.sh $(container):/usr/local/bin/ota ssh $(delegate) docker exec $(container) apk add --no-cache patch ssh $(delegate) docker exec $(container) ota $(commit) From 8f88023d52257fba2152dfcd8bc4bc62ea57214f Mon Sep 17 00:00:00 2001 From: f Date: Thu, 22 Jul 2021 16:16:10 -0300 Subject: [PATCH 139/234] El espacio es importante --- Makefile | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index a4480476..0bb5f582 100644 --- a/Makefile +++ b/Makefile @@ -12,12 +12,13 @@ include .env export -args ?= ## Argumentos para Hain -commit ?= origin/rails ## Commit desde el que actualizar -env ?= staging ## Entorno del nodo delegado -sutty ?= $(SUTTY) ## Dirección local -delegate ?= $(DELEGATE) ## Cambia el nodo delegado -hain ?= $(HAINISH) ## Ubicación de Hainish +# XXX: El espacio antes del comentario cuenta como espacio +args ?=## Argumentos para Hain +commit ?= origin/rails## Commit desde el que actualizar +env ?= staging## Entorno del nodo delegado +sutty ?= $(SUTTY)## Dirección local +delegate ?= $(DELEGATE)## Cambia el nodo delegado +hain ?= $(HAINISH)## Ubicación de Hainish # El nodo delegado tiene dos entornos, production y staging. # Dependiendo del entorno que elijamos, se van a generar los assets y el From d474b819ad60ebe24e3f3ba63616bfe2300c6d5c Mon Sep 17 00:00:00 2001 From: f Date: Thu, 22 Jul 2021 16:19:25 -0300 Subject: [PATCH 140/234] agregar request_uri a los logs sutty/containers/nginx!1 --- .../20210722191718_add_request_uri_to_access_logs.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 db/migrate/20210722191718_add_request_uri_to_access_logs.rb diff --git a/db/migrate/20210722191718_add_request_uri_to_access_logs.rb b/db/migrate/20210722191718_add_request_uri_to_access_logs.rb new file mode 100644 index 00000000..3d225aed --- /dev/null +++ b/db/migrate/20210722191718_add_request_uri_to_access_logs.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +# Agrega la columna de request_uri a la tabla de logs +class AddRequestUriToAccessLogs < ActiveRecord::Migration[6.1] + def change + return unless Rails.env.production? + + add_column :access_logs, :request_uri, :string, default: '' + end +end From d456feac8e70418f52c962e82a40b58559858a4e Mon Sep 17 00:00:00 2001 From: f Date: Thu, 29 Jul 2021 14:46:29 -0300 Subject: [PATCH 141/234] =?UTF-8?q?Cancelar=20la=20tarea=20pendiente=20si?= =?UTF-8?q?=20tom=C3=B3=20mas=20de=2010=20minutos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Si la tarea se reintentó durante 10 minutos, lo más probable es que haya quedado trabada en algún lado. Este cambio permite volver a compilar sitios al cambiar el estado a espera y cancelarse a sí misma. --- app/jobs/deploy_job.rb | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/app/jobs/deploy_job.rb b/app/jobs/deploy_job.rb index f1ceca9e..70997ce1 100644 --- a/app/jobs/deploy_job.rb +++ b/app/jobs/deploy_job.rb @@ -5,13 +5,23 @@ class DeployJob < ApplicationJob class DeployException < StandardError; end # rubocop:disable Metrics/MethodLength - def perform(site, notify = true) + def perform(site, notify = true, time = Time.now) ActiveRecord::Base.connection_pool.with_connection do @site = Site.find(site) - # Si ya hay una tarea corriendo, aplazar esta + # Si ya hay una tarea corriendo, aplazar esta. Si estuvo + # esperando más de 10 minutos, recuperar el estado anterior. + # + # Como el trabajo actual se aplaza al siguiente, arrastrar la + # hora original para poder ir haciendo timeouts. if @site.building? - DeployJob.perform_in(60, site, notify) + if 10.minutes.ago >= time + @site.update status: 'waiting' + raise DeployException, + "#{@site.name} la tarea estuvo más de 10 minutos esperando, volviendo al estado original" + end + + DeployJob.perform_in(60, site, notify, time) return end @@ -29,8 +39,11 @@ class DeployJob < ApplicationJob end deploy_others - notify_usuaries if notify + + # Volver a la espera @site.update status: 'waiting' + + notify_usuaries if notify end end # rubocop:enable Metrics/MethodLength From 4c84389b3561f8b34831736f196a9729f96f9bbb Mon Sep 17 00:00:00 2001 From: Nulo <5151-Nulo@users.noreply.0xacab.org> Date: Mon, 2 Aug 2021 16:17:30 +0000 Subject: [PATCH 142/234] Apply 3 suggestion(s) to 1 file(s) --- Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 0bb5f582..03fd395d 100644 --- a/Makefile +++ b/Makefile @@ -62,13 +62,13 @@ serve-js: /etc/hosts node_modules ## Iniciar el servidor de desarrollo de Javasc serve: /etc/hosts postgresql Gemfile.lock ## Iniciar el servidor de desarrollo de Rails $(MAKE) rails args=server -rails: ## Tareas de Rails +rails: ## Corre rails dentro del entorno de desarrollo (pasar argumentos con args=). $(MAKE) bundle args="exec rails $(args)" -rake: ## Tareas de Rake +rake: ## Corre rake dentro del entorno de desarrollo (pasar argumentos con args=). $(MAKE) bundle args="exec rake $(args)" -bundle: ## Tareas de bundler +bundle: ## Corre bundle dentro del entorno de desarrollo (pasar argumentos con args=). $(hain) 'bundle $(args)' rubocop: ## Yutea el código que está por ser commiteado From 9f84c14a69e3102d96087f589d1fa1bc18b6a877 Mon Sep 17 00:00:00 2001 From: f Date: Sat, 7 Aug 2021 15:55:00 -0300 Subject: [PATCH 143/234] Soportar sitios sin yarn.lock MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Como el chequeo devuelve nil, un sitio sin yarn.lock nunca se compilaría. --- app/models/deploy_local.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/deploy_local.rb b/app/models/deploy_local.rb index 02b837f0..c063b7bc 100644 --- a/app/models/deploy_local.rb +++ b/app/models/deploy_local.rb @@ -80,7 +80,7 @@ class DeployLocal < Deploy # Corre yarn dentro del repositorio def yarn - return unless yarn_lock? + return true unless yarn_lock? run 'yarn' end From 0bd8a2243e88db85da363ad2101056c9361aeec2 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 11 Aug 2021 10:25:05 -0300 Subject: [PATCH 144/234] Solo permitir URLs web al sanitizar fixes #2382 --- app/models/metadata_content.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/metadata_content.rb b/app/models/metadata_content.rb index 437a0dd9..9d3a1040 100644 --- a/app/models/metadata_content.rb +++ b/app/models/metadata_content.rb @@ -56,7 +56,7 @@ class MetadataContent < MetadataTemplate uri = URI element['src'] # No permitimos recursos externos - element.remove unless uri.hostname.end_with? Site.domain + element.remove unless uri.scheme == 'https' && uri.hostname.end_with?(Site.domain) rescue URI::Error element.remove end From 9ccc1be89834df97127370bb6f3552a3ef1003d8 Mon Sep 17 00:00:00 2001 From: f Date: Sat, 28 Aug 2021 12:56:04 -0300 Subject: [PATCH 145/234] =?UTF-8?q?actualizaci=C3=B3n=20de=20cuidados?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Gemfile | 2 + Gemfile.lock | 175 ++++++++++++++++++++++++++------------------------- 2 files changed, 91 insertions(+), 86 deletions(-) diff --git a/Gemfile b/Gemfile index 4256e307..cb78d4c3 100644 --- a/Gemfile +++ b/Gemfile @@ -19,6 +19,8 @@ gem 'sassc-rails' gem 'uglifier', '>= 1.3.0' gem 'bootstrap', '~> 4' +gem 'nokogiri', '~>1.11.0' + # Turbolinks makes navigating your web application faster. Read more: # https://github.com/turbolinks/turbolinks gem 'turbolinks', '~> 5' diff --git a/Gemfile.lock b/Gemfile.lock index 33fba3a0..8ce97f31 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -18,60 +18,60 @@ GIT GEM remote: https://gems.sutty.nl/ specs: - actioncable (6.1.4) - actionpack (= 6.1.4) - activesupport (= 6.1.4) + actioncable (6.1.4.1) + actionpack (= 6.1.4.1) + activesupport (= 6.1.4.1) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (6.1.4) - actionpack (= 6.1.4) - activejob (= 6.1.4) - activerecord (= 6.1.4) - activestorage (= 6.1.4) - activesupport (= 6.1.4) + actionmailbox (6.1.4.1) + actionpack (= 6.1.4.1) + activejob (= 6.1.4.1) + activerecord (= 6.1.4.1) + activestorage (= 6.1.4.1) + activesupport (= 6.1.4.1) mail (>= 2.7.1) - actionmailer (6.1.4) - actionpack (= 6.1.4) - actionview (= 6.1.4) - activejob (= 6.1.4) - activesupport (= 6.1.4) + actionmailer (6.1.4.1) + actionpack (= 6.1.4.1) + actionview (= 6.1.4.1) + activejob (= 6.1.4.1) + activesupport (= 6.1.4.1) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (6.1.4) - actionview (= 6.1.4) - activesupport (= 6.1.4) + actionpack (6.1.4.1) + actionview (= 6.1.4.1) + activesupport (= 6.1.4.1) rack (~> 2.0, >= 2.0.9) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (6.1.4) - actionpack (= 6.1.4) - activerecord (= 6.1.4) - activestorage (= 6.1.4) - activesupport (= 6.1.4) + actiontext (6.1.4.1) + actionpack (= 6.1.4.1) + activerecord (= 6.1.4.1) + activestorage (= 6.1.4.1) + activesupport (= 6.1.4.1) nokogiri (>= 1.8.5) - actionview (6.1.4) - activesupport (= 6.1.4) + actionview (6.1.4.1) + activesupport (= 6.1.4.1) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.1, >= 1.2.0) - activejob (6.1.4) - activesupport (= 6.1.4) + activejob (6.1.4.1) + activesupport (= 6.1.4.1) globalid (>= 0.3.6) - activemodel (6.1.4) - activesupport (= 6.1.4) - activerecord (6.1.4) - activemodel (= 6.1.4) - activesupport (= 6.1.4) - activestorage (6.1.4) - actionpack (= 6.1.4) - activejob (= 6.1.4) - activerecord (= 6.1.4) - activesupport (= 6.1.4) + activemodel (6.1.4.1) + activesupport (= 6.1.4.1) + activerecord (6.1.4.1) + activemodel (= 6.1.4.1) + activesupport (= 6.1.4.1) + activestorage (6.1.4.1) + actionpack (= 6.1.4.1) + activejob (= 6.1.4.1) + activerecord (= 6.1.4.1) + activesupport (= 6.1.4.1) marcel (~> 1.0.0) mini_mime (>= 1.1.0) - activesupport (6.1.4) + activesupport (6.1.4.1) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) @@ -89,13 +89,13 @@ GEM jekyll-relative-urls (~> 0.0) jekyll-seo-tag (~> 2.1) ast (2.4.2) - autoprefixer-rails (10.2.5.1) - execjs (> 0) + autoprefixer-rails (10.3.1.0) + execjs (~> 2) bcrypt (3.1.16-x86_64-linux-musl) bcrypt_pbkdf (1.1.0-x86_64-linux-musl) benchmark-ips (2.9.1) bindex (0.8.1-x86_64-linux-musl) - blazer (2.4.2) + blazer (2.4.3) activerecord (>= 5) chartkick (>= 3.2) railties (>= 5) @@ -104,7 +104,7 @@ GEM autoprefixer-rails (>= 9.1.0) popper_js (>= 1.14.3, < 2) sassc-rails (>= 2.0.0) - brakeman (5.0.4) + brakeman (5.1.1) builder (3.2.4) capybara (2.18.0) addressable @@ -157,8 +157,8 @@ GEM dotenv-rails (2.7.6) dotenv (= 2.7.6) railties (>= 3.2) - down (5.2.2) - addressable (~> 2.5) + down (5.2.3) + addressable (~> 2.8) ed25519 (1.2.4-x86_64-linux-musl) editorial-autogestiva-jekyll-theme (0.3.4) jekyll (~> 4) @@ -194,7 +194,7 @@ GEM factory_bot_rails (6.2.0) factory_bot (~> 6.2.0) railties (>= 5.0.0) - fast_blank (1.0.0-x86_64-linux-musl) + fast_blank (1.0.1-x86_64-linux-musl) fast_jsonparser (0.5.0-x86_64-linux-musl) ffi (1.15.3-x86_64-linux-musl) flamegraph (0.9.5) @@ -203,13 +203,13 @@ GEM activerecord (>= 4.0.0) get_process_mem (0.2.7) ffi (~> 1.0) - globalid (0.4.2) - activesupport (>= 4.2.0) + globalid (0.5.2) + activesupport (>= 5.0) hairtrigger (0.2.24) activerecord (>= 5.0, < 7) ruby2ruby (~> 2.4) ruby_parser (~> 3.10) - haml (5.2.1) + haml (5.2.2) temple (>= 0.8.0) tilt haml-lint (0.999.999) @@ -220,7 +220,7 @@ GEM rainbow rubocop (>= 0.50.0) sysexits (~> 1.1) - hamlit (2.15.0-x86_64-linux-musl) + hamlit (2.15.1-x86_64-linux-musl) temple (>= 0.8.2) thor tilt @@ -276,7 +276,7 @@ GEM jekyll (>= 3.7, < 5.0) jekyll-hardlinks (0.1.2) jekyll (~> 4) - jekyll-ignore-layouts (0.1.0) + jekyll-ignore-layouts (0.1.2) jekyll (~> 4) jekyll-images (0.2.7) jekyll (~> 4) @@ -284,7 +284,7 @@ GEM ruby-vips (~> 2) jekyll-include-cache (0.2.1) jekyll (>= 3.7, < 5.0) - jekyll-linked-posts (0.4.0) + jekyll-linked-posts (0.4.2) jekyll (~> 4) jekyll-locales (0.1.12) jekyll-lunr (0.3.0) @@ -296,9 +296,9 @@ GEM sassc (> 2.0.1, < 3.0) jekyll-seo-tag (2.7.1) jekyll (>= 3.8, < 5.0) - jekyll-spree-client (0.1.15) + jekyll-spree-client (0.1.17) fast_blank (~> 1) - spree-api-client (~> 0.2) + spree-api-client (>= 0.2.3) jekyll-turbolinks (0.0.5) jekyll (~> 4) turbolinks-source (~> 5) @@ -306,7 +306,7 @@ GEM jekyll (~> 4) jekyll-watch (2.2.1) listen (~> 3.0) - jekyll-write-and-commit-changes (0.1.2) + jekyll-write-and-commit-changes (0.2.0) jekyll (~> 4) rugged (~> 1) kramdown (2.3.1) @@ -330,7 +330,7 @@ GEM activesupport (>= 4) railties (>= 4) request_store (~> 1.0) - loofah (2.10.0) + loofah (2.12.0) crass (~> 1.0.2) nokogiri (>= 1.5.9) mail (2.7.1) @@ -344,20 +344,22 @@ GEM mime-types-data (3.2021.0704) mini_histogram (0.3.1) mini_magick (4.11.0) - mini_mime (1.1.0) + mini_mime (1.1.1) + mini_portile2 (2.5.3) minima (2.5.1) jekyll (>= 3.5, < 5.0) jekyll-feed (~> 0.9) jekyll-seo-tag (~> 2.1) minitest (5.14.4) - mobility (1.1.2) + mobility (1.1.3) i18n (>= 0.6.10, < 2) request_store (~> 1.0) multi_xml (0.6.0) net-ssh (6.1.0) netaddr (2.0.4) - nio4r (2.5.7-x86_64-linux-musl) - nokogiri (1.11.7-x86_64-linux) + nio4r (2.5.8-x86_64-linux-musl) + nokogiri (1.11.7-x86_64-linux-musl) + mini_portile2 (~> 2.5.0) racc (~> 1.4) orm_adapter (0.5.0) parallel (1.20.1) @@ -370,15 +372,15 @@ GEM activerecord (>= 5.2) activesupport (>= 5.2) popper_js (1.16.0) - prometheus_exporter (0.8.0) + prometheus_exporter (0.8.1) webrick pry (0.14.1) coderay (~> 1.1) method_source (~> 1.0) public_suffix (4.0.6) - puma (5.3.2-x86_64-linux-musl) + puma (5.4.0-x86_64-linux-musl) nio4r (~> 2.0) - pundit (2.1.0) + pundit (2.1.1) activesupport (>= 3.0.0) racc (1.5.2-x86_64-linux-musl) rack (2.2.3) @@ -401,34 +403,34 @@ GEM jekyll-relative-urls (~> 0.0) jekyll-seo-tag (~> 2.1) jekyll-turbolinks (~> 0) - rails (6.1.4) - actioncable (= 6.1.4) - actionmailbox (= 6.1.4) - actionmailer (= 6.1.4) - actionpack (= 6.1.4) - actiontext (= 6.1.4) - actionview (= 6.1.4) - activejob (= 6.1.4) - activemodel (= 6.1.4) - activerecord (= 6.1.4) - activestorage (= 6.1.4) - activesupport (= 6.1.4) + rails (6.1.4.1) + actioncable (= 6.1.4.1) + actionmailbox (= 6.1.4.1) + actionmailer (= 6.1.4.1) + actionpack (= 6.1.4.1) + actiontext (= 6.1.4.1) + actionview (= 6.1.4.1) + activejob (= 6.1.4.1) + activemodel (= 6.1.4.1) + activerecord (= 6.1.4.1) + activestorage (= 6.1.4.1) + activesupport (= 6.1.4.1) bundler (>= 1.15.0) - railties (= 6.1.4) + railties (= 6.1.4.1) sprockets-rails (>= 2.0.0) rails-dom-testing (2.0.3) activesupport (>= 4.2.0) nokogiri (>= 1.6) - rails-html-sanitizer (1.3.0) + rails-html-sanitizer (1.4.2) loofah (~> 2.3) rails-i18n (6.0.0) i18n (>= 0.7, < 2) railties (>= 6.0.0, < 7) rails_warden (0.6.0) warden (>= 1.2.0) - railties (6.1.4) - actionpack (= 6.1.4) - activesupport (= 6.1.4) + railties (6.1.4.1) + actionpack (= 6.1.4.1) + activesupport (= 6.1.4.1) method_source rake (>= 0.13) thor (~> 1.0) @@ -455,7 +457,7 @@ GEM jekyll-unique-urls (~> 0.1) sutty-archives (~> 2.2) sutty-liquid (~> 0) - redis (4.3.1) + redis (4.4.0) redis-actionpack (5.2.0) actionpack (>= 5, < 7) redis-rack (>= 2.1.0, < 3) @@ -480,16 +482,16 @@ GEM railties (>= 5.0) rexml (3.2.5) rouge (3.26.0) - rubocop (1.18.3) + rubocop (1.20.0) parallel (~> 1.10) parser (>= 3.0.0.0) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 1.8, < 3.0) rexml - rubocop-ast (>= 1.7.0, < 2.0) + rubocop-ast (>= 1.9.1, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 1.4.0, < 3.0) - rubocop-ast (1.7.0) + rubocop-ast (1.11.0) parser (>= 3.0.1.1) rubocop-rails (2.11.3) activesupport (>= 4.2.0) @@ -500,13 +502,13 @@ GEM ruby-filemagic (0.7.2-x86_64-linux-musl) ruby-progressbar (1.11.0) ruby-statistics (2.1.3) - ruby-vips (2.1.2) + ruby-vips (2.1.3) ffi (~> 1.12) ruby2ruby (2.4.4) ruby_parser (~> 3.1) sexp_processor (~> 4.6) ruby_dep (1.5.0) - ruby_parser (3.16.0) + ruby_parser (3.17.0) sexp_processor (~> 4.15, >= 4.15.1) rubyzip (2.3.2) rugged (1.1.1-x86_64-linux-musl) @@ -537,7 +539,7 @@ GEM simpleidn (0.2.1) unf (~> 0.1.4) sourcemap (0.1.1) - spree-api-client (0.2.2) + spree-api-client (0.2.3) fast_blank (~> 1) httparty (~> 0.18.0) spring (2.1.1) @@ -610,7 +612,7 @@ GEM activemodel (>= 6.0.0) bindex (>= 0.4.0) railties (>= 6.0.0) - webpacker (5.4.0) + webpacker (5.4.2) activesupport (>= 5.2) rack-proxy (>= 0.6.1) railties (>= 5.2) @@ -677,6 +679,7 @@ DEPENDENCIES minima mobility net-ssh + nokogiri (~> 1.11.0) pg pg_search prometheus_exporter From be549c5cd518b942ff65abf31ffe8969ddfb6f1f Mon Sep 17 00:00:00 2001 From: f Date: Sat, 28 Aug 2021 13:02:12 -0300 Subject: [PATCH 146/234] ruby 2.7.4 --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 0b3253b4..59352b61 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,7 +17,7 @@ ENV RAILS_MASTER_KEY=$RAILS_MASTER_KEY RUN apk add --no-cache libxslt libxml2 tzdata ruby ruby-bundler ruby-json ruby-bigdecimal ruby-rake RUN apk add --no-cache postgresql-libs git yarn brotli libssh2 python3 -RUN test "2.7.3" = `ruby -e 'puts RUBY_VERSION'` +RUN test "2.7.4" = `ruby -e 'puts RUBY_VERSION'` # https://github.com/rubygems/rubygems/issues/2918 # https://gitlab.alpinelinux.org/alpine/aports/issues/10808 @@ -85,7 +85,7 @@ RUN apk add --no-cache ffmpeg imagemagick pandoc tectonic oxipng jemalloc RUN apk add --no-cache git-lfs openssh-client patch # Chequear que la versión de ruby sea la correcta -RUN test "2.7.3" = `ruby -e 'puts RUBY_VERSION'` +RUN test "2.7.4" = `ruby -e 'puts RUBY_VERSION'` # https://github.com/rubygems/rubygems/issues/2918 # https://gitlab.alpinelinux.org/alpine/aports/issues/10808 From 094a8092de7721edab16cfb052db058dbe3464cd Mon Sep 17 00:00:00 2001 From: f Date: Fri, 3 Sep 2021 15:23:11 -0300 Subject: [PATCH 147/234] Agregar rollups --- Gemfile | 1 + Gemfile.lock | 10 +++++++++- db/migrate/20210807003928_create_rollups.rb | 15 +++++++++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20210807003928_create_rollups.rb diff --git a/Gemfile b/Gemfile index 4256e307..2f5446a0 100644 --- a/Gemfile +++ b/Gemfile @@ -58,6 +58,7 @@ gem 'rails-i18n' gem 'rails_warden' gem 'redis', require: %w[redis redis/connection/hiredis] gem 'redis-rails' +gem 'rollups' gem 'rubyzip' gem 'rugged' gem 'concurrent-ruby-ext' diff --git a/Gemfile.lock b/Gemfile.lock index 33fba3a0..6c873d9b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -205,6 +205,8 @@ GEM ffi (~> 1.0) globalid (0.4.2) activesupport (>= 4.2.0) + groupdate (5.2.2) + activesupport (>= 5) hairtrigger (0.2.24) activerecord (>= 5.0, < 7) ruby2ruby (~> 2.4) @@ -345,6 +347,7 @@ GEM mini_histogram (0.3.1) mini_magick (4.11.0) mini_mime (1.1.0) + mini_portile2 (2.5.3) minima (2.5.1) jekyll (>= 3.5, < 5.0) jekyll-feed (~> 0.9) @@ -357,7 +360,8 @@ GEM net-ssh (6.1.0) netaddr (2.0.4) nio4r (2.5.7-x86_64-linux-musl) - nokogiri (1.11.7-x86_64-linux) + nokogiri (1.11.7-x86_64-linux-musl) + mini_portile2 (~> 2.5.0) racc (~> 1.4) orm_adapter (0.5.0) parallel (1.20.1) @@ -479,6 +483,9 @@ GEM actionpack (>= 5.0) railties (>= 5.0) rexml (3.2.5) + rollups (0.1.2) + activesupport (>= 5.1) + groupdate (>= 5.2) rouge (3.26.0) rubocop (1.18.3) parallel (~> 1.10) @@ -692,6 +699,7 @@ DEPENDENCIES recursero-jekyll-theme redis redis-rails + rollups rubocop-rails rubyzip rugged diff --git a/db/migrate/20210807003928_create_rollups.rb b/db/migrate/20210807003928_create_rollups.rb new file mode 100644 index 00000000..932513a4 --- /dev/null +++ b/db/migrate/20210807003928_create_rollups.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +# Crear la tabla de Rollups +class CreateRollups < ActiveRecord::Migration[6.1] + def change + create_table :rollups do |t| + t.string :name, null: false + t.string :interval, null: false + t.datetime :time, null: false + t.jsonb :dimensions, null: false, default: {} + t.float :value + end + add_index :rollups, %i[name interval time dimensions], unique: true + end +end From 604c16bfb8947b42634bc3f2f8c30f168fecee13 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 3 Sep 2021 15:28:45 -0300 Subject: [PATCH 148/234] Agregar created_at a access_logs para poder agrupar por fecha --- ...10807004941_add_create_at_to_access_logs.rb | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 db/migrate/20210807004941_add_create_at_to_access_logs.rb diff --git a/db/migrate/20210807004941_add_create_at_to_access_logs.rb b/db/migrate/20210807004941_add_create_at_to_access_logs.rb new file mode 100644 index 00000000..0e106061 --- /dev/null +++ b/db/migrate/20210807004941_add_create_at_to_access_logs.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +# Cambia los msec a datetime para poder agregar por tiempos +class AddCreateAtToAccessLogs < ActiveRecord::Migration[6.1] + def up + add_column :access_logs, :created_at, :datetime, precision: 6 + + create_trigger(compatibility: 1).on(:access_logs).before(:insert) do + 'new.created_at := to_timestamp(new.msec)' + end + + ActiveRecord::Base.connection.execute('update access_logs set created_at = to_timestamp(msec);') + end + + def down + remove_column :access_logs, :created_at + end +end From 016da2852926436142e89fcdf1f80c840496a084 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 3 Sep 2021 15:53:45 -0300 Subject: [PATCH 149/234] Inflexiones para Rollup --- config/initializers/inflections.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb index 35b309ea..0e18b987 100644 --- a/config/initializers/inflections.rb +++ b/config/initializers/inflections.rb @@ -11,6 +11,8 @@ ActiveSupport::Inflector.inflections(:en) do |inflect| inflect.singular 'licencias', 'licencia' inflect.plural 'rol', 'roles' inflect.singular 'roles', 'rol' + inflect.plural 'rollup', 'rollups' + inflect.singular 'rollups', 'rollup' end ActiveSupport::Inflector.inflections(:es) do |inflect| @@ -24,4 +26,6 @@ ActiveSupport::Inflector.inflections(:es) do |inflect| inflect.singular 'roles', 'rol' inflect.plural 'licencia', 'licencias' inflect.singular 'licencias', 'licencia' + inflect.plural 'rollup', 'rollups' + inflect.singular 'rollups', 'rollup' end From 4e4b5888a35e403133c8d9a0934a09cd6520f9ae Mon Sep 17 00:00:00 2001 From: f Date: Fri, 3 Sep 2021 15:55:30 -0300 Subject: [PATCH 150/234] Peticiones completas --- app/models/access_log.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/models/access_log.rb b/app/models/access_log.rb index 85cd4c36..0dceb7d7 100644 --- a/app/models/access_log.rb +++ b/app/models/access_log.rb @@ -1,4 +1,9 @@ # frozen_string_literal: true class AccessLog < ApplicationRecord + # Las peticiones completas son las que terminaron bien y se + # respondieron con 200 OK o 304 Not Modified + # + # @see {https://en.wikipedia.org/wiki/List_of_HTTP_status_codes} + scope :completed_requests, -> { where(request_method: 'GET', request_completion: 'OK', status: [200, 304]) } end From 1623ab73dea22fbc63abf759245627926b2a5b63 Mon Sep 17 00:00:00 2001 From: f Date: Sat, 11 Sep 2021 17:00:28 -0300 Subject: [PATCH 151/234] Soportar un campo con una lista de valores predefinidos y elegir uno --- app/models/metadata_predefined_value.rb | 24 +++++++++++++++++++ .../posts/attribute_ro/_predefined_value.haml | 3 +++ .../posts/attributes/_predefined_value.haml | 7 ++++++ 3 files changed, 34 insertions(+) create mode 100644 app/models/metadata_predefined_value.rb create mode 100644 app/views/posts/attribute_ro/_predefined_value.haml create mode 100644 app/views/posts/attributes/_predefined_value.haml diff --git a/app/models/metadata_predefined_value.rb b/app/models/metadata_predefined_value.rb new file mode 100644 index 00000000..9cf36382 --- /dev/null +++ b/app/models/metadata_predefined_value.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +# Un campo de texto seleccionado de una lista de valores posibles +class MetadataPredefinedValue < MetadataString + # Obtiene todos los valores desde el layout, en un formato compatible + # con options_for_select. + # + # @return [Hash] + def values + @values ||= layout.dig(:metadata, name, 'values', I18n.locale.to_s)&.invert || {} + end + + private + + # Solo permite almacenar los valores predefinidos. + # + # @return [String] + def sanitize(string) + v = super string + return '' unless values.values.include? v + + v + end +end diff --git a/app/views/posts/attribute_ro/_predefined_value.haml b/app/views/posts/attribute_ro/_predefined_value.haml new file mode 100644 index 00000000..67642e2c --- /dev/null +++ b/app/views/posts/attribute_ro/_predefined_value.haml @@ -0,0 +1,3 @@ +%tr{ id: attribute } + %th= post_label_t(attribute, post: post) + %td{ dir: dir, lang: locale }= metadata.value diff --git a/app/views/posts/attributes/_predefined_value.haml b/app/views/posts/attributes/_predefined_value.haml new file mode 100644 index 00000000..b0d21f35 --- /dev/null +++ b/app/views/posts/attributes/_predefined_value.haml @@ -0,0 +1,7 @@ +.form-group + = label_tag "#{base}_#{attribute}", post_label_t(attribute, post: post) + = select_tag(plain_field_name_for(base, attribute), + options_for_select(metadata.values, metadata.value), + **field_options(attribute, metadata), include_blank: t('.empty')) + = render 'posts/attribute_feedback', + post: post, attribute: attribute, metadata: metadata From 9b8c09cb004581d2add73638edb3e1b5f7825eda Mon Sep 17 00:00:00 2001 From: f Date: Sat, 11 Sep 2021 17:06:27 -0300 Subject: [PATCH 152/234] =?UTF-8?q?Soportar=20campos=20n=C3=BAmericos=20co?= =?UTF-8?q?n=20decimales?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/metadata_float.rb | 31 ++++++++++++++++++++++++ app/views/posts/attribute_ro/_float.haml | 3 +++ app/views/posts/attributes/_float.haml | 6 +++++ 3 files changed, 40 insertions(+) create mode 100644 app/models/metadata_float.rb create mode 100644 app/views/posts/attribute_ro/_float.haml create mode 100644 app/views/posts/attributes/_float.haml diff --git a/app/models/metadata_float.rb b/app/models/metadata_float.rb new file mode 100644 index 00000000..b4288a3b --- /dev/null +++ b/app/models/metadata_float.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +# Un campo numérico de punto flotante +class MetadataFloat < MetadataTemplate + # Nada + def default_value + super || nil + end + + def save + return true unless changed? + + self[:value] = value.to_f + self[:value] = encrypt(value) if private? + + true + end + + # Indicarle al navegador que acepte números decimales + # + # @return [Float] + def step + 0.05 + end + + private + + def decrypt(value) + super(value).to_f + end +end diff --git a/app/views/posts/attribute_ro/_float.haml b/app/views/posts/attribute_ro/_float.haml new file mode 100644 index 00000000..67642e2c --- /dev/null +++ b/app/views/posts/attribute_ro/_float.haml @@ -0,0 +1,3 @@ +%tr{ id: attribute } + %th= post_label_t(attribute, post: post) + %td{ dir: dir, lang: locale }= metadata.value diff --git a/app/views/posts/attributes/_float.haml b/app/views/posts/attributes/_float.haml new file mode 100644 index 00000000..6239c613 --- /dev/null +++ b/app/views/posts/attributes/_float.haml @@ -0,0 +1,6 @@ +.form-group + = label_tag "#{base}_#{attribute}", post_label_t(attribute, post: post) + = number_field base, attribute, value: metadata.value, step: metadata.step, + **field_options(attribute, metadata) + = render 'posts/attribute_feedback', + post: post, attribute: attribute, metadata: metadata From a242ceee68c7058ca55fa35eb3721b2cb104b364 Mon Sep 17 00:00:00 2001 From: f Date: Sat, 11 Sep 2021 17:26:44 -0300 Subject: [PATCH 153/234] Siempre guardar el valor de los campos booleanos --- app/models/metadata_boolean.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/models/metadata_boolean.rb b/app/models/metadata_boolean.rb index 5e4b456f..53a763fd 100644 --- a/app/models/metadata_boolean.rb +++ b/app/models/metadata_boolean.rb @@ -31,6 +31,11 @@ class MetadataBoolean < MetadataTemplate self[:value] = true_values.include? self[:value] end + # Siempre guardar el valor de este campo a menos que sea nulo + def empty? + !value.nil? + end + private # Los valores que evalúan a verdadero From 47096f20b7dd8c96aaa8cdf334e3698d92d10f37 Mon Sep 17 00:00:00 2001 From: f Date: Sat, 11 Sep 2021 19:43:27 -0300 Subject: [PATCH 154/234] =?UTF-8?q?Traducci=C3=B3n=20de=20valores=20vac?= =?UTF-8?q?=C3=ADos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/locales/en.yml | 2 ++ config/locales/es.yml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/config/locales/en.yml b/config/locales/en.yml index fc194eab..058456b1 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -413,6 +413,8 @@ en: destroy: Remove image belongs_to: empty: "(Empty)" + predefined_value: + empty: "(Empty)" draft: label: Draft reorder: diff --git a/config/locales/es.yml b/config/locales/es.yml index e8185391..f5c13c8b 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -421,6 +421,8 @@ es: destroy: 'Eliminar imagen' belongs_to: empty: "(Vacío)" + predefined_value: + empty: "(Vacío)" draft: label: Borrador reorder: From ed213cda9f040e0e8f6d9de29e1da9609a0c3415 Mon Sep 17 00:00:00 2001 From: f Date: Sat, 11 Sep 2021 20:17:35 -0300 Subject: [PATCH 155/234] =?UTF-8?q?Actualizaci=C3=B3n=20de=20cuidados?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Gemfile.lock | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 8ce97f31..8846d2a5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -89,7 +89,7 @@ GEM jekyll-relative-urls (~> 0.0) jekyll-seo-tag (~> 2.1) ast (2.4.2) - autoprefixer-rails (10.3.1.0) + autoprefixer-rails (10.3.3.0) execjs (~> 2) bcrypt (3.1.16-x86_64-linux-musl) bcrypt_pbkdf (1.1.0-x86_64-linux-musl) @@ -196,7 +196,7 @@ GEM railties (>= 5.0.0) fast_blank (1.0.1-x86_64-linux-musl) fast_jsonparser (0.5.0-x86_64-linux-musl) - ffi (1.15.3-x86_64-linux-musl) + ffi (1.15.4-x86_64-linux-musl) flamegraph (0.9.5) forwardable-extended (2.6.0) friendly_id (5.4.2) @@ -296,7 +296,7 @@ GEM sassc (> 2.0.1, < 3.0) jekyll-seo-tag (2.7.1) jekyll (>= 3.8, < 5.0) - jekyll-spree-client (0.1.17) + jekyll-spree-client (0.1.18) fast_blank (~> 1) spree-api-client (>= 0.2.3) jekyll-turbolinks (0.0.5) @@ -341,7 +341,7 @@ GEM method_source (1.0.0) mime-types (3.3.1) mime-types-data (~> 3.2015) - mime-types-data (3.2021.0704) + mime-types-data (3.2021.0901) mini_histogram (0.3.1) mini_magick (4.11.0) mini_mime (1.1.1) @@ -386,7 +386,7 @@ GEM rack (2.2.3) rack-cors (1.1.1) rack (>= 2.0.0) - rack-mini-profiler (2.3.2) + rack-mini-profiler (2.3.3) rack (>= 1.2.0) rack-proxy (0.7.0) rack @@ -493,7 +493,7 @@ GEM unicode-display_width (>= 1.4.0, < 3.0) rubocop-ast (1.11.0) parser (>= 3.0.1.1) - rubocop-rails (2.11.3) + rubocop-rails (2.12.2) activesupport (>= 4.2.0) rack (>= 1.1) rubocop (>= 1.7.0, < 2.0) @@ -511,7 +511,7 @@ GEM ruby_parser (3.17.0) sexp_processor (~> 4.15, >= 4.15.1) rubyzip (2.3.2) - rugged (1.1.1-x86_64-linux-musl) + rugged (1.2.0-x86_64-linux-musl) safe_yaml (1.0.6) safely_block (0.3.0) errbase (>= 0.1.1) From c601845a27c5f1a7244c4b88b3736cd115b498ff Mon Sep 17 00:00:00 2001 From: f Date: Wed, 15 Sep 2021 19:50:24 -0300 Subject: [PATCH 156/234] Garantizar que todas las lecturas se hacen dentro del directorio del sitio fixes ##2667 fixes ##2655 fixes ##2640 fixes #2675 fixes #2653 fixes #2635 fixes #2624 fixes #2626 fixes #2627 fixes #2629 fixes #2634 fixes #2636 fixes #2637 fixes #2641 fixes #2642 fixes #2643 fixes #2644 fixes #2645 fixes #2646 fixes #2648 fixes #2649 fixes #2650 fixes #2651 fixes #2654 fixes #2657 fixes #2672 fixes #2676 fixes #2677 fixes #2678 fixes #2681 fixes #2682 fixes #2687 fixes #2688 fixes #2689 fixes #2691 fixes #2692 fixes #2693 --- app/models/site.rb | 43 ++++++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/app/models/site.rb b/app/models/site.rb index 58f20745..ddfe2bc9 100644 --- a/app/models/site.rb +++ b/app/models/site.rb @@ -65,9 +65,6 @@ class Site < ApplicationRecord accepts_nested_attributes_for :deploys, allow_destroy: true - # El sitio en Jekyll - attr_reader :jekyll - # XXX: Es importante incluir luego de los callbacks de :load_jekyll include Site::Index @@ -180,29 +177,28 @@ class Site < ApplicationRecord # Trae los datos del directorio _data dentro del sitio def data - unless @jekyll.data.present? - @jekyll.reader.read_data - - # Define los valores por defecto según la llave buscada - @jekyll.data.default_proc = proc do |data, key| - data[key] = case key - when 'layout' then {} - end + unless jekyll.data.present? + run_in_path do + jekyll.reader.read_data + jekyll.data['layouts'] ||= {} end end - @jekyll.data + jekyll.data end # Traer las colecciones. Todos los artículos van a estar dentro de # colecciones. def collections unless @read - @jekyll.reader.read_collections + run_in_path do + jekyll.reader.read_collections + end + @read = true end - @jekyll.collections + jekyll.collections end # Traer la configuración de forma modificable @@ -290,7 +286,9 @@ class Site < ApplicationRecord # # @return [Hash] def theme_layouts - @jekyll.reader.read_layouts + run_in_path do + jekyll.reader.read_layouts + end end # Trae todos los valores disponibles para un campo @@ -332,6 +330,12 @@ class Site < ApplicationRecord status == 'building' end + def jekyll + run_in_path do + @jekyll ||= Jekyll::Site.new(configuration) + end + end + # Cargar el sitio Jekyll # # TODO: En lugar de leer todo junto de una vez, extraer la carga de @@ -345,10 +349,7 @@ class Site < ApplicationRecord def reload_jekyll! reset - - Dir.chdir(path) do - @jekyll = Jekyll::Site.new(configuration) - end + jekyll end def reload @@ -526,4 +527,8 @@ class Site < ApplicationRecord errors.add(:design_id, I18n.t('activerecord.errors.models.site.attributes.design_id.layout_incompatible.error')) end + + def run_in_path(&block) + Dir.chdir path, &block + end end From 5a324ae71f29616b4661c6b364ee5a3282a5dd14 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 15 Sep 2021 21:00:10 -0300 Subject: [PATCH 157/234] No cortar las columnas --- app/views/posts/index.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/posts/index.haml b/app/views/posts/index.haml index 8b776590..c00d59af 100644 --- a/app/views/posts/index.haml +++ b/app/views/posts/index.haml @@ -112,11 +112,11 @@ %span{ lang: post.locale, dir: dir }= category = '/' unless post.front_matter['categories'].last == category - %td + %td.text-nowrap = post.created_at.strftime('%F') %br/ = post.order - %td + %td.text-nowrap - if @usuarie || policy(post).edit? = link_to t('posts.edit'), edit_site_post_path(@site, post.path), class: 'btn btn-block' - if @usuarie || policy(post).destroy? From 859b8518c0f722cfd06aaab0bc59259870c67609 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 15 Sep 2021 21:00:48 -0300 Subject: [PATCH 158/234] =?UTF-8?q?Mostrar=20el=20tipo=20de=20art=C3=ADcul?= =?UTF-8?q?o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/posts/index.haml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/app/views/posts/index.haml b/app/views/posts/index.haml index c00d59af..5c47f679 100644 --- a/app/views/posts/index.haml +++ b/app/views/posts/index.haml @@ -104,13 +104,13 @@ %span{ lang: post.locale, dir: dir }= post.title - if post.front_matter['draft'].present? %span.badge.badge-primary= I18n.t('posts.attributes.draft.label') - - if post.front_matter['categories'].present? - %br - %small - - post.front_matter['categories'].each do |category| - = link_to site_posts_path(@site, **@filter_params.merge(category: category)) do - %span{ lang: post.locale, dir: dir }= category - = '/' unless post.front_matter['categories'].last == category + %br + %small + = link_to @site.layouts[post.layout].humanized_name, site_posts_path(@site, **@filter_params.merge(layout: post.layout)) + - post.front_matter['categories']&.each do |category| + = link_to site_posts_path(@site, **@filter_params.merge(category: category)) do + %span{ lang: post.locale, dir: dir }= category + = '/' unless post.front_matter['categories'].last == category %td.text-nowrap = post.created_at.strftime('%F') From 8e1f5c5558627efa0721703591d940aa288f9b85 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 15 Sep 2021 21:02:09 -0300 Subject: [PATCH 159/234] =?UTF-8?q?Paginaci=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Gemfile | 1 + Gemfile.lock | 17 ++++++++++++++++- app/controllers/posts_controller.rb | 4 ++-- config/routes.rb | 1 + 4 files changed, 20 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index 4256e307..db06827d 100644 --- a/Gemfile +++ b/Gemfile @@ -67,6 +67,7 @@ gem 'terminal-table' gem 'validates_hostname' gem 'webpacker' gem 'yaml_db', git: 'https://0xacab.org/sutty/yaml_db.git' +gem 'kaminari' # database gem 'hairtrigger' diff --git a/Gemfile.lock b/Gemfile.lock index 33fba3a0..e08bcb05 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -309,6 +309,18 @@ GEM jekyll-write-and-commit-changes (0.1.2) jekyll (~> 4) rugged (~> 1) + kaminari (1.2.1) + activesupport (>= 4.1.0) + kaminari-actionview (= 1.2.1) + kaminari-activerecord (= 1.2.1) + kaminari-core (= 1.2.1) + kaminari-actionview (1.2.1) + actionview + kaminari-core (= 1.2.1) + kaminari-activerecord (1.2.1) + activerecord + kaminari-core (= 1.2.1) + kaminari-core (1.2.1) kramdown (2.3.1) rexml kramdown-parser-gfm (1.1.0) @@ -345,6 +357,7 @@ GEM mini_histogram (0.3.1) mini_magick (4.11.0) mini_mime (1.1.0) + mini_portile2 (2.5.3) minima (2.5.1) jekyll (>= 3.5, < 5.0) jekyll-feed (~> 0.9) @@ -357,7 +370,8 @@ GEM net-ssh (6.1.0) netaddr (2.0.4) nio4r (2.5.7-x86_64-linux-musl) - nokogiri (1.11.7-x86_64-linux) + nokogiri (1.11.7-x86_64-linux-musl) + mini_portile2 (~> 2.5.0) racc (~> 1.4) orm_adapter (0.5.0) parallel (1.20.1) @@ -667,6 +681,7 @@ DEPENDENCIES jekyll-data! jekyll-images jekyll-include-cache + kaminari letter_opener listen (>= 3.0.5, < 3.2) loaf diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index 3ef26720..05ae2736 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -24,7 +24,7 @@ class PostsController < ApplicationController # más simple saber si hubo cambios. if stale?([current_usuarie, site, filter_params]) # Todos los artículos de este sitio para el idioma actual - @posts = site.indexed_posts.where(locale: locale) + @posts = site.indexed_posts.where(locale: locale).page(filter_params.delete(:page)) # De este tipo @posts = @posts.where(layout: filter_params[:layout]) if filter_params[:layout] # Que estén dentro de la categoría @@ -154,7 +154,7 @@ class PostsController < ApplicationController # # @return [Hash] def filter_params - @filter_params ||= params.permit(:q, :category, :layout).to_h.select { |_, v| v.present? } + @filter_params ||= params.permit(:q, :category, :layout, :page).to_h.select { |_, v| v.present? } end def site diff --git a/config/routes.rb b/config/routes.rb index 2c5f1c60..15ee4150 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -60,6 +60,7 @@ Rails.application.routes.draw do scope '(:locale)' do post :'posts/reorder', to: 'posts#reorder' resources :posts do + get 'p/:page', action: :index, on: :collection get :preview, to: 'posts#preview' end end From f90c92dc26ff5e69385153ee1547c461d44210a1 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 15 Sep 2021 21:03:07 -0300 Subject: [PATCH 160/234] =?UTF-8?q?Poder=20navegar=20p=C3=A1ginas=20en=20l?= =?UTF-8?q?a=20lista=20de=20art=C3=ADculos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/posts/index.haml | 22 ++++++++++++++-------- config/locales/en.yml | 2 ++ config/locales/es.yml | 2 ++ 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/app/views/posts/index.haml b/app/views/posts/index.haml index 5c47f679..654210f2 100644 --- a/app/views/posts/index.haml +++ b/app/views/posts/index.haml @@ -72,14 +72,20 @@ %thead %tr %th.border-0.background-white.position-sticky{ style: 'top: 0; z-index: 2', colspan: '4' } - = submit_tag t('posts.reorder.submit'), class: 'btn' - %button.btn{ data: { action: 'reorder#unselect' } } - = t('posts.reorder.unselect') - %span.badge{ data: { target: 'reorder.counter' } } 0 - %button.btn{ data: { action: 'reorder#up' } }= t('posts.reorder.up') - %button.btn{ data: { action: 'reorder#down' } }= t('posts.reorder.down') - %button.btn{ data: { action: 'reorder#top' } }= t('posts.reorder.top') - %button.btn{ data: { action: 'reorder#bottom' } }= t('posts.reorder.bottom') + .d-flex.flex-row.justify-content-between + %div + = submit_tag t('posts.reorder.submit'), class: 'btn' + %button.btn{ data: { action: 'reorder#unselect' } } + = t('posts.reorder.unselect') + %span.badge{ data: { target: 'reorder.counter' } } 0 + %button.btn{ data: { action: 'reorder#up' } }= t('posts.reorder.up') + %button.btn{ data: { action: 'reorder#down' } }= t('posts.reorder.down') + %button.btn{ data: { action: 'reorder#top' } }= t('posts.reorder.top') + %button.btn{ data: { action: 'reorder#bottom' } }= t('posts.reorder.bottom') + + %div + = link_to_prev_page @posts, t('posts.prev'), class: 'btn' + = link_to_next_page @posts, t('posts.next'), class: 'btn' %tbody - dir = t("locales.#{@locale}.dir") - size = @posts.size diff --git a/config/locales/en.yml b/config/locales/en.yml index fc194eab..e950fc32 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -376,6 +376,8 @@ en: en: 'English' ar: 'Arabic' posts: + prev: Previous page + next: Next page empty: "There are no results for those search parameters." caption: Post list attribute_ro: diff --git a/config/locales/es.yml b/config/locales/es.yml index e8185391..459d643b 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -384,6 +384,8 @@ es: en: 'inglés' ar: 'árabe' posts: + prev: Página anterior + next: Página siguiente empty: No hay artículos con estos parámetros de búsqueda. caption: Lista de artículos attribute_ro: From 0dece732aadc407b10674158fd86a4460e42e622 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 15 Sep 2021 21:03:28 -0300 Subject: [PATCH 161/234] =?UTF-8?q?Al=20buscar=20eliminar=20la=20paginaci?= =?UTF-8?q?=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/posts/index.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/posts/index.haml b/app/views/posts/index.haml index 654210f2..90e30966 100644 --- a/app/views/posts/index.haml +++ b/app/views/posts/index.haml @@ -40,7 +40,7 @@ %section.col = render 'layouts/flash' .d-flex.justify-content-between.align-items-center.pl-2-plus.pr-2-plus.mb-2 - %form + %form{ action: site_posts_path } - @filter_params.each do |param, value| - next if param == 'q' %input{ type: 'hidden', name: param, value: value } From 5c2e5fd62ee217880f8881f338a2db8130d37b06 Mon Sep 17 00:00:00 2001 From: f Date: Sun, 26 Sep 2021 19:29:03 -0300 Subject: [PATCH 162/234] =?UTF-8?q?Agregar=20=C3=ADndices=20=C3=BAnicos=20?= =?UTF-8?q?que=20pens=C3=A1bamos=20que=20ten=C3=ADamos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `unique: true` es un parámetro de `add_index` no de `add_column`. --- db/migrate/20210926205448_add_uniqueness.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 db/migrate/20210926205448_add_uniqueness.rb diff --git a/db/migrate/20210926205448_add_uniqueness.rb b/db/migrate/20210926205448_add_uniqueness.rb new file mode 100644 index 00000000..7399ba4c --- /dev/null +++ b/db/migrate/20210926205448_add_uniqueness.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +# Agrega índices únicos que pensábamos que ya existían. +class AddUniqueness < ActiveRecord::Migration[6.1] + def change + add_index :designs, :name, unique: true + add_index :designs, :gem, unique: true + add_index :licencias, :name, unique: true + end +end From 662b5eeec4da85d4ae9fdd89f21e13501d6ff940 Mon Sep 17 00:00:00 2001 From: f Date: Mon, 4 Oct 2021 14:48:19 -0300 Subject: [PATCH 163/234] guardar y mostrar la url actual en el campo permalink --- app/models/metadata_permalink.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/models/metadata_permalink.rb b/app/models/metadata_permalink.rb index 58feb9e5..dc65aa86 100644 --- a/app/models/metadata_permalink.rb +++ b/app/models/metadata_permalink.rb @@ -2,6 +2,12 @@ # Este metadato permite generar rutas manuales. class MetadataPermalink < MetadataString + # El valor por defecto una vez creado es la URL que le asigne Jekyll, + # de forma que nunca cambia aunque se cambie el título. + def default_value + document.url unless post.new? + end + # Los permalinks nunca pueden ser privados def private? false From f8b1752c04f08e6599ffd461d6cce3e0f33e1643 Mon Sep 17 00:00:00 2001 From: Maki Date: Mon, 4 Oct 2021 14:49:14 -0300 Subject: [PATCH 164/234] cambio texto cerrar sesion #2896 --- config/locales/en.yml | 2 +- config/locales/es.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config/locales/en.yml b/config/locales/en.yml index fc194eab..28678b2f 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -163,7 +163,7 @@ en: signature: 'With love, Sutty' breadcrumb: title: 'Your location in Sutty' - logout: Exit + logout: Log out mutual_aid: Mutual aid collaborations: collaborate: diff --git a/config/locales/es.yml b/config/locales/es.yml index e8185391..ee55d9e7 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -163,7 +163,7 @@ es: signature: 'Con cariño, Sutty' breadcrumb: title: 'Tu ubicación en Sutty' - logout: Salir + logout: Cerrar sesión mutual_aid: Ayuda mutua collaborations: collaborate: From 930f88903e5c73c0f5936f3124c34c9605bca837 Mon Sep 17 00:00:00 2001 From: f Date: Thu, 7 Oct 2021 15:24:21 -0300 Subject: [PATCH 165/234] =?UTF-8?q?instalar=20chartkick=20para=20generar?= =?UTF-8?q?=20gr=C3=A1ficos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Gemfile | 1 + Gemfile.lock | 1 + package.json | 4 +++- yarn.lock | 24 ++++++++++++++++++++++++ 4 files changed, 29 insertions(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 2f5446a0..b5e8664b 100644 --- a/Gemfile +++ b/Gemfile @@ -28,6 +28,7 @@ gem 'jbuilder', '~> 2.5' # Use ActiveModel has_secure_password gem 'bcrypt', '~> 3.1.7' gem 'blazer' +gem 'chartkick' gem 'commonmarker' gem 'devise' gem 'devise-i18n' diff --git a/Gemfile.lock b/Gemfile.lock index 6c873d9b..9d1f512b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -642,6 +642,7 @@ DEPENDENCIES bootstrap (~> 4) brakeman capybara (~> 2.13) + chartkick commonmarker concurrent-ruby-ext database_cleaner diff --git a/package.json b/package.json index 0a2458a6..184f7ebb 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,8 @@ "zepto": "^1.2.0" }, "devDependencies": { - "@types/rails__activestorage": "^6.0.0" + "@types/rails__activestorage": "^6.0.0", + "chart.js": "^3.5.1", + "chartkick": "^4.0.5" } } diff --git a/yarn.lock b/yarn.lock index 11ff78cb..68b7fd23 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2119,6 +2119,25 @@ chalk@^4.1.0: ansi-styles "^4.1.0" supports-color "^7.1.0" +chart.js@>=3.0.2, chart.js@^3.5.1: + version "3.5.1" + resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-3.5.1.tgz#73e24d23a4134a70ccdb5e79a917f156b6f3644a" + integrity sha512-m5kzt72I1WQ9LILwQC4syla/LD/N413RYv2Dx2nnTkRS9iv/ey1xLTt0DnPc/eWV4zI+BgEgDYBIzbQhZHc/PQ== + +chartjs-adapter-date-fns@>=2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/chartjs-adapter-date-fns/-/chartjs-adapter-date-fns-2.0.0.tgz#5e53b2f660b993698f936f509c86dddf9ed44c6b" + integrity sha512-rmZINGLe+9IiiEB0kb57vH3UugAtYw33anRiw5kS2Tu87agpetDDoouquycWc9pRsKtQo5j+vLsYHyr8etAvFw== + +chartkick@^4.0.5: + version "4.0.5" + resolved "https://registry.yarnpkg.com/chartkick/-/chartkick-4.0.5.tgz#310a60c931e8ceedc39adee2ef8e9d1e474cb0e6" + integrity sha512-xKak4Fsgfvp1hj/LykRKkniDMaZASx2A4TdVc/sfsiNFFNf1m+D7PGwP1vgj1UsbsCjOCSfGWWyJpOYxkUCBug== + optionalDependencies: + chart.js ">=3.0.2" + chartjs-adapter-date-fns ">=2.0.0" + date-fns ">=2.0.0" + chokidar@^2.1.8: version "2.1.8" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917" @@ -2744,6 +2763,11 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" +date-fns@>=2.0.0: + version "2.24.0" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.24.0.tgz#7d86dc0d93c87b76b63d213b4413337cfd1c105d" + integrity sha512-6ujwvwgPID6zbI0o7UbURi2vlLDR9uP26+tW6Lg+Ji3w7dd0i3DOcjcClLjLPranT60SSEFBwdSyYwn/ZkPIuw== + debug@2.6.9, debug@^2.2.0, debug@^2.3.3: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" From a7ae7f8e8d7cec044d2d01a49b51bc100e629e23 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 8 Oct 2021 16:31:02 -0300 Subject: [PATCH 166/234] ver cantidad de visitas --- app/controllers/stats_controller.rb | 64 ++++++++++++++++++++++++++--- app/javascript/packs/application.js | 1 + app/policies/site_stat_policy.rb | 4 ++ app/views/stats/index.haml | 19 ++++----- config/locales/en.yml | 9 ++++ config/locales/es.yml | 13 ++++++ config/routes.rb | 1 + 7 files changed, 94 insertions(+), 17 deletions(-) diff --git a/app/controllers/stats_controller.rb b/app/controllers/stats_controller.rb index 07baaf1a..07be68c4 100644 --- a/app/controllers/stats_controller.rb +++ b/app/controllers/stats_controller.rb @@ -5,14 +5,68 @@ class StatsController < ApplicationController include Pundit before_action :authenticate_usuarie! + INTERVALS = %i[hour day week month].freeze + + # XXX: Permitir a Chart.js inyectar su propio CSS + content_security_policy only: :index do |policy| + policy.style_src :self, :unsafe_inline + policy.script_src :self, :unsafe_inline + end + def index @site = find_site authorize SiteStat.new(@site) - # Solo queremos el promedio de tiempo de compilación, no de - # instalación de dependencias. - stats = @site.build_stats.jekyll - @build_avg = stats.average(:seconds).to_f.round(2) - @build_max = stats.maximum(:seconds).to_f.round(2) + @chart_params = { interval: interval } + hostnames + end + + # Genera un gráfico de visitas por dominio asociado a este sitio + def host + @site = find_site + authorize SiteStat.new(@site) + + @stats = Rollup.where_dimensions(host: hostnames).multi_series('host', interval: interval).tap do |series| + series.each do |serie| + serie[:name] = serie.dig(:dimensions, 'host') + serie[:data].transform_values! do |value| + value * nodes + end + end + end + + render json: @stats + end + + private + + # TODO: Eliminar cuando mergeemos referer-origin + def hostnames + @hostnames ||= [@site.hostname, @site.alternative_hostnames].flatten + end + + # Obtiene y valida los intervalos + # + # @return [Symbol] + def interval + @interval ||= begin + i = params[:interval].to_sym + INTERVALS.include?(i) ? i : :day + end + end + + # Obtiene la cantidad de nodos de Sutty, para poder calcular la + # cantidad de visitas. + # + # Como repartimos las visitas por nodo rotando las IPs en el + # nameserver y los resolvedores de DNS eligen un nameserver + # aleatoriamente, la cantidad de visitas se reparte + # equitativamente. + # + # XXX: Remover cuando podamos centralizar los AccessLog + # + # @return [Integer] + def nodes + @nodes ||= ENV.fetch('NODES', 1).to_i end end diff --git a/app/javascript/packs/application.js b/app/javascript/packs/application.js index 6aa3a2e1..492ca736 100644 --- a/app/javascript/packs/application.js +++ b/app/javascript/packs/application.js @@ -18,6 +18,7 @@ import 'etc' import Rails from '@rails/ujs' import Turbolinks from 'turbolinks' import * as ActiveStorage from '@rails/activestorage' +import 'chartkick/chart.js' Rails.start() Turbolinks.start() diff --git a/app/policies/site_stat_policy.rb b/app/policies/site_stat_policy.rb index a797034c..d11be95d 100644 --- a/app/policies/site_stat_policy.rb +++ b/app/policies/site_stat_policy.rb @@ -12,4 +12,8 @@ class SiteStatPolicy def index? site_stat.site.usuarie? usuarie end + + def host? + index? + end end diff --git a/app/views/stats/index.haml b/app/views/stats/index.haml index f49cdd15..b5943690 100644 --- a/app/views/stats/index.haml +++ b/app/views/stats/index.haml @@ -1,17 +1,12 @@ -= render 'layouts/breadcrumb', - crumbs: [link_to(t('sites.index.title'), sites_path), - link_to(@site.name, site_path(@site)), t('.title')] - .row .col %h1= t('.title') %p.lead= t('.help') - %table.table.table-condensed - %tbody - %tr - %td= t('.build.average') - %td= distance_of_time_in_words_if_more_than_a_minute @build_avg - %tr - %td= t('.build.maximum') - %td= distance_of_time_in_words_if_more_than_a_minute @build_max + .mb-3 + - StatsController::INTERVALS.each do |interval| + = link_to t(".#{interval}"), site_stats_path(interval: interval), class: 'btn' + + .mb-3 + %h2= t('.host', count: @hostnames.size) + = line_chart site_stats_host_path(@chart_params), locale: 'es' diff --git a/config/locales/en.yml b/config/locales/en.yml index fc194eab..96250301 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -255,6 +255,15 @@ en: build: average: 'Average building time' maximum: 'Maximum building time' + hour: 'Hourly' + day: 'Daily' + week: 'Weekly' + month: 'Monthly' + year: 'Yearly' + host: + zero: 'Site visits' + one: 'Site visits' + other: 'Visits by site name' sites: donations: url: 'https://donaciones.sutty.nl/en/' diff --git a/config/locales/es.yml b/config/locales/es.yml index e8185391..7fcebcbe 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -260,6 +260,15 @@ es: build: average: 'Tiempo promedio de generación' maximum: 'Tiempo máximo de generación' + hour: 'Por hora' + day: 'Diarias' + week: 'Semanales' + month: 'Mensuales' + year: 'Anuales' + host: + zero: 'Visitas del sitio' + one: 'Visitas del sitio' + other: 'Visitas por nombre del sitio' sites: donations: url: 'https://donaciones.sutty.nl/' @@ -584,3 +593,7 @@ es: edit: 'Editando' usuaries: index: 'Usuaries' + day: 'Día' + week: 'Semana' + month: 'Mes' + year: 'Año' diff --git a/config/routes.rb b/config/routes.rb index 2c5f1c60..0af58080 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -75,5 +75,6 @@ Rails.application.routes.draw do post 'reorder_posts', to: 'sites#reorder_posts' resources :stats, only: [:index] + get :'stats/host', to: 'stats#host' end end From df2b66afe85d806e32aee341fdc4beb5ee5cd873 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 8 Oct 2021 18:21:09 -0300 Subject: [PATCH 167/234] =?UTF-8?q?llevar=20el=20registro=20de=20las=20rec?= =?UTF-8?q?olecciones=20de=20estad=C3=ADsticas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/stat.rb | 3 +++ db/migrate/20211008201239_create_stats.rb | 12 ++++++++++++ 2 files changed, 15 insertions(+) create mode 100644 app/models/stat.rb create mode 100644 db/migrate/20211008201239_create_stats.rb diff --git a/app/models/stat.rb b/app/models/stat.rb new file mode 100644 index 00000000..cf717b05 --- /dev/null +++ b/app/models/stat.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +class Stat < ApplicationRecord; end diff --git a/db/migrate/20211008201239_create_stats.rb b/db/migrate/20211008201239_create_stats.rb new file mode 100644 index 00000000..e1aff8f6 --- /dev/null +++ b/db/migrate/20211008201239_create_stats.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +# Una tabla que lleva el recuento de recolección de estadísticas, solo +# es necesario para saber cuándo se hicieron, si se hicieron y usar como +# caché. +class CreateStats < ActiveRecord::Migration[6.1] + def change + create_table :stats do |t| + t.timestamps + end + end +end From a2e4e0ab89910f85632ad231bbc2bdc01e69836b Mon Sep 17 00:00:00 2001 From: f Date: Fri, 8 Oct 2021 18:24:19 -0300 Subject: [PATCH 168/234] =?UTF-8?q?informar=20hace=20cu=C3=A1nto=20se=20ac?= =?UTF-8?q?tualizaron=20los=20datos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/stats_controller.rb | 5 +++++ app/views/stats/index.haml | 5 +++++ config/locales/en.yml | 4 +--- config/locales/es.yml | 4 +--- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/app/controllers/stats_controller.rb b/app/controllers/stats_controller.rb index 07be68c4..8d1575bd 100644 --- a/app/controllers/stats_controller.rb +++ b/app/controllers/stats_controller.rb @@ -19,6 +19,7 @@ class StatsController < ApplicationController @chart_params = { interval: interval } hostnames + last_stat end # Genera un gráfico de visitas por dominio asociado a este sitio @@ -40,6 +41,10 @@ class StatsController < ApplicationController private + def last_stat + @last_stat ||= Stat.last + end + # TODO: Eliminar cuando mergeemos referer-origin def hostnames @hostnames ||= [@site.hostname, @site.alternative_hostnames].flatten diff --git a/app/views/stats/index.haml b/app/views/stats/index.haml index b5943690..e77454b1 100644 --- a/app/views/stats/index.haml +++ b/app/views/stats/index.haml @@ -2,6 +2,11 @@ .col %h1= t('.title') %p.lead= t('.help') + %p + %small + = t('.last_update') + %time{ datetime: @last_stat.created_at } + "#{time_ago_in_words @last_stat.created_at}." .mb-3 - StatsController::INTERVALS.each do |interval| diff --git a/config/locales/en.yml b/config/locales/en.yml index 96250301..623d8c2e 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -252,9 +252,7 @@ en: help: | These statistics show information about how your site is generated and how many resources it uses. - build: - average: 'Average building time' - maximum: 'Maximum building time' + last_update: 'Updated every hour. Last update on ' hour: 'Hourly' day: 'Daily' week: 'Weekly' diff --git a/config/locales/es.yml b/config/locales/es.yml index 7fcebcbe..4085a862 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -257,9 +257,7 @@ es: help: | Las estadísticas visibilizan información sobre cómo se genera y cuántos recursos utiliza tu sitio. - build: - average: 'Tiempo promedio de generación' - maximum: 'Tiempo máximo de generación' + last_update: 'Actualizadas cada hora. Última actualización hace ' hour: 'Por hora' day: 'Diarias' week: 'Semanales' From cfb2d7a61d1c29da08d9dd000d7d9039fe2af54e Mon Sep 17 00:00:00 2001 From: f Date: Fri, 8 Oct 2021 18:33:31 -0300 Subject: [PATCH 169/234] todas las peticiones necesitan une usuarie --- app/controllers/stats_controller.rb | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/app/controllers/stats_controller.rb b/app/controllers/stats_controller.rb index 8d1575bd..0c185329 100644 --- a/app/controllers/stats_controller.rb +++ b/app/controllers/stats_controller.rb @@ -4,6 +4,7 @@ class StatsController < ApplicationController include Pundit before_action :authenticate_usuarie! + before_action :authorize_stats INTERVALS = %i[hour day week month].freeze @@ -14,9 +15,6 @@ class StatsController < ApplicationController end def index - @site = find_site - authorize SiteStat.new(@site) - @chart_params = { interval: interval } hostnames last_stat @@ -24,8 +22,6 @@ class StatsController < ApplicationController # Genera un gráfico de visitas por dominio asociado a este sitio def host - @site = find_site - authorize SiteStat.new(@site) @stats = Rollup.where_dimensions(host: hostnames).multi_series('host', interval: interval).tap do |series| series.each do |serie| @@ -45,6 +41,11 @@ class StatsController < ApplicationController @last_stat ||= Stat.last end + def authorize_stats + @site = find_site + authorize SiteStat.new(@site) + end + # TODO: Eliminar cuando mergeemos referer-origin def hostnames @hostnames ||= [@site.hostname, @site.alternative_hostnames].flatten From 38090d7de72d5cfdb1cad2e4349479ed17f04221 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 8 Oct 2021 18:34:16 -0300 Subject: [PATCH 170/234] resaltar el intervalo seleccionado --- app/views/stats/index.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/stats/index.haml b/app/views/stats/index.haml index e77454b1..63aa76cd 100644 --- a/app/views/stats/index.haml +++ b/app/views/stats/index.haml @@ -10,7 +10,7 @@ .mb-3 - StatsController::INTERVALS.each do |interval| - = link_to t(".#{interval}"), site_stats_path(interval: interval), class: 'btn' + = link_to t(".#{interval}"), site_stats_path(interval: interval), class: "btn #{'btn-primary active' if params[:interval].to_sym == interval}" .mb-3 %h2= t('.host', count: @hostnames.size) From d2b1220df6f74ef46486beeb49ce9b1fd833658d Mon Sep 17 00:00:00 2001 From: f Date: Fri, 8 Oct 2021 18:35:40 -0300 Subject: [PATCH 171/234] informar cuando no hay datos --- app/controllers/stats_controller.rb | 24 ++++++++++++++++++++++++ config/locales/en.yml | 2 ++ config/locales/es.yml | 2 ++ 3 files changed, 28 insertions(+) diff --git a/app/controllers/stats_controller.rb b/app/controllers/stats_controller.rb index 0c185329..9cd1e5de 100644 --- a/app/controllers/stats_controller.rb +++ b/app/controllers/stats_controller.rb @@ -3,6 +3,8 @@ # Estadísticas del sitio class StatsController < ApplicationController include Pundit + include ActionView::Helpers::DateHelper + before_action :authenticate_usuarie! before_action :authorize_stats @@ -18,6 +20,7 @@ class StatsController < ApplicationController @chart_params = { interval: interval } hostnames last_stat + chart_options end # Genera un gráfico de visitas por dominio asociado a este sitio @@ -51,6 +54,27 @@ class StatsController < ApplicationController @hostnames ||= [@site.hostname, @site.alternative_hostnames].flatten end + # Opciones por defecto para los gráficos. + # + # La invitación a volver dentro de X tiempo es para dar un estimado de + # cuándo habrá información disponible, porque Rollup genera intervalos + # completos (¿aunque dice que no?) + # + # La diferencia se calcula sumando el intervalo a la hora de última + # toma de estadísticas y restando el tiempo que pasó desde ese + # momento. + def chart_options + time = last_stat.created_at + (1.send(interval)) + please_return_at = { please_return_at: distance_of_time_in_words(Time.now, time) } + + @chart_options ||= { + locale: I18n.locale, + empty: I18n.t('stats.index.empty', **please_return_at), + loading: I18n.t('stats.index.loading'), + html: %(
      %{loading}
      ) + } + end + # Obtiene y valida los intervalos # # @return [Symbol] diff --git a/config/locales/en.yml b/config/locales/en.yml index 623d8c2e..58e0e726 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -253,6 +253,8 @@ en: These statistics show information about how your site is generated and how many resources it uses. last_update: 'Updated every hour. Last update on ' + empty: 'There is no enough information yet. We invite you to come back in %{please_return_at}!' + loading: 'Loading...' hour: 'Hourly' day: 'Daily' week: 'Weekly' diff --git a/config/locales/es.yml b/config/locales/es.yml index 4085a862..8df68896 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -258,6 +258,8 @@ es: Las estadísticas visibilizan información sobre cómo se genera y cuántos recursos utiliza tu sitio. last_update: 'Actualizadas cada hora. Última actualización hace ' + empty: 'Todavía no hay información suficiente. Te invitamos a volver en %{please_return_at} :)' + loading: 'Cargando...' hour: 'Por hora' day: 'Diarias' week: 'Semanales' From ef0055db0535492b62a95f6cd85970ead819ddb3 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 8 Oct 2021 18:36:31 -0300 Subject: [PATCH 172/234] =?UTF-8?q?agrupar=20por=20a=C3=B1o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/stats_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/stats_controller.rb b/app/controllers/stats_controller.rb index 9cd1e5de..a0937477 100644 --- a/app/controllers/stats_controller.rb +++ b/app/controllers/stats_controller.rb @@ -8,7 +8,7 @@ class StatsController < ApplicationController before_action :authenticate_usuarie! before_action :authorize_stats - INTERVALS = %i[hour day week month].freeze + INTERVALS = %i[hour day week month year].freeze # XXX: Permitir a Chart.js inyectar su propio CSS content_security_policy only: :index do |policy| From 8e9401036ce4f3c64aff0db21263d13e56e14e3b Mon Sep 17 00:00:00 2001 From: f Date: Fri, 8 Oct 2021 18:38:02 -0300 Subject: [PATCH 173/234] =?UTF-8?q?cachear=20los=20resultados=20hasta=20la?= =?UTF-8?q?=20pr=C3=B3xima=20actualizaci=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/stats_controller.rb | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/app/controllers/stats_controller.rb b/app/controllers/stats_controller.rb index a0937477..0eae584b 100644 --- a/app/controllers/stats_controller.rb +++ b/app/controllers/stats_controller.rb @@ -25,17 +25,18 @@ class StatsController < ApplicationController # Genera un gráfico de visitas por dominio asociado a este sitio def host - - @stats = Rollup.where_dimensions(host: hostnames).multi_series('host', interval: interval).tap do |series| - series.each do |serie| - serie[:name] = serie.dig(:dimensions, 'host') - serie[:data].transform_values! do |value| - value * nodes + if stale? [last_stat, hostnames, interval] + stats = Rollup.where_dimensions(host: hostnames).multi_series('host', interval: interval).tap do |series| + series.each do |serie| + serie[:name] = serie.dig(:dimensions, 'host') + serie[:data].transform_values! do |value| + value * nodes + end end end end - render json: @stats + render json: stats end private From 224ea1ebc54ef6621614474eeaedd33f5dc633e3 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 8 Oct 2021 18:39:55 -0300 Subject: [PATCH 174/234] =?UTF-8?q?mostrar=20gr=C3=A1ficos=20de=20recursos?= =?UTF-8?q?=20utilizados?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/stats_controller.rb | 25 +++++++++++++++++++++++-- app/views/stats/index.haml | 11 +++++++++-- config/locales/en.yml | 18 +++++++++++++++--- config/locales/es.yml | 18 +++++++++++++++--- 4 files changed, 62 insertions(+), 10 deletions(-) diff --git a/app/controllers/stats_controller.rb b/app/controllers/stats_controller.rb index 0eae584b..d41573e2 100644 --- a/app/controllers/stats_controller.rb +++ b/app/controllers/stats_controller.rb @@ -9,6 +9,7 @@ class StatsController < ApplicationController before_action :authorize_stats INTERVALS = %i[hour day week month year].freeze + RESOURCES = %i[builds space_used build_time].freeze # XXX: Permitir a Chart.js inyectar su propio CSS content_security_policy only: :index do |policy| @@ -34,9 +35,22 @@ class StatsController < ApplicationController end end end - end - render json: stats + render json: stats + end + end + + def resources + if stale? [last_stat, interval, resource] + options = { + interval: interval, + dimensions: { + deploy_id: @site.deploys.where(type: 'DeployLocal').pluck(:id).first + } + } + + render json: Rollup.series(resource, **options) + end end private @@ -86,6 +100,13 @@ class StatsController < ApplicationController end end + def resource + @resource ||= begin + r = params[:resource].to_sym + RESOURCES.include?(r) ? r : :builds + end + end + # Obtiene la cantidad de nodos de Sutty, para poder calcular la # cantidad de visitas. # diff --git a/app/views/stats/index.haml b/app/views/stats/index.haml index 63aa76cd..0251955e 100644 --- a/app/views/stats/index.haml +++ b/app/views/stats/index.haml @@ -13,5 +13,12 @@ = link_to t(".#{interval}"), site_stats_path(interval: interval), class: "btn #{'btn-primary active' if params[:interval].to_sym == interval}" .mb-3 - %h2= t('.host', count: @hostnames.size) - = line_chart site_stats_host_path(@chart_params), locale: 'es' + %h2= t('.host.title', count: @hostnames.size) + %p.lead= t('.host.description') + = line_chart site_stats_host_path(@chart_params), **@chart_options + + - StatsController::RESOURCES.each do |resource| + .mb-3 + %h2= t(".resources.#{resource}.title") + %p.lead= t(".resources.#{resource}.description") + = line_chart site_stats_resources_path(resource: resource, **@chart_params), **@chart_options diff --git a/config/locales/en.yml b/config/locales/en.yml index 58e0e726..08313a03 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -261,9 +261,21 @@ en: month: 'Monthly' year: 'Yearly' host: - zero: 'Site visits' - one: 'Site visits' - other: 'Visits by site name' + title: + zero: 'Site visits' + one: 'Site visits' + other: 'Visits by site name' + description: 'Counts visited pages on your site, grouped by domain names in use.' + resources: + builds: + title: 'Site publication' + description: 'Times you published your site.' + space_used: + title: 'Server disk usage' + description: 'Average storage space used by your site.' + build_time: + title: 'Publication time' + description: 'Average time your site takes to build.' sites: donations: url: 'https://donaciones.sutty.nl/en/' diff --git a/config/locales/es.yml b/config/locales/es.yml index 8df68896..b1161287 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -266,9 +266,21 @@ es: month: 'Mensuales' year: 'Anuales' host: - zero: 'Visitas del sitio' - one: 'Visitas del sitio' - other: 'Visitas por nombre del sitio' + title: + zero: 'Visitas del sitio' + one: 'Visitas del sitio' + other: 'Visitas por nombre del sitio' + description: 'Cuenta la cantidad de páginas visitadas en tu sitio, dividida por los nombres de dominio en uso.' + resources: + builds: + title: 'Publicaciones del sitio' + description: 'Cantidad de veces que publicaste tu sitio.' + space_used: + title: 'Espacio utilizado en el servidor' + description: 'Espacio en disco que ocupa en promedio tu sitio.' + build_time: + title: 'Tiempo de publicación' + description: 'Tiempo promedio que toma en publicarse tu sitio.' sites: donations: url: 'https://donaciones.sutty.nl/' From 833213ec80c343379ccdd566aaa86e92e051b9ad Mon Sep 17 00:00:00 2001 From: f Date: Fri, 8 Oct 2021 18:40:16 -0300 Subject: [PATCH 175/234] agregar opciones a cada recurso --- app/controllers/stats_controller.rb | 6 ++++++ app/views/stats/index.haml | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/controllers/stats_controller.rb b/app/controllers/stats_controller.rb index d41573e2..8045a653 100644 --- a/app/controllers/stats_controller.rb +++ b/app/controllers/stats_controller.rb @@ -10,6 +10,12 @@ class StatsController < ApplicationController INTERVALS = %i[hour day week month year].freeze RESOURCES = %i[builds space_used build_time].freeze + EXTRA_OPTIONS = { + builds: {}, + space_used: { bytes: true }, + build_time: {} + }.freeze + # XXX: Permitir a Chart.js inyectar su propio CSS content_security_policy only: :index do |policy| diff --git a/app/views/stats/index.haml b/app/views/stats/index.haml index 0251955e..101dee1e 100644 --- a/app/views/stats/index.haml +++ b/app/views/stats/index.haml @@ -21,4 +21,4 @@ .mb-3 %h2= t(".resources.#{resource}.title") %p.lead= t(".resources.#{resource}.description") - = line_chart site_stats_resources_path(resource: resource, **@chart_params), **@chart_options + = line_chart site_stats_resources_path(resource: resource, **@chart_params), **@chart_options.merge(StatsController::EXTRA_OPTIONS[resource]) From 86f1ac450469cfce2d53be41b7f9d594e2d3a985 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 8 Oct 2021 18:40:52 -0300 Subject: [PATCH 176/234] =?UTF-8?q?fixup!=20mostrar=20gr=C3=A1ficos=20de?= =?UTF-8?q?=20recursos=20utilizados?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/policies/site_stat_policy.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/policies/site_stat_policy.rb b/app/policies/site_stat_policy.rb index d11be95d..6c98c775 100644 --- a/app/policies/site_stat_policy.rb +++ b/app/policies/site_stat_policy.rb @@ -16,4 +16,8 @@ class SiteStatPolicy def host? index? end + + def resources? + index? + end end From 51c2fdf6d6899d5b931e2ac301007d33deb641b5 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 8 Oct 2021 18:41:07 -0300 Subject: [PATCH 177/234] =?UTF-8?q?fixup!=20fixup!=20mostrar=20gr=C3=A1fic?= =?UTF-8?q?os=20de=20recursos=20utilizados?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/routes.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/config/routes.rb b/config/routes.rb index 0af58080..b59266fb 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -76,5 +76,6 @@ Rails.application.routes.draw do resources :stats, only: [:index] get :'stats/host', to: 'stats#host' + get :'stats/resources', to: 'stats#resources' end end From 034245595587990cd73ca3a34e945d2127471c9d Mon Sep 17 00:00:00 2001 From: f Date: Fri, 8 Oct 2021 18:42:34 -0300 Subject: [PATCH 178/234] =?UTF-8?q?encontrar=20p=C3=A1ginas=20y=20distingu?= =?UTF-8?q?ir=20si=20fueron=20cyborgs=20o=20no?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/access_log.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/models/access_log.rb b/app/models/access_log.rb index 0dceb7d7..3a066b33 100644 --- a/app/models/access_log.rb +++ b/app/models/access_log.rb @@ -6,4 +6,7 @@ class AccessLog < ApplicationRecord # # @see {https://en.wikipedia.org/wiki/List_of_HTTP_status_codes} scope :completed_requests, -> { where(request_method: 'GET', request_completion: 'OK', status: [200, 304]) } + scope :non_robots, -> { where(crawler: false) } + scope :robots, -> { where(crawler: true) } + scope :pages, -> { where(sent_http_content_type: ['text/html', 'text/html; charset=utf-8', 'text/html; charset=UTF-8']) } end From 306d1ed983f69456a219a6c5fae47ea5e98ff8b1 Mon Sep 17 00:00:00 2001 From: f Date: Sat, 9 Oct 2021 14:45:34 -0300 Subject: [PATCH 179/234] =?UTF-8?q?recolectar=20estad=C3=ADsticas=20una=20?= =?UTF-8?q?vez=20por=20hora?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/jobs/stat_collection_job.rb | 66 +++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 app/jobs/stat_collection_job.rb diff --git a/app/jobs/stat_collection_job.rb b/app/jobs/stat_collection_job.rb new file mode 100644 index 00000000..c36d036c --- /dev/null +++ b/app/jobs/stat_collection_job.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +# Genera resúmenes de información para poder mostrar estadísticas y se +# corre regularmente a sí misma. +class StatCollectionJob < ApplicationJob + class CrontabException < StandardError; end + + # Descartar y notificar si pasó algo más. + # + # XXX: En realidad deberíamos seguir reintentando? + discard_on(Exception) do |_, error| + ExceptionNotifier.notify_exception error + end + + # Correr indefinidamente una vez por hora. + # + # XXX: El orden importa, si el descarte viene después, nunca se va a + # reintentar. + retry_on(StatCollectionJob::CrontabException, wait: 1.hour, attempts: Float::INFINITY) + + COLUMNS = %i[uri].freeze + + def perform(once: false) + Stat::INTERVALS.each do |interval| + options = { interval: interval } + + # Visitas por hostname + AccessLog.completed_requests.group(:host).rollup('host', **options) + + combined_columns **options + stats_by_site **options + end + + # Registrar que se hicieron todas las recolecciones + Stat.create! + + raise CrontabException unless once + end + + private + + # Combinación de columnas + def combined_columns(**options) + COLUMNS.each do |column| + AccessLog.completed_requests.group(:host, column).rollup("hostname|#{column}", **options) + end + end + + # Uso de recursos por cada sitio. + # + # XXX: En realidad se agrupan por el deploy_id, que siempre será el + # del DeployLocal. + def stats_by_site(**options) + Site.find_each do |site| + site.build_stats.jekyll.group(:deploy_id).rollup('builds', **options) + + site.build_stats.jekyll.group(:deploy_id).rollup('space_used', **options) do |rollup| + rollup.average(:bytes) + end + + site.build_stats.jekyll.group(:deploy_id).rollup('build_time', **options) do |rollup| + rollup.average(:seconds) + end + end + end +end From e35cbe79eb53f3dceb082865360e060c827a237c Mon Sep 17 00:00:00 2001 From: f Date: Sat, 9 Oct 2021 14:46:58 -0300 Subject: [PATCH 180/234] =?UTF-8?q?recolectar=20visitas=20a=20p=C3=A1ginas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/jobs/stat_collection_job.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/jobs/stat_collection_job.rb b/app/jobs/stat_collection_job.rb index c36d036c..04f62b8a 100644 --- a/app/jobs/stat_collection_job.rb +++ b/app/jobs/stat_collection_job.rb @@ -25,10 +25,10 @@ class StatCollectionJob < ApplicationJob options = { interval: interval } # Visitas por hostname - AccessLog.completed_requests.group(:host).rollup('host', **options) + AccessLog.completed_requests.non_robots.pages.group(:host).rollup('host', **options) - combined_columns **options - stats_by_site **options + combined_columns(**options) + stats_by_site(**options) end # Registrar que se hicieron todas las recolecciones @@ -42,7 +42,7 @@ class StatCollectionJob < ApplicationJob # Combinación de columnas def combined_columns(**options) COLUMNS.each do |column| - AccessLog.completed_requests.group(:host, column).rollup("hostname|#{column}", **options) + AccessLog.completed_requests.non_robots.pages.group(:host, column).rollup("host|#{column}", **options) end end From 11568e6ce8e8471077278590cbd7de5a67b6003d Mon Sep 17 00:00:00 2001 From: f Date: Sat, 9 Oct 2021 15:36:14 -0300 Subject: [PATCH 181/234] no distinguir entre todas las uris MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit para poder buscar descargas de archivos binarios después --- app/jobs/stat_collection_job.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/jobs/stat_collection_job.rb b/app/jobs/stat_collection_job.rb index 04f62b8a..5b7da160 100644 --- a/app/jobs/stat_collection_job.rb +++ b/app/jobs/stat_collection_job.rb @@ -42,7 +42,7 @@ class StatCollectionJob < ApplicationJob # Combinación de columnas def combined_columns(**options) COLUMNS.each do |column| - AccessLog.completed_requests.non_robots.pages.group(:host, column).rollup("host|#{column}", **options) + AccessLog.completed_requests.non_robots.group(:host, column).rollup("host|#{column}", **options) end end From c927a56befb1128deb3e631774e1084361c93744 Mon Sep 17 00:00:00 2001 From: f Date: Sat, 9 Oct 2021 15:47:42 -0300 Subject: [PATCH 182/234] ejecutar una vez por hora exacto --- app/jobs/stat_collection_job.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/jobs/stat_collection_job.rb b/app/jobs/stat_collection_job.rb index 5b7da160..6f8470b8 100644 --- a/app/jobs/stat_collection_job.rb +++ b/app/jobs/stat_collection_job.rb @@ -16,7 +16,7 @@ class StatCollectionJob < ApplicationJob # # XXX: El orden importa, si el descarte viene después, nunca se va a # reintentar. - retry_on(StatCollectionJob::CrontabException, wait: 1.hour, attempts: Float::INFINITY) + retry_on(StatCollectionJob::CrontabException, wait: 1.hour, attempts: Float::INFINITY, jitter: 0) COLUMNS = %i[uri].freeze From f36ea9629de280b7ef110d8d66d3f05097ce7520 Mon Sep 17 00:00:00 2001 From: f Date: Sat, 9 Oct 2021 15:50:38 -0300 Subject: [PATCH 183/234] centralizar valores --- app/controllers/stats_controller.rb | 9 +++------ app/models/stat.rb | 5 ++++- app/views/stats/index.haml | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app/controllers/stats_controller.rb b/app/controllers/stats_controller.rb index 8045a653..093fbe93 100644 --- a/app/controllers/stats_controller.rb +++ b/app/controllers/stats_controller.rb @@ -8,15 +8,12 @@ class StatsController < ApplicationController before_action :authenticate_usuarie! before_action :authorize_stats - INTERVALS = %i[hour day week month year].freeze - RESOURCES = %i[builds space_used build_time].freeze EXTRA_OPTIONS = { builds: {}, space_used: { bytes: true }, build_time: {} }.freeze - # XXX: Permitir a Chart.js inyectar su propio CSS content_security_policy only: :index do |policy| policy.style_src :self, :unsafe_inline @@ -85,7 +82,7 @@ class StatsController < ApplicationController # toma de estadísticas y restando el tiempo que pasó desde ese # momento. def chart_options - time = last_stat.created_at + (1.send(interval)) + time = last_stat.created_at + 1.try(interval) please_return_at = { please_return_at: distance_of_time_in_words(Time.now, time) } @chart_options ||= { @@ -102,14 +99,14 @@ class StatsController < ApplicationController def interval @interval ||= begin i = params[:interval].to_sym - INTERVALS.include?(i) ? i : :day + Stat::INTERVALS.include?(i) ? i : :day end end def resource @resource ||= begin r = params[:resource].to_sym - RESOURCES.include?(r) ? r : :builds + Stat::RESOURCES.include?(r) ? r : :builds end end diff --git a/app/models/stat.rb b/app/models/stat.rb index cf717b05..1e3af9e9 100644 --- a/app/models/stat.rb +++ b/app/models/stat.rb @@ -1,3 +1,6 @@ # frozen_string_literal: true -class Stat < ApplicationRecord; end +class Stat < ApplicationRecord + INTERVALS = %i[hour day week month year].freeze + RESOURCES = %i[builds space_used build_time].freeze +end diff --git a/app/views/stats/index.haml b/app/views/stats/index.haml index 101dee1e..992200a4 100644 --- a/app/views/stats/index.haml +++ b/app/views/stats/index.haml @@ -9,7 +9,7 @@ "#{time_ago_in_words @last_stat.created_at}." .mb-3 - - StatsController::INTERVALS.each do |interval| + - Stat::INTERVALS.each do |interval| = link_to t(".#{interval}"), site_stats_path(interval: interval), class: "btn #{'btn-primary active' if params[:interval].to_sym == interval}" .mb-3 @@ -17,7 +17,7 @@ %p.lead= t('.host.description') = line_chart site_stats_host_path(@chart_params), **@chart_options - - StatsController::RESOURCES.each do |resource| + - Stat::RESOURCES.each do |resource| .mb-3 %h2= t(".resources.#{resource}.title") %p.lead= t(".resources.#{resource}.description") From 5ca8e3c923e88bc696f5644f1053a50ddd345e0d Mon Sep 17 00:00:00 2001 From: f Date: Sat, 9 Oct 2021 15:52:18 -0300 Subject: [PATCH 184/234] usar guards --- app/controllers/stats_controller.rb | 36 ++++++++++++++--------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/app/controllers/stats_controller.rb b/app/controllers/stats_controller.rb index 093fbe93..1dc468bb 100644 --- a/app/controllers/stats_controller.rb +++ b/app/controllers/stats_controller.rb @@ -29,31 +29,31 @@ class StatsController < ApplicationController # Genera un gráfico de visitas por dominio asociado a este sitio def host - if stale? [last_stat, hostnames, interval] - stats = Rollup.where_dimensions(host: hostnames).multi_series('host', interval: interval).tap do |series| - series.each do |serie| - serie[:name] = serie.dig(:dimensions, 'host') - serie[:data].transform_values! do |value| - value * nodes - end + return unless stale? [last_stat, hostnames, interval] + + stats = Rollup.where_dimensions(host: hostnames).multi_series('host', interval: interval).tap do |series| + series.each do |serie| + serie[:name] = serie.dig(:dimensions, 'host') + serie[:data].transform_values! do |value| + value * nodes end end - - render json: stats end + + render json: stats end def resources - if stale? [last_stat, interval, resource] - options = { - interval: interval, - dimensions: { - deploy_id: @site.deploys.where(type: 'DeployLocal').pluck(:id).first - } - } + return unless stale? [last_stat, interval, resource] - render json: Rollup.series(resource, **options) - end + options = { + interval: interval, + dimensions: { + deploy_id: @site.deploys.where(type: 'DeployLocal').pluck(:id).first + } + } + + render json: Rollup.series(resource, **options) end private From 6c485e5e18abf29d3b3cea0de584503417006c6e Mon Sep 17 00:00:00 2001 From: f Date: Sat, 9 Oct 2021 17:27:45 -0300 Subject: [PATCH 185/234] =?UTF-8?q?obtener=20estad=C3=ADsticas=20de=20cual?= =?UTF-8?q?quier=20link?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/stats_controller.rb | 39 +++++++++++++++++++++++++++++ app/policies/site_stat_policy.rb | 4 +++ app/views/stats/index.haml | 18 ++++++++++++- config/locales/en.yml | 6 +++++ config/locales/es.yml | 10 +++++--- config/routes.rb | 1 + 6 files changed, 73 insertions(+), 5 deletions(-) diff --git a/app/controllers/stats_controller.rb b/app/controllers/stats_controller.rb index 1dc468bb..ca834ebd 100644 --- a/app/controllers/stats_controller.rb +++ b/app/controllers/stats_controller.rb @@ -25,6 +25,7 @@ class StatsController < ApplicationController hostnames last_stat chart_options + normalized_urls end # Genera un gráfico de visitas por dominio asociado a este sitio @@ -56,6 +57,22 @@ class StatsController < ApplicationController render json: Rollup.series(resource, **options) end + def uris + return unless stale? [last_stat, hostnames, interval, normalized_urls] + + options = { host: hostnames, uri: normalized_paths } + stats = Rollup.where_dimensions(**options).multi_series('host|uri', interval: interval).tap do |series| + series.each do |serie| + serie[:name] = serie.dig(:dimensions).slice('host', 'uri').values.join.sub('/index.html', '/') + serie[:data].transform_values! do |value| + value * nodes + end + end + end + + render json: stats + end + private def last_stat @@ -72,6 +89,28 @@ class StatsController < ApplicationController @hostnames ||= [@site.hostname, @site.alternative_hostnames].flatten end + # Normalizar las URLs + # + # @return [Array] + def normalized_urls + @normalized_urls ||= params.permit(:urls).try(:[], + :urls)&.split("\n")&.map(&:strip)&.select(&:present?)&.select do |uri| + uri.start_with? 'https://' + end&.map do |u| + # XXX: Eliminar + # @see {https://0xacab.org/sutty/containers/nginx/-/merge_requests/1} + next u unless u.end_with? '/' + + "#{u}index.html" + end&.uniq || [] + end + + def normalized_paths + @normalized_paths ||= normalized_urls.map do |u| + "/#{u.split('/', 4).last}" + end + end + # Opciones por defecto para los gráficos. # # La invitación a volver dentro de X tiempo es para dar un estimado de diff --git a/app/policies/site_stat_policy.rb b/app/policies/site_stat_policy.rb index 6c98c775..cb62b507 100644 --- a/app/policies/site_stat_policy.rb +++ b/app/policies/site_stat_policy.rb @@ -20,4 +20,8 @@ class SiteStatPolicy def resources? index? end + + def uris? + index? + end end diff --git a/app/views/stats/index.haml b/app/views/stats/index.haml index 992200a4..1ce61a98 100644 --- a/app/views/stats/index.haml +++ b/app/views/stats/index.haml @@ -10,13 +10,29 @@ .mb-3 - Stat::INTERVALS.each do |interval| - = link_to t(".#{interval}"), site_stats_path(interval: interval), class: "btn #{'btn-primary active' if params[:interval].to_sym == interval}" + = link_to t(".#{interval}"), site_stats_path(interval: interval, urls: params[:urls]), class: "btn #{'btn-primary active' if params[:interval].to_sym == interval}" .mb-3 %h2= t('.host.title', count: @hostnames.size) %p.lead= t('.host.description') = line_chart site_stats_host_path(@chart_params), **@chart_options + .mb-3 + - original_urls = params[:urls]&.split("\n")&.map(&:strip) + - rows = original_urls.size.zero? ? 3 : original_urls.size + %h2= t('.urls.title') + %p.lead= t('.urls.description') + %form + %input{ type: 'hidden', name: 'interval', value: @interval } + .form-group + %label{ for: 'urls' }= t('.urls.label') + %textarea#urls.form-control{ name: 'urls', autocomplete: 'on', required: true, rows: rows, aria_describedby: 'help-urls' }= params[:urls] + %small#help-urls.feedback.form-text.text-muted= t('.urls.help') + .form-group + %button.btn{ type: 'submit' }= t('.urls.submit') + - if @normalized_urls.present? + = line_chart site_stats_uris_path(urls: params[:urls], **@chart_params), **@chart_options + - Stat::RESOURCES.each do |resource| .mb-3 %h2= t(".resources.#{resource}.title") diff --git a/config/locales/en.yml b/config/locales/en.yml index 08313a03..af864e7b 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -266,6 +266,12 @@ en: one: 'Site visits' other: 'Visits by site name' description: 'Counts visited pages on your site, grouped by domain names in use.' + urls: + title: 'Visits by URL' + description: 'Counts visits or downloads on any URL.' + label: 'URLs ("links")' + help: 'Copy and paste a single URL per line' + submit: 'Get stats' resources: builds: title: 'Site publication' diff --git a/config/locales/es.yml b/config/locales/es.yml index b1161287..7d0fe2bb 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -271,6 +271,12 @@ es: one: 'Visitas del sitio' other: 'Visitas por nombre del sitio' description: 'Cuenta la cantidad de páginas visitadas en tu sitio, dividida por los nombres de dominio en uso.' + urls: + title: 'Visitas por dirección' + description: 'Cantidad de visitas o descargas por dirección.' + label: 'Direcciones web ("links", vínculos)' + help: 'Copia y pega una dirección por línea.' + submit: 'Obtener estadísticas' resources: builds: title: 'Publicaciones del sitio' @@ -605,7 +611,3 @@ es: edit: 'Editando' usuaries: index: 'Usuaries' - day: 'Día' - week: 'Semana' - month: 'Mes' - year: 'Año' diff --git a/config/routes.rb b/config/routes.rb index b59266fb..1b0f9e0b 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -76,6 +76,7 @@ Rails.application.routes.draw do resources :stats, only: [:index] get :'stats/host', to: 'stats#host' + get :'stats/uris', to: 'stats#uris' get :'stats/resources', to: 'stats#resources' end end From 7ab2ec5933613ce23cff5ab17e9141e0cf65c7ac Mon Sep 17 00:00:00 2001 From: f Date: Sat, 9 Oct 2021 17:28:26 -0300 Subject: [PATCH 186/234] =?UTF-8?q?no=20fallar=20si=20el=20intervalo=20est?= =?UTF-8?q?=C3=A1=20vac=C3=ADo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/stats_controller.rb | 2 +- app/views/stats/index.haml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/stats_controller.rb b/app/controllers/stats_controller.rb index ca834ebd..a5ca04e1 100644 --- a/app/controllers/stats_controller.rb +++ b/app/controllers/stats_controller.rb @@ -137,7 +137,7 @@ class StatsController < ApplicationController # @return [Symbol] def interval @interval ||= begin - i = params[:interval].to_sym + i = params[:interval]&.to_sym Stat::INTERVALS.include?(i) ? i : :day end end diff --git a/app/views/stats/index.haml b/app/views/stats/index.haml index 1ce61a98..d437aac1 100644 --- a/app/views/stats/index.haml +++ b/app/views/stats/index.haml @@ -10,7 +10,7 @@ .mb-3 - Stat::INTERVALS.each do |interval| - = link_to t(".#{interval}"), site_stats_path(interval: interval, urls: params[:urls]), class: "btn #{'btn-primary active' if params[:interval].to_sym == interval}" + = link_to t(".#{interval}"), site_stats_path(interval: interval, urls: params[:urls]), class: "btn #{'btn-primary active' if @interval == interval}" .mb-3 %h2= t('.host.title', count: @hostnames.size) From 5dad13bc3c4ff384ae44dc82ea9c9d5435d49832 Mon Sep 17 00:00:00 2001 From: f Date: Sat, 9 Oct 2021 17:28:56 -0300 Subject: [PATCH 187/234] =?UTF-8?q?m=C3=A1s=20espacio=20entre=20los=20gr?= =?UTF-8?q?=C3=A1ficos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/stats/index.haml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/views/stats/index.haml b/app/views/stats/index.haml index d437aac1..e4ef5f61 100644 --- a/app/views/stats/index.haml +++ b/app/views/stats/index.haml @@ -8,16 +8,16 @@ %time{ datetime: @last_stat.created_at } "#{time_ago_in_words @last_stat.created_at}." - .mb-3 + .mb-5 - Stat::INTERVALS.each do |interval| = link_to t(".#{interval}"), site_stats_path(interval: interval, urls: params[:urls]), class: "btn #{'btn-primary active' if @interval == interval}" - .mb-3 + .mb-5 %h2= t('.host.title', count: @hostnames.size) %p.lead= t('.host.description') = line_chart site_stats_host_path(@chart_params), **@chart_options - .mb-3 + .mb-5 - original_urls = params[:urls]&.split("\n")&.map(&:strip) - rows = original_urls.size.zero? ? 3 : original_urls.size %h2= t('.urls.title') @@ -34,7 +34,7 @@ = line_chart site_stats_uris_path(urls: params[:urls], **@chart_params), **@chart_options - Stat::RESOURCES.each do |resource| - .mb-3 + .mb-5 %h2= t(".resources.#{resource}.title") %p.lead= t(".resources.#{resource}.description") = line_chart site_stats_resources_path(resource: resource, **@chart_params), **@chart_options.merge(StatsController::EXTRA_OPTIONS[resource]) From fd2e2509d0e5f681c8866cf1c4eecdd51aac2275 Mon Sep 17 00:00:00 2001 From: f Date: Sat, 9 Oct 2021 17:29:09 -0300 Subject: [PATCH 188/234] sin comillas --- app/views/stats/index.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/stats/index.haml b/app/views/stats/index.haml index e4ef5f61..4119f8ae 100644 --- a/app/views/stats/index.haml +++ b/app/views/stats/index.haml @@ -6,7 +6,7 @@ %small = t('.last_update') %time{ datetime: @last_stat.created_at } - "#{time_ago_in_words @last_stat.created_at}." + #{time_ago_in_words @last_stat.created_at}. .mb-5 - Stat::INTERVALS.each do |interval| From 810f71e9c1348825303b990299ed7ad03e767bd7 Mon Sep 17 00:00:00 2001 From: f Date: Sat, 9 Oct 2021 18:25:43 -0300 Subject: [PATCH 189/234] =?UTF-8?q?contar=20por=20qu=C3=A9=20mostramos=20l?= =?UTF-8?q?os=20recursos=20usados?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/stats/index.haml | 6 +++++- config/locales/en.yml | 6 ++++-- config/locales/es.yml | 10 ++++++---- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/app/views/stats/index.haml b/app/views/stats/index.haml index 4119f8ae..e69c2546 100644 --- a/app/views/stats/index.haml +++ b/app/views/stats/index.haml @@ -33,8 +33,12 @@ - if @normalized_urls.present? = line_chart site_stats_uris_path(urls: params[:urls], **@chart_params), **@chart_options + .mb-5 + %h2= t('.resources.title') + %p.lead= t('.resources.description') + - Stat::RESOURCES.each do |resource| .mb-5 - %h2= t(".resources.#{resource}.title") + %h3= t(".resources.#{resource}.title") %p.lead= t(".resources.#{resource}.description") = line_chart site_stats_resources_path(resource: resource, **@chart_params), **@chart_options.merge(StatsController::EXTRA_OPTIONS[resource]) diff --git a/config/locales/en.yml b/config/locales/en.yml index af864e7b..be327035 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -264,15 +264,17 @@ en: title: zero: 'Site visits' one: 'Site visits' - other: 'Visits by site name' + other: 'Visits by domain name' description: 'Counts visited pages on your site, grouped by domain names in use.' urls: title: 'Visits by URL' description: 'Counts visits or downloads on any URL.' label: 'URLs ("links")' help: 'Copy and paste a single URL per line' - submit: 'Get stats' + submit: 'Update graph' resources: + title: 'Resource usage' + description: "In this section you can find statistics on your site's use of Sutty's shared resources" builds: title: 'Site publication' description: 'Times you published your site.' diff --git a/config/locales/es.yml b/config/locales/es.yml index 7d0fe2bb..774a8cfd 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -269,15 +269,17 @@ es: title: zero: 'Visitas del sitio' one: 'Visitas del sitio' - other: 'Visitas por nombre del sitio' - description: 'Cuenta la cantidad de páginas visitadas en tu sitio, dividida por los nombres de dominio en uso.' + other: 'Visitas agrupadas por nombre de dominio del sitio' + description: 'Cuenta la cantidad de páginas visitadas en tu sitio.' urls: title: 'Visitas por dirección' description: 'Cantidad de visitas o descargas por dirección.' - label: 'Direcciones web ("links", vínculos)' + label: 'Direcciones web (URL, "links", vínculos)' help: 'Copia y pega una dirección por línea.' - submit: 'Obtener estadísticas' + submit: 'Actualizar gráfico' resources: + title: 'Uso de recursos' + description: 'En esta sección podrás acceder a estadísticas del uso de recursos compartidos con otros sitios alojados en Sutty.' builds: title: 'Publicaciones del sitio' description: 'Cantidad de veces que publicaste tu sitio.' From ec775847b9dd8b446ee2947826452c6b5782d3d1 Mon Sep 17 00:00:00 2001 From: f Date: Sat, 9 Oct 2021 18:27:42 -0300 Subject: [PATCH 190/234] sugerir algunas URLs --- app/controllers/stats_controller.rb | 2 +- app/views/stats/index.haml | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/app/controllers/stats_controller.rb b/app/controllers/stats_controller.rb index a5ca04e1..3606f37e 100644 --- a/app/controllers/stats_controller.rb +++ b/app/controllers/stats_controller.rb @@ -102,7 +102,7 @@ class StatsController < ApplicationController next u unless u.end_with? '/' "#{u}index.html" - end&.uniq || [] + end&.uniq || [@site.url, @site.urls].flatten.uniq end def normalized_paths diff --git a/app/views/stats/index.haml b/app/views/stats/index.haml index e69c2546..fb5c0d52 100644 --- a/app/views/stats/index.haml +++ b/app/views/stats/index.haml @@ -18,15 +18,13 @@ = line_chart site_stats_host_path(@chart_params), **@chart_options .mb-5 - - original_urls = params[:urls]&.split("\n")&.map(&:strip) - - rows = original_urls.size.zero? ? 3 : original_urls.size %h2= t('.urls.title') %p.lead= t('.urls.description') %form %input{ type: 'hidden', name: 'interval', value: @interval } .form-group %label{ for: 'urls' }= t('.urls.label') - %textarea#urls.form-control{ name: 'urls', autocomplete: 'on', required: true, rows: rows, aria_describedby: 'help-urls' }= params[:urls] + %textarea#urls.form-control{ name: 'urls', autocomplete: 'on', required: true, rows: @normalized_urls.size, aria_describedby: 'help-urls' }= @normalized_urls.join("\n") %small#help-urls.feedback.form-text.text-muted= t('.urls.help') .form-group %button.btn{ type: 'submit' }= t('.urls.submit') From 356db8546522ac581254f11d2dcdd4419dfc6595 Mon Sep 17 00:00:00 2001 From: f Date: Sat, 9 Oct 2021 18:28:38 -0300 Subject: [PATCH 191/234] =?UTF-8?q?empezar=20a=20recolectar=20estad=C3=ADs?= =?UTF-8?q?ticas=20cuando=20se=20inicia=20el=20panel?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/application.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/config/application.rb b/config/application.rb index 7326ae0f..4922b74f 100644 --- a/config/application.rb +++ b/config/application.rb @@ -48,6 +48,11 @@ module Sutty EmailAddress::Config.error_messages translations.transform_keys(&:to_s), locale.to_s end + + # Empezar el recolector de datos en la próxima hora + now = Time.now + next_hour = now.at_beginning_of_hour + 1.hour + 1.minute + StatCollectionJob.perform_in(next_hour - now, once: false) end end end From aa86ead1124a6cfd865956d2a4d5bd8ace4f81de Mon Sep 17 00:00:00 2001 From: f Date: Sat, 9 Oct 2021 18:29:53 -0300 Subject: [PATCH 192/234] ruboyuta --- app/controllers/stats_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/stats_controller.rb b/app/controllers/stats_controller.rb index 3606f37e..3fe821ac 100644 --- a/app/controllers/stats_controller.rb +++ b/app/controllers/stats_controller.rb @@ -63,7 +63,7 @@ class StatsController < ApplicationController options = { host: hostnames, uri: normalized_paths } stats = Rollup.where_dimensions(**options).multi_series('host|uri', interval: interval).tap do |series| series.each do |serie| - serie[:name] = serie.dig(:dimensions).slice('host', 'uri').values.join.sub('/index.html', '/') + serie[:name] = serie[:dimensions].slice('host', 'uri').values.join.sub('/index.html', '/') serie[:data].transform_values! do |value| value * nodes end From d06edc2f62ce1e2e499684e3446d1a39c90d81f3 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 20 Oct 2021 12:52:31 -0300 Subject: [PATCH 193/234] usar rollups desde git para poder hacer un rollup recursivo --- Gemfile | 2 +- Gemfile.lock | 14 ++++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/Gemfile b/Gemfile index b5e8664b..a04353b2 100644 --- a/Gemfile +++ b/Gemfile @@ -59,7 +59,7 @@ gem 'rails-i18n' gem 'rails_warden' gem 'redis', require: %w[redis redis/connection/hiredis] gem 'redis-rails' -gem 'rollups' +gem 'rollups', git: 'https://github.com/ankane/rollup.git', branch: 'master' gem 'rubyzip' gem 'rugged' gem 'concurrent-ruby-ext' diff --git a/Gemfile.lock b/Gemfile.lock index 9d1f512b..c1f6925c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -6,6 +6,15 @@ GIT rails (>= 3.0) rake (>= 0.8.7) +GIT + remote: https://github.com/ankane/rollup.git + revision: 94ca777d54180c23e96ac4b4285cc9b405ccbd1a + branch: master + specs: + rollups (0.1.2) + activesupport (>= 5.1) + groupdate (>= 5.2) + GIT remote: https://github.com/fauno/email_address revision: 536b51f7071b68a55140c0c1726b4cd401d1c04d @@ -483,9 +492,6 @@ GEM actionpack (>= 5.0) railties (>= 5.0) rexml (3.2.5) - rollups (0.1.2) - activesupport (>= 5.1) - groupdate (>= 5.2) rouge (3.26.0) rubocop (1.18.3) parallel (~> 1.10) @@ -700,7 +706,7 @@ DEPENDENCIES recursero-jekyll-theme redis redis-rails - rollups + rollups! rubocop-rails rubyzip rugged From 245973b5196d478e64d7f3f14ae73fda72cd0e82 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 20 Oct 2021 12:53:42 -0300 Subject: [PATCH 194/234] =?UTF-8?q?no=20fallar=20si=20todav=C3=ADa=20no=20?= =?UTF-8?q?hay=20stats?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/stats/index.haml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/app/views/stats/index.haml b/app/views/stats/index.haml index fb5c0d52..bfcf33ef 100644 --- a/app/views/stats/index.haml +++ b/app/views/stats/index.haml @@ -2,11 +2,12 @@ .col %h1= t('.title') %p.lead= t('.help') - %p - %small - = t('.last_update') - %time{ datetime: @last_stat.created_at } - #{time_ago_in_words @last_stat.created_at}. + - if @last_stat + %p + %small + = t('.last_update') + %time{ datetime: @last_stat.created_at } + #{time_ago_in_words @last_stat.created_at}. .mb-5 - Stat::INTERVALS.each do |interval| From cc3535097e4924ed4d1ba87da04be769ba9b1b9e Mon Sep 17 00:00:00 2001 From: f Date: Wed, 20 Oct 2021 12:57:11 -0300 Subject: [PATCH 195/234] =?UTF-8?q?todav=C3=ADa=20no=20empezar=20la=20reco?= =?UTF-8?q?lecci=C3=B3n=20autom=C3=A1tica?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/application.rb | 5 ----- 1 file changed, 5 deletions(-) diff --git a/config/application.rb b/config/application.rb index 4922b74f..7326ae0f 100644 --- a/config/application.rb +++ b/config/application.rb @@ -48,11 +48,6 @@ module Sutty EmailAddress::Config.error_messages translations.transform_keys(&:to_s), locale.to_s end - - # Empezar el recolector de datos en la próxima hora - now = Time.now - next_hour = now.at_beginning_of_hour + 1.hour + 1.minute - StatCollectionJob.perform_in(next_hour - now, once: false) end end end From 1497113f73a8f3cd5cb243b41236445f1667dfdc Mon Sep 17 00:00:00 2001 From: f Date: Wed, 20 Oct 2021 12:58:19 -0300 Subject: [PATCH 196/234] usar menos intervalos las horas y semanas generan demasiados rollups --- app/models/stat.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/stat.rb b/app/models/stat.rb index 1e3af9e9..c986ba4b 100644 --- a/app/models/stat.rb +++ b/app/models/stat.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true class Stat < ApplicationRecord - INTERVALS = %i[hour day week month year].freeze + INTERVALS = %i[year month day].freeze RESOURCES = %i[builds space_used build_time].freeze end From 849ee4491c0579133f8134719510f079cff10b66 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 20 Oct 2021 12:58:51 -0300 Subject: [PATCH 197/234] =?UTF-8?q?instalar=20las=20librer=C3=ADas=20en=20?= =?UTF-8?q?producci=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 184f7ebb..d520c8f5 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,8 @@ "@rails/ujs": "^6.1.3-1", "@rails/webpacker": "5.2.1", "babel-loader": "^8.2.2", + "chart.js": "^3.5.1", + "chartkick": "^4.0.5", "circular-dependency-plugin": "^5.2.2", "commonmark": "^0.29.0", "fork-awesome": "^1.1.7", @@ -30,8 +32,6 @@ "zepto": "^1.2.0" }, "devDependencies": { - "@types/rails__activestorage": "^6.0.0", - "chart.js": "^3.5.1", - "chartkick": "^4.0.5" + "@types/rails__activestorage": "^6.0.0" } } From 9cf7c6286169ee4f5ee1cac1ea4f3f1ad8106a3d Mon Sep 17 00:00:00 2001 From: f Date: Wed, 20 Oct 2021 13:08:21 -0300 Subject: [PATCH 198/234] procesar uris a demanda --- app/jobs/uri_collection_job.rb | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 app/jobs/uri_collection_job.rb diff --git a/app/jobs/uri_collection_job.rb b/app/jobs/uri_collection_job.rb new file mode 100644 index 00000000..79b83644 --- /dev/null +++ b/app/jobs/uri_collection_job.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +# Procesar una lista de URIs para una lista de dominios. Esto nos +# permite procesar estadísticas a demanada. +class UriCollectionJob < ApplicationJob + def perform(hostnames:, file:) + uris = File.read(file).split("\n") + + hostnames.each do |hostname| + uris.each do |uri| + break if File.exist? Rails.root.join('tmp', 'uri_collection_job_stop') + + AccessLog.where(host: hostname, uri: uri).completed_requests.non_robots.group(:host, :uri).rollup('host|uri', interval: 'day') + end + end + end +end From 8d38d0d2aedca550e5d8815f6d4b45c70138cfdc Mon Sep 17 00:00:00 2001 From: f Date: Wed, 20 Oct 2021 13:14:19 -0300 Subject: [PATCH 199/234] =?UTF-8?q?recolecci=C3=B3n=20de=20estad=C3=ADstic?= =?UTF-8?q?as=20con=20mejor=20performance?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/jobs/stat_collection_job.rb | 42 ++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/app/jobs/stat_collection_job.rb b/app/jobs/stat_collection_job.rb index 6f8470b8..a49a1635 100644 --- a/app/jobs/stat_collection_job.rb +++ b/app/jobs/stat_collection_job.rb @@ -21,14 +21,20 @@ class StatCollectionJob < ApplicationJob COLUMNS = %i[uri].freeze def perform(once: false) - Stat::INTERVALS.each do |interval| - options = { interval: interval } + Site.find_each do |site| + hostnames = [site.hostname, site.alternative_hostnames].flatten + + # Usamos el primero porque luego podemos hacer un rollup recursivo + options = { interval: Stat::INTERVALS.first } # Visitas por hostname - AccessLog.completed_requests.non_robots.pages.group(:host).rollup('host', **options) + hostnames.each do |hostname| + AccessLog.where(host: hostname).completed_requests.non_robots.pages.group(:host).rollup('host', **options) - combined_columns(**options) - stats_by_site(**options) + combined_columns(hostname, **options) + end + + stats_by_site(site, **options) end # Registrar que se hicieron todas las recolecciones @@ -40,9 +46,15 @@ class StatCollectionJob < ApplicationJob private # Combinación de columnas - def combined_columns(**options) + def combined_columns(hostname, **options) + where = { host: hostname } + COLUMNS.each do |column| - AccessLog.completed_requests.non_robots.group(:host, column).rollup("host|#{column}", **options) + AccessLog.where(host: hostname).pluck(Arel.sql("distinct #{column}")).each do |value| + where[column] = value + + AccessLog.where(**where).completed_requests.non_robots.group(:host, column).rollup("host|#{column}", **options) + end end end @@ -50,17 +62,15 @@ class StatCollectionJob < ApplicationJob # # XXX: En realidad se agrupan por el deploy_id, que siempre será el # del DeployLocal. - def stats_by_site(**options) - Site.find_each do |site| - site.build_stats.jekyll.group(:deploy_id).rollup('builds', **options) + def stats_by_site(site, **options) + site.build_stats.jekyll.group(:deploy_id).rollup('builds', **options) - site.build_stats.jekyll.group(:deploy_id).rollup('space_used', **options) do |rollup| - rollup.average(:bytes) - end + site.build_stats.jekyll.group(:deploy_id).rollup('space_used', **options) do |rollup| + rollup.average(:bytes) + end - site.build_stats.jekyll.group(:deploy_id).rollup('build_time', **options) do |rollup| - rollup.average(:seconds) - end + site.build_stats.jekyll.group(:deploy_id).rollup('build_time', **options) do |rollup| + rollup.average(:seconds) end end end From 7e1696204183e73afa307e27d77ecbfd87f7c5b8 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 20 Oct 2021 17:36:30 -0300 Subject: [PATCH 200/234] no fallar si no hay stat closes #3228 --- app/controllers/stats_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/stats_controller.rb b/app/controllers/stats_controller.rb index 3fe821ac..b9b93e9f 100644 --- a/app/controllers/stats_controller.rb +++ b/app/controllers/stats_controller.rb @@ -121,7 +121,7 @@ class StatsController < ApplicationController # toma de estadísticas y restando el tiempo que pasó desde ese # momento. def chart_options - time = last_stat.created_at + 1.try(interval) + time = (last_stat&.created_at || Time.now) + 1.try(interval) please_return_at = { please_return_at: distance_of_time_in_words(Time.now, time) } @chart_options ||= { From 090b6305251ecb068620eb0558659c4551d97d52 Mon Sep 17 00:00:00 2001 From: f Date: Thu, 21 Oct 2021 10:47:32 -0300 Subject: [PATCH 201/234] =?UTF-8?q?la=20l=C3=B3gica=20estaba=20invertida?= =?UTF-8?q?=20y=20los=20valores=20nunca=20llegaban=20al=20archivo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/metadata_boolean.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/metadata_boolean.rb b/app/models/metadata_boolean.rb index 53a763fd..ed27ea92 100644 --- a/app/models/metadata_boolean.rb +++ b/app/models/metadata_boolean.rb @@ -33,7 +33,7 @@ class MetadataBoolean < MetadataTemplate # Siempre guardar el valor de este campo a menos que sea nulo def empty? - !value.nil? + value.nil? end private From d67d43ff1ea19f5acfb31d615cd4e0db97f98b5f Mon Sep 17 00:00:00 2001 From: f Date: Thu, 21 Oct 2021 10:48:34 -0300 Subject: [PATCH 202/234] =?UTF-8?q?hacer=20m=C3=A1s=20claro=20el=20c=C3=B3?= =?UTF-8?q?digo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/metadata_boolean.rb | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/app/models/metadata_boolean.rb b/app/models/metadata_boolean.rb index ed27ea92..90c002a7 100644 --- a/app/models/metadata_boolean.rb +++ b/app/models/metadata_boolean.rb @@ -25,10 +25,14 @@ class MetadataBoolean < MetadataTemplate # * false # * true def value - return document.data.fetch(name.to_s, default_value) if self[:value].nil? - return self[:value] unless self[:value].is_a? String - - self[:value] = true_values.include? self[:value] + case self[:value] + when NilClass + document.data.fetch(name.to_s, default_value) + when String + true_values.include? self[:value] + else + self[:value] + end end # Siempre guardar el valor de este campo a menos que sea nulo From 08e0a0ca9d7fe9ca76332e5ad895f155c7103803 Mon Sep 17 00:00:00 2001 From: f Date: Thu, 21 Oct 2021 10:49:02 -0300 Subject: [PATCH 203/234] traducir correctamente --- config/locales/en.yml | 4 ++-- config/locales/es.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/config/locales/en.yml b/config/locales/en.yml index 5f224ab6..43ab0d0a 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -19,8 +19,8 @@ en: remember_me: 'Keeps session open for %{remember_for}' actions: sr-help: "After this form you'll find links to recover your account and other actions." - _true: Yes - _false: No + _true: 'Yes' + _false: 'No' svg: sutty: title: Sutty diff --git a/config/locales/es.yml b/config/locales/es.yml index ebf4da62..880b9e7c 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -19,8 +19,8 @@ es: remember_me: 'Mantiene la sesión abierta por %{remember_for}' actions: sr-help: 'Después del formulario encontrarás vínculos para recuperar tu cuenta, entre otras acciones.' - _true: Sí - _false: No + _true: 'Sí' + _false: 'No' svg: sutty: title: Sutty From 6349fd7b407a3f4c260ba061d8cbaf4bf63743cd Mon Sep 17 00:00:00 2001 From: f Date: Thu, 21 Oct 2021 13:23:35 -0300 Subject: [PATCH 204/234] los url helpers necesitan hashes o params y params.to_h devuelve un hashwithindifferentaccess, lo que rompia las urls con locale --- app/controllers/posts_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index 05ae2736..6b69fd6a 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -154,7 +154,7 @@ class PostsController < ApplicationController # # @return [Hash] def filter_params - @filter_params ||= params.permit(:q, :category, :layout, :page).to_h.select { |_, v| v.present? } + @filter_params ||= params.permit(:q, :category, :layout, :page).to_hash.select { |_, v| v.present? } end def site From 3335d0d9eb3c75f20b48c427c2dd7a59b52c22dd Mon Sep 17 00:00:00 2001 From: f Date: Thu, 21 Oct 2021 13:25:05 -0300 Subject: [PATCH 205/234] usar una guard como sugiere rubocop --- app/controllers/posts_controller.rb | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index 6b69fd6a..9d0ff8a8 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -22,21 +22,21 @@ class PostsController < ApplicationController # XXX: Cada vez que cambiamos un Post tocamos el sitio con lo que es # más simple saber si hubo cambios. - if stale?([current_usuarie, site, filter_params]) - # Todos los artículos de este sitio para el idioma actual - @posts = site.indexed_posts.where(locale: locale).page(filter_params.delete(:page)) - # De este tipo - @posts = @posts.where(layout: filter_params[:layout]) if filter_params[:layout] - # Que estén dentro de la categoría - @posts = @posts.in_category(filter_params[:category]) if filter_params[:category] - # Aplicar los parámetros de búsqueda - @posts = @posts.search(locale, filter_params[:q]) if filter_params[:q].present? - # A los que este usuarie tiene acceso - @posts = PostPolicy::Scope.new(current_usuarie, @posts).resolve + return unless stale?([current_usuarie, site, filter_params]) - # Filtrar los posts que les invitades no pueden ver - @usuarie = site.usuarie? current_usuarie - end + # Todos los artículos de este sitio para el idioma actual + @posts = site.indexed_posts.where(locale: locale).page(filter_params.delete(:page)) + # De este tipo + @posts = @posts.where(layout: filter_params[:layout]) if filter_params[:layout] + # Que estén dentro de la categoría + @posts = @posts.in_category(filter_params[:category]) if filter_params[:category] + # Aplicar los parámetros de búsqueda + @posts = @posts.search(locale, filter_params[:q]) if filter_params[:q].present? + # A los que este usuarie tiene acceso + @posts = PostPolicy::Scope.new(current_usuarie, @posts).resolve + + # Filtrar los posts que les invitades no pueden ver + @usuarie = site.usuarie? current_usuarie end def show From e3d2213afc0f97852df6ee40c42d70f47596f34f Mon Sep 17 00:00:00 2001 From: f Date: Thu, 21 Oct 2021 13:44:34 -0300 Subject: [PATCH 206/234] especificar el idioma de un post nuevo --- app/controllers/posts_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index 9d0ff8a8..9820d71d 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -54,7 +54,7 @@ class PostsController < ApplicationController def new authorize Post - @post = site.posts.build(lang: locale, layout: params[:layout]) + @post = site.posts(lang: locale).build(layout: params[:layout]) breadcrumb I18n.t('loaf.breadcrumbs.posts.new', layout: @post.layout.humanized_name.downcase), '' end From 883182ffe2413b76c9f9756ddc8ad29cfca0c9ba Mon Sep 17 00:00:00 2001 From: f Date: Thu, 21 Oct 2021 13:45:34 -0300 Subject: [PATCH 207/234] =?UTF-8?q?rutas=20m=C3=A1s=20espec=C3=ADficas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/routes.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/routes.rb b/config/routes.rb index 15ee4150..5e172cda 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -57,7 +57,7 @@ Rails.application.routes.draw do # Gestionar artículos según idioma nested do - scope '(:locale)' do + scope '/(:locale)', constraint: /[a-z]{2}/ do post :'posts/reorder', to: 'posts#reorder' resources :posts do get 'p/:page', action: :index, on: :collection From 69ef571bdb4a4768c37865907fb07629fc017487 Mon Sep 17 00:00:00 2001 From: f Date: Thu, 21 Oct 2021 14:25:05 -0300 Subject: [PATCH 208/234] garantizar que sea una hash de simbolos --- app/controllers/posts_controller.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index 9820d71d..f3bc8aac 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -154,7 +154,9 @@ class PostsController < ApplicationController # # @return [Hash] def filter_params - @filter_params ||= params.permit(:q, :category, :layout, :page).to_hash.select { |_, v| v.present? } + @filter_params ||= params.permit(:q, :category, :layout, :page).to_hash.select do |_, v| + v.present? + end.transform_keys(&:to_sym) end def site From 9c4a0a86f3eeec8f578e3872096896a710e01632 Mon Sep 17 00:00:00 2001 From: f Date: Thu, 21 Oct 2021 14:40:55 -0300 Subject: [PATCH 209/234] deshabilitar el paginado temporalmente no permite mover posts entre paginas! --- app/controllers/posts_controller.rb | 4 ++-- app/views/posts/index.haml | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index f3bc8aac..dbdd4d0a 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -25,7 +25,7 @@ class PostsController < ApplicationController return unless stale?([current_usuarie, site, filter_params]) # Todos los artículos de este sitio para el idioma actual - @posts = site.indexed_posts.where(locale: locale).page(filter_params.delete(:page)) + @posts = site.indexed_posts.where(locale: locale) # De este tipo @posts = @posts.where(layout: filter_params[:layout]) if filter_params[:layout] # Que estén dentro de la categoría @@ -154,7 +154,7 @@ class PostsController < ApplicationController # # @return [Hash] def filter_params - @filter_params ||= params.permit(:q, :category, :layout, :page).to_hash.select do |_, v| + @filter_params ||= params.permit(:q, :category, :layout).to_hash.select do |_, v| v.present? end.transform_keys(&:to_sym) end diff --git a/app/views/posts/index.haml b/app/views/posts/index.haml index 90e30966..5f613e3a 100644 --- a/app/views/posts/index.haml +++ b/app/views/posts/index.haml @@ -84,8 +84,6 @@ %button.btn{ data: { action: 'reorder#bottom' } }= t('posts.reorder.bottom') %div - = link_to_prev_page @posts, t('posts.prev'), class: 'btn' - = link_to_next_page @posts, t('posts.next'), class: 'btn' %tbody - dir = t("locales.#{@locale}.dir") - size = @posts.size From 4456deff9c8eef185f370a57dac519ad0d8801b6 Mon Sep 17 00:00:00 2001 From: f Date: Thu, 21 Oct 2021 15:02:07 -0300 Subject: [PATCH 210/234] =?UTF-8?q?usar=20la=20traducci=C3=B3n=20del=20idi?= =?UTF-8?q?oma=20que=20viene=20del=20sitio?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/posts/index.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/posts/index.haml b/app/views/posts/index.haml index 5f613e3a..ad07b9dc 100644 --- a/app/views/posts/index.haml +++ b/app/views/posts/index.haml @@ -51,7 +51,7 @@ - if @site.locales.size > 1 %nav#locales - @site.locales.each do |locale| - = link_to t("locales.#{locale}.name"), site_posts_path(@site, **@filter_params.merge(locale: locale)), + = link_to @site.data.dig(locale.to_s, 'locale') || locale, site_posts_path(@site, **@filter_params.merge(locale: locale)), class: "mr-2 mt-2 mb-2 #{locale == @locale ? 'active font-weight-bold' : ''}" .pl-2-plus - @filter_params.each do |param, value| From 7227ecfe2a1bfb99c19e5af8998b2253c323f348 Mon Sep 17 00:00:00 2001 From: f Date: Thu, 21 Oct 2021 20:53:25 -0300 Subject: [PATCH 211/234] solo instalar las gemas de assets en desarrollo --- .env.example | 1 + Gemfile | 17 ++++++++++------- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/.env.example b/.env.example index a62e2b0a..f79ff3a4 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,4 @@ +RAILS_GROUPS=assets DELEGATE=athshe.sutty.nl HAINISH=../haini.sh/haini.sh DATABASE= diff --git a/Gemfile b/Gemfile index db06827d..01167fa1 100644 --- a/Gemfile +++ b/Gemfile @@ -11,13 +11,16 @@ gem 'dotenv-rails', require: 'dotenv/rails-now' gem 'rails', '~> 6' # Use Puma as the app server gem 'puma' -# See https://github.com/rails/execjs#readme for more supported runtimes -# gem 'therubyracer', platforms: :ruby -# Use SCSS for stylesheets -gem 'sassc-rails' -# Use Uglifier as compressor for JavaScript assets -gem 'uglifier', '>= 1.3.0' -gem 'bootstrap', '~> 4' + +# Solo incluir las gemas cuando estemos en desarrollo o compilando los +# assets. No es necesario instalarlas en producción. +# +# XXX: Supuestamente Rails ya soporta RAILS_GROUPS, pero Bundler no. +if ENV['RAILS_GROUPS']&.include? 'assets' + gem 'sassc-rails' + gem 'uglifier', '>= 1.3.0' + gem 'bootstrap', '~> 4' +end # Turbolinks makes navigating your web application faster. Read more: # https://github.com/turbolinks/turbolinks From fd784919df49d8703713b0f741521079de37fdc4 Mon Sep 17 00:00:00 2001 From: f Date: Thu, 21 Oct 2021 20:54:12 -0300 Subject: [PATCH 212/234] rails ya corre yarn antes de compilar --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 36139d7a..5aca94f8 100644 --- a/Makefile +++ b/Makefile @@ -48,7 +48,7 @@ help: always ## Ayuda @echo -e "\nArgumentos:\n" @grep -E "^[a-z\-]+ \?=.*##" Makefile | sed -re "s/(.*) \?=.*##(.*)/\1;\2/" | column -s ";" -t | sed -re "s/^([^ ]+) /\x1B[38;5;197m\1\x1B[0m/" -assets: node_modules public/packs/manifest.json.br ## Compilar los assets +assets: public/packs/manifest.json.br ## Compilar los assets test: always ## Ejecutar los tests $(MAKE) rake args="test RAILS_ENV=test $(args)" From 084bf8547f0202d5409d1c36a0cab71a40e7322f Mon Sep 17 00:00:00 2001 From: f Date: Thu, 21 Oct 2021 20:54:50 -0300 Subject: [PATCH 213/234] ya no es necesario hacer limpieza en docker --- Dockerfile | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index 0b3253b4..07322903 100644 --- a/Dockerfile +++ b/Dockerfile @@ -60,10 +60,6 @@ RUN mv ../sutty/.bundle ./.bundle # Instalar secretos COPY --chown=app:root ./config/credentials.yml.enc ./config/ -# Eliminar la necesidad de un runtime JS en producción, porque los -# assets ya están pre-compilados. -RUN sed -re "/(sassc|uglifier|bootstrap|coffee-rails)/d" -i Gemfile -RUN bundle clean RUN rm -rf ./node_modules ./tmp/cache ./.git ./test ./doc # Eliminar archivos innecesarios USER root From 570761fab5efe47f0bbdf389550424c726dacc12 Mon Sep 17 00:00:00 2001 From: f Date: Thu, 21 Oct 2021 20:55:40 -0300 Subject: [PATCH 214/234] =?UTF-8?q?actualizar=20contenedor=20con=20una=20v?= =?UTF-8?q?ersi=C3=B3n=20espec=C3=ADfica=20de=20bundler?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/Dockerfile b/Dockerfile index 07322903..ee6ba871 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ # el mismo repositorio de trabajo. Cuando tengamos CI/CD algunas cosas # como el tarball van a tener que cambiar porque ya vamos a haber hecho # un clone/pull limpio. -FROM alpine:3.13.5 AS build +FROM alpine:3.13.6 AS build MAINTAINER "f " ARG RAILS_MASTER_KEY @@ -14,10 +14,10 @@ ENV SECRET_KEY_BASE solo_es_necesaria_para_correr_rake ENV RAILS_ENV production ENV RAILS_MASTER_KEY=$RAILS_MASTER_KEY -RUN apk add --no-cache libxslt libxml2 tzdata ruby ruby-bundler ruby-json ruby-bigdecimal ruby-rake +RUN apk add --no-cache libxslt libxml2 tzdata ruby ruby-json ruby-bigdecimal ruby-rake RUN apk add --no-cache postgresql-libs git yarn brotli libssh2 python3 -RUN test "2.7.3" = `ruby -e 'puts RUBY_VERSION'` +RUN test "2.7.4" = `ruby -e 'puts RUBY_VERSION'` # https://github.com/rubygems/rubygems/issues/2918 # https://gitlab.alpinelinux.org/alpine/aports/issues/10808 @@ -29,7 +29,7 @@ RUN cd /usr/lib/ruby/2.7.0 && patch -Np 0 -i /tmp/rubygems-platform-musl.patch RUN addgroup -g 82 -S www-data RUN adduser -s /bin/sh -G www-data -h /home/app -D app RUN install -dm750 -o app -g www-data /home/app/sutty -RUN gem install --no-document bundler +RUN gem install --no-document bundler:2.1.4 # Empezamos con la usuaria app USER app @@ -39,7 +39,8 @@ WORKDIR /home/app/sutty # Copiamos solo el Gemfile para poder instalar las gemas necesarias COPY --chown=app:www-data ./Gemfile . COPY --chown=app:www-data ./Gemfile.lock . -RUN bundle config set no-cache 'true' +RUN bundle config set no-cache true +RUN bundle config set specific_platform true RUN bundle install --path=./vendor --without='test development' # Vaciar la caché RUN rm vendor/ruby/2.7.0/cache/*.gem @@ -67,7 +68,7 @@ RUN apk add --no-cache findutils RUN find /home/app/checkout/vendor/ruby/2.7.0 -maxdepth 3 -type d -name test -o -name spec -o -name rubocop | xargs -r rm -rf # Contenedor final -FROM sutty/monit:latest +FROM registry.nulo.in/sutty/monit:3.13.6 ENV RAILS_ENV production # Pandoc @@ -75,13 +76,13 @@ RUN echo 'http://dl-cdn.alpinelinux.org/alpine/edge/testing' >> /etc/apk/reposit # Instalar las dependencias, separamos la librería de base de datos para # poder reutilizar este primer paso desde otros contenedores -RUN apk add --no-cache libxslt libxml2 tzdata ruby ruby-bundler ruby-json ruby-bigdecimal ruby-rake ruby-irb +RUN apk add --no-cache libxslt libxml2 tzdata ruby ruby-json ruby-bigdecimal ruby-rake ruby-irb ruby-io-console ruby-etc RUN apk add --no-cache postgresql-libs libssh2 file rsync git jpegoptim vips RUN apk add --no-cache ffmpeg imagemagick pandoc tectonic oxipng jemalloc RUN apk add --no-cache git-lfs openssh-client patch # Chequear que la versión de ruby sea la correcta -RUN test "2.7.3" = `ruby -e 'puts RUBY_VERSION'` +RUN test "2.7.4" = `ruby -e 'puts RUBY_VERSION'` # https://github.com/rubygems/rubygems/issues/2918 # https://gitlab.alpinelinux.org/alpine/aports/issues/10808 @@ -93,7 +94,7 @@ RUN apk add --no-cache patch && cd /usr/lib/ruby/2.7.0 && patch -Np 0 -i /tmp/ru # principal RUN apk add --no-cache yarn # Instalar foreman para poder correr los servicios -RUN gem install --no-document --no-user-install bundler foreman +RUN gem install --no-document --no-user-install bundler:2.1.4 foreman # Agregar el grupo del servidor web y la usuaria RUN addgroup -g 82 -S www-data From 8bebe155f4bd355087fb6685b47f55709a2df48b Mon Sep 17 00:00:00 2001 From: f Date: Fri, 22 Oct 2021 17:06:34 -0300 Subject: [PATCH 215/234] no hace falta subir los parches dos veces --- Makefile | 1 - 1 file changed, 1 deletion(-) diff --git a/Makefile b/Makefile index 36139d7a..592592bd 100644 --- a/Makefile +++ b/Makefile @@ -108,7 +108,6 @@ ota-js: assets ## Actualizar Javascript en el nodo delegado ota: ## Actualizar Rails en el nodo delegado umask 022; git format-patch $(commit) - scp ./0*.patch $(delegate):/tmp/ ssh $(delegate) mkdir -p /tmp/patches-$(commit)/ scp ./0*.patch $(delegate):/tmp/patches-$(commit)/ scp ./ota.sh $(delegate):/tmp/ From 84e543ac078abfd77ecb8662a791ae67ba1eda01 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 22 Oct 2021 17:13:26 -0300 Subject: [PATCH 216/234] ignorar los cambios de licencia en idiomas no soportados --- app/services/site_service.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/services/site_service.rb b/app/services/site_service.rb index 389549c3..5e2fc706 100644 --- a/app/services/site_service.rb +++ b/app/services/site_service.rb @@ -122,6 +122,8 @@ SiteService = Struct.new(:site, :usuarie, :params, keyword_init: true) do # la búsqueda. def change_licencias site.locales.each do |locale| + next unless I18n.available_locales.include? locale + Mobility.with_locale(locale) do permalink = "#{I18n.t('activerecord.models.licencia').downcase}/" post = site.posts(lang: locale).find_by(permalink: permalink) From 21bf478af700b79806e133e7c698fd3ccbdf8c0e Mon Sep 17 00:00:00 2001 From: f Date: Fri, 22 Oct 2021 17:36:00 -0300 Subject: [PATCH 217/234] actualizacion de cuidados --- Gemfile.lock | 65 ++++++++++++++++++++++++++-------------------------- 1 file changed, 33 insertions(+), 32 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index d8dd4fb4..67698998 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -93,9 +93,9 @@ GEM execjs (~> 2) bcrypt (3.1.16-x86_64-linux-musl) bcrypt_pbkdf (1.1.0-x86_64-linux-musl) - benchmark-ips (2.9.1) + benchmark-ips (2.9.2) bindex (0.8.1-x86_64-linux-musl) - blazer (2.4.3) + blazer (2.4.7) activerecord (>= 5) chartkick (>= 3.2) railties (>= 5) @@ -114,7 +114,7 @@ GEM rack-test (>= 0.5.4) xpath (>= 2.0, < 4.0) chartkick (4.0.5) - childprocess (3.0.0) + childprocess (4.1.0) coderay (1.1.3) colorator (1.1.0) commonmarker (0.21.2-x86_64-linux-musl) @@ -129,7 +129,7 @@ GEM activerecord (>= 5.a) database_cleaner-core (~> 2.0.0) database_cleaner-core (2.0.1) - dead_end (1.1.7) + dead_end (2.0.1) derailed_benchmarks (2.1.1) benchmark-ips (~> 2) dead_end @@ -157,7 +157,7 @@ GEM dotenv-rails (2.7.6) dotenv (= 2.7.6) railties (>= 3.2) - down (5.2.3) + down (5.2.4) addressable (~> 2.8) ed25519 (1.2.4-x86_64-linux-musl) editorial-autogestiva-jekyll-theme (0.3.4) @@ -240,7 +240,7 @@ GEM concurrent-ruby (~> 1.0) icalendar (2.7.1) ice_cube (~> 0.16) - ice_cube (0.16.3) + ice_cube (0.16.4) image_processing (1.12.1) mini_magick (>= 4.9.5, < 5) ruby-vips (>= 2.0.17, < 3) @@ -249,7 +249,7 @@ GEM nokogiri (>= 1.6) jbuilder (2.11.2) activesupport (>= 5.0.0) - jekyll (4.2.0) + jekyll (4.2.1) addressable (~> 2.4) colorator (~> 1.0) em-websocket (~> 0.5) @@ -306,7 +306,7 @@ GEM jekyll (~> 4) jekyll-watch (2.2.1) listen (~> 3.0) - jekyll-write-and-commit-changes (0.2.0) + jekyll-write-and-commit-changes (0.2.1) jekyll (~> 4) rugged (~> 1) kaminari (1.2.1) @@ -336,7 +336,7 @@ GEM ruby_dep (~> 1.2) loaf (0.10.0) railties (>= 3.2) - lockbox (0.6.5) + lockbox (0.6.6) lograge (0.11.2) actionpack (>= 4) activesupport (>= 4) @@ -347,7 +347,7 @@ GEM nokogiri (>= 1.5.9) mail (2.7.1) mini_mime (>= 0.1.1) - marcel (1.0.1) + marcel (1.0.2) memory_profiler (1.0.0) mercenary (0.4.0) method_source (1.0.0) @@ -356,25 +356,25 @@ GEM mime-types-data (3.2021.0901) mini_histogram (0.3.1) mini_magick (4.11.0) - mini_mime (1.1.1) - mini_portile2 (2.5.3) + mini_mime (1.1.2) + mini_portile2 (2.6.1) minima (2.5.1) jekyll (>= 3.5, < 5.0) jekyll-feed (~> 0.9) jekyll-seo-tag (~> 2.1) minitest (5.14.4) - mobility (1.1.3) + mobility (1.2.2) i18n (>= 0.6.10, < 2) request_store (~> 1.0) multi_xml (0.6.0) net-ssh (6.1.0) - netaddr (2.0.4) + netaddr (2.0.5) nio4r (2.5.8-x86_64-linux-musl) - nokogiri (1.11.7-x86_64-linux-musl) - mini_portile2 (~> 2.5.0) + nokogiri (1.12.5-x86_64-linux-musl) + mini_portile2 (~> 2.6.1) racc (~> 1.4) orm_adapter (0.5.0) - parallel (1.20.1) + parallel (1.21.0) parser (3.0.2.0) ast (~> 2.4.1) pathutil (0.16.2) @@ -390,11 +390,11 @@ GEM coderay (~> 1.1) method_source (~> 1.0) public_suffix (4.0.6) - puma (5.4.0-x86_64-linux-musl) + puma (5.5.2-x86_64-linux-musl) nio4r (~> 2.0) pundit (2.1.1) activesupport (>= 3.0.0) - racc (1.5.2-x86_64-linux-musl) + racc (1.6.0-x86_64-linux-musl) rack (2.2.3) rack-cors (1.1.1) rack (>= 2.0.0) @@ -469,7 +469,7 @@ GEM jekyll-unique-urls (~> 0.1) sutty-archives (~> 2.2) sutty-liquid (~> 0) - redis (4.4.0) + redis (4.5.1) redis-actionpack (5.2.0) actionpack (>= 5, < 7) redis-rack (>= 2.1.0, < 3) @@ -493,19 +493,19 @@ GEM actionpack (>= 5.0) railties (>= 5.0) rexml (3.2.5) - rouge (3.26.0) - rubocop (1.20.0) + rouge (3.26.1) + rubocop (1.22.2) parallel (~> 1.10) parser (>= 3.0.0.0) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 1.8, < 3.0) rexml - rubocop-ast (>= 1.9.1, < 2.0) + rubocop-ast (>= 1.12.0, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 1.4.0, < 3.0) - rubocop-ast (1.11.0) + rubocop-ast (1.12.0) parser (>= 3.0.1.1) - rubocop-rails (2.12.2) + rubocop-rails (2.12.4) activesupport (>= 4.2.0) rack (>= 1.1) rubocop (>= 1.7.0, < 2.0) @@ -513,7 +513,7 @@ GEM i18n ruby-filemagic (0.7.2-x86_64-linux-musl) ruby-progressbar (1.11.0) - ruby-statistics (2.1.3) + ruby-statistics (3.0.0) ruby-vips (2.1.3) ffi (~> 1.12) ruby2ruby (2.4.4) @@ -535,8 +535,9 @@ GEM sprockets (> 3.0) sprockets-rails tilt - selenium-webdriver (3.142.7) - childprocess (>= 0.5, < 4.0) + selenium-webdriver (4.0.3) + childprocess (>= 0.5, < 5.0) + rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2) semantic_range (3.0.0) sexp_processor (4.15.3) @@ -612,8 +613,8 @@ GEM execjs (>= 0.3.0, < 3) unf (0.1.4) unf_ext - unf_ext (0.0.7.7-x86_64-linux-musl) - unicode-display_width (1.7.0) + unf_ext (0.0.8-x86_64-linux-musl) + unicode-display_width (1.8.0) validates_hostname (1.0.11) activerecord (>= 3.0) activesupport (>= 3.0) @@ -624,7 +625,7 @@ GEM activemodel (>= 6.0.0) bindex (>= 0.4.0) railties (>= 6.0.0) - webpacker (5.4.2) + webpacker (5.4.3) activesupport (>= 5.2) rack-proxy (>= 0.6.1) railties (>= 5.2) @@ -635,7 +636,7 @@ GEM websocket-extensions (0.1.5) xpath (3.2.0) nokogiri (~> 1.8) - zeitwerk (2.4.2) + zeitwerk (2.5.1) PLATFORMS ruby From ab004fae700db1f3b72cd1a44ec403fdabb8c452 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 22 Oct 2021 18:21:47 -0300 Subject: [PATCH 218/234] decodificar las urls para poder buscarlas en el log --- app/controllers/stats_controller.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/controllers/stats_controller.rb b/app/controllers/stats_controller.rb index b9b93e9f..44073c1f 100644 --- a/app/controllers/stats_controller.rb +++ b/app/controllers/stats_controller.rb @@ -108,6 +108,8 @@ class StatsController < ApplicationController def normalized_paths @normalized_paths ||= normalized_urls.map do |u| "/#{u.split('/', 4).last}" + end.map do |u| + URI.decode_www_form_component u end end From c84462c4a8342ac9331718335c529d636ebb9298 Mon Sep 17 00:00:00 2001 From: f Date: Tue, 26 Oct 2021 11:33:15 -0300 Subject: [PATCH 219/234] =?UTF-8?q?recolectar=20estad=C3=ADsticas=20usando?= =?UTF-8?q?=20menos=20recursos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/jobs/periodic_job.rb | 55 +++++++++++++++++ app/jobs/stat_collection_job.rb | 93 +++++++++++++---------------- app/jobs/uri_collection_job.rb | 101 ++++++++++++++++++++++++++++++-- app/models/site.rb | 1 + app/models/stat.rb | 6 +- 5 files changed, 198 insertions(+), 58 deletions(-) create mode 100644 app/jobs/periodic_job.rb diff --git a/app/jobs/periodic_job.rb b/app/jobs/periodic_job.rb new file mode 100644 index 00000000..8d9453a3 --- /dev/null +++ b/app/jobs/periodic_job.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +# Una tarea que se corre periódicamente +class PeriodicJob < ApplicationJob + class RunAgainException < StandardError; end + + STARTING_INTERVAL = Stat::INTERVALS.first + + # Tener el sitio a mano + attr_reader :site + + # Descartar y notificar si pasó algo más. + # + # XXX: En realidad deberíamos seguir reintentando? + discard_on(StandardError) do |_, error| + ExceptionNotifier.notify_exception(error) + end + + # Correr indefinidamente una vez por hora. + # + # XXX: El orden importa, si el descarte viene después, nunca se va a + # reintentar. + retry_on(PeriodicJob::RunAgainException, wait: 1.try(STARTING_INTERVAL), attempts: Float::INFINITY, jitter: 0) + + private + + # Las clases que implementen esta tienen que usar este método al + # terminar. + def run_again! + raise PeriodicJob::RunAgainException, 'Reintentando' + end + + # El intervalo de inicio + # + # @return [Symbol] + def starting_interval + STARTING_INTERVAL + end + + # La última recolección de estadísticas o empezar desde el principio + # de los tiempos. + # + # @return [Stat] + def last_stat + @last_stat ||= site.stats.where(name: stat_name).last || + site.stats.build(created_at: Time.new(1970, 1, 1)) + end + + # Devuelve el comienzo del intervalo + # + # @return [Time] + def beginning_of_interval + @beginning_of_interval ||= last_stat.created_at.try(:"beginning_of_#{starting_interval}") + end +end diff --git a/app/jobs/stat_collection_job.rb b/app/jobs/stat_collection_job.rb index a49a1635..2aa8d702 100644 --- a/app/jobs/stat_collection_job.rb +++ b/app/jobs/stat_collection_job.rb @@ -3,74 +3,65 @@ # Genera resúmenes de información para poder mostrar estadísticas y se # corre regularmente a sí misma. class StatCollectionJob < ApplicationJob - class CrontabException < StandardError; end + STAT_NAME = 'stat_collection_job' - # Descartar y notificar si pasó algo más. - # - # XXX: En realidad deberíamos seguir reintentando? - discard_on(Exception) do |_, error| - ExceptionNotifier.notify_exception error - end + def perform(site_id:, once: true) + @site = Site.find site_id - # Correr indefinidamente una vez por hora. - # - # XXX: El orden importa, si el descarte viene después, nunca se va a - # reintentar. - retry_on(StatCollectionJob::CrontabException, wait: 1.hour, attempts: Float::INFINITY, jitter: 0) + scope.rollup('builds', **options) - COLUMNS = %i[uri].freeze + scope.rollup('space_used', **options) do |rollup| + rollup.average(:bytes) + end - def perform(once: false) - Site.find_each do |site| - hostnames = [site.hostname, site.alternative_hostnames].flatten + scope.rollup('build_time', **options) do |rollup| + rollup.average(:seconds) + end - # Usamos el primero porque luego podemos hacer un rollup recursivo - options = { interval: Stat::INTERVALS.first } + # XXX: Es correcto promediar promedios? + Stat::INTERVALS.reduce do |previous, current| + rollup(name: 'builds', interval_previous: previous, interval: current) + rollup(name: 'space_used', interval_previous: previous, interval: current, operation: :average) + rollup(name: 'build_time', interval_previous: previous, interval: current, operation: :average) - # Visitas por hostname - hostnames.each do |hostname| - AccessLog.where(host: hostname).completed_requests.non_robots.pages.group(:host).rollup('host', **options) - - combined_columns(hostname, **options) - end - - stats_by_site(site, **options) + current end # Registrar que se hicieron todas las recolecciones - Stat.create! + site.stats.create! name: STAT_NAME - raise CrontabException unless once + run_again! unless once end private - # Combinación de columnas - def combined_columns(hostname, **options) - where = { host: hostname } - - COLUMNS.each do |column| - AccessLog.where(host: hostname).pluck(Arel.sql("distinct #{column}")).each do |value| - where[column] = value - - AccessLog.where(**where).completed_requests.non_robots.group(:host, column).rollup("host|#{column}", **options) - end - end + # Genera un rollup recursivo en base al período anterior y aplica una + # operación. + # + # @return [NilClass] + def rollup(name:, interval_previous:, interval:, operation: :sum) + Rollup.where(name: name, interval: interval_previous) + .where_dimensions(site_id: site.id) + .group("dimensions->'site_id'") + .rollup(name, interval: interval, update: true) do |rollup| + rollup.try(:operation, :value) + end end - # Uso de recursos por cada sitio. + # Los registros a procesar # - # XXX: En realidad se agrupan por el deploy_id, que siempre será el - # del DeployLocal. - def stats_by_site(site, **options) - site.build_stats.jekyll.group(:deploy_id).rollup('builds', **options) + # @return [ActiveRecord::Relation] + def scope + @scope ||= site.build_stats + .jekyll + .where('created_at => ?', beginning_of_interval) + .group(:site_id) + end - site.build_stats.jekyll.group(:deploy_id).rollup('space_used', **options) do |rollup| - rollup.average(:bytes) - end - - site.build_stats.jekyll.group(:deploy_id).rollup('build_time', **options) do |rollup| - rollup.average(:seconds) - end + # Las opciones por defecto + # + # @return [Hash] + def options + @options ||= { interval: starting_interval, update: true } end end diff --git a/app/jobs/uri_collection_job.rb b/app/jobs/uri_collection_job.rb index 79b83644..9ec333cd 100644 --- a/app/jobs/uri_collection_job.rb +++ b/app/jobs/uri_collection_job.rb @@ -1,16 +1,105 @@ # frozen_string_literal: true # Procesar una lista de URIs para una lista de dominios. Esto nos -# permite procesar estadísticas a demanada. -class UriCollectionJob < ApplicationJob - def perform(hostnames:, file:) - uris = File.read(file).split("\n") +# permite procesar estadísticas a demanda. +# +# Hay varias cosas acá que van a convertirse en métodos propios, como la +# detección de URIs de un sitio (aunque la versión actual detecta todas +# las páginas y no solo las de posts como tenemos planeado, hay que +# resolver eso). +# +# Los hostnames de un sitio van a poder obtenerse a partir de +# Site#hostnames con la garantía de que son únicos. +class UriCollectionJob < PeriodicJob + # Ignoramos imágenes porque suelen ser demasiadas y no aportan a las + # estadísticas. + IMAGES = %w[.png .jpg .jpeg .gif .webp].freeze + STAT_NAME = 'uri_collection_job' + + def perform(site_id:, once: true) + @site = Site.find site_id hostnames.each do |hostname| uris.each do |uri| - break if File.exist? Rails.root.join('tmp', 'uri_collection_job_stop') + return if File.exist? Rails.root.join('tmp', 'uri_collection_job_stop') - AccessLog.where(host: hostname, uri: uri).completed_requests.non_robots.group(:host, :uri).rollup('host|uri', interval: 'day') + AccessLog.where(host: hostname, uri: uri) + .where('created_at >= ?', beginning_of_interval) + .completed_requests + .non_robots + .group(:host, :uri) + .rollup('host|uri', interval: starting_interval, update: true) + + # Reducir las estadísticas calculadas aplicando un rollup sobre el + # intervalo más amplio. + Stat::INTERVALS.reduce do |previous, current| + Rollup.where(name: 'host|uri', interval: previous) + .where_dimensions(host: hostname, uri: uri) + .group("dimensions->'host'", "dimensions->'uri'") + .rollup('host|uri', interval: current, update: true) do |rollup| + rollup.sum(:value) + end + + # Devolver el intervalo actual + current + end + end + end + + # Recordar la última vez que se corrió la tarea + site.stats.create! name: STAT_NAME + + run_again! unless once + end + + private + + def stat_name + STAT_NAME + end + + # @return [String] + # + # TODO: Cambiar al mergear origin-referer + def destination + @destination ||= site.deploys.find_by(type: 'DeployLocal').destination + end + + # TODO: Cambiar al mergear origin-referer + # + # @return [Array] + def hostnames + @hostnames ||= site.deploys.map do |deploy| + case deploy + when DeployLocal + site.hostname + when DeployWww + deploy.fqdn + when DeployAlternativeDomain + deploy.hostname.dup.tap do |h| + h.replace(h.end_with?('.') ? h[0..-2] : "#{h}.#{Site.domain}") + end + when DeployHiddenService + deploy.onion + end + end.compact + end + + # Recolecta todas las URIs menos imágenes + # + # @return [Array] + def uris + @uris ||= Dir.chdir destination do + (Dir.glob('**/*.html') + Dir.glob('public/**/*').reject do |p| + File.directory? p + end.reject do |p| + p = p.downcase + + IMAGES.any? do |i| + p.end_with? i + end + end).map do |uri| + "/#{uri}" end end end diff --git a/app/models/site.rb b/app/models/site.rb index ddfe2bc9..df92264a 100644 --- a/app/models/site.rb +++ b/app/models/site.rb @@ -37,6 +37,7 @@ class Site < ApplicationRecord belongs_to :design belongs_to :licencia + has_many :stats has_many :log_entries, dependent: :destroy has_many :deploys, dependent: :destroy has_many :build_stats, through: :deploys diff --git a/app/models/stat.rb b/app/models/stat.rb index c986ba4b..5f72ccd0 100644 --- a/app/models/stat.rb +++ b/app/models/stat.rb @@ -1,6 +1,10 @@ # frozen_string_literal: true +# Registran cuándo fue la última recolección de datos. class Stat < ApplicationRecord - INTERVALS = %i[year month day].freeze + # XXX: Los intervalos van en orden de mayor especificidad a menor + INTERVALS = %i[day month year].freeze RESOURCES = %i[builds space_used build_time].freeze + + belongs_to :site end From 7a780188e4f85030d619014061e3ce09ef060b85 Mon Sep 17 00:00:00 2001 From: f Date: Tue, 26 Oct 2021 16:19:42 -0300 Subject: [PATCH 220/234] asegurarse que siempre buscamos assets --- Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 01167fa1..981ce7fd 100644 --- a/Gemfile +++ b/Gemfile @@ -16,7 +16,7 @@ gem 'puma' # assets. No es necesario instalarlas en producción. # # XXX: Supuestamente Rails ya soporta RAILS_GROUPS, pero Bundler no. -if ENV['RAILS_GROUPS']&.include? 'assets' +if ENV['RAILS_GROUPS']&.split(',')&.include? 'assets' gem 'sassc-rails' gem 'uglifier', '>= 1.3.0' gem 'bootstrap', '~> 4' From 25fe41bfa258d167c978152939bc65c9eb4c03b7 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 27 Oct 2021 10:12:28 -0300 Subject: [PATCH 221/234] enviar el idioma al reordenar! MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit los tests envían el idioma pero no el panel --- app/views/posts/index.haml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/views/posts/index.haml b/app/views/posts/index.haml index ad07b9dc..90d65670 100644 --- a/app/views/posts/index.haml +++ b/app/views/posts/index.haml @@ -67,6 +67,7 @@ %h2= t('posts.empty') - else = form_tag site_posts_reorder_path, method: :post do + %input{ type: 'hidden', name: 'post[lang]', value: @locale } %table.table{ data: { controller: 'reorder' } } %caption.sr-only= t('posts.caption') %thead From 09f5583d313801d42f5b930fd5eac28699302148 Mon Sep 17 00:00:00 2001 From: Nulo Date: Thu, 28 Oct 2021 15:32:13 -0300 Subject: [PATCH 222/234] Makefile: setear ENV_FILE en hain Requiere de https://0xacab.org/sutty/haini.sh/-/merge_requests/33. Pasa RAILS_GROUPS y otras variables. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 18914f43..1f0ca914 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,7 @@ commit ?= origin/rails## Commit desde el que actualizar env ?= staging## Entorno del nodo delegado sutty ?= $(SUTTY)## Dirección local delegate ?= $(DELEGATE)## Cambia el nodo delegado -hain ?= $(HAINISH)## Ubicación de Hainish +hain ?= ENV_FILE=.env $(HAINISH)## Ubicación de Hainish # El nodo delegado tiene dos entornos, production y staging. # Dependiendo del entorno que elijamos, se van a generar los assets y el From 2844ca9f5f08c0d307734e5d52b2c7e63b989ec7 Mon Sep 17 00:00:00 2001 From: Nulo Date: Thu, 28 Oct 2021 16:06:31 -0300 Subject: [PATCH 223/234] .env.example: setear RAILS_ENV=development --- .env.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env.example b/.env.example index f79ff3a4..fb086224 100644 --- a/.env.example +++ b/.env.example @@ -2,7 +2,7 @@ RAILS_GROUPS=assets DELEGATE=athshe.sutty.nl HAINISH=../haini.sh/haini.sh DATABASE= -RAILS_ENV= +RAILS_ENV=development IMAP_SERVER= DEFAULT_FROM= EXCEPTION_TO= From 21f650fc575656e174a22d36d3f89f6ea0e33f7f Mon Sep 17 00:00:00 2001 From: Nulo Date: Thu, 28 Oct 2021 16:13:39 -0300 Subject: [PATCH 224/234] Editor: forzar modo claro MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Esto hace que sea legible y más usable cuando el modo oscuro está activado. https://0xacab.org/sutty/sutty/-/issues/2135 Idealmente, me gustaría tener modo oscuro real en el editor. --- app/assets/stylesheets/editor.scss | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/assets/stylesheets/editor.scss b/app/assets/stylesheets/editor.scss index 5d218c7e..e0886533 100644 --- a/app/assets/stylesheets/editor.scss +++ b/app/assets/stylesheets/editor.scss @@ -2,6 +2,13 @@ box-sizing: border-box; *, *::before, *::after { box-sizing: inherit; } + // Arreglo temporal para que las cosas sean legibles en modo oscuro + --foreground: black; + --background: white; + --color: #f206f9; + background: var(--background); + color: var(--foreground); + h1, h2, h3, h4, h5, h6, p, li { min-height: 1.5rem; } From 66ec5269378c908f83975295d052fe07fd0a4203 Mon Sep 17 00:00:00 2001 From: f Date: Thu, 4 Nov 2021 10:42:57 -0300 Subject: [PATCH 225/234] permitir descripciones mas cortas --- app/models/site.rb | 2 +- app/views/sites/_form.haml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/site.rb b/app/models/site.rb index df92264a..5b78d625 100644 --- a/app/models/site.rb +++ b/app/models/site.rb @@ -26,7 +26,7 @@ class Site < ApplicationRecord validates :design_id, presence: true validates_inclusion_of :status, in: %w[waiting enqueued building] validates_presence_of :title - validates :description, length: { in: 50..160 } + validates :description, length: { in: 10..160 } validate :deploy_local_presence validate :compatible_layouts, on: :update diff --git a/app/views/sites/_form.haml b/app/views/sites/_form.haml index d26eeebf..6f15d570 100644 --- a/app/views/sites/_form.haml +++ b/app/views/sites/_form.haml @@ -39,7 +39,7 @@ %h2= f.label :description %p.lead= t('.help.description') = f.text_area :description, class: form_control(site, :description), - maxlength: 160, minlength: 50, required: true + maxlength: 160, minlength: 10, required: true - if invalid? site, :description .invalid-feedback= site.errors.messages[:description].join(', ') %hr/ From 6dcbe97ac6273fdf637f6143ecc1f88cb0255193 Mon Sep 17 00:00:00 2001 From: f Date: Thu, 4 Nov 2021 10:58:49 -0300 Subject: [PATCH 226/234] permalinks relativos --- app/models/metadata_permalink.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/metadata_permalink.rb b/app/models/metadata_permalink.rb index dc65aa86..59b68461 100644 --- a/app/models/metadata_permalink.rb +++ b/app/models/metadata_permalink.rb @@ -5,7 +5,7 @@ class MetadataPermalink < MetadataString # El valor por defecto una vez creado es la URL que le asigne Jekyll, # de forma que nunca cambia aunque se cambie el título. def default_value - document.url unless post.new? + document.url.sub(%r{\A/}, '') unless post.new? end # Los permalinks nunca pueden ser privados From d85f21c3ca08161255c13ead2c756414b7122f50 Mon Sep 17 00:00:00 2001 From: f Date: Thu, 25 Nov 2021 09:42:58 -0300 Subject: [PATCH 227/234] =?UTF-8?q?actualizaci=C3=B3n=20de=20cuidados?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Gemfile | 2 +- Gemfile.lock | 68 ++++++++++++++++++++++++++-------------------------- 2 files changed, 35 insertions(+), 35 deletions(-) diff --git a/Gemfile b/Gemfile index 88dde566..2b304ee0 100644 --- a/Gemfile +++ b/Gemfile @@ -22,7 +22,7 @@ if ENV['RAILS_GROUPS']&.split(',')&.include? 'assets' gem 'bootstrap', '~> 4' end -gem 'nokogiri', '~>1.11.0' +gem 'nokogiri' # Turbolinks makes navigating your web application faster. Read more: # https://github.com/turbolinks/turbolinks diff --git a/Gemfile.lock b/Gemfile.lock index 814175f0..9f0b56c5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -8,10 +8,10 @@ GIT GIT remote: https://github.com/ankane/rollup.git - revision: 94ca777d54180c23e96ac4b4285cc9b405ccbd1a + revision: 0ab6c603450175eb1004f7793e86486943cb9f72 branch: master specs: - rollups (0.1.2) + rollups (0.1.3) activesupport (>= 5.1) groupdate (>= 5.2) @@ -113,7 +113,7 @@ GEM autoprefixer-rails (>= 9.1.0) popper_js (>= 1.14.3, < 2) sassc-rails (>= 2.0.0) - brakeman (5.1.1) + brakeman (5.1.2) builder (3.2.4) capybara (2.18.0) addressable @@ -122,7 +122,7 @@ GEM rack (>= 1.0.0) rack-test (>= 0.5.4) xpath (>= 2.0, < 4.0) - chartkick (4.0.5) + chartkick (4.1.2) childprocess (4.1.0) coderay (1.1.3) colorator (1.1.0) @@ -138,7 +138,7 @@ GEM activerecord (>= 5.a) database_cleaner-core (~> 2.0.0) database_cleaner-core (2.0.1) - dead_end (2.0.1) + dead_end (3.1.0) derailed_benchmarks (2.1.1) benchmark-ips (~> 2) dead_end @@ -157,7 +157,7 @@ GEM railties (>= 4.1.0) responders warden (~> 1.2.3) - devise-i18n (1.10.0) + devise-i18n (1.10.1) devise (>= 4.8.0) devise_invitable (2.0.5) actionmailer (>= 5.0) @@ -188,9 +188,9 @@ GEM jekyll-unique-urls (~> 0) jekyll-write-and-commit-changes (~> 0) sutty-liquid (~> 0) - em-websocket (0.5.2) + em-websocket (0.5.3) eventmachine (>= 0.12.9) - http_parser.rb (~> 0.6.0) + http_parser.rb (~> 0) errbase (0.2.1) erubi (1.10.0) eventmachine (1.2.7-x86_64-linux-musl) @@ -212,7 +212,7 @@ GEM activerecord (>= 4.0.0) get_process_mem (0.2.7) ffi (~> 1.0) - globalid (0.5.2) + globalid (0.6.0) activesupport (>= 5.0) groupdate (5.2.2) activesupport (>= 5) @@ -243,11 +243,11 @@ GEM heapy (0.2.0) thor hiredis (0.6.3-x86_64-linux-musl) - http_parser.rb (0.6.0-x86_64-linux-musl) + http_parser.rb (0.8.0-x86_64-linux-musl) httparty (0.18.1) mime-types (~> 3.0) multi_xml (>= 0.5.2) - i18n (1.8.10) + i18n (1.8.11) concurrent-ruby (~> 1.0) icalendar (2.7.1) ice_cube (~> 0.16) @@ -258,7 +258,7 @@ GEM inline_svg (1.7.2) activesupport (>= 3.0) nokogiri (>= 1.6) - jbuilder (2.11.2) + jbuilder (2.11.3) activesupport (>= 5.0.0) jekyll (4.2.1) addressable (~> 2.4) @@ -289,7 +289,7 @@ GEM jekyll (~> 4) jekyll-ignore-layouts (0.1.2) jekyll (~> 4) - jekyll-images (0.2.7) + jekyll-images (0.3.0) jekyll (~> 4) ruby-filemagic (~> 0.7) ruby-vips (~> 2) @@ -297,7 +297,7 @@ GEM jekyll (>= 3.7, < 5.0) jekyll-linked-posts (0.4.2) jekyll (~> 4) - jekyll-locales (0.1.12) + jekyll-locales (0.1.13) jekyll-lunr (0.3.0) loofah (~> 2.4) jekyll-order (0.1.4) @@ -307,9 +307,9 @@ GEM sassc (> 2.0.1, < 3.0) jekyll-seo-tag (2.7.1) jekyll (>= 3.8, < 5.0) - jekyll-spree-client (0.1.18) + jekyll-spree-client (0.1.19) fast_blank (~> 1) - spree-api-client (>= 0.2.3) + spree-api-client (>= 0.2.4) jekyll-turbolinks (0.0.5) jekyll (~> 4) turbolinks-source (~> 5) @@ -362,9 +362,9 @@ GEM memory_profiler (1.0.0) mercenary (0.4.0) method_source (1.0.0) - mime-types (3.3.1) + mime-types (3.4.1) mime-types-data (~> 3.2015) - mime-types-data (3.2021.0901) + mime-types-data (3.2021.1115) mini_histogram (0.3.1) mini_magick (4.11.0) mini_mime (1.1.2) @@ -374,7 +374,7 @@ GEM jekyll-feed (~> 0.9) jekyll-seo-tag (~> 2.1) minitest (5.14.4) - mobility (1.2.2) + mobility (1.2.3) i18n (>= 0.6.10, < 2) request_store (~> 1.0) multi_xml (0.6.0) @@ -395,7 +395,7 @@ GEM activerecord (>= 5.2) activesupport (>= 5.2) popper_js (1.16.0) - prometheus_exporter (0.8.1) + prometheus_exporter (1.0.0) webrick pry (0.14.1) coderay (~> 1.1) @@ -505,7 +505,7 @@ GEM railties (>= 5.0) rexml (3.2.5) rouge (3.26.1) - rubocop (1.22.2) + rubocop (1.23.0) parallel (~> 1.10) parser (>= 3.0.0.0) rainbow (>= 2.2.2, < 4.0) @@ -514,7 +514,7 @@ GEM rubocop-ast (>= 1.12.0, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 1.4.0, < 3.0) - rubocop-ast (1.12.0) + rubocop-ast (1.13.0) parser (>= 3.0.1.1) rubocop-rails (2.12.4) activesupport (>= 4.2.0) @@ -525,14 +525,14 @@ GEM ruby-filemagic (0.7.2-x86_64-linux-musl) ruby-progressbar (1.11.0) ruby-statistics (3.0.0) - ruby-vips (2.1.3) + ruby-vips (2.1.4) ffi (~> 1.12) ruby2ruby (2.4.4) ruby_parser (~> 3.1) sexp_processor (~> 4.6) ruby_dep (1.5.0) - ruby_parser (3.17.0) - sexp_processor (~> 4.15, >= 4.15.1) + ruby_parser (3.18.1) + sexp_processor (~> 4.16) rubyzip (2.3.2) rugged (1.2.0-x86_64-linux-musl) safe_yaml (1.0.6) @@ -546,12 +546,12 @@ GEM sprockets (> 3.0) sprockets-rails tilt - selenium-webdriver (4.0.3) + selenium-webdriver (4.1.0) childprocess (>= 0.5, < 5.0) rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2) semantic_range (3.0.0) - sexp_processor (4.15.3) + sexp_processor (4.16.0) share-to-fediverse-jekyll-theme (0.1.4) jekyll (~> 4.0) jekyll-data (~> 1.1) @@ -563,7 +563,7 @@ GEM simpleidn (0.2.1) unf (~> 0.1.4) sourcemap (0.1.1) - spree-api-client (0.2.3) + spree-api-client (0.2.4) fast_blank (~> 1) httparty (~> 0.18.0) spring (2.1.1) @@ -573,9 +573,9 @@ GEM sprockets (4.0.2) concurrent-ruby (~> 1.0) rack (> 1, < 3) - sprockets-rails (3.2.2) - actionpack (>= 4.0) - activesupport (>= 4.0) + sprockets-rails (3.4.1) + actionpack (>= 5.2) + activesupport (>= 5.2) sprockets (>= 3.0.0) sqlite3 (1.4.2-x86_64-linux-musl) stackprof (0.2.17-x86_64-linux-musl) @@ -600,7 +600,7 @@ GEM jekyll-include-cache (~> 0) jekyll-relative-urls (~> 0.0) jekyll-seo-tag (~> 2.1) - sutty-liquid (0.7.3) + sutty-liquid (0.7.4) fast_blank (~> 1.0) jekyll (~> 4) sutty-minima (2.5.0) @@ -631,7 +631,7 @@ GEM activesupport (>= 3.0) warden (1.2.9) rack (>= 2.0.9) - web-console (4.1.0) + web-console (4.2.0) actionview (>= 6.0.0) activemodel (>= 6.0.0) bindex (>= 0.4.0) @@ -705,7 +705,7 @@ DEPENDENCIES minima mobility net-ssh - nokogiri (~> 1.11.0) + nokogiri pg pg_search prometheus_exporter From 1fa97854a6858cfe90491b3b69814a360a92b1ff Mon Sep 17 00:00:00 2001 From: f Date: Mon, 29 Nov 2021 11:30:55 -0300 Subject: [PATCH 228/234] actualizar mobility para recuperar performance --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 9f0b56c5..8df2d77e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -374,7 +374,7 @@ GEM jekyll-feed (~> 0.9) jekyll-seo-tag (~> 2.1) minitest (5.14.4) - mobility (1.2.3) + mobility (1.2.4) i18n (>= 0.6.10, < 2) request_store (~> 1.0) multi_xml (0.6.0) From f2a0654ebcb2fd63992a7c146d48b36f45d8a832 Mon Sep 17 00:00:00 2001 From: Nulo Date: Tue, 30 Nov 2021 16:45:19 +0000 Subject: [PATCH 229/234] =?UTF-8?q?Permitir=20clickear=20en=20el=20p=C3=A1?= =?UTF-8?q?rrafo=20de=20ejemplo=20para=20subir=20multimedia?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/assets/stylesheets/editor.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/assets/stylesheets/editor.scss b/app/assets/stylesheets/editor.scss index e0886533..30fab60a 100644 --- a/app/assets/stylesheets/editor.scss +++ b/app/assets/stylesheets/editor.scss @@ -71,6 +71,10 @@ strong, em, del, u, sub, sup, small { background: #0002; } a { background: #13fefe50; } [data-editor-selected] { outline: #f206f9 solid thick; } + p[data-multimedia-inner] { + // Ignorar clicks en el párrafo placeholder + pointer-events: none; + } } *[data-editor-loading] { From 380d80ded4d3ffd4291d3d98d5f0d9ad0c6cef03 Mon Sep 17 00:00:00 2001 From: f Date: Mon, 6 Dec 2021 14:55:46 -0300 Subject: [PATCH 230/234] =?UTF-8?q?solo=20instalar=20paquetes=20de=20produ?= =?UTF-8?q?cci=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit closes #3892 --- app/models/deploy_local.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/deploy_local.rb b/app/models/deploy_local.rb index c063b7bc..fb1142b2 100644 --- a/app/models/deploy_local.rb +++ b/app/models/deploy_local.rb @@ -82,7 +82,7 @@ class DeployLocal < Deploy def yarn return true unless yarn_lock? - run 'yarn' + run 'yarn install --production' end def bundle From 4e461e7af6f648fc4b5d91437160ec363e5cdc8b Mon Sep 17 00:00:00 2001 From: f Date: Thu, 9 Dec 2021 10:42:03 -0300 Subject: [PATCH 231/234] =?UTF-8?q?usar=20una=20cach=C3=A9=20com=C3=BAn=20?= =?UTF-8?q?para=20yarn?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/deploy_local.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/models/deploy_local.rb b/app/models/deploy_local.rb index fb1142b2..f9225f39 100644 --- a/app/models/deploy_local.rb +++ b/app/models/deploy_local.rb @@ -62,10 +62,15 @@ class DeployLocal < Deploy 'AIRBRAKE_PROJECT_ID' => site.id.to_s, 'AIRBRAKE_PROJECT_KEY' => site.airbrake_api_key, 'JEKYLL_ENV' => Rails.env, - 'LANG' => ENV['LANG'] + 'LANG' => ENV['LANG'], + 'YARN_CACHE_FOLDER' => yarn_cache_dir } end + def yarn_cache_dir + Rails.root.join('_yarn_cache') + end + def yarn_lock File.join(site.path, 'yarn.lock') end From 36cf199548805b498b810cb8789b84254c76443b Mon Sep 17 00:00:00 2001 From: f Date: Thu, 23 Dec 2021 22:47:15 -0300 Subject: [PATCH 232/234] devolver una string --- app/models/deploy_local.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/deploy_local.rb b/app/models/deploy_local.rb index f9225f39..4fa588f5 100644 --- a/app/models/deploy_local.rb +++ b/app/models/deploy_local.rb @@ -68,7 +68,7 @@ class DeployLocal < Deploy end def yarn_cache_dir - Rails.root.join('_yarn_cache') + Rails.root.join('_yarn_cache').to_s end def yarn_lock From 87c94e95484e0765fbdb8a5094af47c0895d8eb2 Mon Sep 17 00:00:00 2001 From: f Date: Mon, 7 Mar 2022 11:47:37 -0300 Subject: [PATCH 233/234] mostrar el html --- app/views/posts/show.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/posts/show.haml b/app/views/posts/show.haml index da6ac9db..e46114af 100644 --- a/app/views/posts/show.haml +++ b/app/views/posts/show.haml @@ -38,4 +38,4 @@ - cache [metadata, I18n.locale] do %section.editor{ id: attr, dir: dir } - = @post.public_send(attr).to_s.html_safe + = @post.public_send(attr).value.html_safe From c9edc86be34d8728d5a15f599553a90977893efc Mon Sep 17 00:00:00 2001 From: f Date: Mon, 7 Mar 2022 11:48:46 -0300 Subject: [PATCH 234/234] =?UTF-8?q?copiar=20una=20tabla=20desde=20producci?= =?UTF-8?q?=C3=B3n=20a=20desarrollo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Makefile | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 1f0ca914..5a9ad7a8 100644 --- a/Makefile +++ b/Makefile @@ -71,6 +71,15 @@ rake: ## Corre rake dentro del entorno de desarrollo (pasar argumentos con args= bundle: ## Corre bundle dentro del entorno de desarrollo (pasar argumentos con args=). $(hain) 'bundle $(args)' +psql := psql -h $(PG_HOST) -U $(PG_USER) -p $(PG_PORT) -d sutty +copy-table: + test -n "$(table)" + echo "truncate $(table) $(cascade);" | $(psql) + ssh $(delegate) docker exec postgresql pg_dump -U sutty -d sutty -t $(table) | $(psql) + +psql: + $(psql) + rubocop: ## Yutea el código que está por ser commiteado git status --porcelain \ | grep -E "^(A|M)" \ @@ -102,8 +111,7 @@ save: ## Subir la imagen Docker al nodo delegado @echo -e "\a" ota-js: assets ## Actualizar Javascript en el nodo delegado - sudo chgrp -R 82 public/ - rsync -avi --delete-after public/ root@$(delegate):/srv/sutty/srv/http/data/_$(public)/ + rsync -avi --delete-after --chown 1000:82 public/ root@$(delegate):/srv/sutty/srv/http/data/_$(public)/ ssh root@$(delegate) docker exec $(container) sh -c "cat /srv/http/tmp/puma.pid | xargs -r kill -USR2" ota: ## Actualizar Rails en el nodo delegado