5
0
Fork 0
mirror of https://0xacab.org/sutty/sutty synced 2024-10-05 03:16:56 +00:00

Merge branch 'rails' into prosemirror

This commit is contained in:
void 2020-02-28 12:24:26 -03:00
commit 2d492538db
107 changed files with 1166 additions and 498 deletions

View file

@ -1,7 +1,15 @@
RAILS_ENV=production RAILS_ENV=
IMAP_SERVER= IMAP_SERVER=
DEFAULT_FROM= DEFAULT_FROM=
SKEL_SUTTY=https://0xacab.org/sutty/skel.sutty.nl SKEL_SUTTY=https://0xacab.org/sutty/skel.sutty.nl
SUTTY=sutty.nl SUTTY=sutty.local
SUTTY_WITH_PORT=sutty.local:3000
REDIS_SERVER= REDIS_SERVER=
REDIS_CLIENT= REDIS_CLIENT=
# API authentication
HTTP_BASIC_USER=
HTTP_BASIC_PASSWORD=
BLAZER_DATABASE_URL=
BLAZER_SLACK_WEBHOOK_URL=
BLAZER_USERNAME=
BLAZER_PASSWORD=

3
.gitignore vendored
View file

@ -39,3 +39,6 @@
yarn-debug.log* yarn-debug.log*
.yarn-integrity .yarn-integrity
/vendor /vendor
*.key
*.crt

View file

@ -1,61 +1,5 @@
AllCops: AllCops:
TargetRubyVersion: '2.5' TargetRubyVersion: '2.6'
Style/AsciiComments: Style/AsciiComments:
Enabled: false Enabled: false
# Sólo existe para molestarnos (?)
Metrics/AbcSize:
Enabled: false
Metrics/LineLength:
Exclude:
- 'db/schema.rb'
- 'db/migrate/*.rb'
- 'app/models/site.rb'
Metrics/MethodLength:
Exclude:
- 'db/schema.rb'
- 'db/migrate/*.rb'
- 'app/models/site.rb'
- 'app/controllers/sites_controller.rb'
- 'app/controllers/posts_controller.rb'
- 'app/controllers/invitadxs_controller.rb'
- 'app/controllers/i18n_controller.rb'
- 'app/controllers/collaborations_controller.rb'
- 'app/controllers/usuaries_controller.rb'
- 'app/models/post.rb'
Metrics/BlockLength:
Exclude:
- 'config/environments/development.rb'
- 'config/environments/production.rb'
- 'config/initializers/devise.rb'
- 'db/schema.rb'
- 'config/routes.rb'
- 'test/controllers/sites_controller_test.rb'
Metrics/ClassLength:
Exclude:
- 'app/models/site.rb'
- 'app/controllers/posts_controller.rb'
- 'app/controllers/sites_controller.rb'
- 'test/models/post_test.rb'
- 'test/controllers/sites_controller_test.rb'
Lint/HandleExceptions:
Exclude:
- 'app/controllers/posts_controller.rb'
Style/GuardClause:
Exclude:
- 'app/controllers/posts_controller.rb'
Metrics/PerceivedComplexity:
Exclude:
- 'app/controllers/posts_controller.rb'
Lint/UnreachableCode:
Exclude:
- 'app/policies/post_policy.rb'

View file

@ -1 +1 @@
2.5.7 2.6.5

View file

@ -2,7 +2,8 @@
# el mismo repositorio de trabajo. Cuando tengamos CI/CD algunas cosas # 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 # como el tarball van a tener que cambiar porque ya vamos a haber hecho
# un clone/pull limpio. # un clone/pull limpio.
FROM sutty/sdk-ruby:latest as build FROM sutty/oxipng:latest as oxipng
FROM alpine:3.11 as build
MAINTAINER "f <f@sutty.nl>" MAINTAINER "f <f@sutty.nl>"
ARG RAILS_MASTER_KEY ARG RAILS_MASTER_KEY
@ -13,10 +14,21 @@ 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 ENV RAILS_MASTER_KEY=$RAILS_MASTER_KEY
# Para compilar los assets en brotli RUN apk add --no-cache libxslt libxml2 tzdata ruby ruby-bundler ruby-json ruby-bigdecimal ruby-rake
RUN apk add --no-cache brotli libssh2 RUN apk add --no-cache postgresql-libs git yarn brotli libssh2 python
# Empezamos con la usuaria app creada por sdk-ruby # https://github.com/rubygems/rubygems/issues/2918
# https://gitlab.alpinelinux.org/alpine/aports/issues/10808
COPY ./rubygems-platform-musl.patch /tmp/
RUN cd /usr/lib/ruby/2.6.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.0.2
# Empezamos con la usuaria app
USER app USER app
# Vamos a trabajar dentro de este directorio # Vamos a trabajar dentro de este directorio
WORKDIR /home/app/sutty WORKDIR /home/app/sutty
@ -24,25 +36,23 @@ WORKDIR /home/app/sutty
# Copiamos solo el Gemfile para poder instalar las gemas necesarias # Copiamos solo el Gemfile para poder instalar las gemas necesarias
COPY --chown=app:www-data ./Gemfile . COPY --chown=app:www-data ./Gemfile .
COPY --chown=app:www-data ./Gemfile.lock . COPY --chown=app:www-data ./Gemfile.lock .
# XXX: No usamos la flag --production porque luego no nos deja
# desinstalar las gemas de los assets
# RUN --mount=type=cache,target=/home/app/.ccache \
RUN bundle install --no-cache --path=./vendor --without='test development' RUN bundle install --no-cache --path=./vendor --without='test development'
# Vaciar la caché # Vaciar la caché
RUN rm vendor/ruby/2.5.0/cache/*.gem RUN rm vendor/ruby/2.6.0/cache/*.gem
# Limpiar las librerías nativas, esto ahorra más espacio y uso de
# memoria ya que no hay que cargar símbolos que no se van a usar.
RUN find vendor -name "*.so" | xargs -rn 1 strip --strip-unneeded
# Copiar el repositorio git # Copiar el repositorio git
COPY --chown=app:www-data ./.git/ ./.git/ COPY --chown=app:www-data ./.git/ ./.git/
# Hacer un tarball de los archivos desde el repositorio # Hacer un clon limpio del repositorio en lugar de copiar todos los
RUN git archive -o ../sutty.tar.gz HEAD # archivos
RUN cd .. && git clone sutty checkout
WORKDIR /home/app/checkout
# Traer las gemas:
RUN mv ../sutty/vendor ./vendor
RUN mv ../sutty/.bundle ./.bundle
# Extraer archivos necesarios para compilar los assets
RUN tar xf ../sutty.tar.gz Rakefile config app bin yarn.lock package.json
# Instalar secretos # Instalar secretos
COPY --chown=app:www-data ./config/credentials.yml.enc ./config/ COPY --chown=app:root ./config/credentials.yml.enc ./config/
# Pre-compilar los assets # Pre-compilar los assets
RUN bundle exec rake assets:precompile RUN bundle exec rake assets:precompile
# Comprimirlos usando brotli # Comprimirlos usando brotli
@ -52,71 +62,51 @@ RUN find public -type f -name "*.gz" | sed -re "s/\.gz$//" | xargs -r brotli -k
# assets ya están pre-compilados. # assets ya están pre-compilados.
RUN sed -re "/(uglifier|bootstrap|coffee-rails)/d" -i Gemfile RUN sed -re "/(uglifier|bootstrap|coffee-rails)/d" -i Gemfile
RUN bundle clean RUN bundle clean
RUN rm -rf ./node_modules ./tmp/cache ./.git
# Contenedor final # Contenedor final
FROM sutty/monit:latest FROM sutty/monit:latest
ENV RAILS_ENV production ENV RAILS_ENV production
# Instalar oxipng
COPY --from=oxipng --chown=root:root /root/.cargo/bin/oxipng /usr/bin/oxipng
RUN chmod 755 /usr/bin/oxipng
# 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-bundler ruby-json ruby-bigdecimal ruby-rake RUN apk add --no-cache libxslt libxml2 tzdata ruby ruby-bundler ruby-json ruby-bigdecimal ruby-rake
RUN apk add --no-cache postgresql-libs libssh2 file rsync git jpegoptim vips
# Chequear que la versión de ruby sea la correcta # Chequear que la versión de ruby sea la correcta
RUN test "2.5.7" = `ruby -e 'puts RUBY_VERSION'` RUN test "2.6.5" = `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 cd /usr/lib/ruby/2.6.0 && patch -Np 0 -i /tmp/rubygems-platform-musl.patch
RUN apk add --no-cache postgresql-libs libssh2
# 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 yarn
# Instalar foreman para poder correr los servicios # Instalar foreman para poder correr los servicios
RUN gem install --no-document --no-user-install foreman RUN gem install --no-document --no-user-install bundler foreman
RUN apk add --no-cache file
# Agregar el grupo del servidor web # Agregar el grupo del servidor web y la usuaria
RUN addgroup -g 82 -S www-data RUN addgroup -g 82 -S www-data
# Agregar la usuaria
RUN adduser -s /bin/sh -G www-data -h /srv/http -D app RUN adduser -s /bin/sh -G www-data -h /srv/http -D app
# https://github.com/rubygems/rubygems/issues/2918
# https://gitlab.alpinelinux.org/alpine/aports/issues/10808
COPY ./rubygems-platform-musl.patch /tmp/
RUN cd /usr/lib/ruby/2.5.0 && patch -Np 0 -i /tmp/rubygems-platform-musl.patch
# Convertirse en app para instalar # Convertirse en app para instalar
USER app USER app
WORKDIR /srv/http COPY --from=build --chown=app:www-data /home/app/checkout /srv/http
# Traer los archivos y colocarlos donde van definitivamente
COPY --from=build --chown=app:www-data /home/app/sutty.tar.gz /tmp/
RUN tar xf /tmp/sutty.tar.gz
# Publicar el código!
RUN mv /tmp/sutty.tar.gz ./public/
# Traer los assets compilados y las gemas
COPY --from=build /home/app/sutty/public/assets public/assets
COPY --from=build /home/app/sutty/public/packs public/packs
COPY --from=build /home/app/sutty/vendor vendor
COPY --from=build /home/app/sutty/.bundle .bundle
COPY --from=build /home/app/sutty/Gemfile Gemfile
COPY --from=build /home/app/sutty/Gemfile.lock Gemfile.lock
# Volver a root para cerrar la compilación # Volver a root para cerrar la compilación
USER root USER root
# Convertir la aplicación en solo lectura
#RUN chown -R root:root /srv/http
#RUN chmod -R o=g /srv/http
#RUN chown -R app:www-data _deploy _sites
# Sincronizar los assets a un directorio compartido # Sincronizar los assets a un directorio compartido
RUN apk add --no-cache rsync RUN install -m 755 /srv/http/sync_assets.sh /usr/local/bin/sync_assets
COPY ./sync_assets.sh /usr/local/bin/sync_assets # Instalar la configuración de monit
RUN chmod 755 /usr/local/bin/sync_assets RUN install -m 640 -o root -g root /srv/http/monit.conf /etc/monit.d/sutty.conf
# Instalar la configuración de monit y comprobarla
RUN install -m 640 -o root -g root ./monit.conf /etc/monit.d/sutty.conf
RUN monit -t
RUN apk add --no-cache git
# Mantener estos directorios! # Mantener estos directorios!
VOLUME "/srv/http/_deploy" VOLUME "/srv/http/_deploy"

21
Gemfile
View file

@ -15,7 +15,7 @@ git_source(:github) do |repo_name|
end end
# Cambiar en Dockerfile también # Cambiar en Dockerfile también
ruby '2.5.7' ruby '2.6.5'
gem 'dotenv-rails', require: 'dotenv/rails-now' gem 'dotenv-rails', require: 'dotenv/rails-now'
@ -24,7 +24,7 @@ gem 'rails', '~> 6'
# Use Puma as the app server # Use Puma as the app server
gem 'puma' gem 'puma'
# Use SCSS for stylesheets # Use SCSS for stylesheets
gem 'sass-rails', '~> 5.0' gem 'sassc-rails'
# Use Uglifier as compressor for JavaScript assets # Use Uglifier as compressor for JavaScript assets
gem 'uglifier', '>= 1.3.0' gem 'uglifier', '>= 1.3.0'
# See https://github.com/rails/execjs#readme for more supported runtimes # See https://github.com/rails/execjs#readme for more supported runtimes
@ -38,10 +38,7 @@ gem 'turbolinks', '~> 5'
gem 'jbuilder', '~> 2.5' gem 'jbuilder', '~> 2.5'
# Use ActiveModel has_secure_password # Use ActiveModel has_secure_password
gem 'bcrypt', '~> 3.1.7' gem 'bcrypt', '~> 3.1.7'
gem 'blazer'
# Use Capistrano for deployment
# gem 'capistrano-rails', group: :development
gem 'bootstrap', '~> 4' gem 'bootstrap', '~> 4'
gem 'commonmarker' gem 'commonmarker'
gem 'devise' gem 'devise'
@ -83,23 +80,17 @@ end
group :development do group :development do
# Access an IRB console on exception pages or by using <%= console %> # Access an IRB console on exception pages or by using <%= console %>
# anywhere in the code. # anywhere in the code.
gem 'listen', '>= 3.0.5', '< 3.2'
gem 'web-console', '>= 3.3.0'
# Spring speeds up development by keeping your application running in
# the background. Read more: https://github.com/rails/spring
gem 'bcrypt_pbkdf' gem 'bcrypt_pbkdf'
gem 'capistrano' gem 'brakeman'
gem 'capistrano-bundler'
gem 'capistrano-passenger'
gem 'capistrano-rails'
gem 'capistrano-rbenv'
gem 'ed25519' gem 'ed25519'
gem 'haml-lint', require: false gem 'haml-lint', require: false
gem 'letter_opener' gem 'letter_opener'
gem 'listen', '>= 3.0.5', '< 3.2'
gem 'rbnacl', '< 5.0' gem 'rbnacl', '< 5.0'
gem 'rubocop-rails' gem 'rubocop-rails'
gem 'spring' gem 'spring'
gem 'spring-watcher-listen', '~> 2.0.0' gem 'spring-watcher-listen', '~> 2.0.0'
gem 'web-console', '>= 3.3.0'
end end
group :test do group :test do

View file

@ -16,56 +16,56 @@ GIT
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
actioncable (6.0.1) actioncable (6.0.2.1)
actionpack (= 6.0.1) actionpack (= 6.0.2.1)
nio4r (~> 2.0) nio4r (~> 2.0)
websocket-driver (>= 0.6.1) websocket-driver (>= 0.6.1)
actionmailbox (6.0.1) actionmailbox (6.0.2.1)
actionpack (= 6.0.1) actionpack (= 6.0.2.1)
activejob (= 6.0.1) activejob (= 6.0.2.1)
activerecord (= 6.0.1) activerecord (= 6.0.2.1)
activestorage (= 6.0.1) activestorage (= 6.0.2.1)
activesupport (= 6.0.1) activesupport (= 6.0.2.1)
mail (>= 2.7.1) mail (>= 2.7.1)
actionmailer (6.0.1) actionmailer (6.0.2.1)
actionpack (= 6.0.1) actionpack (= 6.0.2.1)
actionview (= 6.0.1) actionview (= 6.0.2.1)
activejob (= 6.0.1) activejob (= 6.0.2.1)
mail (~> 2.5, >= 2.5.4) mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
actionpack (6.0.1) actionpack (6.0.2.1)
actionview (= 6.0.1) actionview (= 6.0.2.1)
activesupport (= 6.0.1) activesupport (= 6.0.2.1)
rack (~> 2.0) rack (~> 2.0, >= 2.0.8)
rack-test (>= 0.6.3) rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0)
actiontext (6.0.1) actiontext (6.0.2.1)
actionpack (= 6.0.1) actionpack (= 6.0.2.1)
activerecord (= 6.0.1) activerecord (= 6.0.2.1)
activestorage (= 6.0.1) activestorage (= 6.0.2.1)
activesupport (= 6.0.1) activesupport (= 6.0.2.1)
nokogiri (>= 1.8.5) nokogiri (>= 1.8.5)
actionview (6.0.1) actionview (6.0.2.1)
activesupport (= 6.0.1) activesupport (= 6.0.2.1)
builder (~> 3.1) builder (~> 3.1)
erubi (~> 1.4) erubi (~> 1.4)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.1, >= 1.2.0) rails-html-sanitizer (~> 1.1, >= 1.2.0)
activejob (6.0.1) activejob (6.0.2.1)
activesupport (= 6.0.1) activesupport (= 6.0.2.1)
globalid (>= 0.3.6) globalid (>= 0.3.6)
activemodel (6.0.1) activemodel (6.0.2.1)
activesupport (= 6.0.1) activesupport (= 6.0.2.1)
activerecord (6.0.1) activerecord (6.0.2.1)
activemodel (= 6.0.1) activemodel (= 6.0.2.1)
activesupport (= 6.0.1) activesupport (= 6.0.2.1)
activestorage (6.0.1) activestorage (6.0.2.1)
actionpack (= 6.0.1) actionpack (= 6.0.2.1)
activejob (= 6.0.1) activejob (= 6.0.2.1)
activerecord (= 6.0.1) activerecord (= 6.0.2.1)
marcel (~> 0.3.1) marcel (~> 0.3.1)
activesupport (6.0.1) activesupport (6.0.2.1)
concurrent-ruby (~> 1.0, >= 1.0.2) concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 0.7, < 2) i18n (>= 0.7, < 2)
minitest (~> 5.1) minitest (~> 5.1)
@ -73,34 +73,23 @@ GEM
zeitwerk (~> 2.2) zeitwerk (~> 2.2)
addressable (2.7.0) addressable (2.7.0)
public_suffix (>= 2.0.2, < 5.0) public_suffix (>= 2.0.2, < 5.0)
airbrussh (1.3.4)
sshkit (>= 1.6.1, != 1.7.0)
ast (2.4.0) ast (2.4.0)
autoprefixer-rails (9.6.1.1) autoprefixer-rails (9.7.3)
execjs execjs
bcrypt (3.1.13) bcrypt (3.1.13)
bcrypt_pbkdf (1.0.1) bcrypt_pbkdf (1.0.1)
bindex (0.8.1) bindex (0.8.1)
bootstrap (4.3.1) blazer (2.2.1)
activerecord (>= 5)
chartkick (>= 3.2)
railties (>= 5)
safely_block (>= 0.1.1)
bootstrap (4.4.1)
autoprefixer-rails (>= 9.1.0) autoprefixer-rails (>= 9.1.0)
popper_js (>= 1.14.3, < 2) popper_js (>= 1.14.3, < 2)
sassc-rails (>= 2.0.0) sassc-rails (>= 2.0.0)
builder (3.2.3) brakeman (4.7.2)
capistrano (3.11.1) builder (3.2.4)
airbrussh (>= 1.0.0)
i18n
rake (>= 10.0.0)
sshkit (>= 1.9.0)
capistrano-bundler (1.6.0)
capistrano (~> 3.1)
capistrano-passenger (0.2.0)
capistrano (~> 3.0)
capistrano-rails (1.4.0)
capistrano (~> 3.1)
capistrano-bundler (~> 1.1)
capistrano-rbenv (2.1.4)
capistrano (~> 3.1)
sshkit (~> 1.3)
capybara (2.18.0) capybara (2.18.0)
addressable addressable
mini_mime (>= 0.1.3) mini_mime (>= 0.1.3)
@ -108,11 +97,11 @@ GEM
rack (>= 1.0.0) rack (>= 1.0.0)
rack-test (>= 0.5.4) rack-test (>= 0.5.4)
xpath (>= 2.0, < 4.0) xpath (>= 2.0, < 4.0)
childprocess (2.0.0) chartkick (3.3.1)
rake (< 13.0) childprocess (3.0.0)
coderay (1.1.2) coderay (1.1.2)
colorator (1.1.0) colorator (1.1.0)
commonmarker (0.20.1) commonmarker (0.20.2)
ruby-enum (~> 0.5) ruby-enum (~> 0.5)
concurrent-ruby (1.1.5) concurrent-ruby (1.1.5)
crass (1.0.5) crass (1.0.5)
@ -123,8 +112,8 @@ GEM
railties (>= 4.1.0) railties (>= 4.1.0)
responders responders
warden (~> 1.2.3) warden (~> 1.2.3)
devise-i18n (1.8.2) devise-i18n (1.9.0)
devise (>= 4.6) devise (>= 4.7.1)
devise_invitable (2.0.1) devise_invitable (2.0.1)
actionmailer (>= 5.0) actionmailer (>= 5.0)
devise (>= 4.6) devise (>= 4.6)
@ -136,23 +125,24 @@ GEM
em-websocket (0.5.1) em-websocket (0.5.1)
eventmachine (>= 0.12.9) eventmachine (>= 0.12.9)
http_parser.rb (~> 0.6.0) http_parser.rb (~> 0.6.0)
email_address (0.1.11) email_address (0.1.12)
netaddr (~> 2.0) netaddr (>= 2.0.4, < 3)
simpleidn simpleidn
errbase (0.2.0)
erubi (1.9.0) erubi (1.9.0)
eventmachine (1.2.7) eventmachine (1.2.7)
exception_notification (4.4.0) exception_notification (4.4.0)
actionmailer (>= 4.0, < 7) actionmailer (>= 4.0, < 7)
activesupport (>= 4.0, < 7) activesupport (>= 4.0, < 7)
execjs (2.7.0) execjs (2.7.0)
factory_bot (5.0.2) factory_bot (5.1.1)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
factory_bot_rails (5.0.2) factory_bot_rails (5.1.1)
factory_bot (~> 5.0.2) factory_bot (~> 5.1.0)
railties (>= 4.2.0) railties (>= 4.2.0)
ffi (1.11.1) ffi (1.11.3)
forwardable-extended (2.6.0) forwardable-extended (2.6.0)
friendly_id (5.2.5) friendly_id (5.3.0)
activerecord (>= 4.0.0) activerecord (>= 4.0.0)
globalid (0.4.2) globalid (0.4.2)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
@ -161,13 +151,12 @@ GEM
tilt tilt
haml-lint (0.999.999) haml-lint (0.999.999)
haml_lint haml_lint
haml_lint (0.33.0) haml_lint (0.34.1)
haml (>= 4.0, < 5.2) haml (>= 4.0, < 5.2)
rainbow rainbow
rake (>= 10, < 13)
rubocop (>= 0.50.0) rubocop (>= 0.50.0)
sysexits (~> 1.1) sysexits (~> 1.1)
hamlit (2.10.0) hamlit (2.11.0)
temple (>= 0.8.2) temple (>= 0.8.2)
thor thor
tilt tilt
@ -180,13 +169,13 @@ GEM
http_parser.rb (0.6.0) http_parser.rb (0.6.0)
i18n (1.7.0) i18n (1.7.0)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
image_processing (1.9.3) image_processing (1.10.0)
mini_magick (>= 4.9.5, < 5) mini_magick (>= 4.9.5, < 5)
ruby-vips (>= 2.0.13, < 3) ruby-vips (>= 2.0.13, < 3)
inline_svg (1.5.2) inline_svg (1.6.0)
activesupport (>= 3.0) activesupport (>= 3.0)
nokogiri (>= 1.6) nokogiri (>= 1.6)
jaro_winkler (1.5.3) jaro_winkler (1.5.4)
jbuilder (2.9.1) jbuilder (2.9.1)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
jekyll (4.0.0) jekyll (4.0.0)
@ -204,7 +193,7 @@ GEM
rouge (~> 3.0) rouge (~> 3.0)
safe_yaml (~> 1.0) safe_yaml (~> 1.0)
terminal-table (~> 1.8) terminal-table (~> 1.8)
jekyll-sass-converter (2.0.0) jekyll-sass-converter (2.0.1)
sassc (> 2.0.1, < 3.0) sassc (> 2.0.1, < 3.0)
jekyll-watch (2.2.1) jekyll-watch (2.2.1)
listen (~> 3.0) listen (~> 3.0)
@ -220,7 +209,7 @@ GEM
rb-fsevent (~> 0.9, >= 0.9.4) rb-fsevent (~> 0.9, >= 0.9.4)
rb-inotify (~> 0.9, >= 0.9.7) rb-inotify (~> 0.9, >= 0.9.7)
ruby_dep (~> 1.2) ruby_dep (~> 1.2)
loofah (2.3.1) loofah (2.4.0)
crass (~> 1.0.2) crass (~> 1.0.2)
nokogiri (>= 1.5.9) nokogiri (>= 1.5.9)
mail (2.7.1) mail (2.7.1)
@ -237,48 +226,45 @@ GEM
mobility (0.8.9) mobility (0.8.9)
i18n (>= 0.6.10, < 2) i18n (>= 0.6.10, < 2)
request_store (~> 1.0) request_store (~> 1.0)
net-scp (2.0.0) netaddr (2.0.4)
net-ssh (>= 2.6.5, < 6.0.0)
net-ssh (5.2.0)
netaddr (2.0.3)
nio4r (2.5.2) nio4r (2.5.2)
nokogiri (1.10.5) nokogiri (1.10.7)
mini_portile2 (~> 2.4.0) mini_portile2 (~> 2.4.0)
orm_adapter (0.5.0) orm_adapter (0.5.0)
parallel (1.17.0) parallel (1.19.1)
parser (2.6.4.1) parser (2.7.0.1)
ast (~> 2.4.0) ast (~> 2.4.0)
pathutil (0.16.2) pathutil (0.16.2)
forwardable-extended (~> 2.6) forwardable-extended (~> 2.6)
pg (1.1.4) pg (1.2.0)
popper_js (1.14.5) popper_js (1.14.5)
pry (0.12.2) pry (0.12.2)
coderay (~> 1.1.0) coderay (~> 1.1.0)
method_source (~> 0.9.0) method_source (~> 0.9.0)
public_suffix (4.0.1) public_suffix (4.0.2)
puma (4.3.0) puma (4.3.1)
nio4r (~> 2.0) nio4r (~> 2.0)
pundit (2.1.0) pundit (2.1.0)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
rack (2.0.7) rack (2.0.8)
rack-proxy (0.6.5) rack-proxy (0.6.5)
rack rack
rack-test (1.1.0) rack-test (1.1.0)
rack (>= 1.0, < 3) rack (>= 1.0, < 3)
rails (6.0.1) rails (6.0.2.1)
actioncable (= 6.0.1) actioncable (= 6.0.2.1)
actionmailbox (= 6.0.1) actionmailbox (= 6.0.2.1)
actionmailer (= 6.0.1) actionmailer (= 6.0.2.1)
actionpack (= 6.0.1) actionpack (= 6.0.2.1)
actiontext (= 6.0.1) actiontext (= 6.0.2.1)
actionview (= 6.0.1) actionview (= 6.0.2.1)
activejob (= 6.0.1) activejob (= 6.0.2.1)
activemodel (= 6.0.1) activemodel (= 6.0.2.1)
activerecord (= 6.0.1) activerecord (= 6.0.2.1)
activestorage (= 6.0.1) activestorage (= 6.0.2.1)
activesupport (= 6.0.1) activesupport (= 6.0.2.1)
bundler (>= 1.3.0) bundler (>= 1.3.0)
railties (= 6.0.1) railties (= 6.0.2.1)
sprockets-rails (>= 2.0.0) sprockets-rails (>= 2.0.0)
rails-dom-testing (2.0.3) rails-dom-testing (2.0.3)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
@ -290,16 +276,16 @@ GEM
railties (>= 6.0.0, < 7) railties (>= 6.0.0, < 7)
rails_warden (0.6.0) rails_warden (0.6.0)
warden (>= 1.2.0) warden (>= 1.2.0)
railties (6.0.1) railties (6.0.2.1)
actionpack (= 6.0.1) actionpack (= 6.0.2.1)
activesupport (= 6.0.1) activesupport (= 6.0.2.1)
method_source method_source
rake (>= 0.8.7) rake (>= 0.8.7)
thor (>= 0.20.3, < 2.0) thor (>= 0.20.3, < 2.0)
rainbow (3.0.0) rainbow (3.0.0)
rake (12.3.3) rake (13.0.1)
rb-fsevent (0.10.3) rb-fsevent (0.10.3)
rb-inotify (0.10.0) rb-inotify (0.10.1)
ffi (~> 1.0) ffi (~> 1.0)
rbnacl (4.0.2) rbnacl (4.0.2)
ffi ffi
@ -311,52 +297,43 @@ GEM
redis-activesupport (5.2.0) redis-activesupport (5.2.0)
activesupport (>= 3, < 7) activesupport (>= 3, < 7)
redis-store (>= 1.3, < 2) redis-store (>= 1.3, < 2)
redis-rack (2.0.5) redis-rack (2.0.6)
rack (>= 1.5, < 3) rack (>= 1.5, < 3)
redis-store (>= 1.2, < 2) redis-store (>= 1.2, < 2)
redis-rails (5.0.2) redis-rails (5.0.2)
redis-actionpack (>= 5.0, < 6) redis-actionpack (>= 5.0, < 6)
redis-activesupport (>= 5.0, < 6) redis-activesupport (>= 5.0, < 6)
redis-store (>= 1.2, < 2) redis-store (>= 1.2, < 2)
redis-store (1.6.0) redis-store (1.8.1)
redis (>= 2.2, < 5) redis (>= 4, < 5)
request_store (1.4.1) request_store (1.5.0)
rack (>= 1.4) rack (>= 1.4)
responders (3.0.0) responders (3.0.0)
actionpack (>= 5.0) actionpack (>= 5.0)
railties (>= 5.0) railties (>= 5.0)
rouge (3.11.0) rouge (3.14.0)
rubocop (0.74.0) rubocop (0.78.0)
jaro_winkler (~> 1.5.1) jaro_winkler (~> 1.5.1)
parallel (~> 1.10) parallel (~> 1.10)
parser (>= 2.6) parser (>= 2.6)
rainbow (>= 2.2.2, < 4.0) rainbow (>= 2.2.2, < 4.0)
ruby-progressbar (~> 1.7) ruby-progressbar (~> 1.7)
unicode-display_width (>= 1.4.0, < 1.7) unicode-display_width (>= 1.4.0, < 1.7)
rubocop-rails (2.3.2) rubocop-rails (2.4.1)
rack (>= 1.1) rack (>= 1.1)
rubocop (>= 0.72.0) rubocop (>= 0.72.0)
ruby-enum (0.7.2) ruby-enum (0.7.2)
i18n i18n
ruby-progressbar (1.10.1) ruby-progressbar (1.10.1)
ruby-vips (2.0.15) ruby-vips (2.0.16)
ffi (~> 1.9) ffi (~> 1.9)
ruby_dep (1.5.0) ruby_dep (1.5.0)
rubyzip (1.3.0) rubyzip (2.0.0)
rugged (0.28.3.1) rugged (0.28.4.1)
safe_yaml (1.0.5) safe_yaml (1.0.5)
sass (3.7.4) safely_block (0.3.0)
sass-listen (~> 4.0.0) errbase (>= 0.1.1)
sass-listen (4.0.0) sassc (2.2.1)
rb-fsevent (~> 0.9, >= 0.9.4)
rb-inotify (~> 0.9, >= 0.9.7)
sass-rails (5.1.0)
railties (>= 5.2.0)
sass (~> 3.1)
sprockets (>= 2.8, < 4.0)
sprockets-rails (>= 2.0, < 4.0)
tilt (>= 1.1, < 3)
sassc (2.2.0)
ffi (~> 1.9) ffi (~> 1.9)
sassc-rails (2.1.2) sassc-rails (2.1.2)
railties (>= 4.0.0) railties (>= 4.0.0)
@ -364,41 +341,38 @@ GEM
sprockets (> 3.0) sprockets (> 3.0)
sprockets-rails sprockets-rails
tilt tilt
selenium-webdriver (3.142.4) selenium-webdriver (3.142.7)
childprocess (>= 0.5, < 3.0) childprocess (>= 0.5, < 4.0)
rubyzip (~> 1.2, >= 1.2.2) rubyzip (>= 1.2.2)
simpleidn (0.1.1) simpleidn (0.1.1)
unf (~> 0.1.4) unf (~> 0.1.4)
spring (2.1.0) spring (2.1.0)
spring-watcher-listen (2.0.1) spring-watcher-listen (2.0.1)
listen (>= 2.7, < 4.0) listen (>= 2.7, < 4.0)
spring (>= 1.2, < 3.0) spring (>= 1.2, < 3.0)
sprockets (3.7.2) sprockets (4.0.0)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
rack (> 1, < 3) rack (> 1, < 3)
sprockets-rails (3.2.1) sprockets-rails (3.2.1)
actionpack (>= 4.0) actionpack (>= 4.0)
activesupport (>= 4.0) activesupport (>= 4.0)
sprockets (>= 3.0.0) sprockets (>= 3.0.0)
sqlite3 (1.4.1) sqlite3 (1.4.2)
sshkit (1.20.0)
net-scp (>= 1.1.2)
net-ssh (>= 2.8.0)
sucker_punch (2.1.2) sucker_punch (2.1.2)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
sysexits (1.2.0) sysexits (1.2.0)
temple (0.8.2) temple (0.8.2)
terminal-table (1.8.0) terminal-table (1.8.0)
unicode-display_width (~> 1.1, >= 1.1.1) unicode-display_width (~> 1.1, >= 1.1.1)
thor (0.20.3) thor (1.0.1)
thread_safe (0.3.6) thread_safe (0.3.6)
tilt (2.0.9) tilt (2.0.10)
turbolinks (5.2.0) turbolinks (5.2.1)
turbolinks-source (~> 5.2) turbolinks-source (~> 5.2)
turbolinks-source (5.2.0) turbolinks-source (5.2.0)
tzinfo (1.2.5) tzinfo (1.2.6)
thread_safe (~> 0.1) thread_safe (~> 0.1)
uglifier (4.1.20) uglifier (4.2.0)
execjs (>= 0.3.0, < 3) execjs (>= 0.3.0, < 3)
unf (0.1.4) unf (0.1.4)
unf_ext unf_ext
@ -414,7 +388,7 @@ GEM
activemodel (>= 6.0.0) activemodel (>= 6.0.0)
bindex (>= 0.4.0) bindex (>= 0.4.0)
railties (>= 6.0.0) railties (>= 6.0.0)
webpacker (4.0.7) webpacker (4.2.2)
activesupport (>= 4.2) activesupport (>= 4.2)
rack-proxy (>= 0.6.1) rack-proxy (>= 0.6.1)
railties (>= 4.2) railties (>= 4.2)
@ -423,7 +397,7 @@ GEM
websocket-extensions (0.1.4) websocket-extensions (0.1.4)
xpath (3.2.0) xpath (3.2.0)
nokogiri (~> 1.8) nokogiri (~> 1.8)
zeitwerk (2.2.1) zeitwerk (2.2.2)
PLATFORMS PLATFORMS
ruby ruby
@ -431,12 +405,9 @@ PLATFORMS
DEPENDENCIES DEPENDENCIES
bcrypt (~> 3.1.7) bcrypt (~> 3.1.7)
bcrypt_pbkdf bcrypt_pbkdf
blazer
bootstrap (~> 4) bootstrap (~> 4)
capistrano brakeman
capistrano-bundler
capistrano-passenger
capistrano-rails
capistrano-rbenv
capybara (~> 2.13) capybara (~> 2.13)
commonmarker commonmarker
database_cleaner database_cleaner
@ -474,7 +445,7 @@ DEPENDENCIES
rubocop-rails rubocop-rails
rubyzip rubyzip
rugged rugged
sass-rails (~> 5.0) sassc-rails
selenium-webdriver selenium-webdriver
spring spring
spring-watcher-listen (~> 2.0.0) spring-watcher-listen (~> 2.0.0)
@ -489,7 +460,7 @@ DEPENDENCIES
yaml_db! yaml_db!
RUBY VERSION RUBY VERSION
ruby 2.5.7p206 ruby 2.6.5p114
BUNDLED WITH BUNDLED WITH
2.0.2 2.1.4

View file

@ -3,6 +3,9 @@ mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST)))
root_dir := $(patsubst %/,%,$(dir $(mkfile_path))) root_dir := $(patsubst %/,%,$(dir $(mkfile_path)))
include $(root_dir)/.env include $(root_dir)/.env
serve:
bundle exec rails s -b "ssl://0.0.0.0:3000?key=config/sutty.local.key&cert=config/sutty.local.crt"
# Limpiar los archivos de testeo # Limpiar los archivos de testeo
clean: clean:
rm -rf _sites/test-* _deploy/test-* rm -rf _sites/test-* _deploy/test-*
@ -11,13 +14,22 @@ clean:
build: build:
docker build --build-arg="RAILS_MASTER_KEY=`cat config/master.key`" -t sutty/sutty . docker build --build-arg="RAILS_MASTER_KEY=`cat config/master.key`" -t sutty/sutty .
save:
docker save sutty/sutty:latest | gzip | ssh root@sutty.nl docker load
load:
ssh root@sutty.nl sh -c "gunzip -c sutty.latest.gz | docker load"
# Crear el directorio donde se almacenan las gemas binarias # Crear el directorio donde se almacenan las gemas binarias
../gems/: ../gems/:
mkdir -p $@ mkdir -p $@
ifeq ($(MAKECMDGOALS),convert-gems)
gem_dir := $(shell readlink -f ../gems) gem_dir := $(shell readlink -f ../gems)
gems := $(shell bundle show --paths | xargs -I {} sh -c 'test -d {}/ext && basename {}') gems := $(shell bundle show --paths | xargs -I {} sh -c 'test -f {}/ext/*/extconf.rb && basename {}')
gems += $(shell bundle show --paths | xargs -I {} sh -c 'test -f {}/ext/extconf.rb && basename {}')
gems_musl := $(patsubst %,$(gem_dir)/%-x86_64-linux-musl.gem,$(gems)) gems_musl := $(patsubst %,$(gem_dir)/%-x86_64-linux-musl.gem,$(gems))
endif
$(gem_dir)/%-x86_64-linux-musl.gem: $(gem_dir)/%-x86_64-linux-musl.gem:
docker run \ docker run \
@ -45,8 +57,8 @@ test-container: $(dirs)
-v $(PWD)/root/public:/srv/http/_public \ -v $(PWD)/root/public:/srv/http/_public \
-v $(PWD)/config/credentials.yml.enc:/srv/http/config/credentials.yml.enc \ -v $(PWD)/config/credentials.yml.enc:/srv/http/config/credentials.yml.enc \
-e RAILS_MASTER_KEY=`cat config/master.key` \ -e RAILS_MASTER_KEY=`cat config/master.key` \
-e RAILS_ENV=production \
-it \ -it \
--rm \ --rm \
--name=sutty \ --name=sutty \
sutty/sutty \ sutty/sutty /bin/sh
/bin/sh

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -1,5 +1,6 @@
//= require rails-ujs //= require rails-ujs
//= require turbolinks //= require turbolinks
//= require input-tag/input-tag.js //= require input-tag/input-tag.js
//= require input-map/input-map.js
//= require zepto/dist/zepto.min.js //= require zepto/dist/zepto.min.js
//= require_tree . //= require_tree .

View file

@ -7,4 +7,13 @@ $(document).on('turbolinks:load', function() {
props: { ...this.dataset } props: { ...this.dataset }
}); });
}); });
$('.mapable').each(function() {
this.innerHTML = '';
new InputMap({
target: this,
props: { ...this.dataset }
});
});
}); });

View file

@ -20,6 +20,18 @@ $magenta: #f206f9;
--background: #{$black}; --background: #{$black};
--color: #{$cyan}; --color: #{$cyan};
} }
trix-toolbar {
.trix-button--icon {
background-color: var(--color);
}
}
}
trix-toolbar {
background-color: var(--background);
position: sticky;
top: 0;
} }
// TODO: Encontrar la forma de generar esto desde los locales de Rails // TODO: Encontrar la forma de generar esto desde los locales de Rails
@ -34,7 +46,8 @@ $custom-file-text: (
font-weight: 500; font-weight: 500;
font-display: optional; font-display: optional;
src: local('Saira Medium'), local('Saira-Medium'), src: local('Saira Medium'), local('Saira-Medium'),
font-url('saira/v3/SairaMedium.ttf') format('truetype'); font-url('saira/v3/SairaMedium-subset.woff2') format('woff2'),
font-url('saira/v3/SairaMedium-subset.zopfli.woff') format('woff');
} }
@font-face { @font-face {
@ -43,7 +56,8 @@ $custom-file-text: (
font-weight: 700; font-weight: 700;
font-display: optional; font-display: optional;
src: local('Saira Bold'), local('Saira-Bold'), src: local('Saira Bold'), local('Saira-Bold'),
font-url('saira/v3/SairaBold.ttf') format('truetype'); font-url('saira/v3/SairaBold-subset.woff2') format('woff2'),
font-url('saira/v3/SairaBold-subset.zopfli.woff') format('woff');
} }
body { body {
@ -128,7 +142,9 @@ ol.breadcrumb {
transition: all 3s; transition: all 3s;
} }
.mapable,
.taggable { .taggable {
.input-map,
.input-tag { .input-tag {
legend { legend {
@extend .sr-only @extend .sr-only
@ -144,7 +160,7 @@ ol.breadcrumb {
&[type=text] { &[type=text] {
@extend .form-control; @extend .form-control;
display: inline-block; display: inline-block;
width: calc(100% - 90px); width: calc(100% - 93px);
} }
&[type=checkbox] { &[type=checkbox] {
@ -169,6 +185,8 @@ svg {
color: var(--background); color: var(--background);
border: none; border: none;
border-radius: 0; border-radius: 0;
margin-right: 0.3rem;
margin-bottom: 0.3rem;
&:hover { &:hover {
color: var(--background); color: var(--background);

View file

@ -4,9 +4,6 @@ module Api
module V1 module V1
# API # API
class BaseController < ActionController::Base class BaseController < ActionController::Base
http_basic_authenticate_with name: ENV['HTTP_BASIC_USER'],
password: ENV['HTTP_BASIC_PASSWORD']
protect_from_forgery with: :null_session protect_from_forgery with: :null_session
respond_to :json respond_to :json
end end

View file

@ -0,0 +1,42 @@
# frozen_string_literal: true
module Api
module V1
# Recibe los reportes de Content Security Policy
class CspReportsController < BaseController
# Crea un reporte de CSP intercambiando los guiones medios por
# bajos
#
# TODO: Aplicar rate_limit
def create
csp = CspReport.new(csp_report_params.to_h.map do |k, v|
{ k.tr('-', '_') => v }
end.inject(&:merge))
csp.id = SecureRandom.uuid
csp.save
render json: {}, status: :created
end
private
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only#Violation_report_syntax
def csp_report_params
params.require(:'csp-report')
.permit(:disposition,
:referrer,
:'blocked-uri',
:'document-uri',
:'effective-directive',
:'original-policy',
:'script-sample',
:'status-code',
:'violated-directive',
:'line-number',
:'column-number',
:'source-file')
end
end
end
end

View file

@ -4,8 +4,12 @@ module Api
module V1 module V1
# API para sitios # API para sitios
class SitesController < BaseController class SitesController < BaseController
http_basic_authenticate_with name: ENV['HTTP_BASIC_USER'],
password: ENV['HTTP_BASIC_PASSWORD']
def index def index
render json: Site.all.order(:name).pluck(:name) render json: Site.all.order(:name).pluck(:name) +
DeployAlternativeDomain.all.map(&:hostname)
end end
# Detecta si se puede generar un certificado # Detecta si se puede generar un certificado

View file

@ -12,7 +12,8 @@ class PostsController < ApplicationController
@layout = params.dig(:layout).try :to_sym @layout = params.dig(:layout).try :to_sym
# TODO: Aplicar policy_scope # TODO: Aplicar policy_scope
@posts = @site.posts(lang: I18n.locale) @posts = @site.posts(lang: I18n.locale)
@posts.sort_by! :order, :date @posts.sort_by!(:order, :date).reverse!
@usuarie = @site.usuarie? current_usuarie
end end
def show def show
@ -34,7 +35,7 @@ class PostsController < ApplicationController
usuarie: current_usuarie, usuarie: current_usuarie,
params: params) params: params)
if service.create.persisted? if (@post = service.create.persisted?)
redirect_to site_posts_path(@site) redirect_to site_posts_path(@site)
else else
render 'posts/new' render 'posts/new'

View file

@ -8,7 +8,7 @@ class SitesController < ApplicationController
# Ver un listado de sitios # Ver un listado de sitios
def index def index
authorize Site authorize Site
@sites = current_usuarie.sites @sites = current_usuarie.sites.order(:title)
end end
# No tenemos propiedades de un sitio aún, así que vamos al listado de # No tenemos propiedades de un sitio aún, así que vamos al listado de

View file

@ -186,7 +186,11 @@ document.addEventListener('turbolinks:load', e => {
onlyBody: true, onlyBody: true,
dragHandler: '.handle', dragHandler: '.handle',
}).on('drop', (from, to, el, mode) => { }).on('drop', (from, to, el, mode) => {
$('.reorder').val((i, v) => i) Array.from(document.querySelectorAll('.reorder'))
$('.submit-reorder').removeClass('d-none') .reverse()
.map((o,i) => o.value = i)
Array.from(document.querySelectorAll('.submit-reorder'))
.map(s => s.classList.remove('d-none'))
}) })
}) })

View file

@ -15,6 +15,9 @@ class DeployJob < ApplicationJob
# No es opcional # No es opcional
unless @deployed[:deploy_local] unless @deployed[:deploy_local]
@site.update_attribute :status, 'waiting' @site.update_attribute :status, 'waiting'
notify_usuaries
# Hacer fallar la tarea
raise DeployException, deploy_local.build_stats.last.log raise DeployException, deploy_local.build_stats.last.log
end end

4
app/models/csp_report.rb Normal file
View file

@ -0,0 +1,4 @@
# frozen_string_literal: true
# Almacena un reporte de CSP
class CspReport < ApplicationRecord; end

View file

@ -0,0 +1,23 @@
# frozen_string_literal: true
# Soportar dominios alternativos
class DeployAlternativeDomain < Deploy
store :values, accessors: %i[hostname], coder: JSON
# Generar un link simbólico del sitio principal al alternativo
def deploy
File.symlink?(destination) ||
File.symlink(site.hostname, destination).zero?
end
# No hay límite para los dominios alternativos
def limit; end
def size
File.size destination
end
def destination
File.join(Rails.root, '_deploy', hostname.gsub(/\.\z/, ''))
end
end

View file

@ -13,7 +13,11 @@ class DeployLocal < Deploy
# Pasamos variables de entorno mínimas para no filtrar secretos de # Pasamos variables de entorno mínimas para no filtrar secretos de
# Sutty # Sutty
def deploy def deploy
mkdir && yarn && bundle && jekyll_build return false unless mkdir
return false unless yarn
return false unless bundle
jekyll_build
end end
# Sólo permitimos un deploy local # Sólo permitimos un deploy local
@ -49,7 +53,8 @@ class DeployLocal < Deploy
{ {
'HOME' => home_dir, 'HOME' => home_dir,
'PATH' => paths.join(':'), 'PATH' => paths.join(':'),
'JEKYLL_ENV' => Rails.env 'JEKYLL_ENV' => Rails.env,
'LANG' => ENV['LANG']
} }
end end
@ -61,6 +66,10 @@ class DeployLocal < Deploy
File.exist? yarn_lock File.exist? yarn_lock
end end
def gem
run %(gem install bundler --no-document)
end
# Corre yarn dentro del repositorio # Corre yarn dentro del repositorio
def yarn def yarn
return unless yarn_lock? return unless yarn_lock?

View file

@ -0,0 +1,4 @@
# frozen_string_literal: true
# Un campo de correo
class MetadataColor < MetadataString; end

View file

@ -0,0 +1,5 @@
# frozen_string_literal: true
# Un campo de correo
# TODO: Validar que tenga un formato correo
class MetadataEmail < MetadataString; end

View file

@ -0,0 +1,23 @@
# frozen_string_literal: true
# Define un campo de coordenadas geográficas
class MetadataGeo < MetadataTemplate
def default_value
{ 'lat' => nil, 'lng' => nil }
end
def empty?
value == default_value
end
def to_param
{ name => %i[lat lng] }
end
def save
self[:value] = {
'lat' => self[:value]['lat'].to_f,
'lng' => self[:value]['lng'].to_f
}
end
end

View file

@ -2,9 +2,9 @@
# Un campo de orden # Un campo de orden
class MetadataOrder < MetadataTemplate class MetadataOrder < MetadataTemplate
# El valor según la posición del post en la relación, siguiendo el # El valor según la posición del post en la relación ordenada por
# orden cronológico inverso # fecha, a fecha más alta, posición más alta
def default_value def default_value
site.posts(lang: post.lang.value).index(post) site.posts(lang: post.lang.value).sort_by(:date).index(post)
end end
end end

View file

@ -0,0 +1,23 @@
# frozen_string_literal: true
# Devuelve una lista de títulos y UUID de todos los posts del mismo
# idioma que el actual, para usar con input-map.js
class MetadataRelatedPosts < MetadataArray
# Genera un Hash de { title | slug => uuid }
def values
site.posts(lang: lang).map do |p|
{ title(p) => p.uuid.value }
end.inject(:merge)
end
private
def title(post)
post.try(:title).try(:value) || post.try(:slug).try(:value)
end
# TODO: Traer el idioma actual de otra forma
def lang
post.try(:lang).try(:value) || I18n.locale
end
end

View file

@ -15,6 +15,8 @@ MetadataTemplate = Struct.new(:site, :document, :name, :label, :type,
# Valores posibles, busca todos los valores actuales en otros # Valores posibles, busca todos los valores actuales en otros
# artículos del mismo sitio # artículos del mismo sitio
#
# TODO: Implementar lang!
def values def values
site.everything_of(name) site.everything_of(name)
end end

View file

@ -0,0 +1,8 @@
# frozen_string_literal: true
# Asigna un identificador único al artículo
class MetadataUuid < MetadataTemplate
def default_value
SecureRandom.uuid
end
end

View file

@ -1,7 +1,5 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'jekyll/utils'
# Esta clase representa un post en un sitio jekyll e incluye métodos # Esta clase representa un post en un sitio jekyll e incluye métodos
# para modificarlos y crear nuevos. # para modificarlos y crear nuevos.
# #
@ -13,7 +11,16 @@ class Post < OpenStruct
DEFAULT_ATTRIBUTES = %i[site document layout].freeze DEFAULT_ATTRIBUTES = %i[site document layout].freeze
# Otros atributos que no vienen en los metadatos # Otros atributos que no vienen en los metadatos
PRIVATE_ATTRIBUTES = %i[path slug attributes errors].freeze PRIVATE_ATTRIBUTES = %i[path slug attributes errors].freeze
PUBLIC_ATTRIBUTES = %i[lang date].freeze PUBLIC_ATTRIBUTES = %i[lang date uuid].freeze
class << self
# Obtiene el layout sin leer el Document
def find_layout(doc)
SafeYAML.load(IO.foreach(doc.path).lazy.grep(/^layout: /).take(1).first)
.try(:[], 'layout')
.try(:to_sym)
end
end
# Redefinir el inicializador de OpenStruct # Redefinir el inicializador de OpenStruct
# #
@ -33,6 +40,10 @@ class Post < OpenStruct
# MetadataFactory devuelve un tipo de campo por cada campo. A # MetadataFactory devuelve un tipo de campo por cada campo. A
# partir de ahí se pueden obtener los valores actuales y una lista # partir de ahí se pueden obtener los valores actuales y una lista
# de valores por defecto. # de valores por defecto.
#
# XXX: En el primer intento de hacerlo más óptimo, movimos esta
# lógica a instanciación bajo demanda, pero no solo no logramos
# optimizar sino que aumentamos el tiempo de carga :/
layout.metadata.each_pair do |name, template| layout.metadata.each_pair do |name, template|
send "#{name}=".to_sym, send "#{name}=".to_sym,
MetadataFactory.build(document: document, MetadataFactory.build(document: document,
@ -47,31 +58,27 @@ class Post < OpenStruct
required: template['required']) required: template['required'])
end end
# TODO: Llamar dinámicamente
load_lang! load_lang!
load_slug! load_slug!
load_date! load_date!
load_path! load_path!
load_uuid!
# Leer el documento # XXX: No usamos Post#read porque a esta altura todavía no sabemos
read # nada del Document
document.read! if File.exist? document.path
end end
# TODO: Convertir a UUID?
def id def id
path.basename path.basename
end end
def sha1 def updated_at
Digest::SHA1.hexdigest id File.mtime(path.absolute)
end end
# Levanta un error si al construir el artículo no pasamos un atributo.
def default_attributes_missing(**args)
DEFAULT_ATTRIBUTES.each do |attr|
i18n = I18n.t("exceptions.post.#{attr}_missing")
raise ArgumentError, i18n unless args[attr].present?
end
end
# Solo ejecuta la magia de OpenStruct si el campo existe en la # Solo ejecuta la magia de OpenStruct si el campo existe en la
# plantilla # plantilla
@ -82,14 +89,18 @@ class Post < OpenStruct
# XXX: rubocop dice que tenemos que usar super cuando ya lo estamos # XXX: rubocop dice que tenemos que usar super cuando ya lo estamos
# usando... # usando...
def method_missing(mid, *args) def method_missing(mid, *args)
unless attribute? mid # Limpiar el nombre del atributo, para que todos los ayudantes
# reciban el método en limpio
name = attribute_name mid
unless attribute? name
raise NoMethodError, I18n.t('exceptions.post.no_method', raise NoMethodError, I18n.t('exceptions.post.no_method',
method: mid) method: mid)
end end
# Definir los attribute_* # Definir los attribute_*
new_attribute_was(mid) new_attribute_was(name)
new_attribute_changed(mid) new_attribute_changed(name)
# OpenStruct # OpenStruct
super(mid, *args) super(mid, *args)
@ -101,12 +112,15 @@ class Post < OpenStruct
# Detecta si es un atributo válido o no, a partir de la tabla de la # Detecta si es un atributo válido o no, a partir de la tabla de la
# plantilla # plantilla
def attribute?(mid) def attribute?(mid)
attrs = DEFAULT_ATTRIBUTES + PRIVATE_ATTRIBUTES + PUBLIC_ATTRIBUTES included = DEFAULT_ATTRIBUTES.include?(mid) ||
if singleton_class.method_defined? :attributes PRIVATE_ATTRIBUTES.include?(mid) ||
(attrs + attributes).include? attribute_name(mid) PUBLIC_ATTRIBUTES.include?(mid)
else
attrs.include? attribute_name(mid) if !included && singleton_class.method_defined?(:attributes)
included = attributes.include? mid
end end
included
end end
# Devuelve los strong params para el layout # Devuelve los strong params para el layout
@ -133,8 +147,10 @@ class Post < OpenStruct
{ metadata.to_s => template.value } { metadata.to_s => template.value }
end.compact.inject(:merge) end.compact.inject(:merge)
# TODO: Convertir a Metadata?
# Asegurarse que haya un layout # Asegurarse que haya un layout
yaml['layout'] = layout.name.to_s yaml['layout'] = layout.name.to_s
yaml['uuid'] = uuid.value
# Y que no se procese liquid # Y que no se procese liquid
yaml['liquid'] = false yaml['liquid'] = false
yaml['usuaries'] = usuaries.map(&:id).uniq yaml['usuaries'] = usuaries.map(&:id).uniq
@ -156,8 +172,8 @@ class Post < OpenStruct
# Guarda los cambios # Guarda los cambios
# rubocop:disable Metrics/CyclomaticComplexity # rubocop:disable Metrics/CyclomaticComplexity
def save(validation = true) def save(validate: true)
return false if validation && !valid? return false if validate && !valid?
# Salir si tenemos que cambiar el nombre del archivo y no pudimos # Salir si tenemos que cambiar el nombre del archivo y no pudimos
return false if !new? && path_changed? && !update_path! return false if !new? && path_changed? && !update_path!
return false unless save_attributes! return false unless save_attributes!
@ -176,7 +192,7 @@ class Post < OpenStruct
return unless written? return unless written?
document.path = path.absolute document.path = path.absolute
document.read document.read!
end end
def new? def new?
@ -197,12 +213,6 @@ class Post < OpenStruct
# Detecta si el artículo es válido para guardar # Detecta si el artículo es válido para guardar
def valid? def valid?
validate
errors.blank?
end
# Requisitos para que el post sea válido
def validate
self.errors = {} self.errors = {}
layout.metadata.keys.map(&:to_sym).each do |metadata| layout.metadata.keys.map(&:to_sym).each do |metadata|
@ -210,8 +220,9 @@ class Post < OpenStruct
errors[metadata] = template.errors unless template.valid? errors[metadata] = template.errors unless template.valid?
end end
errors.blank?
end end
alias validate! validate
# Guarda los cambios en el archivo destino # Guarda los cambios en el archivo destino
def write def write
@ -239,18 +250,35 @@ class Post < OpenStruct
end end
alias update update_attributes alias update update_attributes
# El Document guarda un Array de los ids de Usuarie. Si está vacío,
# no hacemos una consulta vacía. Si no, traemos todes les Usuaries
# por su id y convertimos a Array para poder agregar o quitar luego
# sin pasar por ActiveRecord.
def usuaries def usuaries
@usuaries ||= Usuarie.where(id: document_usuaries).to_a @usuaries ||= if (d = document_usuaries).empty?
[]
else
Usuarie.where(id: d).to_a
end
end end
private private
# Levanta un error si al construir el artículo no pasamos un atributo.
def default_attributes_missing(**args)
DEFAULT_ATTRIBUTES.each do |attr|
i18n = I18n.t("exceptions.post.#{attr}_missing")
raise ArgumentError, i18n unless args[attr].present?
end
end
def document_usuaries def document_usuaries
document.data.fetch('usuaries', []) document.data.fetch('usuaries', [])
end end
def new_attribute_was(method) def new_attribute_was(method)
attr_was = (attribute_name(method).to_s + '_was').to_sym attr_was = "#{method}_was".to_sym
return attr_was if singleton_class.method_defined? attr_was return attr_was if singleton_class.method_defined? attr_was
define_singleton_method(attr_was) do define_singleton_method(attr_was) do
@ -265,7 +293,7 @@ class Post < OpenStruct
# Pregunta si el atributo cambió # Pregunta si el atributo cambió
def new_attribute_changed(method) def new_attribute_changed(method)
attr_changed = (attribute_name(method).to_s + '_changed?').to_sym attr_changed = "#{method}_changed?".to_sym
return attr_changed if singleton_class.method_defined? attr_changed return attr_changed if singleton_class.method_defined? attr_changed
@ -314,6 +342,13 @@ class Post < OpenStruct
required: true) required: true)
end end
def load_uuid!
self.uuid = MetadataUuid.new(document: document, site: site,
layout: layout, name: :uuid,
type: :uuid, post: self,
required: true)
end
# Ejecuta la acción de guardado en cada atributo # Ejecuta la acción de guardado en cada atributo
def save_attributes! def save_attributes!
attributes.map do |attr| attributes.map do |attr|

View file

@ -31,15 +31,18 @@ class PostRelation < Array
post post
end end
alias sort_by_generic sort_by
alias sort_by_generic! sort_by! alias sort_by_generic! sort_by!
# Permite ordenar los artículos por sus atributos # Permite ordenar los artículos por sus atributos
# #
# XXX: Prestar atención cuando estamos mezclando artículos con # XXX: Prestar atención cuando estamos mezclando artículos con
# diferentes tipos de atributos. # diferentes tipos de atributos.
def sort_by!(*attrs) def sort_by(*attrs)
sort_by_generic! do |post| sort_by_generic do |post|
attrs.map do |attr| attrs.map do |attr|
# TODO: detectar el tipo de atributo faltante y obtener el valor
# por defecto para hacer la comparación
return 0 unless post.attributes.include? attr return 0 unless post.attributes.include? attr
post.public_send(attr).value post.public_send(attr).value
@ -47,12 +50,20 @@ class PostRelation < Array
end end
end end
def sort_by!(*attrs)
replace sort_by(*attrs)
end
alias find_generic find alias find_generic find
# Encontra un post por su id convertido a SHA1 # Encontrar un post por su UUID
def find(id, sha1: false) def find(id, uuid: false)
find_generic do |p| find_generic do |p|
p.sha1 == (sha1 ? id : Digest::SHA1.hexdigest(id)) if uuid
p.uuid.value == id
else
p.id == id
end
end end
end end
@ -65,8 +76,10 @@ class PostRelation < Array
end end
# Intenta guardar todos y devuelve true si pudo # Intenta guardar todos y devuelve true si pudo
def save_all def save_all(validate: true)
map(&:save).all? map do |post|
post.save(validate: validate)
end.all?
end end
private private

View file

@ -9,7 +9,10 @@ class Site < ApplicationRecord
# @see app/services/site_service.rb # @see app/services/site_service.rb
DEPLOYS = %i[local www zip].freeze DEPLOYS = %i[local www zip].freeze
validates :name, uniqueness: true, hostname: true validates :name, uniqueness: true, hostname: {
allow_root_label: true
}
validates :design_id, presence: true validates :design_id, presence: true
validate :deploy_local_presence validate :deploy_local_presence
validates_inclusion_of :status, in: %w[waiting enqueued building] validates_inclusion_of :status, in: %w[waiting enqueued building]
@ -173,14 +176,10 @@ class Site < ApplicationRecord
@posts[lang] = PostRelation.new site: self @posts[lang] = PostRelation.new site: self
# Jekyll lee los documentos en orden cronológico pero los invierte
# durante la renderización. Usamos el orden cronológico inverso por
# defecto para mostrar los artículos más nuevos primero.
docs = collections[lang.to_s].try(:docs).try(:sort) { |a, b| b <=> a }
# No fallar si no existe colección para este idioma # No fallar si no existe colección para este idioma
# XXX: queremos fallar silenciosamente? # XXX: queremos fallar silenciosamente?
(docs || []).each do |doc| (collections[lang.to_s].try(:docs) || []).each do |doc|
layout = layouts[doc.data['layout'].to_sym] layout = layouts[Post.find_layout(doc)]
@posts[lang].build(document: doc, layout: layout, lang: lang) @posts[lang].build(document: doc, layout: layout, lang: lang)
end end

View file

@ -79,7 +79,7 @@ class Site
end end
# Guardamos los cambios # Guardamos los cambios
unless doc.save(false) unless doc.save(validate: false)
log.write "#{doc.path.relative} no se pudo guardar\n" log.write "#{doc.path.relative} no se pudo guardar\n"
end end

View file

@ -8,8 +8,8 @@ PostService = Struct.new(:site, :usuarie, :post, :params, keyword_init: true) do
# #
# @return Post # @return Post
def create def create
# TODO: Implementar layout self.post = site.posts(lang: params[:post][:lang] || I18n.locale)
self.post = site.posts(lang: params[:post][:lang] || I18n.locale).build .build(layout: params[:post][:layout])
post.usuaries << usuarie post.usuaries << usuarie
params[:post][:draft] = true if site.invitade? usuarie params[:post][:draft] = true if site.invitade? usuarie
@ -40,25 +40,31 @@ PostService = Struct.new(:site, :usuarie, :post, :params, keyword_init: true) do
end end
# Reordena todos los posts que soporten orden de acuerdo a un hash de # Reordena todos los posts que soporten orden de acuerdo a un hash de
# ids y nuevas posiciones. La posición actual la da la posición en # uuids y nuevas posiciones. La posición actual la da la posición en
# el array. # el array.
# #
# { sha1 => 2, sha1 => 1, sha1 => 0 } # { uuid => 2, uuid => 1, uuid => 0 }
def reorder def reorder
posts = site.posts(lang: lang) posts = site.posts(lang: lang)
reorder = params.require(:post).permit(reorder: {}).try(:[], :reorder) reorder = params.require(:post).permit(reorder: {}).try(:[], :reorder)
modified = PostRelation.new(site: site)
files = reorder.keys.map do |id| files = reorder.keys.map do |uuid|
post = posts.find(id, sha1: true) post = posts.find(uuid, uuid: true)
order = reorder[uuid].to_i
next unless post
next unless post.attributes.include? :order next unless post.attributes.include? :order
next if post.order.value == order
post.usuaries << usuarie modified << post
post.order.value = reorder[id].to_i post.order.value = order
post.path.absolute post.path.absolute
end.compact end.compact
# TODO: Implementar transacciones! # TODO: Implementar transacciones!
posts.save_all && commit(action: :reorder, file: files) modified.save_all(validate: false) &&
commit(action: :reorder, file: files)
end end
private private

View file

@ -0,0 +1,5 @@
= t('.greeting', recipient: @email)
\
= t('.instruction')
\
= confirmation_url(@resource, confirmation_token: @token)

View file

@ -0,0 +1,6 @@
= t('.greeting', recipient: @email)
\
- if @resource.try(:unconfirmed_email?)
= t('.message', email: @resource.unconfirmed_email)
- else
= t('.message', email: @resource.email)

View file

@ -0,0 +1,3 @@
= t('.greeting', recipient: @resource.email)
\
= t('.message')

View file

@ -0,0 +1,9 @@
= t('.greeting', recipient: @resource.email)
\
= t('.instruction')
\
= edit_password_url(@resource, reset_password_token: @token)
\
= t('.instruction_2')
\
= t('.instruction_3')

View file

@ -0,0 +1,7 @@
= t('.greeting', recipient: @resource.email)
\
= t('.message')
\
= t('.instruction')
\
= unlock_url(@resource, unlock_token: @token)

View file

@ -5,7 +5,6 @@
.row.align-items-center.justify-content-center.full-height .row.align-items-center.justify-content-center.full-height
.col-md-5.align-self-center .col-md-5.align-self-center
.sr-only
%h2= t('.sign_up') %h2= t('.sign_up')
%p= t('.help') %p= t('.help')

View file

@ -5,7 +5,8 @@
%br/ %br/
- if devise_mapping.registerable? && controller_name != 'registrations' - if devise_mapping.registerable? && controller_name != 'registrations'
= link_to t('.sign_up'), new_registration_path(resource_name) = link_to t('.sign_up'), new_registration_path(resource_name),
class: 'btn btn-lg btn-block btn-success'
%br/ %br/
- if devise_mapping.recoverable? - if devise_mapping.recoverable?

View file

@ -1,6 +1,6 @@
%nav.navbar %nav.navbar
%a.navbar-brand.d-none.d-sm-block{ href: '/' } %a.navbar-brand.d-none.d-sm-block{ href: '/' }
= inline_svg 'sutty.svg', class: 'black', aria: true, = inline_svg_tag 'sutty.svg', class: 'black', aria: true,
title: t('svg.sutty.title'), desc: t('svg.sutty.desc') title: t('svg.sutty.title'), desc: t('svg.sutty.desc')
- if crumbs - if crumbs
@ -21,9 +21,6 @@
- if current_usuarie - if current_usuarie
%ul.navbar-nav %ul.navbar-nav
%li.nav-item
= link_to t('.mutual_aid'), mutual_aid_url(local_channel),
class: 'btn'
%li.nav-item %li.nav-item
= link_to t('.logout'), destroy_usuarie_session_path, = link_to t('.logout'), destroy_usuarie_session_path,
method: :delete, role: 'button', class: 'btn' method: :delete, role: 'button', class: 'btn'

View file

@ -1,3 +1,4 @@
= yield = yield
\
= '-- '
= t('.signature') = t('.signature')

View file

@ -19,6 +19,8 @@
-# Botones de guardado -# Botones de guardado
= render 'posts/submit', site: site, post: post = render 'posts/submit', site: site, post: post
= hidden_field_tag 'post[layout]', params[:layout] || 'post'
-# Dibuja cada atributo -# Dibuja cada atributo
- post.attributes.each do |attribute| - post.attributes.each do |attribute|
- metadata = post.send(attribute) - metadata = post.send(attribute)

View file

@ -0,0 +1,3 @@
%tr{ id: attribute }
%th= post_label_t(attribute, post: post)
%td{ style: "background-color: #{metadata.value}" } = metadata.value

View file

@ -1,3 +1,3 @@
%tr{ id: attribute } %tr{ id: attribute }
%th= post_label_t(attribute, post: post) %th= post_label_t(attribute, post: post)
%td= sanitize_markdown metadata.value, tags: tags %td= metadata.value

View file

@ -0,0 +1,4 @@
%tr{ id: attribute }
%th= post_label_t(attribute, post: post)
%td
%a{ href: "mailto:#{metadata.value}" }= metadata.value

View file

@ -2,6 +2,5 @@
%th= post_label_t(attribute, :path, post: post) %th= post_label_t(attribute, :path, post: post)
%td %td
- if metadata.value['path'].present? - if metadata.value['path'].present?
%figure = link_to t('.download'), url_for(metadata.static_file)
= link_to url_for(metadata.static_file) %p= metadata.value['description']
%figcaption= metadata.value['description']

View file

@ -0,0 +1,9 @@
- lat = metadata.value['lat']
- lng = metadata.value['lng']
%tr{ id: attribute }
%th= post_label_t(attribute, post: post)
%td
= link_to t('posts.attributes.geo.uri'), "geo:#{lat},#{lng}"
%br/
= link_to t('posts.attributes.geo.osm'),
"https://www.openstreetmap.org/?mlat=#{lat}&mlon=#{lng}#map=9/#{lat}/#{lng}"

View file

@ -0,0 +1,11 @@
%tr{ id: attribute }
%th= post_label_t(attribute, post: post)
%td
%ul
- metadata.value.each do |v|
- p = site.posts(lang: post.lang.value).find(v, uuid: true)
-#
XXX: Ignorar todos los posts no encontrados (ej: fueron
borrados o el uuid cambió)
- next unless p
%li= link_to p.title.value, site_post_path(site, p.id)

View file

@ -0,0 +1,3 @@
%tr{ id: attribute }
%th= post_label_t(attribute, post: post)
%td= metadata.value

View file

@ -0,0 +1,6 @@
.form-group
= label_tag "post_#{attribute}", post_label_t(attribute, post: post)
= color_field 'post', attribute, value: metadata.value,
**field_options(attribute, metadata)
= render 'posts/attribute_feedback',
post: post, attribute: attribute, metadata: metadata

View file

@ -1,6 +1,6 @@
.form-group .form-group
= label_tag "post_#{attribute}", post_label_t(attribute, post: post) = label_tag "post_#{attribute}", post_label_t(attribute, post: post)
= date_field 'post', attribute, value: metadata.value.strftime('%F'), = date_field 'post', attribute, value: metadata.value.to_date.strftime('%F'),
**field_options(attribute, metadata) **field_options(attribute, metadata)
= render 'posts/attribute_feedback', = render 'posts/attribute_feedback',
post: post, attribute: attribute, metadata: metadata post: post, attribute: attribute, metadata: metadata

View file

@ -0,0 +1,6 @@
.form-group
= label_tag "post_#{attribute}", post_label_t(attribute, post: post)
= email_field 'post', attribute, value: metadata.value,
**field_options(attribute, metadata)
= render 'posts/attribute_feedback',
post: post, attribute: attribute, metadata: metadata

View file

@ -0,0 +1,19 @@
.row
.col
.form-group
= label_tag "post_#{attribute}_lat",
post_label_t(attribute, :lat, post: post)
= text_field(*field_name_for('post', attribute, :lat),
value: metadata.value['lat'],
**field_options(attribute, metadata))
= render 'posts/attribute_feedback',
post: post, attribute: [attribute, :lat], metadata: metadata
.col
.form-group
= label_tag "post_#{attribute}_lng",
post_label_t(attribute, :lng, post: post)
= text_field(*field_name_for('post', attribute, :lng),
value: metadata.value['lng'],
**field_options(attribute, metadata))
= render 'posts/attribute_feedback',
post: post, attribute: [attribute, :lat], metadata: metadata

View file

@ -0,0 +1,19 @@
.form-group
= label_tag "post_#{attribute}", post_label_t(attribute, post: post)
.mapable{ data: { values: metadata.value.to_json,
'default-values': metadata.values.to_json,
name: "post[#{attribute}][]", list: id_for_datalist(attribute),
remove: 'false', legend: post_label_t(attribute, post: post),
described: id_for_help(attribute) } }
= text_field(*field_name_for('post', attribute, '[]'),
value: metadata.value.join(', '),
**field_options(attribute, metadata))
= render 'posts/attribute_feedback',
post: post, attribute: attribute, metadata: metadata
%datalist{ id: id_for_datalist(attribute) }
- metadata.values.keys.each do |value|
%option{ value: value }

View file

@ -0,0 +1 @@
-# nada

View file

@ -25,11 +25,11 @@
%section.col %section.col
= render 'layouts/flash' = render 'layouts/flash'
- if @posts.present? - if @posts.empty?
.row %h2= t('posts.none')
.col - else
= form_tag site_posts_reorder_path, method: :post do = form_tag site_posts_reorder_path, method: :post do
= submit_tag t('posts.reorder'), class: 'btn submit-reorder d-none' = submit_tag t('posts.reorder'), class: 'btn submit-reorder'
-# TODO: Permitir cambiar el idioma -# TODO: Permitir cambiar el idioma
%table.table.table-condensed.table-draggable %table.table.table-condensed.table-draggable
%tbody %tbody
@ -42,13 +42,14 @@
- next unless post.categories.value.include?(@category) - next unless post.categories.value.include?(@category)
- if @layout - if @layout
- next unless post.layout.name == @layout - next unless post.layout.name == @layout
- next unless policy(post).show? - next unless @usuarie || policy(post).show?
%tr %tr
%td %td
.handle .handle
= image_tag 'arrows-alt-v.svg' = image_tag 'arrows-alt-v.svg'
= hidden_field 'post[reorder]', post.sha1, -# Orden más alto es mayor prioridad
value: i, class: 'reorder' = hidden_field 'post[reorder]', post.uuid.value,
value: @posts.length - i, class: 'reorder'
%td %td
%small %small
= link_to post.layout.name.to_s.humanize, = link_to post.layout.name.to_s.humanize,
@ -72,15 +73,13 @@
%br/ %br/
= post.try(:order).try(:value) = post.try(:order).try(:value)
%td %td
- if policy(post).edit? - if @usuarie || policy(post).edit?
= link_to t('posts.edit'), = link_to t('posts.edit'),
edit_site_post_path(@site, post.id), edit_site_post_path(@site, post.id),
class: 'btn' class: 'btn'
- if policy(post).destroy? - if @usuarie || policy(post).destroy?
= link_to t('posts.destroy'), = link_to t('posts.destroy'),
site_post_path(@site, post.id), site_post_path(@site, post.id),
class: 'btn', class: 'btn',
method: :delete, method: :delete,
data: { confirm: t('posts.confirm_destroy') } data: { confirm: t('posts.confirm_destroy') }
- else
%h2= t('posts.none')

View file

@ -26,6 +26,7 @@
= render("posts/attribute_ro/#{metadata.type}", = render("posts/attribute_ro/#{metadata.type}",
post: @post, attribute: attr, post: @post, attribute: attr,
metadata: metadata, metadata: metadata,
site: @site,
tags: all_html_tags) tags: all_html_tags)
-# Mostrar todo lo que no va en el front_matter (el contenido) -# Mostrar todo lo que no va en el front_matter (el contenido)
@ -33,4 +34,4 @@
- next if @post.send(attr).front_matter? - next if @post.send(attr).front_matter?
%section{ id: attr } %section{ id: attr }
= sanitize_markdown @post.send(attr).value, tags: all_html_tags = raw @post.send(attr).value

View file

@ -7,7 +7,7 @@
link: nil link: nil
- else - else
= form_tag site_enqueue_path(site), = form_tag site_enqueue_path(site),
method: :post, class: 'form-inline' do method: :post, class: 'form-inline inline' do
= button_tag type: 'submit', = button_tag type: 'submit',
class: 'btn no-border-radius', class: 'btn no-border-radius',
title: t('help.sites.enqueue'), title: t('help.sites.enqueue'),

View file

@ -10,7 +10,7 @@
= f.text_field :name, = f.text_field :name,
class: form_control(site, :name), class: form_control(site, :name),
required: true, required: true,
pattern: '^([a-z0-9][a-z0-9\-]*)?[a-z0-9]$', pattern: '^([a-z0-9][a-z0-9\-]*)?[a-z0-9\.]$',
minlength: 1, minlength: 1,
maxlength: 63 maxlength: 63
- if invalid? site, :name - if invalid? site, :name
@ -86,6 +86,7 @@
%hr/ %hr/
- if site.persisted?
.form-group .form-group
%h2= t('.deploys.title') %h2= t('.deploys.title')
%p.lead= t('.help.deploys') %p.lead= t('.help.deploys')
@ -94,6 +95,11 @@
= render "deploys/#{deploy.object.type.underscore}", = render "deploys/#{deploy.object.type.underscore}",
deploy: deploy, site: site deploy: deploy, site: site
%hr/ %hr/
- else
= f.fields_for :deploys do |deploy|
- next unless deploy.object.is_a? DeployLocal
= deploy.hidden_field :type
.form-group .form-group
= f.submit submit, class: 'btn btn-lg btn-block' = f.submit submit, class: 'btn btn-lg btn-block'

View file

@ -1,8 +1,8 @@
= render 'layouts/breadcrumb', = render 'layouts/breadcrumb',
crumbs: [link_to(t('sites.index.title'), sites_path), crumbs: [link_to(t('sites.index.title'), sites_path),
t('.title', site: @site.name)] t('.title', site: @site.name)]
.row .row.justify-content-center
.col .col-md-8
%h1= t('.title', site: @site.name) %h1= t('.title', site: @site.name)
= render 'form', site: @site, submit: t('.submit') = render 'form', site: @site, submit: t('.submit')

View file

@ -9,6 +9,9 @@
class: 'btn' class: 'btn'
%section.col %section.col
- if @sites.empty?
:markdown
#{t('.welcome')}
%table.table.table-condensed %table.table.table-condensed
%tbody %tbody
- @sites.each do |site| - @sites.each do |site|

View file

@ -1,8 +1,9 @@
= render 'layouts/breadcrumb', = render 'layouts/breadcrumb',
crumbs: [link_to(t('sites.index.title'), sites_path), t('.title')] crumbs: [link_to(t('sites.index.title'), sites_path), t('.title')]
.row .row.justify-content-center
.col .col-md-8
%h1= t('.title') %h1= t('.title')
%p.lead= t('.help')
= render 'form', site: @site, submit: t('.submit') = render 'form', site: @site, submit: t('.submit')

77
config/blazer.yml Normal file
View file

@ -0,0 +1,77 @@
# see https://github.com/ankane/blazer for more info
data_sources:
main:
url: <%= ENV["BLAZER_DATABASE_URL"] %>
# statement timeout, in seconds
# none by default
# timeout: 15
# caching settings
# can greatly improve speed
# off by default
# cache:
# mode: slow # or all
# expires_in: 60 # min
# slow_threshold: 15 # sec, only used in slow mode
# wrap queries in a transaction for safety
# not necessary if you use a read-only user
# true by default
# use_transaction: false
smart_variables:
site_id: 'select id, name from sites order by name asc'
# zone_id: "SELECT id, name FROM zones ORDER BY name ASC"
# period: ["day", "week", "month"]
# status: {0: "Active", 1: "Archived"}
linked_columns:
# user_id: "/admin/users/{value}"
smart_columns:
site_id: 'select id, name from sites where id in {value}'
# user_id: "SELECT id, name FROM users WHERE id IN {value}"
# create audits
audit: true
# change the time zone
# time_zone: "Pacific Time (US & Canada)"
# class name of the user model
user_class: Usuarie
# method name for the current user
user_method: current_usuarie
# method name for the display name
user_name: email
# custom before_action to use for auth
# before_action_method: require_admin
# email to send checks from
from_email: blazer@<%= ENV.fetch('SUTTY', 'sutty.nl') %>
# webhook for Slack
# slack_webhook_url: <%= ENV["BLAZER_SLACK_WEBHOOK_URL"] %>
check_schedules:
- "1 day"
- "1 hour"
- "5 minutes"
override_csp: true
# enable anomaly detection
# note: with trend, time series are sent to https://trendapi.org
# anomaly_checks: trend / r
# enable forecasting
# note: with trend, time series are sent to https://trendapi.org
# forecasting: trend
# enable map
# mapbox_access_token: <%= ENV["MAPBOX_ACCESS_TOKEN"] %>

View file

@ -6,31 +6,35 @@
# For further information see the following documentation # For further information see the following documentation
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
# Rails.application.config.content_security_policy do |policy| Rails.application.config.content_security_policy do |policy|
# policy.default_src :self, :https policy.default_src :self
# policy.font_src :self, :https, :data # XXX: Varios scripts generan estilos en línea
# policy.img_src :self, :https, :data policy.style_src :self, :unsafe_inline
# policy.object_src :none # Repetimos la default para poder saber cuál es la política en falta
# policy.script_src :self, :https policy.script_src :self
# policy.style_src :self, :https policy.font_src :self
# # If you are using webpack-dev-server then specify # TODO: Permitimos cargar imágenes remotas?
# # webpack-dev-server host # XXX: Los íconos de Trix se cargan vía data:
# policy.connect_src :self, :https, "http://localhost:3035", policy.img_src :self, :data
# "ws://localhost:3035" if Rails.env.development? # Ya no usamos applets!
policy.object_src :none
if Rails.env.development?
policy.connect_src :self,
'http://localhost:3035',
'ws://localhost:3035'
end
# # Specify URI for violation reports # Specify URI for violation reports
# # policy.report_uri "/csp-violation-report-endpoint" policy.report_uri "https://api.#{ENV.fetch('SUTTY_WITH_PORT', 'sutty.nl')}/v1/csp_reports.json"
# end end
# If you are using UJS then enable automatic nonce generation # If you are using UJS then enable automatic nonce generation
# Rails.application.config.content_security_policy_nonce_generator = # Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) }
# -> request { SecureRandom.base64(16) }
# Set the nonce only to specific directives # Set the nonce only to specific directives
# Rails.application.config.content_security_policy_nonce_directives = # Rails.application.config.content_security_policy_nonce_directives = %w(script-src)
# %w(script-src)
# Report CSP violations to a specified URI # Report CSP violations to a specified URI
# For further information see the following documentation: # For further information see the following documentation:
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only
# Rails.application.config.content_security_policy_report_only = true Rails.application.config.content_security_policy_report_only = false

View file

@ -1,6 +1,36 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'jekyll/document'
String.include CoreExtensions::String::StripTags String.include CoreExtensions::String::StripTags
Jekyll::Document.include CoreExtensions::Jekyll::Document::Path Jekyll::Document.include CoreExtensions::Jekyll::Document::Path
# Lazy Loading de Jekyll, deshabilitando la instanciación de elementos
# que no necesitamos
#
# TODO: Aplicar monkey patches en otro lado...
module Jekyll
Site.class_eval do
def setup
ensure_not_in_dest
end
end
Reader.class_eval do
def retrieve_posts(_); end
def retrieve_dirs(_, _, _); end
def retrieve_pages(_, _); end
def retrieve_static_files(_, _); end
end
ThemeAssetsReader.class_eval do
def read; end
end
# Prevenir la lectura del documento
Document.class_eval do
alias_method :read!, :read
def read; end
end
end

View file

@ -24,7 +24,7 @@ Devise.setup do |config|
# config.mailer = 'Devise::Mailer' # config.mailer = 'Devise::Mailer'
# Configure the parent class responsible to send e-mails. # Configure the parent class responsible to send e-mails.
# config.parent_mailer = 'ActionMailer::Base' config.parent_mailer = 'ApplicationMailer'
# ==> ORM configuration # ==> ORM configuration
# Load and configure the ORM. Supports :active_record (default) and # Load and configure the ORM. Supports :active_record (default) and
@ -197,7 +197,7 @@ Devise.setup do |config|
# website without confirming their account. # website without confirming their account.
# Default is 0.days, meaning the user cannot access the website # Default is 0.days, meaning the user cannot access the website
# without confirming their account. # without confirming their account.
# config.allow_unconfirmed_access_for = 2.days config.allow_unconfirmed_access_for = 2.days
# A period that the user is allowed to confirm their account before # A period that the user is allowed to confirm their account before
# their token becomes invalid. For example, if set to 3.days, the user # their token becomes invalid. For example, if set to 3.days, the user

View file

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
Rails.application.configure do Rails.application.configure do
next if ENV['RAILS_ENV'] == 'test' next unless ENV['RAILS_ENV'] == 'development'
domain = ENV.fetch('SUTTY', 'sutty.nl') domain = ENV.fetch('SUTTY', 'sutty.nl')

View file

@ -1,6 +1,3 @@
# frozen_string_literal: true # frozen_string_literal: true
# Be sure to restart your server when you modify this file. Mime::Type.register 'application/csp-report', :json
# Add new mime types for use in respond_to blocks:
# Mime::Type.register "text/richtext", :rtf

View file

@ -44,7 +44,7 @@ en:
locked: Your account is locked. locked: Your account is locked.
not_found_in_database: Invalid %{authentication_keys} or password. not_found_in_database: Invalid %{authentication_keys} or password.
timeout: Your session expired. Please sign in again to continue. timeout: Your session expired. Please sign in again to continue.
unauthenticated: You need to sign in or sign up before continuing. unauthenticated: Hi! You need to sign in or sign up before managing your sites.
unconfirmed: You have to confirm your email address before continuing. unconfirmed: You have to confirm your email address before continuing.
mailer: mailer:
confirmation_instructions: confirmation_instructions:
@ -103,6 +103,7 @@ en:
we_need_your_current_password_to_confirm_your_changes: We need your current password to confirm your changes we_need_your_current_password_to_confirm_your_changes: We need your current password to confirm your changes
new: new:
sign_up: Sign up sign_up: Sign up
help: We only ask for an e-mail address and a password. The password is safely stored, no one else besides you knows it! You'll also receive an e-mail to confirm your account.
signed_up: Welcome! You have signed up successfully. signed_up: Welcome! You have signed up successfully.
signed_up_but_inactive: You have signed up successfully. However, we could not sign you in because your account is not yet activated. signed_up_but_inactive: You have signed up successfully. However, we could not sign you in because your account is not yet activated.
signed_up_but_locked: You have signed up successfully. However, we could not sign you in because your account is locked. signed_up_but_locked: You have signed up successfully. However, we could not sign you in because your account is locked.

View file

@ -16,7 +16,7 @@ es:
last_sign_in_ip: IP del último inicio last_sign_in_ip: IP del último inicio
locked_at: Fecha de bloqueo locked_at: Fecha de bloqueo
password: Contraseña password: Contraseña
password_confirmation: Confirmación de la contraseña password_confirmation: Confirma tu contraseña
remember_created_at: Fecha de 'Recordarme' remember_created_at: Fecha de 'Recordarme'
remember_me: Recordarme remember_me: Recordarme
reset_password_sent_at: Fecha de envío de código para contraseña reset_password_sent_at: Fecha de envío de código para contraseña
@ -44,7 +44,7 @@ es:
locked: Tu cuenta está bloqueada. locked: Tu cuenta está bloqueada.
not_found_in_database: "%{authentication_keys} o contraseña inválidos." not_found_in_database: "%{authentication_keys} o contraseña inválidos."
timeout: Tu sesión expiró. Por favor, inicia sesión nuevamente para continuar. timeout: Tu sesión expiró. Por favor, inicia sesión nuevamente para continuar.
unauthenticated: Tienes que iniciar sesión o registrarte para poder continuar. unauthenticated: "¡Hola! Tienes que iniciar sesión o registrarte para poder gestionar tus sitios."
unconfirmed: Te enviamos un correo electrónico para confirmar tu cuenta, por favor acéptalo para poder continuar. unconfirmed: Te enviamos un correo electrónico para confirmar tu cuenta, por favor acéptalo para poder continuar.
mailer: mailer:
confirmation_instructions: confirmation_instructions:
@ -80,7 +80,7 @@ es:
edit: edit:
change_my_password: Cambiar mi contraseña change_my_password: Cambiar mi contraseña
change_your_password: Cambia tu contraseña change_your_password: Cambia tu contraseña
confirm_new_password: Confirme la nueva contraseña confirm_new_password: Confirma la nueva contraseña
new_password: Nueva contraseña new_password: Nueva contraseña
new: new:
forgot_your_password: "¿Has olvidado tu contraseña?" forgot_your_password: "¿Has olvidado tu contraseña?"
@ -103,7 +103,7 @@ es:
we_need_your_current_password_to_confirm_your_changes: Necesitamos tu contraseña actual para confirmar los cambios. we_need_your_current_password_to_confirm_your_changes: Necesitamos tu contraseña actual para confirmar los cambios.
new: new:
sign_up: Registrarme por primera vez sign_up: Registrarme por primera vez
email: O simplemente continuar con tu dirección de correo y contraseña 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: 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. 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.
signed_up_but_locked: Tu cuenta ha sido creada correctamente. Sin embargo, no hemos podido iniciar la sesión porque que tu cuenta está bloqueada. signed_up_but_locked: Tu cuenta ha sido creada correctamente. Sin embargo, no hemos podido iniciar la sesión porque que tu cuenta está bloqueada.

View file

@ -148,7 +148,6 @@ en:
anexo: 'Appendix' anexo: 'Appendix'
simple: 'Simple' simple: 'Simple'
sites: sites:
static_file_migration: 'File migration'
index: 'This is the list of sites you can edit.' index: 'This is the list of sites you can edit.'
edit_translations: "You can edit texts from your site other than edit_translations: "You can edit texts from your site other than
posts', and you can also translate them to other languages." posts', and you can also translate them to other languages."
@ -247,11 +246,27 @@ en:
average: 'Average building time' average: 'Average building time'
maximum: 'Maximum building time' maximum: 'Maximum building time'
sites: sites:
static_file_migration: 'File migration'
index: index:
title: 'Sites' title: 'Sites'
pull: 'Upgrade' pull: 'Upgrade'
help: 'This is the list of sites you can edit' help: 'This is the list of sites you can edit'
visit: 'Visitar el sitio' visit: 'Visit the site'
welcome: |
# Welcome!
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
notification.
[Create my first site](/sites/new)
repository: repository:
config: 'Changes in config' config: 'Changes in config'
actions: 'Actions' actions: 'Actions'
@ -332,6 +347,9 @@ en:
en: 'English' en: 'English'
ar: 'Arabic' ar: 'Arabic'
posts: posts:
attribute_ro:
file:
download: Download file
show: show:
front_matter: Post metadata front_matter: Post metadata
submit: submit:
@ -348,6 +366,11 @@ en:
required: required:
label: ' (required)' label: ' (required)'
feedback: 'This field cannot be empty!' feedback: 'This field cannot be empty!'
uuid:
label: 'Unique identifier'
geo:
uri: 'Open in app'
osm: 'Open in web map'
reorder: 'Reorder posts' reorder: 'Reorder posts'
sort: sort:
by: 'Sort by' by: 'Sort by'

View file

@ -71,7 +71,7 @@ es:
usuarie: usuarie:
email: 'Correo electrónico' email: 'Correo electrónico'
password: 'Contraseña' password: 'Contraseña'
password_confirmation: 'Confirmación de contraseña' password_confirmation: 'Confirma tu contraseña'
current_password: 'Contraseña actual' current_password: 'Contraseña actual'
lang: Idioma principal lang: Idioma principal
remember_me: Recordarme remember_me: Recordarme
@ -149,7 +149,6 @@ es:
anexo: 'Anexo' anexo: 'Anexo'
simple: 'Simple' simple: 'Simple'
sites: sites:
static_file_migration: 'Migración de archivos'
index: 'Este es el listado de sitios que puedes editar.' index: 'Este es el listado de sitios que puedes editar.'
edit_translations: 'Puedes editar los textos que salen en tu sitio edit_translations: 'Puedes editar los textos que salen en tu sitio
que no corresponden a artículos aquí, además de traducirlos a que no corresponden a artículos aquí, además de traducirlos a
@ -252,11 +251,24 @@ es:
average: 'Tiempo promedio de generación' average: 'Tiempo promedio de generación'
maximum: 'Tiempo máximo de generación' maximum: 'Tiempo máximo de generación'
sites: sites:
static_file_migration: 'Migración de archivos'
index: index:
title: 'Sitios' title: 'Sitios'
pull: 'Actualizar' pull: 'Actualizar'
help: 'Este es el listado de sitios que puedes editar' help: 'Este es el listado de sitios que puedes editar'
visit: 'Visitar el sitio' visit: 'Visitar el sitio'
welcome: |
# ¡Bienvenide!
Todavía no tienes ningún sitio. Puedes crear todos los sitios
que quieras usando el botón **Crear sitio**.
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.
[Crear mi primer sitio](/sites/new)
repository: repository:
config: 'Cambios en la configuración' config: 'Cambios en la configuración'
actions: 'Acciones' actions: 'Acciones'
@ -272,6 +284,7 @@ es:
new: new:
title: 'Crear un sitio' title: 'Crear un sitio'
submit: 'Crear sitio' submit: 'Crear sitio'
help: 'Podrás editar estas opciones más adelante en la configuración del sitio.'
edit: edit:
title: 'Editar %{site}' title: 'Editar %{site}'
submit: 'Guardar cambios' submit: 'Guardar cambios'
@ -342,6 +355,9 @@ es:
en: 'inglés' en: 'inglés'
ar: 'árabe' ar: 'árabe'
posts: posts:
attribute_ro:
file:
download: Descargar archivo
show: show:
front_matter: Metadatos del artículo front_matter: Metadatos del artículo
submit: submit:
@ -358,6 +374,11 @@ es:
required: required:
label: ' (requerido)' label: ' (requerido)'
feedback: '¡Este campo no puede estar vacío!' feedback: '¡Este campo no puede estar vacío!'
uuid:
label: 'Identificador único'
geo:
uri: 'Abrir en aplicación'
osm: 'Abrir en mapa web'
reorder: 'Reordenar artículos' reorder: 'Reordenar artículos'
sort: sort:
by: 'Ordenar por' by: 'Ordenar por'

View file

@ -38,3 +38,22 @@ preload_app!
plugin :tmp_restart plugin :tmp_restart
pidfile 'tmp/puma.pid' pidfile 'tmp/puma.pid'
# If you are preloading your application and using Active Record, it's
# recommended that you close any connections to the database before workers
# are forked to prevent connection leakage.
#
before_fork do
ActiveRecord::Base.connection_pool.disconnect! if defined?(ActiveRecord)
end
# The code in the `on_worker_boot` will be called if you are using
# clustered mode by specifying a number of `workers`. After each worker
# process is booted, this block will be run. If you are using the `preload_app!`
# option, you will want to use this block to reconnect to any threads
# or connections that may have been created at application boot, as Ruby
# cannot share connections between processes.
#
on_worker_boot do
ActiveRecord::Base.establish_connection if defined?(ActiveRecord)
end

View file

@ -2,6 +2,7 @@
Rails.application.routes.draw do Rails.application.routes.draw do
devise_for :usuaries devise_for :usuaries
mount Blazer::Engine, at: 'blazer'
root 'application#index' root 'application#index'
@ -18,6 +19,7 @@ Rails.application.routes.draw do
constraints subdomain: 'api' do constraints subdomain: 'api' do
scope module: 'api' do scope module: 'api' do
namespace :v1 do namespace :v1 do
resources :csp_reports, only: %i[create]
get 'sites/allowed', to: 'sites#allowed' get 'sites/allowed', to: 'sites#allowed'
resources :sites, only: %i[index] resources :sites, only: %i[index]
end end

View file

@ -57,10 +57,10 @@ development:
# Reference: https://webpack.js.org/configuration/dev-server/ # Reference: https://webpack.js.org/configuration/dev-server/
dev_server: dev_server:
https: false https: true
host: localhost host: <%= ENV.fetch('SUTTY', 'localhost') %>
port: 3035 port: 3035
public: localhost:3035 public: <%= ENV.fetch('SUTTY', 'localhost') %>:3035
hmr: false hmr: false
# Inline should be set to true if using HMR # Inline should be set to true if using HMR
inline: true inline: true

View file

@ -0,0 +1,65 @@
# frozen_string_literal: true
class CreateAccessLog < ActiveRecord::Migration[6.0]
def change
create_table :access_logs, id: :uuid do |t|
t.string :host, index: true
t.float :msec
t.string :server_protocol
t.string :request_method
t.string :request_completion
t.string :uri, index: true
t.string :query_string
t.integer :status, index: true
t.string :sent_http_content_type
t.string :sent_http_content_encoding
t.string :sent_http_etag
t.datetime :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, index: true
t.string :http_referer, index: true
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, index: true
t.string :geoip2_data_city_name, index: true
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
end
end
end

View file

@ -0,0 +1,7 @@
# frozen_string_literal: true
class ChangeLastModified < ActiveRecord::Migration[6.0]
def change
change_column :access_logs, :sent_http_last_modified, :string
end
end

View file

@ -0,0 +1,17 @@
class CreateCspReports < ActiveRecord::Migration[6.0]
def change
create_table :csp_reports, id: :uuid do |t|
t.timestamps
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
end
end
end

View file

@ -0,0 +1,7 @@
class AddSourceToCspReports < ActiveRecord::Migration[6.0]
def change
add_column :csp_reports, :column_number, :integer
add_column :csp_reports, :line_number, :integer
add_column :csp_reports, :source_file, :string
end
end

View file

@ -0,0 +1,51 @@
# frozen_string_literal: true
# Blazer
class InstallBlazer < ActiveRecord::Migration[6.0]
def change
return unless Rails.env.production?
create_table :blazer_queries do |t|
t.references :creator
t.string :name
t.text :description
t.text :statement
t.string :data_source
t.timestamps null: false
end
create_table :blazer_audits do |t|
t.references :user
t.references :query
t.text :statement
t.string :data_source
t.timestamp :created_at
end
create_table :blazer_dashboards do |t|
t.references :creator
t.text :name
t.timestamps null: false
end
create_table :blazer_dashboard_queries do |t|
t.references :dashboard
t.references :query
t.integer :position
t.timestamps null: false
end
create_table :blazer_checks do |t|
t.references :creator
t.references :query
t.string :state
t.string :schedule
t.text :emails
t.text :slack_channels
t.string :check_type
t.text :message
t.timestamp :last_run_at
t.timestamps null: false
end
end
end

View file

@ -12,7 +12,10 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20_190_829_180_743) do ActiveRecord::Schema.define(version: 20_200_206_151_057) 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| create_table 'action_text_rich_texts', force: :cascade do |t|
t.string 'name', null: false t.string 'name', null: false
t.text 'body' t.text 'body'
@ -55,6 +58,9 @@ ActiveRecord::Schema.define(version: 20_190_829_180_743) do
t.index ['deploy_id'], name: 'index_build_stats_on_deploy_id' t.index ['deploy_id'], name: 'index_build_stats_on_deploy_id'
end end
# Could not dump table "csp_reports" because of following StandardError
# Unknown type 'uuid' for column 'id'
create_table 'deploys', force: :cascade do |t| create_table 'deploys', force: :cascade do |t|
t.datetime 'created_at', null: false t.datetime 'created_at', null: false
t.datetime 'updated_at', null: false t.datetime 'updated_at', null: false
@ -132,6 +138,7 @@ ActiveRecord::Schema.define(version: 20_190_829_180_743) do
t.string 'status', default: 'waiting' t.string 'status', default: 'waiting'
t.text 'description' t.text 'description'
t.string 'title' t.string 'title'
t.boolean 'colaboracion_anonima', default: false
t.index ['design_id'], name: 'index_sites_on_design_id' t.index ['design_id'], name: 'index_sites_on_design_id'
t.index ['licencia_id'], name: 'index_sites_on_licencia_id' t.index ['licencia_id'], name: 'index_sites_on_licencia_id'
t.index ['name'], name: 'index_sites_on_name', unique: true t.index ['name'], name: 'index_sites_on_name', unique: true

View file

@ -4,15 +4,15 @@
gem: 'sutty-theme-none' gem: 'sutty-theme-none'
url: 'https://sutty.nl' url: 'https://sutty.nl'
disabled: true disabled: true
description_en: "Upload your own theme. [This feature is in development, help us!](https://sutty.neocities.org/en/#contact)" 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.neocities.org/es/#contacto)" 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 create a site for me'
name_es: 'Quiero que desarrollen mi sitio' name_es: 'Quiero que desarrollen mi sitio'
gem: 'sutty-theme-custom' gem: 'sutty-theme-custom'
url: 'https://sutty.nl' url: 'https://sutty.nl'
disabled: true disabled: true
description_en: "If you want us to create your site, you're welcome to [contact us!](https://sutty.neocities.org/en/#contact) :)" description_en: "If you want us to create 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.neocities.org/es/#contacto) :)" description_es: "Si querés que desarrollemos tu sitio, [escribinos](https://sutty.nl/#contacto) :)"
- name_en: 'Minima' - name_en: 'Minima'
name_es: 'Mínima' name_es: 'Mínima'
gem: 'minima' gem: 'minima'
@ -25,5 +25,5 @@
gem: 'sutty-theme-own' gem: 'sutty-theme-own'
url: 'https://jekyllthemes.org' url: 'https://jekyllthemes.org'
disabled: true disabled: true
description_en: "We're working to add more themes for you to use. [Contact us!](https://sutty.neocities.org/en/#contact)" description_en: "We're working to add 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.neocities.org/es/#contacto)" description_es: "Estamos trabajando para que puedas tener más diseños. [¡Escribinos!](https://sutty.nl/#contacto)"

View file

@ -17,3 +17,4 @@
- name: mail - name: mail
- name: email - name: email
- name: xmpp - name: xmpp
- name: radicale

View file

@ -23,3 +23,11 @@ otra vez.
Lo más controlado sería enviar exactamente el id del post con su nueva Lo más controlado sería enviar exactamente el id del post con su nueva
ubicación en el orden. Esta es la implementación anterior. ubicación en el orden. Esta es la implementación anterior.
***
El orden es descendiente (fechas más nuevas primero), pero el orden que
estuvimos usando es ascendientes (números más bajos primero). Es más
simple invertir la lógica y hacer todo el orden descendiente. Para eso
los artículos más nuevos tienen que tener el número de orden
correspondiente a la posición en el array ordenado por fecha.

28
doc/uuid.md Normal file
View file

@ -0,0 +1,28 @@
# Identificadores para los artículos
Para poder vincular artículos entre sí y para otros usos, necesitamos
identificarlos únicamente. Un identificador incremental es problemático
porque tendríamos que mantener el estado y poder responder preguntas
como ¿cuál es el último identificador que asignamos?
Para poder identificar artículos sin mantener estado, usamos
[UUIDs](https://en.wikipedia.org/wiki/Universally_unique_identifier),
que son cadenas aleatorias que se pueden asignar adhoc. Así, en lugar
de un ID numérico que va incrementando, podemos asociar cadenas al
estilo `fb4a5048-5fa1-4b85-b70e-6c502feecdb9` (generada con la
herramienta `uuidgen`).
## MetadataUUID
Cada artículo se crea con un metadato `uuid` cuyo valor por defecto es
un UUID autogenerado utilizando `SecureRandom.uuid`. Este valor no
cambia (a menos que se lo vacíe intencionalmente).
## Migración
Para todos los artículos que existen, hay que escribir una migración que
se los agregue.
Para esto hay que cargar sitio por sitio, recorrer los artículos
asignando un UUID y guardando todos los cambios como un solo commit de
git.

View file

@ -1,5 +1,6 @@
check process sutty with pidfile /srv/http/tmp/puma.pid check process sutty with pidfile /srv/http/tmp/puma.pid
start program = "/bin/sh -c 'cd /srv/http && foreman start migrate && foreman start sutty'" as uid app start program = "/bin/sh -c 'cd /srv/http && foreman start migrate && foreman start sutty'"
as uid "app" and gid "www-data"
stop program = "/bin/sh -c 'cat /srv/http/tmp/puma.pid | xargs kill'" stop program = "/bin/sh -c 'cat /srv/http/tmp/puma.pid | xargs kill'"
check program sync_assets check program sync_assets

View file

@ -5,6 +5,7 @@
"@rails/activestorage": "^6.0.0", "@rails/activestorage": "^6.0.0",
"@rails/webpacker": "^4.0.7", "@rails/webpacker": "^4.0.7",
"commonmark": "^0.29.0", "commonmark": "^0.29.0",
"input-map": "https://0xacab.org/sutty/input-map.git",
"input-tag": "https://0xacab.org/sutty/input-tag.git", "input-tag": "https://0xacab.org/sutty/input-tag.git",
"prosemirror-commands": "^1.0.8", "prosemirror-commands": "^1.0.8",
"prosemirror-gapcursor": "^1.0.4", "prosemirror-gapcursor": "^1.0.4",

Some files were not shown because too many files have changed in this diff Show more