mirror of
https://0xacab.org/sutty/sutty
synced 2024-11-16 13:41:41 +00:00
Merge branch 'rails' into recuperar-partials
This commit is contained in:
commit
7272a37e23
31 changed files with 420 additions and 314 deletions
|
@ -1,10 +1,4 @@
|
||||||
# Excluir todo
|
# Excluir todo
|
||||||
*
|
*
|
||||||
# Solo agregar lo que usamos en COPY
|
# Solo agregar lo que usamos en COPY
|
||||||
!./.git/
|
# !./archivo
|
||||||
!./rubygems-platform-musl.patch
|
|
||||||
!./Gemfile
|
|
||||||
!./Gemfile.lock
|
|
||||||
!./config/credentials.yml.enc
|
|
||||||
!./public/assets/
|
|
||||||
!./public/packs/
|
|
||||||
|
|
122
Dockerfile
122
Dockerfile
|
@ -1,125 +1,21 @@
|
||||||
# Este Dockerfile está armado pensando en una compilación lanzada desde
|
FROM registry.nulo.in/sutty/rails:3.13.6-2.7.5
|
||||||
# el mismo repositorio de trabajo. Cuando tengamos CI/CD algunas cosas
|
ARG PANDOC_VERSION=2.17.1.1
|
||||||
# como el tarball van a tener que cambiar porque ya vamos a haber hecho
|
|
||||||
# un clone/pull limpio.
|
|
||||||
FROM alpine:3.13.6 AS build
|
|
||||||
MAINTAINER "f <f@sutty.nl>"
|
|
||||||
|
|
||||||
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_ENV production
|
||||||
ENV RAILS_MASTER_KEY=$RAILS_MASTER_KEY
|
|
||||||
|
|
||||||
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.4" = `ruby -e 'puts RUBY_VERSION'`
|
|
||||||
|
|
||||||
# https://github.com/rubygems/rubygems/issues/2918
|
|
||||||
# https://gitlab.alpinelinux.org/alpine/aports/issues/10808
|
|
||||||
RUN apk add --no-cache patch
|
|
||||||
COPY ./rubygems-platform-musl.patch /tmp/
|
|
||||||
RUN cd /usr/lib/ruby/2.7.0 && patch -Np 0 -i /tmp/rubygems-platform-musl.patch
|
|
||||||
|
|
||||||
# Agregar el usuario
|
|
||||||
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:2.1.4
|
|
||||||
|
|
||||||
# Empezamos con la usuaria app
|
|
||||||
USER app
|
|
||||||
# Vamos a trabajar dentro de este directorio
|
|
||||||
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 specific_platform true
|
|
||||||
RUN bundle install --path=./vendor --without='test development'
|
|
||||||
# Vaciar la caché
|
|
||||||
RUN rm vendor/ruby/2.7.0/cache/*.gem
|
|
||||||
|
|
||||||
# Copiar el repositorio git
|
|
||||||
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 -rf ./vendor
|
|
||||||
RUN mv ../sutty/vendor ./vendor
|
|
||||||
RUN mv ../sutty/.bundle ./.bundle
|
|
||||||
|
|
||||||
# Instalar secretos
|
|
||||||
COPY --chown=app:root ./config/credentials.yml.enc ./config/
|
|
||||||
|
|
||||||
RUN rm -rf ./node_modules ./tmp/cache ./.git ./test ./doc
|
|
||||||
# Eliminar archivos innecesarios
|
|
||||||
USER root
|
|
||||||
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 registry.nulo.in/sutty/monit:3.13.6
|
|
||||||
ENV RAILS_ENV production
|
|
||||||
|
|
||||||
# Pandoc
|
|
||||||
RUN echo 'http://dl-cdn.alpinelinux.org/alpine/edge/testing' >> /etc/apk/repositories
|
|
||||||
|
|
||||||
# Instalar las dependencias, separamos la librería de base de datos para
|
# Instalar las dependencias, separamos la librería de base de datos para
|
||||||
# poder reutilizar este primer paso desde otros contenedores
|
# poder reutilizar este primer paso desde otros contenedores
|
||||||
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.4" = `ruby -e 'puts RUBY_VERSION'`
|
|
||||||
|
|
||||||
# https://github.com/rubygems/rubygems/issues/2918
|
|
||||||
# https://gitlab.alpinelinux.org/alpine/aports/issues/10808
|
|
||||||
COPY ./rubygems-platform-musl.patch /tmp/
|
|
||||||
RUN apk add --no-cache patch && cd /usr/lib/ruby/2.7.0 && patch -Np 0 -i /tmp/rubygems-platform-musl.patch && apk del patch
|
|
||||||
|
|
||||||
# Necesitamos yarn para que Jekyll pueda generar los sitios
|
# Necesitamos yarn para que Jekyll pueda generar los sitios
|
||||||
# XXX: Eliminarlo cuando extraigamos la generación de sitios del proceso
|
# XXX: Eliminarlo cuando extraigamos la generación de sitios del proceso
|
||||||
# principal
|
# principal
|
||||||
RUN apk add --no-cache yarn
|
RUN apk add --no-cache libxslt libxml2 postgresql-libs libssh2 \
|
||||||
# Instalar foreman para poder correr los servicios
|
rsync git jpegoptim vips tectonic oxipng git-lfs openssh-client \
|
||||||
RUN gem install --no-document --no-user-install bundler:2.1.4 foreman
|
yarn daemonize ruby-webrick
|
||||||
|
|
||||||
# Agregar el grupo del servidor web y la usuaria
|
RUN gem install --no-document --no-user-install foreman
|
||||||
RUN addgroup -g 82 -S www-data
|
RUN wget https://github.com/jgm/pandoc/releases/download/${PANDOC_VERSION}/pandoc-${PANDOC_VERSION}-linux-amd64.tar.gz -O - | tar --strip-components 1 -xvzf - pandoc-${PANDOC_VERSION}/bin/pandoc && mv /bin/pandoc /usr/bin/pandoc
|
||||||
RUN adduser -s /bin/sh -G www-data -h /srv/http -D app
|
|
||||||
|
|
||||||
# Convertirse en app para instalar
|
VOLUME "/srv"
|
||||||
USER app
|
|
||||||
COPY --from=build --chown=app:www-data /home/app/checkout /srv/http
|
|
||||||
COPY --chown=app:www-data ./.git/ ./.git/
|
|
||||||
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/_private /srv/http/_private
|
|
||||||
|
|
||||||
# Volver a root para cerrar la compilación
|
|
||||||
USER root
|
|
||||||
# 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
|
|
||||||
RUN install -m 755 /srv/http/entrypoint.sh /usr/local/bin/sutty
|
|
||||||
|
|
||||||
# Mantener estos directorios!
|
|
||||||
VOLUME "/srv/http/data"
|
|
||||||
|
|
||||||
# El puerto de puma
|
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
EXPOSE 9394
|
EXPOSE 9394
|
||||||
|
|
14
Makefile
14
Makefile
|
@ -26,7 +26,7 @@ hain ?= ENV_FILE=.env $(HAINISH)## Ubicación de Hainish
|
||||||
#
|
#
|
||||||
# Production es el entorno de panel.sutty.nl
|
# Production es el entorno de panel.sutty.nl
|
||||||
ifeq ($(env),production)
|
ifeq ($(env),production)
|
||||||
container ?= sutty
|
container ?= panel
|
||||||
## TODO: Cambiar a otra cosa
|
## TODO: Cambiar a otra cosa
|
||||||
branch ?= rails
|
branch ?= rails
|
||||||
public ?= public
|
public ?= public
|
||||||
|
@ -115,15 +115,9 @@ ota-js: assets ## Actualizar Javascript en el nodo delegado
|
||||||
ssh root@$(delegate) docker exec $(container) sh -c "cat /srv/http/tmp/puma.pid | xargs -r kill -USR2"
|
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
|
ota: ## Actualizar Rails en el nodo delegado
|
||||||
umask 022; git format-patch $(commit)
|
ssh $(delegate) git -C /srv/sutty/srv/http/panel.sutty.nl pull ; true
|
||||||
ssh $(delegate) mkdir -p /tmp/patches-$(commit)/
|
ssh $(delegate) chown -R 1000:82 /srv/sutty/srv/http/panel.sutty.nl
|
||||||
scp ./0*.patch $(delegate):/tmp/patches-$(commit)/
|
ssh $(delegate) docker exec $(container) rails reload
|
||||||
scp ./ota.sh $(delegate):/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)
|
|
||||||
rm ./0*.patch
|
|
||||||
|
|
||||||
# Todos los archivos de assets. Si alguno cambia, se van a recompilar
|
# Todos los archivos de assets. Si alguno cambia, se van a recompilar
|
||||||
# los assets que luego se suben al nodo delegado.
|
# los assets que luego se suben al nodo delegado.
|
||||||
|
|
|
@ -67,7 +67,11 @@
|
||||||
|
|
||||||
.editor-content {
|
.editor-content {
|
||||||
min-height: 480px;
|
min-height: 480px;
|
||||||
p, h1, h2, h3, h4, h5, h6, ul, li, figcaption { outline: #ccc solid thin; }
|
p, h1, h2, h3, h4, h5, h6, ul, li, blockquote, figcaption { outline: #ccc solid thin; }
|
||||||
|
blockquote {
|
||||||
|
border-left: #555 solid .25em;
|
||||||
|
padding: .75em;
|
||||||
|
}
|
||||||
strong, em, del, u, sub, sup, small { background: #0002; }
|
strong, em, del, u, sub, sup, small { background: #0002; }
|
||||||
a { background: #13fefe50; }
|
a { background: #13fefe50; }
|
||||||
[data-editor-selected] { outline: #f206f9 solid thick; }
|
[data-editor-selected] { outline: #f206f9 solid thick; }
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module ActiveStorage
|
||||||
|
# Modifica la creación de un blob antes de subir el archivo para que
|
||||||
|
# incluya el JekyllService adecuado.
|
||||||
|
module DirectUploadsControllerDecorator
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
included do
|
||||||
|
def create
|
||||||
|
blob = ActiveStorage::Blob.create_before_direct_upload!(service_name: session[:service_name], **blob_args)
|
||||||
|
render json: direct_upload_json(blob)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Normalizar los caracteres unicode en los nombres de archivos
|
||||||
|
# para que puedan propagarse correctamente a través de todo el
|
||||||
|
# stack.
|
||||||
|
def blob_args
|
||||||
|
params.require(:blob).permit(:filename, :byte_size, :checksum, :content_type, metadata: {}).to_h.symbolize_keys.tap do |ba|
|
||||||
|
ba[:filename] = ba[:filename].unicode_normalize
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
ActiveStorage::DirectUploadsController.include ActiveStorage::DirectUploadsControllerDecorator
|
33
app/controllers/active_storage/disk_controller_decorator.rb
Normal file
33
app/controllers/active_storage/disk_controller_decorator.rb
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module ActiveStorage
|
||||||
|
# Modificar {DiskController} para poder asociar el blob a un sitio
|
||||||
|
module DiskControllerDecorator
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
included do
|
||||||
|
# Asociar el archivo subido al sitio correspondiente. Cada sitio
|
||||||
|
# tiene su propio servicio de subida de archivos.
|
||||||
|
def update
|
||||||
|
if (token = decode_verified_token)
|
||||||
|
if acceptable_content?(token)
|
||||||
|
named_disk_service(token[:service_name]).upload token[:key], request.body, checksum: token[:checksum]
|
||||||
|
|
||||||
|
blob = ActiveStorage::Blob.find_by_key token[:key]
|
||||||
|
site = Site.find_by_name token[:service_name]
|
||||||
|
|
||||||
|
site.static_files.attach(blob)
|
||||||
|
else
|
||||||
|
head :unprocessable_entity
|
||||||
|
end
|
||||||
|
else
|
||||||
|
head :not_found
|
||||||
|
end
|
||||||
|
rescue ActiveStorage::IntegrityError
|
||||||
|
head :unprocessable_entity
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
ActiveStorage::DiskController.include ActiveStorage::DiskControllerDecorator
|
|
@ -6,19 +6,22 @@ module Api
|
||||||
class CspReportsController < BaseController
|
class CspReportsController < BaseController
|
||||||
skip_forgery_protection
|
skip_forgery_protection
|
||||||
|
|
||||||
|
# No queremos indicar que algo salió mal
|
||||||
|
rescue_from ActionController::ParameterMissing, with: :csp_report_created
|
||||||
|
|
||||||
# Crea un reporte de CSP intercambiando los guiones medios por
|
# Crea un reporte de CSP intercambiando los guiones medios por
|
||||||
# bajos
|
# bajos
|
||||||
#
|
#
|
||||||
# TODO: Aplicar rate_limit
|
# TODO: Aplicar rate_limit
|
||||||
def create
|
def create
|
||||||
csp = CspReport.new(csp_report_params.to_h.map do |k, v|
|
csp = CspReport.new(csp_report_params.to_h.transform_keys do |k|
|
||||||
[k.tr('-', '_'), v]
|
k.tr('-', '_')
|
||||||
end.to_h)
|
end)
|
||||||
|
|
||||||
csp.id = SecureRandom.uuid
|
csp.id = SecureRandom.uuid
|
||||||
csp.save
|
csp.save
|
||||||
|
|
||||||
render json: {}, status: :created
|
csp_report_created
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
@ -39,6 +42,10 @@ module Api
|
||||||
:'column-number',
|
:'column-number',
|
||||||
:'source-file')
|
:'source-file')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def csp_report_created
|
||||||
|
render json: {}, status: :created
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,6 +6,7 @@ class PostsController < ApplicationController
|
||||||
rescue_from Pundit::NilPolicyError, with: :page_not_found
|
rescue_from Pundit::NilPolicyError, with: :page_not_found
|
||||||
|
|
||||||
before_action :authenticate_usuarie!
|
before_action :authenticate_usuarie!
|
||||||
|
before_action :service_for_direct_upload, only: %i[new edit]
|
||||||
|
|
||||||
# TODO: Traer los comunes desde ApplicationController
|
# TODO: Traer los comunes desde ApplicationController
|
||||||
breadcrumb -> { current_usuarie.email }, :edit_usuarie_registration_path
|
breadcrumb -> { current_usuarie.email }, :edit_usuarie_registration_path
|
||||||
|
@ -166,4 +167,9 @@ class PostsController < ApplicationController
|
||||||
def post
|
def post
|
||||||
@post ||= site.posts(lang: locale).find(params[:post_id] || params[:id])
|
@post ||= site.posts(lang: locale).find(params[:post_id] || params[:id])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Recuerda el nombre del servicio de subida de archivos
|
||||||
|
def service_for_direct_upload
|
||||||
|
session[:service_name] = site.name.to_sym
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -21,11 +21,12 @@ function makeBlock(tag: string): EditorBlock {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const li: EditorBlock = makeBlock("li");
|
export const li: EditorBlock = makeBlock("li");
|
||||||
|
const paragraph: EditorBlock = makeBlock("p");
|
||||||
|
|
||||||
// XXX: si agregás algo acá, agregalo a blockNames
|
// XXX: si agregás algo acá, agregalo a blockNames
|
||||||
// (y probablemente le quieras hacer un botón en app/views/posts/attributes/_content.haml)
|
// (y probablemente le quieras hacer un botón en app/views/posts/attributes/_content.haml)
|
||||||
export const blocks: { [propName: string]: EditorBlock } = {
|
export const blocks: { [propName: string]: EditorBlock } = {
|
||||||
paragraph: makeBlock("p"),
|
paragraph,
|
||||||
h1: makeBlock("h1"),
|
h1: makeBlock("h1"),
|
||||||
h2: makeBlock("h2"),
|
h2: makeBlock("h2"),
|
||||||
h3: makeBlock("h3"),
|
h3: makeBlock("h3"),
|
||||||
|
@ -42,6 +43,11 @@ export const blocks: { [propName: string]: EditorBlock } = {
|
||||||
allowedChildren: ["li"],
|
allowedChildren: ["li"],
|
||||||
handleEmpty: li,
|
handleEmpty: li,
|
||||||
},
|
},
|
||||||
|
blockquote: {
|
||||||
|
...makeBlock("blockquote"),
|
||||||
|
allowedChildren: blockNames,
|
||||||
|
handleEmpty: paragraph,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export function setupButtons(editor: Editor): void {
|
export function setupButtons(editor: Editor): void {
|
||||||
|
|
|
@ -10,6 +10,7 @@ export const blockNames = [
|
||||||
"h6",
|
"h6",
|
||||||
"unordered_list",
|
"unordered_list",
|
||||||
"ordered_list",
|
"ordered_list",
|
||||||
|
"blockquote",
|
||||||
];
|
];
|
||||||
export const markNames = [
|
export const markNames = [
|
||||||
"bold",
|
"bold",
|
||||||
|
|
21
app/lib/action_dispatch/http/uploaded_file_decorator.rb
Normal file
21
app/lib/action_dispatch/http/uploaded_file_decorator.rb
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module ActionDispatch
|
||||||
|
module Http
|
||||||
|
# Normaliza los nombres de archivo para que se propaguen
|
||||||
|
# correctamente a través de todo el stack.
|
||||||
|
module UploadedFileDecorator
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
included do
|
||||||
|
# Devolver el nombre de archivo con caracteres unicode
|
||||||
|
# normalizados
|
||||||
|
def original_filename
|
||||||
|
@original_filename.unicode_normalize
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
ActionDispatch::Http::UploadedFile.include ActionDispatch::Http::UploadedFileDecorator
|
|
@ -0,0 +1,19 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module ActiveStorage
|
||||||
|
module Attached::Changes::CreateOneDecorator
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
included do
|
||||||
|
private
|
||||||
|
|
||||||
|
# A partir de ahora todos los archivos se suben al servicio de
|
||||||
|
# cada sitio.
|
||||||
|
def attachment_service_name
|
||||||
|
record.name.to_sym
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
ActiveStorage::Attached::Changes::CreateOne.include ActiveStorage::Attached::Changes::CreateOneDecorator
|
82
app/lib/active_storage/service/jekyll_service.rb
Normal file
82
app/lib/active_storage/service/jekyll_service.rb
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module ActiveStorage
|
||||||
|
class Service
|
||||||
|
# Sube los archivos a cada repositorio y los agrega al LFS de su
|
||||||
|
# repositorio git.
|
||||||
|
#
|
||||||
|
# @todo: Implementar LFS. No nos gusta mucho la idea porque duplica
|
||||||
|
# el espacio en disco, pero es la única forma que tenemos (hasta que
|
||||||
|
# implementemos IPFS) para poder transferir los archivos junto con el
|
||||||
|
# sitio.
|
||||||
|
class JekyllService < Service::DiskService
|
||||||
|
# Genera un servicio para un sitio determinado
|
||||||
|
#
|
||||||
|
# @param :site [Site]
|
||||||
|
# @return [ActiveStorage::Service::JekyllService]
|
||||||
|
def self.build_for_site(site:)
|
||||||
|
new(root: File.join(site.path, 'public'), public: true).tap do |js|
|
||||||
|
js.name = site.name.to_sym
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Lo mismo que en DiskService agregando el nombre de archivo en la
|
||||||
|
# firma. Esto permite que luego podamos guardar el archivo donde
|
||||||
|
# corresponde.
|
||||||
|
#
|
||||||
|
# @param :key [String]
|
||||||
|
# @param :expires_in [Integer]
|
||||||
|
# @param :content_type [String]
|
||||||
|
# @param :content_length [Integer]
|
||||||
|
# @param :checksum [String]
|
||||||
|
# @return [String]
|
||||||
|
def url_for_direct_upload(key, expires_in:, content_type:, content_length:, checksum:)
|
||||||
|
instrument :url, key: key do |payload|
|
||||||
|
verified_token_with_expiration = ActiveStorage.verifier.generate(
|
||||||
|
{
|
||||||
|
key: key,
|
||||||
|
content_type: content_type,
|
||||||
|
content_length: content_length,
|
||||||
|
checksum: checksum,
|
||||||
|
service_name: name,
|
||||||
|
filename: filename_for(key)
|
||||||
|
},
|
||||||
|
expires_in: expires_in,
|
||||||
|
purpose: :blob_token
|
||||||
|
)
|
||||||
|
|
||||||
|
generated_url = url_helpers.update_rails_disk_service_url(verified_token_with_expiration, host: current_host)
|
||||||
|
|
||||||
|
payload[:url] = generated_url
|
||||||
|
|
||||||
|
generated_url
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Mantener retrocompatibilidad con cómo gestionamos los archivos
|
||||||
|
# subidos hasta ahora.
|
||||||
|
#
|
||||||
|
# @param :key [String]
|
||||||
|
# @return [String]
|
||||||
|
def folder_for(key)
|
||||||
|
key
|
||||||
|
end
|
||||||
|
|
||||||
|
# Obtiene el nombre de archivo para esta key
|
||||||
|
#
|
||||||
|
# @param :key [String]
|
||||||
|
# @return [String]
|
||||||
|
def filename_for(key)
|
||||||
|
ActiveStorage::Blob.where(key: key).limit(1).pluck(:filename).first
|
||||||
|
end
|
||||||
|
|
||||||
|
# Crea una ruta para la llave con un nombre conocido.
|
||||||
|
#
|
||||||
|
# @param :key [String]
|
||||||
|
# @return [String]
|
||||||
|
def path_for(key)
|
||||||
|
File.join root, folder_for(key), filename_for(key)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
31
app/lib/active_storage/service/registry_decorator.rb
Normal file
31
app/lib/active_storage/service/registry_decorator.rb
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module ActiveStorage
|
||||||
|
class Service
|
||||||
|
# Modificaciones a ActiveStorage::Service::Registry
|
||||||
|
module RegistryDecorator
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
included do
|
||||||
|
# El mismo comportamiento que #fetch con el agregado de generar
|
||||||
|
# un {JekyllService} para cada sitio.
|
||||||
|
def fetch(name)
|
||||||
|
services.fetch(name.to_sym) do |key|
|
||||||
|
if configurations.include?(key)
|
||||||
|
services[key] = configurator.build(key)
|
||||||
|
elsif (site = Site.find_by_name(key))
|
||||||
|
services[key] = ActiveStorage::Service::JekyllService.build_for_site(site: site)
|
||||||
|
elsif block_given?
|
||||||
|
yield key
|
||||||
|
else
|
||||||
|
raise KeyError, "Missing configuration for the #{key} Active Storage service. " \
|
||||||
|
"Configurations available for the #{configurations.keys.to_sentence} services."
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
ActiveStorage::Service::Registry.include ActiveStorage::Service::RegistryDecorator
|
26
app/models/active_storage/blob_decorator.rb
Normal file
26
app/models/active_storage/blob_decorator.rb
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module ActiveStorage
|
||||||
|
# Modificaciones a ActiveStorage::Blob
|
||||||
|
module BlobDecorator
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
included do
|
||||||
|
# Permitir que llegue el nombre de archivo al servicio de subida de
|
||||||
|
# archivos.
|
||||||
|
#
|
||||||
|
# @return [Hash]
|
||||||
|
def service_metadata
|
||||||
|
if forcibly_serve_as_binary?
|
||||||
|
{ content_type: ActiveStorage.binary_content_type, disposition: :attachment, filename: filename }
|
||||||
|
elsif !allowed_inline?
|
||||||
|
{ content_type: content_type, disposition: :attachment, filename: filename }
|
||||||
|
else
|
||||||
|
{ content_type: content_type, filename: filename }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
ActiveStorage::Blob.include ActiveStorage::BlobDecorator
|
|
@ -13,12 +13,18 @@ class MetadataFile < MetadataTemplate
|
||||||
value == default_value
|
value == default_value
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# No hay valores sugeridos para archivos subidos.
|
||||||
|
#
|
||||||
|
# XXX: Esto ayuda a deserializar en {Site#everything_of}
|
||||||
|
def values; end
|
||||||
|
|
||||||
def validate
|
def validate
|
||||||
super
|
super
|
||||||
|
|
||||||
errors << I18n.t("metadata.#{type}.site_invalid") if site.invalid?
|
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}.path_required") if path_missing?
|
||||||
errors << I18n.t("metadata.#{type}.no_file_for_description") if no_file_for_description?
|
errors << I18n.t("metadata.#{type}.no_file_for_description") if no_file_for_description?
|
||||||
|
errors << I18n.t("metadata.#{type}.attachment_missing") if path? && !static_file
|
||||||
|
|
||||||
errors.compact!
|
errors.compact!
|
||||||
errors.empty?
|
errors.empty?
|
||||||
|
@ -34,12 +40,6 @@ class MetadataFile < MetadataTemplate
|
||||||
value['path'].is_a?(String)
|
value['path'].is_a?(String)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Determina si la ruta es opcional pero deja pasar si la ruta se
|
|
||||||
# especifica
|
|
||||||
def path_optional?
|
|
||||||
!required && !path?
|
|
||||||
end
|
|
||||||
|
|
||||||
# Asociar la imagen subida al sitio y obtener la ruta
|
# Asociar la imagen subida al sitio y obtener la ruta
|
||||||
#
|
#
|
||||||
# XXX: Si evitamos guardar cambios con changed? no tenemos forma de
|
# XXX: Si evitamos guardar cambios con changed? no tenemos forma de
|
||||||
|
@ -48,13 +48,7 @@ class MetadataFile < MetadataTemplate
|
||||||
# repetida.
|
# repetida.
|
||||||
def save
|
def save
|
||||||
value['description'] = sanitize value['description']
|
value['description'] = sanitize value['description']
|
||||||
|
value['path'] = static_file ? relative_destination_path_with_filename.to_s : nil
|
||||||
if path?
|
|
||||||
hardlink
|
|
||||||
value['path'] = relative_destination_path
|
|
||||||
else
|
|
||||||
value['path'] = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
@ -71,101 +65,88 @@ class MetadataFile < MetadataTemplate
|
||||||
# XXX: La última opción provoca archivos duplicados, pero es lo mejor
|
# XXX: La última opción provoca archivos duplicados, pero es lo mejor
|
||||||
# que tenemos hasta que resolvamos https://0xacab.org/sutty/sutty/-/issues/213
|
# que tenemos hasta que resolvamos https://0xacab.org/sutty/sutty/-/issues/213
|
||||||
#
|
#
|
||||||
# @return [ActiveStorage::Attachment]
|
# @todo encontrar una forma de obtener el attachment sin tener que
|
||||||
|
# recurrir al último subido.
|
||||||
|
#
|
||||||
|
# @return [ActiveStorage::Attachment,nil]
|
||||||
def static_file
|
def static_file
|
||||||
return unless path?
|
|
||||||
|
|
||||||
@static_file ||=
|
@static_file ||=
|
||||||
case value['path']
|
case value['path']
|
||||||
when ActionDispatch::Http::UploadedFile
|
when ActionDispatch::Http::UploadedFile
|
||||||
site.static_files.last if site.static_files.attach(value['path'])
|
site.static_files.last if site.static_files.attach(value['path'])
|
||||||
when String
|
when String
|
||||||
if (blob = ActiveStorage::Blob.where(key: key_from_path).pluck(:id).first)
|
if (blob_id = ActiveStorage::Blob.where(key: key_from_path).pluck(:id).first)
|
||||||
site.static_files.find_by(blob_id: blob)
|
site.static_files.find_by(blob_id: blob_id)
|
||||||
elsif site.static_files.attach(io: path.open, filename: path.basename)
|
elsif path? && pathname.exist? && site.static_files.attach(io: pathname.open, filename: pathname.basename)
|
||||||
site.static_files.last
|
site.static_files.last.tap do |s|
|
||||||
|
s.blob.update(key: key_from_path)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Obtiene la ruta absoluta al archivo
|
||||||
|
#
|
||||||
|
# @return [Pathname]
|
||||||
|
def pathname
|
||||||
|
raise NoMethodError unless uploaded?
|
||||||
|
|
||||||
|
@pathname ||= Pathname.new(File.join(site.path, value['path']))
|
||||||
|
end
|
||||||
|
|
||||||
|
# Obtiene la key del attachment a partir de la ruta
|
||||||
|
#
|
||||||
|
# @return [String]
|
||||||
def key_from_path
|
def key_from_path
|
||||||
path.dirname.basename.to_s
|
pathname.dirname.basename.to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
def path?
|
def path?
|
||||||
value['path'].present?
|
value['path'].present?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def description?
|
||||||
|
value['description'].present?
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def filemagic
|
# Obtener la ruta al archivo relativa al sitio
|
||||||
@filemagic ||= FileMagic.new(FileMagic::MAGIC_MIME)
|
#
|
||||||
end
|
|
||||||
|
|
||||||
# @return [Pathname]
|
# @return [Pathname]
|
||||||
def path
|
|
||||||
@path ||= Pathname.new(File.join(site.path, value['path']))
|
|
||||||
end
|
|
||||||
|
|
||||||
def file
|
|
||||||
return unless path?
|
|
||||||
|
|
||||||
@file ||=
|
|
||||||
case value['path']
|
|
||||||
when ActionDispatch::Http::UploadedFile then value['path'].tempfile.path
|
|
||||||
when String then File.join(site.path, value['path'])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Hacemos un link duro para colocar el archivo dentro del repositorio
|
|
||||||
# y no duplicar el espacio que ocupan. Esto requiere que ambos
|
|
||||||
# directorios estén dentro del mismo punto de montaje.
|
|
||||||
#
|
|
||||||
# XXX: Asumimos que el archivo destino no existe porque siempre
|
|
||||||
# contiene una key única.
|
|
||||||
#
|
|
||||||
# @return [Boolean]
|
|
||||||
def hardlink
|
|
||||||
return if hardlink?
|
|
||||||
return if File.exist? destination_path
|
|
||||||
|
|
||||||
FileUtils.mkdir_p(File.dirname(destination_path))
|
|
||||||
FileUtils.ln(uploaded_path, destination_path).zero?
|
|
||||||
end
|
|
||||||
|
|
||||||
def hardlink?
|
|
||||||
File.stat(uploaded_path).ino == File.stat(destination_path).ino
|
|
||||||
rescue Errno::ENOENT
|
|
||||||
false
|
|
||||||
end
|
|
||||||
|
|
||||||
# Obtener la ruta al archivo
|
|
||||||
# https://stackoverflow.com/a/53908358
|
|
||||||
def uploaded_relative_path
|
|
||||||
ActiveStorage::Blob.service.path_for(static_file.key)
|
|
||||||
end
|
|
||||||
|
|
||||||
# @return [String]
|
|
||||||
def uploaded_path
|
|
||||||
Rails.root.join uploaded_relative_path
|
|
||||||
end
|
|
||||||
|
|
||||||
# La ruta del archivo mantiene el nombre original pero contiene el
|
|
||||||
# nombre interno y único del archivo para poder relacionarlo con el
|
|
||||||
# archivo subido en Sutty.
|
|
||||||
#
|
|
||||||
# @return [String]
|
|
||||||
def relative_destination_path
|
|
||||||
@relative_destination_path ||= File.join('public', static_file.key, static_file.filename.to_s)
|
|
||||||
end
|
|
||||||
|
|
||||||
# @return [String]
|
|
||||||
def destination_path
|
def destination_path
|
||||||
@destination_path ||= File.join(site.path, relative_destination_path)
|
Pathname.new(static_file_path)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Agrega el nombre de archivo a la ruta para tener retrocompatibilidad
|
||||||
|
#
|
||||||
|
# @return [Pathname]
|
||||||
|
def destination_path_with_filename
|
||||||
|
destination_path.realpath
|
||||||
|
# Si el archivo no llegara a existir, en lugar de hacer fallar todo,
|
||||||
|
# devolvemos la ruta original, que puede ser el archivo que no existe
|
||||||
|
# o vacía si se está subiendo uno.
|
||||||
|
rescue Errno::ENOENT => e
|
||||||
|
ExceptionNotifier.notify_exception(e)
|
||||||
|
|
||||||
|
value['path']
|
||||||
|
end
|
||||||
|
|
||||||
|
def relative_destination_path_with_filename
|
||||||
|
destination_path_with_filename.relative_path_from(Pathname.new(site.path).realpath)
|
||||||
|
end
|
||||||
|
|
||||||
|
def static_file_path
|
||||||
|
case static_file.blob.service.name
|
||||||
|
when :local
|
||||||
|
File.join(site.path, 'public', static_file.key, static_file.filename.to_s)
|
||||||
|
else
|
||||||
|
static_file.blob.service.path_for(static_file.key)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# No hay archivo pero se lo describió
|
# No hay archivo pero se lo describió
|
||||||
def no_file_for_description?
|
def no_file_for_description?
|
||||||
value['description'].present? && value['path'].blank?
|
!path? && description?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,7 +5,7 @@ class MetadataImage < MetadataFile
|
||||||
def validate
|
def validate
|
||||||
super
|
super
|
||||||
|
|
||||||
errors << I18n.t('metadata.image.not_an_image') unless image?
|
errors << I18n.t('metadata.image.not_an_image') if path? && !image?
|
||||||
|
|
||||||
errors.compact!
|
errors.compact!
|
||||||
errors.empty?
|
errors.empty?
|
||||||
|
@ -13,8 +13,6 @@ class MetadataImage < MetadataFile
|
||||||
|
|
||||||
# Determina si es una imagen
|
# Determina si es una imagen
|
||||||
def image?
|
def image?
|
||||||
return true unless file
|
static_file&.blob&.send(:web_image?)
|
||||||
|
|
||||||
filemagic.file(file).starts_with? 'image/'
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -12,6 +12,6 @@ class MetadataMarkdown < MetadataText
|
||||||
# markdown y se eliminan autolinks. Mejor es habilitar la generación
|
# markdown y se eliminan autolinks. Mejor es habilitar la generación
|
||||||
# SAFE de CommonMark en la configuración del sitio.
|
# SAFE de CommonMark en la configuración del sitio.
|
||||||
def sanitize(string)
|
def sanitize(string)
|
||||||
string
|
string.unicode_normalize
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -25,6 +25,6 @@ class MetadataMarkdownContent < MetadataText
|
||||||
# markdown y se eliminan autolinks. Mejor es deshabilitar la
|
# markdown y se eliminan autolinks. Mejor es deshabilitar la
|
||||||
# generación SAFE de CommonMark en la configuración del sitio.
|
# generación SAFE de CommonMark en la configuración del sitio.
|
||||||
def sanitize(string)
|
def sanitize(string)
|
||||||
string.tr("\r", '')
|
string.tr("\r", '').unicode_normalize
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -19,7 +19,7 @@ class MetadataPermalink < MetadataString
|
||||||
# puntos suspensivos, la primera / para que siempre sea relativa y
|
# puntos suspensivos, la primera / para que siempre sea relativa y
|
||||||
# agregamos una / al final si la ruta no tiene extensión.
|
# agregamos una / al final si la ruta no tiene extensión.
|
||||||
def sanitize(value)
|
def sanitize(value)
|
||||||
value = value.strip.gsub('..', '/').gsub('./', '').squeeze('/')
|
value = value.strip.unicode_normalize.gsub('..', '/').gsub('./', '').squeeze('/')
|
||||||
value = value[1..-1] if value.start_with? '/'
|
value = value[1..-1] if value.start_with? '/'
|
||||||
value += '/' if File.extname(value).blank?
|
value += '/' if File.extname(value).blank?
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ class MetadataString < MetadataTemplate
|
||||||
def sanitize(string)
|
def sanitize(string)
|
||||||
return '' if string.blank?
|
return '' if string.blank?
|
||||||
|
|
||||||
sanitizer.sanitize(string.strip,
|
sanitizer.sanitize(string.strip.unicode_normalize,
|
||||||
tags: [],
|
tags: [],
|
||||||
attributes: []).strip.html_safe
|
attributes: []).strip.html_safe
|
||||||
end
|
end
|
||||||
|
|
|
@ -184,9 +184,12 @@ MetadataTemplate = Struct.new(:site, :document, :name, :label, :type,
|
||||||
return if string.nil?
|
return if string.nil?
|
||||||
return string unless string.is_a? String
|
return string unless string.is_a? String
|
||||||
|
|
||||||
sanitizer.sanitize(string.tr("\r", ''),
|
sanitizer
|
||||||
|
.sanitize(string.tr("\r", '').unicode_normalize,
|
||||||
tags: allowed_tags,
|
tags: allowed_tags,
|
||||||
attributes: allowed_attributes).strip.html_safe
|
attributes: allowed_attributes)
|
||||||
|
.strip
|
||||||
|
.html_safe
|
||||||
end
|
end
|
||||||
|
|
||||||
def sanitizer
|
def sanitizer
|
||||||
|
@ -199,7 +202,7 @@ MetadataTemplate = Struct.new(:site, :document, :name, :label, :type,
|
||||||
end
|
end
|
||||||
|
|
||||||
def allowed_tags
|
def allowed_tags
|
||||||
@allowed_tags ||= %w[strong em del u mark p h1 h2 h3 h4 h5 h6 ul ol li img iframe audio video div figure
|
@allowed_tags ||= %w[strong em del u mark p h1 h2 h3 h4 h5 h6 ul ol li img iframe audio video div figure blockquote
|
||||||
figcaption a sub sup small].freeze
|
figcaption a sub sup small].freeze
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -57,7 +57,7 @@ class Site < ApplicationRecord
|
||||||
# Carga el sitio Jekyll una vez que se inicializa el modelo o después
|
# Carga el sitio Jekyll una vez que se inicializa el modelo o después
|
||||||
# de crearlo
|
# de crearlo
|
||||||
after_initialize :load_jekyll
|
after_initialize :load_jekyll
|
||||||
after_create :load_jekyll, :static_file_migration!
|
after_create :load_jekyll
|
||||||
# Cambiar el nombre del directorio
|
# Cambiar el nombre del directorio
|
||||||
before_update :update_name!
|
before_update :update_name!
|
||||||
before_save :add_private_key_if_missing!
|
before_save :add_private_key_if_missing!
|
||||||
|
@ -474,11 +474,6 @@ class Site < ApplicationRecord
|
||||||
config.hostname = hostname
|
config.hostname = hostname
|
||||||
end
|
end
|
||||||
|
|
||||||
# Migra los archivos a Sutty
|
|
||||||
def static_file_migration!
|
|
||||||
Site::StaticFileMigration.new(site: self).migrate!
|
|
||||||
end
|
|
||||||
|
|
||||||
# Valida si el sitio tiene al menos una forma de alojamiento asociada
|
# Valida si el sitio tiene al menos una forma de alojamiento asociada
|
||||||
# y es la local
|
# y es la local
|
||||||
#
|
#
|
||||||
|
|
|
@ -1,52 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
class Site
|
|
||||||
# Obtiene todos los archivos relacionados en artículos del sitio y los
|
|
||||||
# sube a Sutty.
|
|
||||||
class StaticFileMigration
|
|
||||||
# Tipos de metadatos que contienen archivos
|
|
||||||
STATIC_TYPES = %i[file image].freeze
|
|
||||||
|
|
||||||
attr_reader :site
|
|
||||||
|
|
||||||
def initialize(site:)
|
|
||||||
@site = site
|
|
||||||
end
|
|
||||||
|
|
||||||
def migrate!
|
|
||||||
modified = site.docs.map do |doc|
|
|
||||||
next unless STATIC_TYPES.map do |field|
|
|
||||||
next unless doc.attribute? field
|
|
||||||
next unless doc[field].path?
|
|
||||||
next unless doc[field].static_file
|
|
||||||
|
|
||||||
true
|
|
||||||
end.any?
|
|
||||||
|
|
||||||
log.write "#{doc.path.relative};no se pudo guardar\n" unless doc.save(validate: false)
|
|
||||||
|
|
||||||
doc.path.absolute
|
|
||||||
end.compact
|
|
||||||
|
|
||||||
log.close
|
|
||||||
|
|
||||||
return if modified.empty?
|
|
||||||
|
|
||||||
# TODO: Hacer la migración desde el servicio de creación de sitios?
|
|
||||||
site.repository.commit(file: modified,
|
|
||||||
message: I18n.t('sites.static_file_migration'),
|
|
||||||
usuarie: author)
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def author
|
|
||||||
@author ||= GitAuthor.new email: "sutty@#{Site.domain}",
|
|
||||||
name: 'Sutty'
|
|
||||||
end
|
|
||||||
|
|
||||||
def log
|
|
||||||
@log ||= File.open(File.join(site.path, 'migration.csv'), 'w')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -95,6 +95,9 @@
|
||||||
%button.btn{ type: 'button', title: t('editor.right'), data: { editor_button: 'parentBlock-right' } }>
|
%button.btn{ type: 'button', title: t('editor.right'), data: { editor_button: 'parentBlock-right' } }>
|
||||||
%i.fa.fa-fw.fa-align-right>
|
%i.fa.fa-fw.fa-align-right>
|
||||||
%span.sr-only>= t('editor.right')
|
%span.sr-only>= t('editor.right')
|
||||||
|
%button.btn{ type: 'button', title: t('editor.blockquote'), data: { editor_button: 'block-blockquote' } }>
|
||||||
|
%i.fa.fa-fw.fa-quote-left>
|
||||||
|
%span.sr-only>= t('editor.blockquote')
|
||||||
|
|
||||||
-# HAML cringe
|
-# HAML cringe
|
||||||
.editor-auxiliary-toolbar.mt-1.scrollbar-black{ data: { editor_auxiliary_toolbar: '' } }
|
.editor-auxiliary-toolbar.mt-1.scrollbar-black{ data: { editor_auxiliary_toolbar: '' } }
|
||||||
|
|
|
@ -38,6 +38,12 @@ module Sutty
|
||||||
|
|
||||||
config.active_storage.variant_processor = :vips
|
config.active_storage.variant_processor = :vips
|
||||||
|
|
||||||
|
config.to_prepare do
|
||||||
|
Dir.glob(File.join(File.dirname(__FILE__), '..', 'app', '**', '*_decorator.rb')).sort.each do |c|
|
||||||
|
Rails.configuration.cache_classes ? require(c) : load(c)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
config.after_initialize do
|
config.after_initialize do
|
||||||
ActiveStorage::DirectUploadsController.include ActiveStorage::AuthenticatedDirectUploadsController
|
ActiveStorage::DirectUploadsController.include ActiveStorage::AuthenticatedDirectUploadsController
|
||||||
|
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
# TODO: Estamos procesando el análisis de los archivos en el momento
|
|
||||||
# porque queremos obtener la ruta del archivo en el momento y no
|
|
||||||
# después. Necesitaríamos poder generar el vínculo en el
|
|
||||||
# repositorio a destiempo, modificando el Job de ActiveStorage
|
|
||||||
ActiveStorage::AnalyzeJob.queue_adapter = :inline
|
|
|
@ -41,10 +41,12 @@ en:
|
||||||
not_an_image: 'Not an image'
|
not_an_image: 'Not an image'
|
||||||
path_required: 'Missing image for upload'
|
path_required: 'Missing image for upload'
|
||||||
no_file_for_description: "Description with no associated image"
|
no_file_for_description: "Description with no associated image"
|
||||||
|
attachment_missing: "I couldn't save the image :("
|
||||||
file:
|
file:
|
||||||
site_invalid: 'The file cannot be stored if the site configuration is not valid'
|
site_invalid: 'The file cannot be stored if the site configuration is not valid'
|
||||||
path_required: "Missing file for upload"
|
path_required: "Missing file for upload"
|
||||||
no_file_for_description: "Description with no associated file"
|
no_file_for_description: "Description with no associated file"
|
||||||
|
attachment_missing: "I couldn't save the file :("
|
||||||
event:
|
event:
|
||||||
zone_missing: 'Inexistent timezone'
|
zone_missing: 'Inexistent timezone'
|
||||||
date_missing: 'Event date is required'
|
date_missing: 'Event date is required'
|
||||||
|
@ -568,6 +570,7 @@ en:
|
||||||
left: Left
|
left: Left
|
||||||
right: Right
|
right: Right
|
||||||
center: Center
|
center: Center
|
||||||
|
blockquote: Quote
|
||||||
color: Color
|
color: Color
|
||||||
text-color: Text color
|
text-color: Text color
|
||||||
multimedia: Media
|
multimedia: Media
|
||||||
|
|
|
@ -41,10 +41,12 @@ es:
|
||||||
not_an_image: 'No es una imagen'
|
not_an_image: 'No es una imagen'
|
||||||
path_required: 'Se necesita una imagen'
|
path_required: 'Se necesita una imagen'
|
||||||
no_file_for_description: 'Se envió una descripción sin imagen asociada'
|
no_file_for_description: 'Se envió una descripción sin imagen asociada'
|
||||||
|
attachment_missing: 'no pude guardar el archivo :('
|
||||||
file:
|
file:
|
||||||
site_invalid: 'El archivo no se puede almacenar si la configuración del sitio no es válida'
|
site_invalid: 'El archivo no se puede almacenar si la configuración del sitio no es válida'
|
||||||
path_required: 'Se necesita un archivo'
|
path_required: 'Se necesita un archivo'
|
||||||
no_file_for_description: 'se envió una descripción sin archivo asociado'
|
no_file_for_description: 'se envió una descripción sin archivo asociado'
|
||||||
|
attachment_missing: 'no pude guardar el archivo :('
|
||||||
event:
|
event:
|
||||||
zone_missing: 'El huso horario no es correcto'
|
zone_missing: 'El huso horario no es correcto'
|
||||||
date_missing: 'La fecha es obligatoria'
|
date_missing: 'La fecha es obligatoria'
|
||||||
|
@ -576,6 +578,7 @@ es:
|
||||||
left: Izquierda
|
left: Izquierda
|
||||||
right: Derecha
|
right: Derecha
|
||||||
center: Centro
|
center: Centro
|
||||||
|
blockquote: Cita
|
||||||
color: Color
|
color: Color
|
||||||
text-color: Color del texto
|
text-color: Color del texto
|
||||||
multimedia: Multimedia
|
multimedia: Multimedia
|
||||||
|
|
|
@ -1,10 +1,38 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
|
s_pid=/srv/tmp/puma.pid
|
||||||
|
p_pid=/tmp/prometheus.pid
|
||||||
|
|
||||||
case $1 in
|
case $1 in
|
||||||
sutty)
|
start)
|
||||||
su app -c "cd /srv/http && foreman start migrate"
|
su rails -c "cd /srv && foreman run migrate"
|
||||||
daemonize -c /srv/http -u app /usr/bin/foreman start sutty
|
daemonize -c /srv -u rails /usr/bin/foreman start sutty
|
||||||
|
;;
|
||||||
|
|
||||||
|
stop)
|
||||||
|
cat $s_pid | xargs -r kill
|
||||||
|
;;
|
||||||
|
|
||||||
|
reload)
|
||||||
|
cat $s_pid | xargs -r kill -USR2
|
||||||
|
;;
|
||||||
|
|
||||||
|
prometheus)
|
||||||
|
case $2 in
|
||||||
|
start)
|
||||||
|
rm -f $p_pid
|
||||||
|
daemonize -c /srv -p $p_pid -l $p_pid -u rails /usr/bin/foreman start prometheus
|
||||||
|
;;
|
||||||
|
stop)
|
||||||
|
cat $p_pid | xargs -r kill
|
||||||
|
rm -f $p_pid
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
|
||||||
|
blazer)
|
||||||
|
test -z "$2" || b="_$2"
|
||||||
|
su rails -c "cd /srv && foreman run blazer$b"
|
||||||
;;
|
;;
|
||||||
prometheus) daemonize -c /srv/http -p /tmp/prometheus.pid -l /tmp/prometheus.pid -u app /usr/bin/foreman start prometheus ;;
|
|
||||||
esac
|
esac
|
||||||
|
|
22
monit.conf
22
monit.conf
|
@ -1,31 +1,27 @@
|
||||||
check process sutty with pidfile /srv/http/tmp/puma.pid
|
check process sutty with pidfile /srv/tmp/puma.pid
|
||||||
start program = "/usr/local/bin/sutty sutty"
|
start program = "/usr/local/bin/sutty start"
|
||||||
stop program = "/bin/sh -c 'cat /srv/http/tmp/puma.pid | xargs kill'"
|
stop program = "/usr/local/bin/sutty stop"
|
||||||
|
|
||||||
check process prometheus with pidfile /tmp/prometheus.pid
|
check process prometheus with pidfile /tmp/prometheus.pid
|
||||||
start program = "/usr/local/bin/sutty prometheus"
|
start program = "/usr/local/bin/sutty prometheus start"
|
||||||
stop program = "/bin/sh -c 'cat /tmp/prometheus.pid | xargs kill'"
|
stop program = "/usr/local/bin/sutty prometheus start"
|
||||||
|
|
||||||
check program blazer_5m
|
check program blazer_5m
|
||||||
with path "/bin/sh -c 'cd /srv/http && foreman start blazer_5m'"
|
with path "/usr/local/bin/sutty blazer 5m"
|
||||||
as uid "app" and gid "www-data"
|
|
||||||
every 5 cycles
|
every 5 cycles
|
||||||
if status != 0 then alert
|
if status != 0 then alert
|
||||||
|
|
||||||
check program blazer_1h
|
check program blazer_1h
|
||||||
with path "/bin/sh -c 'cd /srv/http && foreman start blazer_1h'"
|
with path "/usr/local/bin/sutty blazer 1h"
|
||||||
as uid "app" and gid "www-data"
|
|
||||||
every 60 cycles
|
every 60 cycles
|
||||||
if status != 0 then alert
|
if status != 0 then alert
|
||||||
|
|
||||||
check program blazer_1d
|
check program blazer_1d
|
||||||
with path "/bin/sh -c 'cd /srv/http && foreman start blazer_1d'"
|
with path "/usr/local/bin/sutty blazer 1d"
|
||||||
as uid "app" and gid "www-data"
|
|
||||||
every 1440 cycles
|
every 1440 cycles
|
||||||
if status != 0 then alert
|
if status != 0 then alert
|
||||||
|
|
||||||
check program blazer
|
check program blazer
|
||||||
with path "/bin/sh -c 'cd /srv/http && foreman start blazer'"
|
with path "/usr/local/bin/sutty blazer"
|
||||||
as uid "app" and gid "www-data"
|
|
||||||
every 61 cycles
|
every 61 cycles
|
||||||
if status != 0 then alert
|
if status != 0 then alert
|
||||||
|
|
Loading…
Reference in a new issue