mirror of
https://0xacab.org/sutty/sutty
synced 2024-11-29 22:16:22 +00:00
Merge branch 'crear_sitios' into rails
This commit is contained in:
commit
42bf7e173b
128 changed files with 3303 additions and 580 deletions
|
@ -1,4 +1,7 @@
|
|||
SECRET_KEY_BASE=
|
||||
RAILS_ENV=production
|
||||
IMAP_SERVER=
|
||||
DEFAULT_FROM=
|
||||
DEVISE_PEPPER=
|
||||
SKEL_SUTTY=https://0xacab.org/sutty/skel.sutty.nl
|
||||
SUTTY=sutty.nl
|
||||
REDIS_SERVER=
|
||||
REDIS_CLIENT=
|
||||
|
|
8
.gitignore
vendored
8
.gitignore
vendored
|
@ -24,6 +24,10 @@
|
|||
|
||||
/_sites/*
|
||||
/_deploy/*
|
||||
/_usuarias/*
|
||||
/_invitadxs/*
|
||||
/data/*
|
||||
|
||||
.env
|
||||
|
||||
# Ignore master key for decrypting credentials and more.
|
||||
/config/master.key
|
||||
/config/credentials.yml.enc
|
||||
|
|
|
@ -36,18 +36,17 @@ Metrics/MethodLength:
|
|||
|
||||
Metrics/BlockLength:
|
||||
Exclude:
|
||||
- 'config/environments/development.rb'
|
||||
- 'config/environments/production.rb'
|
||||
- 'config/initializers/devise.rb'
|
||||
- 'db/schema.rb'
|
||||
- 'config/routes.rb'
|
||||
|
||||
Metrics/ClassLength:
|
||||
Exclude:
|
||||
- 'app/models/site.rb'
|
||||
- 'app/controllers/posts_controller.rb'
|
||||
|
||||
Performance/TimesMap:
|
||||
Exclude:
|
||||
- 'app/models/site.rb'
|
||||
- 'app/controllers/sites_controller.rb'
|
||||
|
||||
Lint/HandleExceptions:
|
||||
Exclude:
|
||||
|
|
1
.ruby-version
Normal file
1
.ruby-version
Normal file
|
@ -0,0 +1 @@
|
|||
2.5.5
|
1
Capfile
1
Capfile
|
@ -11,7 +11,6 @@ require 'capistrano/passenger'
|
|||
require 'capistrano/bundler'
|
||||
require 'capistrano/rbenv'
|
||||
require 'capistrano/rails'
|
||||
require 'whenever/capistrano'
|
||||
require 'capistrano/scm/git'
|
||||
install_plugin Capistrano::SCM::Git
|
||||
|
||||
|
|
116
Dockerfile
Normal file
116
Dockerfile
Normal file
|
@ -0,0 +1,116 @@
|
|||
# Este Dockerfile está armado pensando en una compilación lanzada desde
|
||||
# el mismo repositorio de trabajo. Cuando tengamos CI/CD algunas cosas
|
||||
# como el tarball van a tener que cambiar porque ya vamos a haber hecho
|
||||
# un clone/pull limpio.
|
||||
FROM sutty/sdk-ruby:latest as build
|
||||
MAINTAINER "f <f@sutty.nl>"
|
||||
|
||||
# Un entorno base
|
||||
ENV NOKOGIRI_USE_SYSTEM_LIBRARIES=1
|
||||
ENV SECRET_KEY_BASE solo_es_necesaria_para_correr_rake
|
||||
ENV RAILS_ENV production
|
||||
|
||||
# Para compilar los assets en brotli
|
||||
RUN apk add --no-cache brotli libgit2-dev rsync cmake
|
||||
|
||||
# Empezamos con la usuaria app creada por sdk-ruby
|
||||
USER app
|
||||
# Vamos a trabajar dentro de este directorio
|
||||
WORKDIR /home/app/sutty
|
||||
|
||||
# Copiamos solo el Gemfile para poder instalar las gemas necesarias
|
||||
COPY --chown=app:www-data ./Gemfile .
|
||||
COPY --chown=app:www-data ./Gemfile.lock .
|
||||
# XXX: Esto va a tener permisos de 1000, idealmente el usuario que lanza
|
||||
# la compilación
|
||||
RUN rsync -a 172.17.0.1::ccache/ /home/app/.ccache/
|
||||
# Instalar las gemas de producción usando ccache para no recompilar
|
||||
# gemas nativas
|
||||
# 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 if ! bundle install --path=./vendor --without test development ; then rsync -a /home/app/.ccache/ 172.17.0.1::ccache/ ; exit 1 ; fi
|
||||
RUN rsync -a /home/app/.ccache/ 172.17.0.1::ccache/
|
||||
# Vaciar la caché
|
||||
RUN rm vendor/ruby/2.5.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
|
||||
COPY --chown=app:www-data ./.git/ ./.git/
|
||||
# Hacer un tarball de los archivos desde el repositorio
|
||||
RUN git archive -o ../sutty.tar.gz HEAD
|
||||
|
||||
# Extraer archivos necesarios para compilar los assets
|
||||
RUN tar xf ../sutty.tar.gz Rakefile config app yarn.lock package.json
|
||||
# Instalar los paquetes JS
|
||||
RUN yarn
|
||||
# Pre-compilar los assets
|
||||
RUN bundle exec rake assets:precompile
|
||||
# Comprimirlos usando brotli
|
||||
RUN find public/assets -type f | grep -v ".gz$" | xargs -r brotli -k -9
|
||||
|
||||
# Eliminar la necesidad de un runtime JS en producción, porque los
|
||||
# assets ya están pre-compilados.
|
||||
RUN sed -re "/(uglifier|bootstrap|coffee-rails)/d" -i Gemfile
|
||||
RUN bundle clean
|
||||
|
||||
|
||||
# Contenedor final
|
||||
FROM sutty/monit:latest
|
||||
ENV RAILS_ENV production
|
||||
|
||||
# Instalar las dependencias, separamos la librería de base de datos para
|
||||
# poder reutilizar este primer paso desde otros contenedores
|
||||
RUN apk add --no-cache libxslt libxml2 tzdata ruby ruby-bundler ruby-json ruby-bigdecimal ruby-rake
|
||||
RUN apk add --no-cache sqlite-libs
|
||||
# Necesitamos yarn para que Jekyll pueda generar los sitios
|
||||
# XXX: Eliminarlo cuando extraigamos la generación de sitios del proceso
|
||||
# principal
|
||||
RUN apk add --no-cache yarn
|
||||
RUN apk add --no-cache libgit2
|
||||
# Instalar foreman para poder correr los servicios
|
||||
RUN gem install --no-document --no-user-install foreman
|
||||
|
||||
# Agregar el grupo del servidor web
|
||||
RUN addgroup -g 82 -S www-data
|
||||
# Agregar la usuaria
|
||||
RUN adduser -s /bin/sh -G www-data -h /srv/http -D app
|
||||
|
||||
# Convertirse en app para instalar
|
||||
USER app
|
||||
WORKDIR /srv/http
|
||||
|
||||
# Traer los archivos y colocarlos donde van definitivamente
|
||||
COPY --from=build --chown=app:www-data /home/app/sutty.tar.gz /tmp/
|
||||
# XXX: No vale la pena borrarlo porque sigue ocupando espacio en la capa
|
||||
# anterior
|
||||
RUN tar xf /tmp/sutty.tar.gz && rm /tmp/sutty.tar.gz
|
||||
|
||||
# Traer los assets compilados y las gemas
|
||||
COPY --from=build --chown=app:www-data /home/app/sutty/public/assets public/assets
|
||||
COPY --from=build --chown=app:www-data /home/app/sutty/vendor vendor
|
||||
COPY --from=build --chown=app:www-data /home/app/sutty/.bundle .bundle
|
||||
COPY --from=build --chown=app:www-data /home/app/sutty/Gemfile Gemfile
|
||||
COPY --from=build --chown=app:www-data /home/app/sutty/Gemfile.lock Gemfile.lock
|
||||
COPY ./config/credentials.yml.enc ./config/credentials.yml.enc
|
||||
|
||||
# Volver a root para cerrar la compilación
|
||||
USER root
|
||||
|
||||
# Sincronizar los assets a un directorio compartido
|
||||
RUN apk add --no-cache rsync
|
||||
COPY ./sync_assets.sh /usr/local/bin/sync_assets
|
||||
RUN chmod 755 /usr/local/bin/sync_assets
|
||||
# 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
|
||||
|
||||
# Mantener estos directorios!
|
||||
VOLUME "/srv/http/_deploy"
|
||||
VOLUME "/srv/http/_sites"
|
||||
VOLUME "/srv/http/_public"
|
||||
|
||||
# El puerto de puma
|
||||
EXPOSE 3000
|
20
Gemfile
20
Gemfile
|
@ -46,14 +46,22 @@ gem 'email_address'
|
|||
gem 'exception_notification'
|
||||
gem 'font-awesome-rails'
|
||||
gem 'friendly_id'
|
||||
gem 'haml-rails'
|
||||
gem 'hamlit-rails'
|
||||
gem 'hiredis'
|
||||
gem 'jekyll'
|
||||
gem 'jquery-rails'
|
||||
gem 'mini_magick'
|
||||
gem 'mobility'
|
||||
gem 'pundit'
|
||||
gem 'rails-i18n'
|
||||
gem 'rails_warden'
|
||||
gem 'whenever', require: false
|
||||
gem 'redis', require: %w[redis redis/connection/hiredis]
|
||||
gem 'redis-rails'
|
||||
gem 'rubyzip'
|
||||
gem 'rugged'
|
||||
gem 'sidekiq'
|
||||
gem 'terminal-table'
|
||||
gem 'validates_hostname'
|
||||
|
||||
group :development, :test do
|
||||
gem 'pry'
|
||||
|
@ -76,9 +84,15 @@ group :development do
|
|||
gem 'capistrano-rails'
|
||||
gem 'capistrano-rbenv'
|
||||
gem 'ed25519'
|
||||
gem 'haml-lint', require: false
|
||||
gem 'letter_opener'
|
||||
gem 'rbnacl', '< 5.0'
|
||||
gem 'rubocop'
|
||||
gem 'rubocop-rails'
|
||||
gem 'spring'
|
||||
gem 'spring-watcher-listen', '~> 2.0.0'
|
||||
end
|
||||
|
||||
group :test do
|
||||
gem 'database_cleaner'
|
||||
gem 'factory_bot_rails'
|
||||
end
|
||||
|
|
115
Gemfile.lock
115
Gemfile.lock
|
@ -91,13 +91,14 @@ GEM
|
|||
carrierwave-i18n (0.2.0)
|
||||
childprocess (0.9.0)
|
||||
ffi (~> 1.0, >= 1.0.11)
|
||||
chronic (0.10.2)
|
||||
coderay (1.1.2)
|
||||
colorator (1.1.0)
|
||||
commonmarker (0.18.2)
|
||||
ruby-enum (~> 0.5)
|
||||
concurrent-ruby (1.1.5)
|
||||
connection_pool (2.2.2)
|
||||
crass (1.0.4)
|
||||
database_cleaner (1.7.0)
|
||||
devise (4.6.2)
|
||||
bcrypt (~> 3.0)
|
||||
orm_adapter (~> 0.1)
|
||||
|
@ -121,12 +122,16 @@ GEM
|
|||
netaddr (~> 2.0)
|
||||
simpleidn
|
||||
erubi (1.8.0)
|
||||
erubis (2.7.0)
|
||||
eventmachine (1.2.7)
|
||||
exception_notification (4.3.0)
|
||||
actionmailer (>= 4.0, < 6)
|
||||
activesupport (>= 4.0, < 6)
|
||||
execjs (2.7.0)
|
||||
factory_bot (5.0.2)
|
||||
activesupport (>= 4.2.0)
|
||||
factory_bot_rails (5.0.2)
|
||||
factory_bot (~> 5.0.2)
|
||||
railties (>= 4.2.0)
|
||||
fastimage (2.1.5)
|
||||
ffi (1.11.1)
|
||||
font-awesome-rails (4.7.0.4)
|
||||
|
@ -139,21 +144,28 @@ GEM
|
|||
haml (5.0.4)
|
||||
temple (>= 0.8.0)
|
||||
tilt
|
||||
haml-rails (1.0.0)
|
||||
haml-lint (0.999.999)
|
||||
haml_lint
|
||||
haml_lint (0.32.0)
|
||||
haml (>= 4.0, < 5.2)
|
||||
rainbow
|
||||
rake (>= 10, < 13)
|
||||
rubocop (>= 0.50.0)
|
||||
sysexits (~> 1.1)
|
||||
hamlit (2.9.3)
|
||||
temple (>= 0.8.0)
|
||||
thor
|
||||
tilt
|
||||
hamlit-rails (0.2.3)
|
||||
actionpack (>= 4.0.1)
|
||||
activesupport (>= 4.0.1)
|
||||
haml (>= 4.0.6, < 6.0)
|
||||
html2haml (>= 1.0.1)
|
||||
hamlit (>= 1.2.0)
|
||||
railties (>= 4.0.1)
|
||||
html2haml (2.2.0)
|
||||
erubis (~> 2.7.0)
|
||||
haml (>= 4.0, < 6)
|
||||
nokogiri (>= 1.6.0)
|
||||
ruby_parser (~> 3.5)
|
||||
hiredis (0.6.3)
|
||||
http_parser.rb (0.6.0)
|
||||
i18n (0.9.5)
|
||||
concurrent-ruby (~> 1.0)
|
||||
jaro_winkler (1.5.2)
|
||||
jaro_winkler (1.5.3)
|
||||
jbuilder (2.8.0)
|
||||
activesupport (>= 4.2.0)
|
||||
multi_json (>= 1.2)
|
||||
|
@ -201,10 +213,13 @@ GEM
|
|||
mime-types-data (~> 3.2015)
|
||||
mime-types-data (3.2018.0812)
|
||||
mimemagic (0.3.3)
|
||||
mini_magick (4.9.3)
|
||||
mini_magick (4.9.4)
|
||||
mini_mime (1.0.1)
|
||||
mini_portile2 (2.4.0)
|
||||
minitest (5.11.3)
|
||||
mobility (0.8.7)
|
||||
i18n (>= 0.6.10, < 2)
|
||||
request_store (~> 1.0)
|
||||
multi_json (1.13.1)
|
||||
net-scp (2.0.0)
|
||||
net-ssh (>= 2.6.5, < 6.0.0)
|
||||
|
@ -214,8 +229,8 @@ GEM
|
|||
nokogiri (1.10.3)
|
||||
mini_portile2 (~> 2.4.0)
|
||||
orm_adapter (0.5.0)
|
||||
parallel (1.16.0)
|
||||
parser (2.6.2.0)
|
||||
parallel (1.17.0)
|
||||
parser (2.6.3.0)
|
||||
ast (~> 2.4.0)
|
||||
pathutil (0.16.2)
|
||||
forwardable-extended (~> 2.6)
|
||||
|
@ -223,12 +238,13 @@ GEM
|
|||
pry (0.12.2)
|
||||
coderay (~> 1.1.0)
|
||||
method_source (~> 0.9.0)
|
||||
psych (3.1.0)
|
||||
public_suffix (3.0.3)
|
||||
puma (3.12.1)
|
||||
pundit (2.0.1)
|
||||
activesupport (>= 3.0.0)
|
||||
rack (2.0.6)
|
||||
rack-protection (2.0.5)
|
||||
rack
|
||||
rack-test (1.1.0)
|
||||
rack (>= 1.0, < 3)
|
||||
rails (5.2.3)
|
||||
|
@ -267,25 +283,45 @@ GEM
|
|||
ffi (~> 1.0)
|
||||
rbnacl (4.0.2)
|
||||
ffi
|
||||
redis (4.1.2)
|
||||
redis-actionpack (5.0.2)
|
||||
actionpack (>= 4.0, < 6)
|
||||
redis-rack (>= 1, < 3)
|
||||
redis-store (>= 1.1.0, < 2)
|
||||
redis-activesupport (5.0.7)
|
||||
activesupport (>= 3, < 6)
|
||||
redis-store (>= 1.3, < 2)
|
||||
redis-rack (2.0.5)
|
||||
rack (>= 1.5, < 3)
|
||||
redis-store (>= 1.2, < 2)
|
||||
redis-rails (5.0.2)
|
||||
redis-actionpack (>= 5.0, < 6)
|
||||
redis-activesupport (>= 5.0, < 6)
|
||||
redis-store (>= 1.2, < 2)
|
||||
redis-store (1.6.0)
|
||||
redis (>= 2.2, < 5)
|
||||
request_store (1.4.1)
|
||||
rack (>= 1.4)
|
||||
responders (3.0.0)
|
||||
actionpack (>= 5.0)
|
||||
railties (>= 5.0)
|
||||
rouge (3.3.0)
|
||||
rubocop (0.66.0)
|
||||
rubocop (0.72.0)
|
||||
jaro_winkler (~> 1.5.1)
|
||||
parallel (~> 1.10)
|
||||
parser (>= 2.5, != 2.5.1.1)
|
||||
psych (>= 3.1.0)
|
||||
parser (>= 2.6)
|
||||
rainbow (>= 2.2.2, < 4.0)
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (>= 1.4.0, < 1.6)
|
||||
unicode-display_width (>= 1.4.0, < 1.7)
|
||||
rubocop-rails (2.2.0)
|
||||
rack (>= 1.1)
|
||||
rubocop (>= 0.72.0)
|
||||
ruby-enum (0.7.2)
|
||||
i18n
|
||||
ruby-progressbar (1.10.0)
|
||||
ruby-progressbar (1.10.1)
|
||||
ruby_dep (1.5.0)
|
||||
ruby_parser (3.13.1)
|
||||
sexp_processor (~> 4.9)
|
||||
rubyzip (1.2.2)
|
||||
rugged (0.28.2)
|
||||
safe_yaml (1.0.5)
|
||||
sass (3.7.4)
|
||||
sass-listen (~> 4.0.0)
|
||||
|
@ -310,7 +346,11 @@ GEM
|
|||
selenium-webdriver (3.141.0)
|
||||
childprocess (~> 0.5)
|
||||
rubyzip (~> 1.2, >= 1.2.2)
|
||||
sexp_processor (4.12.0)
|
||||
sidekiq (5.2.7)
|
||||
connection_pool (~> 2.2, >= 2.2.2)
|
||||
rack (>= 1.5.0)
|
||||
rack-protection (>= 1.5.0)
|
||||
redis (>= 3.3.5, < 5)
|
||||
simpleidn (0.1.1)
|
||||
unf (~> 0.1.4)
|
||||
spring (2.0.2)
|
||||
|
@ -329,7 +369,10 @@ GEM
|
|||
sshkit (1.18.2)
|
||||
net-scp (>= 1.1.2)
|
||||
net-ssh (>= 2.8.0)
|
||||
sysexits (1.2.0)
|
||||
temple (0.8.1)
|
||||
terminal-table (1.8.0)
|
||||
unicode-display_width (~> 1.1, >= 1.1.1)
|
||||
thor (0.20.3)
|
||||
thread_safe (0.3.6)
|
||||
tilt (2.0.9)
|
||||
|
@ -343,7 +386,10 @@ GEM
|
|||
unf (0.1.4)
|
||||
unf_ext
|
||||
unf_ext (0.0.7.5)
|
||||
unicode-display_width (1.5.0)
|
||||
unicode-display_width (1.6.0)
|
||||
validates_hostname (1.0.8)
|
||||
activerecord (>= 3.0)
|
||||
activesupport (>= 3.0)
|
||||
warden (1.2.8)
|
||||
rack (>= 2.0.6)
|
||||
web-console (3.7.0)
|
||||
|
@ -354,8 +400,6 @@ GEM
|
|||
websocket-driver (0.7.0)
|
||||
websocket-extensions (>= 0.1.0)
|
||||
websocket-extensions (0.1.3)
|
||||
whenever (0.10.0)
|
||||
chronic (>= 0.6.3)
|
||||
xpath (3.2.0)
|
||||
nokogiri (~> 1.8)
|
||||
|
||||
|
@ -376,6 +420,7 @@ DEPENDENCIES
|
|||
carrierwave-bombshelter
|
||||
carrierwave-i18n
|
||||
commonmarker
|
||||
database_cleaner
|
||||
devise
|
||||
devise-i18n
|
||||
devise_invitable
|
||||
|
@ -383,15 +428,19 @@ DEPENDENCIES
|
|||
ed25519
|
||||
email_address
|
||||
exception_notification
|
||||
factory_bot_rails
|
||||
font-awesome-rails
|
||||
friendly_id
|
||||
haml-rails
|
||||
haml-lint
|
||||
hamlit-rails
|
||||
hiredis
|
||||
jbuilder (~> 2.5)
|
||||
jekyll
|
||||
jquery-rails
|
||||
letter_opener
|
||||
listen (>= 3.0.5, < 3.2)
|
||||
mini_magick
|
||||
mobility
|
||||
pry
|
||||
puma (~> 3.7)
|
||||
pundit
|
||||
|
@ -399,16 +448,22 @@ DEPENDENCIES
|
|||
rails-i18n
|
||||
rails_warden
|
||||
rbnacl (< 5.0)
|
||||
rubocop
|
||||
redis
|
||||
redis-rails
|
||||
rubocop-rails
|
||||
rubyzip
|
||||
rugged
|
||||
sass-rails (~> 5.0)
|
||||
selenium-webdriver
|
||||
sidekiq
|
||||
spring
|
||||
spring-watcher-listen (~> 2.0.0)
|
||||
sqlite3 (~> 1.3.6)
|
||||
terminal-table
|
||||
turbolinks (~> 5)
|
||||
uglifier (>= 1.3.0)
|
||||
validates_hostname
|
||||
web-console (>= 3.3.0)
|
||||
whenever
|
||||
|
||||
BUNDLED WITH
|
||||
1.17.3
|
||||
2.0.2
|
||||
|
|
3
Procfile
Normal file
3
Procfile
Normal file
|
@ -0,0 +1,3 @@
|
|||
migrate: bundle exec rake db:migrate db:seed
|
||||
sutty: bundle exec puma -d config.ru
|
||||
sidekiq: bundle exec sidekiq -t 1
|
Binary file not shown.
Before Width: | Height: | Size: 92 KiB |
BIN
app/assets/images/icon_external_link.png
Normal file
BIN
app/assets/images/icon_external_link.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 144 B |
BIN
app/assets/images/logo.png
Normal file
BIN
app/assets/images/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2 KiB |
5
app/assets/javascripts/external_links.js
Normal file
5
app/assets/javascripts/external_links.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
$(document).on('turbolinks:load', function() {
|
||||
$("a[href^='http://']").attr('target', '_blank');
|
||||
$("a[href^='https://']").attr('target', '_blank');
|
||||
$("a[href^='//']").attr('target', '_blank');
|
||||
});
|
|
@ -5,6 +5,37 @@
|
|||
@import "select2-theme-bootstrap4/dist/select2-bootstrap";
|
||||
@import "dragula-with-animation/dist/dragula";
|
||||
|
||||
@font-face {
|
||||
font-family: 'Saira';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: optional;
|
||||
src: local('Saira Medium'), local('Saira-Medium'),
|
||||
font-url('saira/v3/SairaMedium.ttf') format('truetype');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Saira';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: optional;
|
||||
src: local('Saira Bold'), local('Saira-Bold'),
|
||||
font-url('saira/v3/SairaBold.ttf') format('truetype');
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: Saira, sans-serif;
|
||||
}
|
||||
|
||||
a {
|
||||
&[target=_blank] {
|
||||
/* TODO: Convertir a base64 para no hacer peticiones extra */
|
||||
&:after {
|
||||
content: image-url('icon_external_link.png');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$footer-height: 60px;
|
||||
|
||||
/* Colores */
|
||||
|
@ -55,7 +86,6 @@ ol.breadcrumb {
|
|||
|
||||
|
||||
.background-cover {
|
||||
background: image-url("background.jpg") no-repeat center center fixed;
|
||||
-webkit-background-size: cover;
|
||||
-moz-background-size: cover;
|
||||
-o-background-size: cover;
|
||||
|
|
|
@ -5,8 +5,12 @@ class ApplicationController < ActionController::Base
|
|||
include ExceptionHandler
|
||||
|
||||
protect_from_forgery with: :exception
|
||||
|
||||
before_action :configure_permitted_parameters, if: :devise_controller?
|
||||
before_action :set_locale
|
||||
|
||||
layout :layout_by_usuarie
|
||||
|
||||
# No tenemos índice de sutty, vamos directamente a ver el listado de
|
||||
# sitios
|
||||
def index
|
||||
|
@ -17,6 +21,14 @@ class ApplicationController < ActionController::Base
|
|||
|
||||
private
|
||||
|
||||
def layout_by_usuarie
|
||||
if current_usuarie
|
||||
'application'
|
||||
else
|
||||
'devise'
|
||||
end
|
||||
end
|
||||
|
||||
# Encontrar un sitio por su nombre
|
||||
def find_site
|
||||
id = params[:site_id] || params[:id]
|
||||
|
@ -50,6 +62,12 @@ class ApplicationController < ActionController::Base
|
|||
end
|
||||
|
||||
def set_locale
|
||||
I18n.locale = session[:lang] if session[:lang].present?
|
||||
I18n.locale = current_usuarie.lang
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def configure_permitted_parameters
|
||||
devise_parameter_sanitizer.permit(:account_update, keys: %i[lang])
|
||||
end
|
||||
end
|
||||
|
|
|
@ -56,7 +56,7 @@ class PostsController < ApplicationController
|
|||
|
||||
# Las usuarias pueden especificar una autora, de la contrario por
|
||||
# defecto es la usuaria actual
|
||||
if current_user.is_a? Usuaria
|
||||
if @site.usuarie? current_usuarie
|
||||
@post.update_attributes(author: params[:post][:author])
|
||||
else
|
||||
# Todo lo que crean lxs invitadxs es borrador
|
||||
|
@ -99,7 +99,7 @@ class PostsController < ApplicationController
|
|||
@post.update_attributes(repair_nested_params(post_params))
|
||||
|
||||
# Solo las usuarias pueden modificar la autoría
|
||||
if current_user.is_a? Usuaria
|
||||
if @site.usuarie? current_usuarie
|
||||
if params[:post][:author].present?
|
||||
@post.update_attributes(author: params[:post][:author])
|
||||
end
|
||||
|
|
|
@ -14,12 +14,54 @@ class SitesController < ApplicationController
|
|||
# No tenemos propiedades de un sitio aún, así que vamos al listado de
|
||||
# artículos
|
||||
def show
|
||||
authorize Site
|
||||
site = find_site
|
||||
authorize site
|
||||
|
||||
redirect_to site_posts_path(site)
|
||||
end
|
||||
|
||||
def new
|
||||
@site = Site.new
|
||||
authorize @site
|
||||
|
||||
@site.deploys.build type: 'DeployLocal'
|
||||
@site.deploys.build type: 'DeployZip'
|
||||
end
|
||||
|
||||
def create
|
||||
@site = Site.new(site_params)
|
||||
@site.roles << Rol.new(site: @site,
|
||||
usuarie: current_usuarie,
|
||||
temporal: false,
|
||||
rol: 'usuarie')
|
||||
|
||||
# XXX: Necesitamos escribir la configuración después porque queremos
|
||||
# registrar quién hizo los cambios en el repositorio
|
||||
if @site.save && @site.config.write(current_usuarie)
|
||||
redirect_to site_path(@site)
|
||||
else
|
||||
render 'new'
|
||||
end
|
||||
end
|
||||
|
||||
def edit
|
||||
@site = find_site
|
||||
authorize @site
|
||||
end
|
||||
|
||||
def update
|
||||
@site = find_site
|
||||
authorize @site
|
||||
|
||||
# XXX: Necesitamos escribir la configuración después porque queremos
|
||||
# registrar quién hizo los cambios en el repositorio
|
||||
if @site.update(site_params) && @site.config.write(current_usuarie)
|
||||
redirect_to sites_path
|
||||
else
|
||||
render 'edit'
|
||||
end
|
||||
end
|
||||
|
||||
# Envía un archivo del directorio público de Jekyll
|
||||
def send_public_file
|
||||
authorize Site
|
||||
|
@ -42,23 +84,15 @@ class SitesController < ApplicationController
|
|||
end
|
||||
|
||||
def enqueue
|
||||
@site = find_site
|
||||
authorize @site
|
||||
@site.enqueue!
|
||||
site = find_site
|
||||
authorize site
|
||||
|
||||
# XXX: Convertir en una máquina de estados?
|
||||
DeployWorker.perform_async site.id if site.enqueue!
|
||||
|
||||
redirect_to sites_path
|
||||
end
|
||||
|
||||
def build_log
|
||||
@site = find_site
|
||||
authorize @site
|
||||
|
||||
# TODO: eliminar ANSI
|
||||
render file: @site.build_log,
|
||||
layout: false,
|
||||
content_type: 'text/plain; charset=utf-8'
|
||||
end
|
||||
|
||||
def reorder_posts
|
||||
@site = find_site
|
||||
authorize @site
|
||||
|
@ -79,4 +113,32 @@ class SitesController < ApplicationController
|
|||
|
||||
redirect_to site_posts_path @site
|
||||
end
|
||||
|
||||
def fetch
|
||||
@site = find_site
|
||||
authorize @site
|
||||
|
||||
@commits = @site.repository.commits
|
||||
end
|
||||
|
||||
def merge
|
||||
@site = find_site
|
||||
authorize @site
|
||||
|
||||
if @site.repository.merge(current_usuarie)
|
||||
flash[:success] = I18n.t('sites.fetch.merge.success')
|
||||
else
|
||||
flash[:error] = I18n.t('sites.fetch.merge.error')
|
||||
end
|
||||
|
||||
redirect_to sites_path
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def site_params
|
||||
params.require(:site)
|
||||
.permit(:name, :design_id, :licencia_id, :description, :title,
|
||||
deploys_attributes: %i[type id _destroy])
|
||||
end
|
||||
end
|
||||
|
|
18
app/controllers/stats_controller.rb
Normal file
18
app/controllers/stats_controller.rb
Normal file
|
@ -0,0 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Estadísticas del sitio
|
||||
class StatsController < ApplicationController
|
||||
include Pundit
|
||||
before_action :authenticate_usuarie!
|
||||
|
||||
def index
|
||||
@site = find_site
|
||||
authorize SiteStat.new(@site)
|
||||
|
||||
# Solo queremos el promedio de tiempo de compilación, no de
|
||||
# instalación de dependencias.
|
||||
stats = @site.build_stats.jekyll
|
||||
@build_avg = stats.average(:seconds).to_f.round(2)
|
||||
@build_max = stats.maximum(:seconds).to_f.round(2)
|
||||
end
|
||||
end
|
|
@ -1,5 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Helpers
|
||||
module ApplicationHelper
|
||||
# Devuelve el atributo name de un campo posiblemente anidado
|
||||
def field_name_for_post(names)
|
||||
|
@ -21,7 +22,31 @@ module ApplicationHelper
|
|||
"#{f.first}[#{f.last}]"
|
||||
end
|
||||
|
||||
def distance_of_time_in_words_if_more_than_a_minute(seconds)
|
||||
if seconds > 60
|
||||
distance_of_time_in_words seconds
|
||||
else
|
||||
I18n.t('seconds', seconds: seconds)
|
||||
end
|
||||
end
|
||||
|
||||
def sanitize_markdown(text, options = {})
|
||||
sanitize(CommonMarker.render_html(text), options)
|
||||
end
|
||||
|
||||
def invalid?(model, field)
|
||||
model.errors.messages[field].present?
|
||||
end
|
||||
|
||||
def form_control(model, field)
|
||||
if invalid? model, field
|
||||
'form-control is-invalid'
|
||||
else
|
||||
'form-control'
|
||||
end
|
||||
end
|
||||
|
||||
def form_class(model)
|
||||
model.errors.messages.empty? ? 'needs-validation' : 'was-validated'
|
||||
end
|
||||
end
|
||||
|
|
12
app/lib/core_extensions/string/strip_tags.rb
Normal file
12
app/lib/core_extensions/string/strip_tags.rb
Normal file
|
@ -0,0 +1,12 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module CoreExtensions
|
||||
module String
|
||||
# Elimina el HTML
|
||||
module StripTags
|
||||
def strip_tags
|
||||
ActionController::Base.helpers.strip_tags(self)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,6 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Configuración base del correo
|
||||
class ApplicationMailer < ActionMailer::Base
|
||||
default from: 'from@example.com'
|
||||
helper :application
|
||||
before_action :inline_logo!
|
||||
|
||||
default from: ENV.fetch('DEFAULT_FROM', "noreply@#{Site.domain}")
|
||||
layout 'mailer'
|
||||
|
||||
private
|
||||
|
||||
def inline_logo!
|
||||
attachments.inline['logo.png'] ||=
|
||||
File.read('app/assets/images/logo.png')
|
||||
end
|
||||
end
|
||||
|
|
28
app/mailers/deploy_mailer.rb
Normal file
28
app/mailers/deploy_mailer.rb
Normal file
|
@ -0,0 +1,28 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Notifica a les usuaries cuando un sitio se generó con éxito
|
||||
#
|
||||
# XXX: No será mejor enviarles un correo con copia?
|
||||
# TODO: Agregar headers de desuscripción de notificaciones cuando
|
||||
# tengamos opciones de usuarie
|
||||
# TODO: Agregar firma GPG y header Autocrypt
|
||||
# TODO: Cifrar con GPG si le usuarie nos dio su llave
|
||||
class DeployMailer < ApplicationMailer
|
||||
# rubocop:disable Metrics/AbcSize
|
||||
def deployed(which_ones)
|
||||
@usuarie = Usuarie.find(params[:usuarie])
|
||||
@site = @usuarie.sites.find(params[:site])
|
||||
@deploys = which_ones
|
||||
@deploy_local = @site.deploys.find_by(type: 'DeployLocal')
|
||||
|
||||
# Informamos a cada quien en su idioma y damos una dirección de
|
||||
# respuesta porque a veces les usuaries nos escriben
|
||||
I18n.with_locale(@usuarie.lang) do
|
||||
mail(to: @usuarie.email,
|
||||
reply_to: "sutty@#{Site.domain}",
|
||||
subject: I18n.t('mailers.deploy_mailer.deployed.subject',
|
||||
site: @site.name))
|
||||
end
|
||||
end
|
||||
# rubocop:enable Metrics/AbcSize
|
||||
end
|
8
app/models/build_stat.rb
Normal file
8
app/models/build_stat.rb
Normal file
|
@ -0,0 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Recolecta estadísticas durante la generación del sitio
|
||||
class BuildStat < ApplicationRecord
|
||||
belongs_to :deploy
|
||||
|
||||
scope :jekyll, -> { where(action: 'bundle_exec_jekyll_build') }
|
||||
end
|
65
app/models/deploy.rb
Normal file
65
app/models/deploy.rb
Normal file
|
@ -0,0 +1,65 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'open3'
|
||||
# Este modelo implementa los distintos tipos de alojamiento que provee
|
||||
# Sutty.
|
||||
#
|
||||
# Los datos se guardan en la tabla `deploys`. Para guardar los
|
||||
# atributos, cada modelo tiene que definir su propio `store
|
||||
# :attributes`.
|
||||
class Deploy < ApplicationRecord
|
||||
belongs_to :site
|
||||
has_many :build_stats
|
||||
|
||||
def deploy
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def limit
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def size
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def time_start
|
||||
@start = Time.now
|
||||
end
|
||||
|
||||
def time_stop
|
||||
@stop = Time.now
|
||||
end
|
||||
|
||||
def time_spent_in_seconds
|
||||
(@stop - @start).round(3)
|
||||
end
|
||||
|
||||
# Corre un comando y devuelve true si terminó correctamente
|
||||
# rubocop:disable Metrics/MethodLength
|
||||
# rubocop:disable Metrics/AbcSize
|
||||
def run(cmd)
|
||||
# XXX: prestar atención a la concurrencia de sqlite3, se podría
|
||||
# enviar los datos directamente a una API para que se manejen desde
|
||||
# el proceso principal de rails y evitar problemas.
|
||||
stat = build_stats.build action: cmd.split(' -', 2).first.tr(' ', '_')
|
||||
r = nil
|
||||
|
||||
time_start
|
||||
Dir.chdir(site.path) do
|
||||
Open3.popen2e(env, cmd, unsetenv_others: true) do |_, o, t|
|
||||
r = t.value
|
||||
stat.log = o.read
|
||||
end
|
||||
end
|
||||
time_stop
|
||||
|
||||
stat.seconds = time_spent_in_seconds
|
||||
stat.bytes = size
|
||||
stat.save
|
||||
|
||||
r.try :exited?
|
||||
end
|
||||
# rubocop:enable Metrics/MethodLength
|
||||
# rubocop:enable Metrics/AbcSize
|
||||
end
|
88
app/models/deploy_local.rb
Normal file
88
app/models/deploy_local.rb
Normal file
|
@ -0,0 +1,88 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Alojamiento local, solo genera el sitio, con lo que no necesita hacer
|
||||
# nada más
|
||||
class DeployLocal < Deploy
|
||||
store :values, accessors: %i[fqdn destination], coder: JSON
|
||||
|
||||
before_create :fqdn!, :destination!
|
||||
before_destroy :remove_destination!
|
||||
|
||||
# Realizamos la construcción del sitio usando Jekyll y un entorno
|
||||
# limpio para no pasarle secretos
|
||||
#
|
||||
# Pasamos variables de entorno mínimas para no filtrar secretos de
|
||||
# Sutty
|
||||
#
|
||||
# TODO: Recolectar estadísticas y enviarlas a la base de datos
|
||||
def deploy
|
||||
yarn && bundle && jekyll_build
|
||||
end
|
||||
|
||||
# Sólo permitimos un deploy local
|
||||
def limit
|
||||
1
|
||||
end
|
||||
|
||||
# Obtener el tamaño de todos los archivos y directorios (los
|
||||
# directorios son archivos :)
|
||||
def size
|
||||
paths = [destination, File.join(destination, '**', '**')]
|
||||
|
||||
Dir.glob(paths).map do |file|
|
||||
File.size file
|
||||
end.inject(:+)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Un entorno que solo tiene lo que necesitamos
|
||||
def env
|
||||
# XXX: This doesn't support Windows paths :B
|
||||
paths = [File.dirname(`which bundle`), '/usr/bin']
|
||||
|
||||
{ 'PATH' => paths.join(':'), 'JEKYLL_ENV' => 'production' }
|
||||
end
|
||||
|
||||
def yarn_lock
|
||||
File.join(site.path, 'yarn.lock')
|
||||
end
|
||||
|
||||
def yarn_lock?
|
||||
File.exist? yarn_lock
|
||||
end
|
||||
|
||||
# Corre yarn dentro del repositorio
|
||||
def yarn
|
||||
return unless yarn_lock?
|
||||
|
||||
run 'yarn'
|
||||
end
|
||||
|
||||
def bundle
|
||||
run 'bundle'
|
||||
end
|
||||
|
||||
def jekyll_build
|
||||
run "bundle exec jekyll build --destination \"#{escaped_destination}\""
|
||||
end
|
||||
|
||||
def fqdn!
|
||||
self.fqdn ||= "#{site.name}.#{Site.domain}"
|
||||
end
|
||||
|
||||
def destination!
|
||||
self.destination ||= File.join(Rails.root, '_deploy', fqdn)
|
||||
end
|
||||
|
||||
# no debería haber espacios ni caracteres especiales, pero por si
|
||||
# acaso...
|
||||
def escaped_destination
|
||||
Shellwords.escape destination
|
||||
end
|
||||
|
||||
# Eliminar el destino si se elimina el deploy
|
||||
def remove_destination!
|
||||
FileUtils.rm_rf destination
|
||||
end
|
||||
end
|
64
app/models/deploy_zip.rb
Normal file
64
app/models/deploy_zip.rb
Normal file
|
@ -0,0 +1,64 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Genera un ZIP a partir del sitio ya construido
|
||||
#
|
||||
# TODO: Firmar con minisign
|
||||
class DeployZip < Deploy
|
||||
store :values, accessors: %i[fqdn destination file path], coder: JSON
|
||||
|
||||
before_create :fqdn!, :destination!
|
||||
before_create :file!, :path!
|
||||
|
||||
# Una vez que el sitio está generado, tomar todos los archivos y
|
||||
# y generar un zip accesible públicamente.
|
||||
#
|
||||
# rubocop:disable Metrics/MethodLength
|
||||
# rubocop:disable Metrics/AbcSize
|
||||
def deploy
|
||||
time_start
|
||||
Dir.chdir(destination) do
|
||||
Zip::File.open(path, Zip::File::CREATE) do |z|
|
||||
Dir.glob('./**/**').each do |f|
|
||||
File.directory?(f) ? z.mkdir(f) : z.add(f, f)
|
||||
end
|
||||
end
|
||||
end
|
||||
time_stop
|
||||
|
||||
build_stats.create action: 'zip',
|
||||
seconds: time_spent_in_seconds,
|
||||
bytes: size
|
||||
|
||||
File.exist? path
|
||||
end
|
||||
# rubocop:enable Metrics/MethodLength
|
||||
# rubocop:enable Metrics/AbcSize
|
||||
|
||||
def limit
|
||||
1
|
||||
end
|
||||
|
||||
def size
|
||||
File.size path
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Copiamos de DeployLocal para no cargar todos los métodos de
|
||||
# compilación...
|
||||
def fqdn!
|
||||
self.fqdn ||= "#{site.name}.#{Site.domain}"
|
||||
end
|
||||
|
||||
def destination!
|
||||
self.destination ||= File.join(Rails.root, '_deploy', fqdn)
|
||||
end
|
||||
|
||||
def file!
|
||||
self.file ||= "#{fqdn}.zip"
|
||||
end
|
||||
|
||||
def path!
|
||||
self.path = File.join(destination, file)
|
||||
end
|
||||
end
|
19
app/models/design.rb
Normal file
19
app/models/design.rb
Normal file
|
@ -0,0 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# El diseño de un sitio es la plantilla/tema. En este modelo cargamos
|
||||
# las propiedades para poder verlas desde el panel y elegir un diseño
|
||||
# para el sitio.
|
||||
#
|
||||
# TODO: Agregar captura de pantalla con ActiveStorage
|
||||
class Design < ApplicationRecord
|
||||
extend Mobility
|
||||
|
||||
translates :name, type: :string, locale_accessors: true
|
||||
translates :description, type: :text, locale_accessors: true
|
||||
|
||||
has_many :sites
|
||||
|
||||
validates :name, presence: true, uniqueness: true
|
||||
validates :gem, presence: true, uniqueness: true
|
||||
validates :description, presence: true
|
||||
end
|
18
app/models/licencia.rb
Normal file
18
app/models/licencia.rb
Normal file
|
@ -0,0 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Las licencias son completamente traducibles
|
||||
class Licencia < ApplicationRecord
|
||||
extend Mobility
|
||||
|
||||
translates :name, type: :string, locale_accessors: true
|
||||
translates :url, type: :string, locale_accessors: true
|
||||
translates :description, type: :text, locale_accessors: true
|
||||
translates :deed, type: :text, locale_accessors: true
|
||||
|
||||
has_many :sites
|
||||
|
||||
validates :name, presence: true, uniqueness: true
|
||||
validates :url, presence: true
|
||||
validates :description, presence: true
|
||||
validates :deed, presence: true
|
||||
end
|
|
@ -5,19 +5,64 @@
|
|||
class Site < ApplicationRecord
|
||||
include FriendlyId
|
||||
|
||||
validates :name, uniqueness: true, hostname: true
|
||||
validates :design_id, presence: true
|
||||
validate :deploy_local_presence
|
||||
validates_inclusion_of :status, in: %w[waiting enqueued building]
|
||||
validates_presence_of :title
|
||||
validates :description, length: { in: 50..160 }
|
||||
|
||||
friendly_id :name, use: %i[finders]
|
||||
|
||||
belongs_to :design
|
||||
belongs_to :licencia
|
||||
|
||||
has_many :deploys
|
||||
has_many :build_stats, through: :deploys
|
||||
has_many :roles
|
||||
has_many :usuaries, -> { where('roles.rol = ?', 'usuarie') },
|
||||
through: :roles
|
||||
has_many :invitades, -> { where('roles.rol = ?', 'invitade') },
|
||||
through: :roles, source: :usuarie
|
||||
|
||||
# Carga el sitio Jekyll una vez que se inicializa el modelo
|
||||
# Clonar el directorio de esqueleto antes de crear el sitio
|
||||
before_create :clone_skel!
|
||||
# Elimina el directorio al destruir un sitio
|
||||
before_destroy :remove_directories!
|
||||
# Carga el sitio Jekyll una vez que se inicializa el modelo o después
|
||||
# de crearlo
|
||||
after_initialize :load_jekyll!
|
||||
after_create :load_jekyll!
|
||||
# Cambiar el nombre del directorio
|
||||
before_update :update_name!
|
||||
# Guardar la configuración si hubo cambios
|
||||
after_save :sync_attributes_with_config!
|
||||
|
||||
attr_accessor :jekyll, :collections
|
||||
|
||||
accepts_nested_attributes_for :deploys, allow_destroy: true
|
||||
|
||||
# No permitir HTML en estos atributos
|
||||
def title=(title)
|
||||
super(title.strip_tags)
|
||||
end
|
||||
|
||||
def description=(description)
|
||||
super(description.strip_tags)
|
||||
end
|
||||
|
||||
# El repositorio git para este sitio
|
||||
def repository
|
||||
@repository ||= Site::Repository.new path
|
||||
end
|
||||
|
||||
# Trae los cambios del skel y verifica que haya cambios
|
||||
def needs_pull?
|
||||
!repository.commits.empty?
|
||||
end
|
||||
|
||||
# TODO: Mover esta consulta a la base de datos para no traer un montón
|
||||
# de cosas a la memoria
|
||||
def invitade?(usuarie)
|
||||
invitades.pluck(:id).include? usuarie.id
|
||||
end
|
||||
|
@ -30,12 +75,16 @@ class Site < ApplicationRecord
|
|||
#
|
||||
# Equivale a _sites + nombre
|
||||
def path
|
||||
@path ||= File.join(Site.site_path, name)
|
||||
File.join(Site.site_path, name)
|
||||
end
|
||||
|
||||
def old_path
|
||||
File.join(Site.site_path, name_was)
|
||||
end
|
||||
|
||||
# Este sitio acepta invitadxs?
|
||||
def invitadxs?
|
||||
jekyll.config.fetch('invitadxs', false)
|
||||
config.fetch('invitadxs', false)
|
||||
end
|
||||
|
||||
def cover
|
||||
|
@ -49,12 +98,12 @@ class Site < ApplicationRecord
|
|||
|
||||
# Define si el sitio tiene un glosario
|
||||
def glossary?
|
||||
jekyll.config.fetch('glossary', false)
|
||||
config.fetch('glossary', false)
|
||||
end
|
||||
|
||||
# Obtiene la lista de traducciones actuales
|
||||
def translations
|
||||
@jekyll.config.dig('i18n') || []
|
||||
config.fetch('i18n', [])
|
||||
end
|
||||
|
||||
# Devuelve el idioma por defecto del sitio
|
||||
|
@ -111,14 +160,10 @@ class Site < ApplicationRecord
|
|||
end
|
||||
|
||||
def config
|
||||
if @jekyll.config.empty?
|
||||
read
|
||||
Rails.logger.info 'Leyendo config'
|
||||
end
|
||||
|
||||
@jekyll.config
|
||||
@config ||= Site::Config.new(self)
|
||||
end
|
||||
|
||||
# TODO: Cambiar a Site::Config apenas empecemos a testear esto
|
||||
def collections_names
|
||||
@jekyll.config['collections'].keys
|
||||
end
|
||||
|
@ -182,65 +227,13 @@ class Site < ApplicationRecord
|
|||
end.flatten.uniq.compact
|
||||
end
|
||||
|
||||
def failed_file
|
||||
File.join(path, '.failed')
|
||||
end
|
||||
|
||||
def failed?
|
||||
File.exist? failed_file
|
||||
end
|
||||
|
||||
def defail
|
||||
FileUtils.rm failed_file if failed?
|
||||
end
|
||||
alias defail! defail
|
||||
|
||||
def build_log
|
||||
File.join(path, 'build.log')
|
||||
end
|
||||
|
||||
def build_log?
|
||||
File.exist? build_log
|
||||
end
|
||||
|
||||
def queue_file
|
||||
File.join(path, '.generate')
|
||||
def enqueue!
|
||||
!enqueued? && update_attribute(:status, 'enqueued')
|
||||
end
|
||||
|
||||
def enqueued?
|
||||
File.exist? queue_file
|
||||
status == 'enqueued'
|
||||
end
|
||||
alias queued? enqueued?
|
||||
|
||||
# El sitio se genera cuando se coloca en una cola de generación, para
|
||||
# que luego lo construya un cronjob
|
||||
def enqueue
|
||||
defail!
|
||||
# TODO: ya van tres métodos donde usamos esta idea, convertir en un
|
||||
# helper o algo
|
||||
File.open(queue_file, File::RDWR | File::CREAT, 0o640) do |f|
|
||||
# Bloquear el archivo para que no sea accedido por otro
|
||||
# proceso u otra editora
|
||||
f.flock(File::LOCK_EX)
|
||||
|
||||
# Empezar por el principio
|
||||
f.rewind
|
||||
|
||||
# Escribir la fecha de creación
|
||||
f.write(Time.now.to_i.to_s)
|
||||
|
||||
# Eliminar el resto
|
||||
f.flush
|
||||
f.truncate(f.pos)
|
||||
end
|
||||
end
|
||||
alias enqueue! enqueue
|
||||
|
||||
# Eliminar de la cola
|
||||
def dequeue
|
||||
FileUtils.rm(queue_file) if enqueued?
|
||||
end
|
||||
alias dequeue! dequeue
|
||||
|
||||
# Verifica si los posts están ordenados
|
||||
def ordered?(collection = 'posts')
|
||||
|
@ -287,31 +280,14 @@ class Site < ApplicationRecord
|
|||
File.join('/', 'sites', id, path.gsub('..', ''))
|
||||
end
|
||||
|
||||
def get_url_from_site(path)
|
||||
"https://#{name}#{path}"
|
||||
end
|
||||
|
||||
# El directorio donde se almacenan los sitios
|
||||
def self.site_path
|
||||
File.join(Rails.root, '_sites')
|
||||
end
|
||||
|
||||
# El directorio de los sitios de una usuaria
|
||||
#
|
||||
# Los sitios se organizan por usuaria, entonces los sitios que
|
||||
# administra pueden encontrarse directamente en su directorio.
|
||||
#
|
||||
# Si comparten gestión con otras usuarias, se hacen links simbólicos
|
||||
# entre sí.
|
||||
def self.site_path_for(site)
|
||||
File.join(Site.site_path, site)
|
||||
end
|
||||
|
||||
# Comprueba que el directorio parezca ser de jekyll
|
||||
def self.jekyll?(dir)
|
||||
File.directory?(dir) && File.exist?(File.join(dir, '_config.yml'))
|
||||
end
|
||||
|
||||
# TODO: En lugar de leer todo junto de una vez, extraer la carga de
|
||||
# documentos de Jekyll hacia Sutty para que podamos leer los datos que
|
||||
# necesitamos.
|
||||
def self.load_jekyll(path)
|
||||
# Pasamos destination porque configuration() toma el directorio
|
||||
# actual y se mezclan :/
|
||||
|
@ -339,12 +315,58 @@ class Site < ApplicationRecord
|
|||
Jekyll::Site.new(config)
|
||||
end
|
||||
|
||||
def self.domain
|
||||
ENV.fetch('SUTTY', 'sutty.nl')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Clona el esqueleto de Sutty para crear el sitio nuevo, no pasa nada
|
||||
# si el sitio ya existe
|
||||
def clone_skel!
|
||||
return if File.directory? path
|
||||
|
||||
Rugged::Repository.clone_at ENV['SKEL_SUTTY'], path
|
||||
end
|
||||
|
||||
# Carga el sitio Jekyll
|
||||
def load_jekyll!
|
||||
return unless name.present? && File.directory?(path)
|
||||
|
||||
Dir.chdir(path) do
|
||||
@jekyll ||= Site.load_jekyll(Dir.pwd)
|
||||
end
|
||||
end
|
||||
|
||||
# Elimina el directorio del sitio
|
||||
def remove_directories!
|
||||
FileUtils.rm_rf path
|
||||
end
|
||||
|
||||
def update_name!
|
||||
return unless name_changed?
|
||||
|
||||
FileUtils.mv old_path, path
|
||||
end
|
||||
|
||||
# Sincroniza algunos atributos del sitio con su configuración y
|
||||
# guarda los cambios
|
||||
def sync_attributes_with_config!
|
||||
config.theme = design.gem unless design_id_changed?
|
||||
config.description = description unless description_changed?
|
||||
config.title = title unless title_changed?
|
||||
end
|
||||
|
||||
# Valida si el sitio tiene al menos una forma de alojamiento asociada
|
||||
# y es la local
|
||||
#
|
||||
# TODO: Volver opcional el alojamiento local, pero ahora mismo está
|
||||
# atado a la generación del sitio así que no puede faltar
|
||||
def deploy_local_presence
|
||||
# Usamos size porque queremos saber la cantidad de deploys sin
|
||||
# guardar también
|
||||
return if deploys.size.positive? && deploys.map(&:type).include?('DeployLocal')
|
||||
|
||||
errors.add(:deploys, I18n.t('activerecord.errors.models.site.attributes.deploys.deploy_local_presence'))
|
||||
end
|
||||
end
|
||||
|
|
62
app/models/site/config.rb
Normal file
62
app/models/site/config.rb
Normal file
|
@ -0,0 +1,62 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Site
|
||||
# Representa la configuración del sitio de forma que podamos leer y
|
||||
# escribir en el archivo _config.yml
|
||||
class Config < OpenStruct
|
||||
def initialize(site)
|
||||
# Iniciar el OpenStruct con el sitio
|
||||
super(site: site)
|
||||
|
||||
read
|
||||
end
|
||||
|
||||
# Obtener un valor por defecto a partir de la configuración
|
||||
def fetch(key, default)
|
||||
send(:[], key) || default
|
||||
end
|
||||
|
||||
# Leer el archivo de configuración y setear los atributos en el
|
||||
# objeto actual, creando los metodos de ostruct
|
||||
def read
|
||||
data = YAML.safe_load(File.read(path))
|
||||
@hash = data.hash
|
||||
|
||||
data.each do |key, value|
|
||||
send("#{key}=".to_sym, value)
|
||||
end
|
||||
end
|
||||
|
||||
# Escribe los cambios en el repositorio
|
||||
def write(usuarie = nil)
|
||||
return if persisted?
|
||||
|
||||
I18n.with_locale(usuarie.try(:lang) || I18n.default_locale) do
|
||||
Site::Writer.new(site: site, file: path,
|
||||
content: content.to_yaml, usuarie: usuarie,
|
||||
message: I18n.t('sites.repository.config')).save
|
||||
end
|
||||
# Actualizar el hash para no escribir dos veces
|
||||
@hash = content.hash
|
||||
end
|
||||
|
||||
# Detecta si la configuración cambió comparando con el valor inicial
|
||||
def persisted?
|
||||
@hash == content.hash
|
||||
end
|
||||
|
||||
# Obtener el contenido de la configuración como un hash, sin el
|
||||
# sitio correspondiente.
|
||||
def content
|
||||
h = to_h.stringify_keys
|
||||
h.delete 'site'
|
||||
|
||||
h
|
||||
end
|
||||
|
||||
# Obtener la ruta donde se encuentra la configuración.
|
||||
def path
|
||||
File.join site.path, '_config.yml'
|
||||
end
|
||||
end
|
||||
end
|
106
app/models/site/repository.rb
Normal file
106
app/models/site/repository.rb
Normal file
|
@ -0,0 +1,106 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Site
|
||||
# Acciones para el repositorio Git de un sitio. Por ahora hacemos un
|
||||
# uso muy básico de Git, con lo que asumimos varias cosas, por ejemplo
|
||||
# que un sitio tiene un solo origen, que siempre se trabaja con la
|
||||
# rama master, etc.
|
||||
class Repository
|
||||
attr_reader :rugged, :changes
|
||||
|
||||
def initialize(path)
|
||||
@rugged = Rugged::Repository.new(path)
|
||||
@changes = 0
|
||||
end
|
||||
|
||||
def remote
|
||||
@remote ||= rugged.remotes.first
|
||||
end
|
||||
|
||||
# Trae los cambios del repositorio de origen sin aplicarlos y
|
||||
# devuelve la cantidad de commits pendientes.
|
||||
#
|
||||
# XXX: Prestar atención a la velocidad de respuesta cuando tengamos
|
||||
# repositorios remotos.
|
||||
def fetch
|
||||
if remote.check_connection :fetch
|
||||
@changes = rugged.fetch(remote)[:received_objects]
|
||||
else
|
||||
0
|
||||
end
|
||||
end
|
||||
|
||||
# Incorpora los cambios en el repositorio actual
|
||||
#
|
||||
# rubocop:disable Metrics/AbcSize
|
||||
# rubocop:disable Metrics/MethodLength
|
||||
def merge(author)
|
||||
master = rugged.branches['master'].target
|
||||
origin = rugged.branches['origin/master'].target
|
||||
merge = rugged.merge_commits(master, origin)
|
||||
|
||||
# No hacemos nada si hay conflictos
|
||||
#
|
||||
# TODO: Enviar un correo a administración para poder revisar
|
||||
# manualmente. Idealmente no deberíamos tener conflictos pero
|
||||
# quién sabe.
|
||||
return if merge.conflicts?
|
||||
|
||||
author = { name: author.name, email: author.email }
|
||||
commit = Rugged::Commit
|
||||
.create(rugged,
|
||||
parents: [master, origin],
|
||||
tree: merge.write_tree(rugged),
|
||||
message: I18n.t('sites.fetch.merge.message'),
|
||||
author: author,
|
||||
committer: author,
|
||||
update_ref: 'HEAD')
|
||||
|
||||
# Forzamos el checkout para mover el HEAD al último commit y
|
||||
# escribir los cambios
|
||||
rugged.checkout 'HEAD', strategy: :force
|
||||
commit
|
||||
end
|
||||
# rubocop:enable Metrics/AbcSize
|
||||
# rubocop:enable Metrics/MethodLength
|
||||
|
||||
# Compara los commits entre el repositorio remoto y el actual para
|
||||
# que luego los podamos mostrar.
|
||||
def commits
|
||||
walker = Rugged::Walker.new rugged
|
||||
|
||||
# Obtenemos todos los commits que existen en origin/master que no
|
||||
# están en la rama master local
|
||||
#
|
||||
# XXX: monitorear esto por performance
|
||||
walker.push 'refs/remotes/origin/master'
|
||||
walker.hide 'refs/heads/master'
|
||||
|
||||
walker.each.to_a
|
||||
end
|
||||
|
||||
# Guarda los cambios en git, de a un archivo por vez
|
||||
# rubocop:disable Metrics/AbcSize
|
||||
def commit(file:, usuarie:, message:)
|
||||
rugged.index.add(file)
|
||||
rugged.index.write
|
||||
|
||||
Rugged::Commit.create(rugged,
|
||||
update_ref: 'HEAD',
|
||||
parents: [rugged.head.target],
|
||||
tree: rugged.index.write_tree,
|
||||
message: message,
|
||||
author: author(usuarie),
|
||||
committer: committer)
|
||||
end
|
||||
# rubocop:enable Metrics/AbcSize
|
||||
|
||||
def author(author)
|
||||
{ name: author.name, email: author.email, time: Time.now }
|
||||
end
|
||||
|
||||
def committer
|
||||
{ name: 'Sutty', email: "sutty@#{Site.domain}", time: Time.now }
|
||||
end
|
||||
end
|
||||
end
|
46
app/models/site/writer.rb
Normal file
46
app/models/site/writer.rb
Normal file
|
@ -0,0 +1,46 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Site
|
||||
# Se encarga de guardar los cambios en los archivos y mantenerlos
|
||||
# actualizados en git
|
||||
class Writer
|
||||
attr_reader :site, :file, :content, :usuarie, :message
|
||||
|
||||
def initialize(site:, file:, content:, usuarie:, message:)
|
||||
@site = site
|
||||
@content = content
|
||||
@file = file
|
||||
@usuarie = usuarie
|
||||
@message = message
|
||||
end
|
||||
|
||||
# rubocop:disable Metrics/AbcSize
|
||||
def save
|
||||
r = File.open(file, File::RDWR | File::CREAT, 0o640) do |f|
|
||||
# Bloquear el archivo para que no sea accedido por otro
|
||||
# proceso u otra editora
|
||||
f.flock(File::LOCK_EX)
|
||||
|
||||
# Empezar por el principio
|
||||
f.rewind
|
||||
|
||||
# Escribir el contenido
|
||||
f.write(content)
|
||||
|
||||
# Eliminar el resto
|
||||
f.flush
|
||||
f.truncate(f.pos)
|
||||
end
|
||||
|
||||
r.zero? && site.repository.commit(file: relative_file,
|
||||
usuarie: usuarie,
|
||||
message: message)
|
||||
end
|
||||
# rubocop:enable Metrics/AbcSize
|
||||
|
||||
# Devuelve la ruta relativa a la raíz del sitio
|
||||
def relative_file
|
||||
Pathname.new(file).relative_path_from(Pathname.new(site.path)).to_s
|
||||
end
|
||||
end
|
||||
end
|
3
app/models/site_stat.rb
Normal file
3
app/models/site_stat.rb
Normal file
|
@ -0,0 +1,3 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
SiteStat = Struct.new(:site)
|
|
@ -11,6 +11,10 @@ class Usuarie < ApplicationRecord
|
|||
has_many :roles
|
||||
has_many :sites, through: :roles
|
||||
|
||||
def name
|
||||
email.split('@', 2).first
|
||||
end
|
||||
|
||||
def rol_for_site(site)
|
||||
site.roles.merge(roles).first
|
||||
end
|
||||
|
|
|
@ -16,13 +16,35 @@ class SitePolicy
|
|||
|
||||
# Todes les usuaries pueden ver el sitio si aceptaron la invitación
|
||||
def show?
|
||||
!@usuarie.rol_for_site(@site).temporal
|
||||
!current_role.temporal
|
||||
end
|
||||
|
||||
# Todes pueden crear nuevos sitios
|
||||
def new?
|
||||
true
|
||||
end
|
||||
|
||||
def create?
|
||||
new?
|
||||
end
|
||||
|
||||
# Para poder editarlos también tienen que haber aceptado la invitación
|
||||
def edit?
|
||||
show? && usuarie?
|
||||
end
|
||||
|
||||
def update?
|
||||
edit?
|
||||
end
|
||||
|
||||
def destroy?
|
||||
edit?
|
||||
end
|
||||
|
||||
# Les invitades no pueden generar el sitio y les usuaries solo hasta
|
||||
# que aceptan la invitación
|
||||
def build?
|
||||
show? && !site.invitade?(usuarie)
|
||||
show? && usuarie?
|
||||
end
|
||||
|
||||
def send_public_file?
|
||||
|
@ -33,11 +55,33 @@ class SitePolicy
|
|||
build?
|
||||
end
|
||||
|
||||
def build_log?
|
||||
build?
|
||||
end
|
||||
|
||||
def reorder_posts?
|
||||
build?
|
||||
end
|
||||
|
||||
def pull?
|
||||
build?
|
||||
end
|
||||
|
||||
def fetch?
|
||||
pull?
|
||||
end
|
||||
|
||||
def merge?
|
||||
pull?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def current_role
|
||||
usuarie.rol_for_site(site)
|
||||
end
|
||||
|
||||
def usuarie?
|
||||
site.usuarie? usuarie
|
||||
end
|
||||
|
||||
def invitade?
|
||||
site.invitade? usuarie
|
||||
end
|
||||
end
|
||||
|
|
15
app/policies/site_stat_policy.rb
Normal file
15
app/policies/site_stat_policy.rb
Normal file
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Política de acceso a las estadísticas
|
||||
class SiteStatPolicy
|
||||
attr_reader :site_stat, :usuarie
|
||||
|
||||
def initialize(usuarie, site_stat)
|
||||
@usuarie = usuarie
|
||||
@site_stat = site_stat
|
||||
end
|
||||
|
||||
def index?
|
||||
site_stat.site.usuarie? usuarie
|
||||
end
|
||||
end
|
17
app/views/deploy_mailer/deployed.html.haml
Normal file
17
app/views/deploy_mailer/deployed.html.haml
Normal file
|
@ -0,0 +1,17 @@
|
|||
%h1= t('.hi')
|
||||
|
||||
= sanitize_markdown t('.explanation', fqdn: @deploy_local.fqdn),
|
||||
tags: %w[p a strong em]
|
||||
|
||||
%table
|
||||
%thead
|
||||
%tr
|
||||
%th= t('.th.type')
|
||||
%th= t('.th.status')
|
||||
%tbody
|
||||
- @deploys.each do |deploy, value|
|
||||
%tr
|
||||
%td= t(".#{deploy}.title")
|
||||
%td= value ? t(".#{deploy}.success") : t(".#{deploy}.error")
|
||||
|
||||
= sanitize_markdown t('.help'), tags: %w[p a strong em]
|
12
app/views/deploy_mailer/deployed.text.haml
Normal file
12
app/views/deploy_mailer/deployed.text.haml
Normal file
|
@ -0,0 +1,12 @@
|
|||
= "# #{t('.hi')}"
|
||||
\
|
||||
= t('.explanation', fqdn: @deploy_local.fqdn)
|
||||
\
|
||||
= Terminal::Table.new do |table|
|
||||
- table << [t('.th.type'), t('.th.status')]
|
||||
- table.add_separator
|
||||
- @deploys.each do |deploy, value|
|
||||
- table << [t(".#{deploy}.title"),
|
||||
value ? t(".#{deploy}.success") : t(".#{deploy}.error")]
|
||||
\
|
||||
= t('.help')
|
14
app/views/deploys/_deploy_local.haml
Normal file
14
app/views/deploys/_deploy_local.haml
Normal file
|
@ -0,0 +1,14 @@
|
|||
-#
|
||||
Formulario para alojamiento local. Como el alojamiento local no es
|
||||
opcional aun, solo enviamos el tipo con el formulario, no necesitamos
|
||||
nada más.
|
||||
|
||||
.row
|
||||
.col
|
||||
%h3= t('.title')
|
||||
- name = site.name || t('.ejemplo')
|
||||
= sanitize_markdown t('.help',
|
||||
fqdn: deploy.object.fqdn || "#{name}.#{Site.domain}"),
|
||||
tags: %w[p strong em a]
|
||||
|
||||
= deploy.hidden_field :type
|
21
app/views/deploys/_deploy_zip.haml
Normal file
21
app/views/deploys/_deploy_zip.haml
Normal file
|
@ -0,0 +1,21 @@
|
|||
-# Formulario para "alojar" en un zip
|
||||
|
||||
.row
|
||||
.col
|
||||
= deploy.hidden_field :id
|
||||
= deploy.hidden_field :type
|
||||
%h3
|
||||
-#
|
||||
El checkbox invierte la lógica de destrucción porque queremos
|
||||
crear el deploy si está activado y destruirlo si está
|
||||
desactivado.
|
||||
= deploy.check_box :_destroy,
|
||||
{ checked: deploy.object.persisted? },
|
||||
'0', '1'
|
||||
= deploy.label :_destroy, t('.title')
|
||||
-# TODO: secar la generación de URLs
|
||||
- name = site.name || t('.ejemplo')
|
||||
= sanitize_markdown t('.help',
|
||||
fqdn: deploy.object.fqdn || "#{name}.#{Site.domain}",
|
||||
file: deploy.object.file || "#{name}.zip"),
|
||||
tags: %w[p strong em a]
|
|
@ -1,4 +1,3 @@
|
|||
- binding.pry
|
||||
%p= t("devise.mailer.invitation_instructions.hello", email: @resource.email)
|
||||
%p= t("devise.mailer.invitation_instructions.someone_invited_you", url: @resource.sites.first.name)
|
||||
%p= link_to t("devise.mailer.invitation_instructions.accept"), accept_invitation_url(@resource, invitation_token: @token)
|
||||
|
|
|
@ -1,42 +1,65 @@
|
|||
.row
|
||||
.col
|
||||
= render 'layouts/breadcrumb',
|
||||
crumbs: [link_to(t('.index'), sites_path), t('.title')]
|
||||
|
||||
.row.align-items-center.justify-content-center.full-height
|
||||
.col-md-6.align-self-center
|
||||
%h2= t('.title', resource: resource.model_name.human)
|
||||
= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f|
|
||||
%h2= t('.title')
|
||||
= form_for(resource,
|
||||
as: resource_name,
|
||||
url: registration_path(resource_name),
|
||||
html: { method: :put }) do |f|
|
||||
|
||||
= render 'devise/shared/error_messages', resource: resource
|
||||
|
||||
.form-group
|
||||
= f.label :email
|
||||
= f.email_field :email, autofocus: true, autocomplete: 'email',
|
||||
class: 'form-control'
|
||||
- if devise_mapping.confirmable? && resource.pending_reconfirmation?
|
||||
%div= t('.currently_waiting_confirmation_for_email', email: resource.unconfirmed_email)
|
||||
%div
|
||||
= t('.currently_waiting_confirmation_for_email',
|
||||
email: resource.unconfirmed_email)
|
||||
|
||||
.form-group
|
||||
= f.label :lang
|
||||
= f.select :lang,
|
||||
I18n.available_locales.map { |lang| [t(lang), lang] }, {},
|
||||
class: 'form-control'
|
||||
|
||||
.form-group
|
||||
= f.label :password
|
||||
%i
|
||||
(#{t('.leave_blank_if_you_don_t_want_to_change_it')})
|
||||
= f.password_field :password, autocomplete: 'new-password',
|
||||
class: 'form-control'
|
||||
- if @minimum_password_length
|
||||
%em= t('devise.shared.minimum_password_length', count: @minimum_password_length)
|
||||
class: 'form-control', 'aria-describedby': 'password-help'
|
||||
%small.text-muted.form-text#password-help
|
||||
= t('.leave_blank_if_you_don_t_want_to_change_it')
|
||||
- if @minimum_password_length
|
||||
= t('devise.shared.minimum_password_length',
|
||||
count: @minimum_password_length)
|
||||
|
||||
.form-group
|
||||
= f.label :password_confirmation
|
||||
= f.password_field :password_confirmation,
|
||||
autocomplete: 'new-password',
|
||||
class: 'form-control'
|
||||
|
||||
.form-group
|
||||
= f.label :current_password
|
||||
%i
|
||||
(#{t('.we_need_your_current_password_to_confirm_your_changes')})
|
||||
= f.password_field :current_password,
|
||||
autocomplete: 'current-password',
|
||||
class: 'form-control'
|
||||
required: true,
|
||||
class: 'form-control',
|
||||
'aria-describedby': 'current-password-help'
|
||||
%small.text-muted.form-text#current-password-help
|
||||
= t('.we_need_your_current_password_to_confirm_your_changes')
|
||||
.actions
|
||||
= f.submit t('.update'),
|
||||
class: 'btn btn-lg btn-primary btn-block'
|
||||
%hr/
|
||||
%h3= t('.cancel_my_account')
|
||||
%p
|
||||
= t('.unhappy')
|
||||
= button_to t('.cancel_my_account'),
|
||||
registration_path(resource_name),
|
||||
data: { confirm: t('.are_you_sure') },
|
||||
method: :delete
|
||||
= link_to t('devise.shared.links.back'), :back
|
||||
method: :delete, class: 'btn btn-danger btn-block'
|
||||
|
|
|
@ -1,17 +1,20 @@
|
|||
%nav{'aria-label': 'breadcrumb', role: 'navigation'}
|
||||
%nav{ 'aria-label': 'breadcrumb', role: 'navigation' }
|
||||
%ol.breadcrumb
|
||||
%li.breadcrumb-item
|
||||
= link_to destroy_usuarie_session_path, method: :delete,
|
||||
data: { toggle: 'tooltip' }, title: t('help.logout'),
|
||||
role: 'button', class: 'btn-text' do
|
||||
= fa_icon 'sign-out', title: t('help.logout')
|
||||
- if help = @site.try(:config).try(:dig, 'help')
|
||||
%li.breadcrumb-item
|
||||
= link_to edit_usuarie_registration_path,
|
||||
data: { toggle: 'tooltip' }, title: t('help.usuarie.edit') do
|
||||
= current_usuarie.email
|
||||
|
||||
- if @site.try(:persisted?) && (help = @site.try(:config).try(:dig, 'help'))
|
||||
%li.breadcrumb-item= link_to t('.help'), help, target: '_blank'
|
||||
|
||||
- crumbs.compact.each do |crumb|
|
||||
- if current_user.is_a? Invitadx
|
||||
- if /\/sites/ =~ crumb
|
||||
- next
|
||||
- if crumb == crumbs.last
|
||||
%li.breadcrumb-item.active{'aria-current': 'page'}= crumb
|
||||
%li.breadcrumb-item.active{ 'aria-current': 'page' }= crumb
|
||||
- else
|
||||
%li.breadcrumb-item= crumb
|
||||
|
|
1
app/views/layouts/_time.haml
Normal file
1
app/views/layouts/_time.haml
Normal file
|
@ -0,0 +1 @@
|
|||
%time{ datetime: time, title: time }= time_ago_in_words time
|
|
@ -1,14 +1,26 @@
|
|||
!!!
|
||||
%html
|
||||
%head
|
||||
%meta{content: "text/html; charset=UTF-8", 'http-equiv': "Content-Type"}/
|
||||
%meta{ content: 'text/html; charset=UTF-8',
|
||||
'http-equiv': 'Content-Type' }/
|
||||
%title Sutty
|
||||
|
||||
= csrf_meta_tags
|
||||
= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload'
|
||||
= javascript_include_tag 'application', 'data-turbolinks-track': 'reload'
|
||||
- if @site.try(:config).try(:dig, 'css')
|
||||
%link{rel: 'stylesheet', type: 'text/css', href: @site.get_url_from_site(@site.config.dig('css'))}
|
||||
- style = "background-image: url(#{@site.try(:cover) || image_url('background.jpg')})"
|
||||
%body{class: @has_cover ? 'background-cover' : '', style: @has_cover ? style : ''}
|
||||
#sutty.container-fluid
|
||||
= stylesheet_link_tag 'application', media: 'all',
|
||||
'data-turbolinks-track': 'reload'
|
||||
= javascript_include_tag 'application',
|
||||
'data-turbolinks-track': 'reload'
|
||||
|
||||
- if @site.try(:persisted?) && @site.try(:config).try(:dig, 'css')
|
||||
%link{ rel: 'stylesheet',
|
||||
type: 'text/css',
|
||||
href: @site.get_url_from_site(@site.config.dig('css')) }
|
||||
|
||||
- style = "background-image: url(#{@site.try(:cover)})"
|
||||
-# haml-lint:disable InlineStyles
|
||||
%body{ class: @has_cover ? 'background-cover' : '',
|
||||
style: @has_cover ? style : '' }
|
||||
|
||||
.container-fluid#sutty
|
||||
= yield
|
||||
-# haml-lint:enable InlineStyles
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
!!!
|
||||
%html
|
||||
%head
|
||||
%meta{:content => "text/html; charset=utf-8", "http-equiv" => "Content-Type"}/
|
||||
:css
|
||||
/* Email styles need to be inline */
|
||||
%body
|
||||
= yield
|
|
@ -1,3 +1,12 @@
|
|||
!!!
|
||||
%html
|
||||
%head
|
||||
%meta{ content: 'text/html; charset=utf-8',
|
||||
'http-equiv': 'Content-Type' }/
|
||||
:css
|
||||
/* Inline */
|
||||
%body
|
||||
= yield
|
||||
|
||||
= image_tag attachments['logo.png'].url, alt: 'Logo de Sutty'
|
||||
= t('.signature')
|
||||
|
|
|
@ -1 +1,3 @@
|
|||
= yield
|
||||
|
||||
= t('.signature')
|
||||
|
|
|
@ -1,19 +1,22 @@
|
|||
- tags = %w[h1 h2 h3 h4 h5 h6 p a ul ol li table tr td th tbody thead tfoot em strong sup blockquote cite pre]
|
||||
|
||||
.row
|
||||
.col
|
||||
= render 'layouts/breadcrumb',
|
||||
crumbs: [ link_to(t('sites.index'), sites_path),
|
||||
@site.name,
|
||||
link_to(t('posts.index'),
|
||||
site_posts_path(@site)),
|
||||
@post.title ]
|
||||
crumbs: [link_to(t('sites.index'), sites_path),
|
||||
@site.name,
|
||||
link_to(t('posts.index'),
|
||||
site_posts_path(@site)),
|
||||
@post.title]
|
||||
|
||||
.row
|
||||
.col
|
||||
%h1{class: @post.get_front_matter(:dir)}= @post.title
|
||||
%h1{ class: @post.get_front_matter(:dir) }= @post.title
|
||||
|
||||
%p
|
||||
- translations = @post.translations.map do |translation|
|
||||
- link_to translation.title, site_post_path(@site, translation, lang: translation.lang)
|
||||
- link_to translation.title,
|
||||
site_post_path(@site, translation, lang: translation.lang)
|
||||
= raw translations.join(' / ')
|
||||
|
||||
.row
|
||||
|
@ -24,16 +27,17 @@
|
|||
|
||||
.row
|
||||
.col
|
||||
.content{class: @post.get_front_matter(:dir)}
|
||||
:markdown
|
||||
#{@post.content}
|
||||
.content{ class: @post.get_front_matter(:dir) }
|
||||
= sanitize_markdown @post.content,
|
||||
tags: tags
|
||||
|
||||
-# Representar los datos en una tabla:
|
||||
-# Texto: tal cual en una celda
|
||||
-# Array: píldoras
|
||||
-# Array de Hashes: Tabla
|
||||
-# Hash: Tabla
|
||||
-# TODO DRY
|
||||
-#
|
||||
Representar los datos en una tabla:
|
||||
Texto: tal cual en una celda
|
||||
Array: píldoras
|
||||
Array de Hashes: Tabla
|
||||
Hash: Tabla
|
||||
TODO DRY
|
||||
%table.table.table-condensed.table-striped.table-responsive
|
||||
%tbody
|
||||
- @post.front_matter.each do |key, data|
|
||||
|
@ -51,7 +55,7 @@
|
|||
%tbody
|
||||
- data.each do |r|
|
||||
%tr
|
||||
- r.each do |_,v|
|
||||
- r.each do |_, v|
|
||||
%td
|
||||
- if v.is_a? Array
|
||||
- v.each do |s|
|
||||
|
@ -73,16 +77,15 @@
|
|||
%td= v
|
||||
- elsif data.respond_to? :content
|
||||
-# Contenido del artículo
|
||||
:markdown
|
||||
#{data.content}
|
||||
= sanitize_markdown data.content, tags: tags
|
||||
- elsif data.respond_to? :strftime
|
||||
-# Fecha
|
||||
= data.strftime('%F')
|
||||
- else
|
||||
-# Texto
|
||||
- if @post.image? key
|
||||
%img.img-fluid{src: @site.get_url_for_sutty(data)}
|
||||
%img.img-fluid{ src: @site.get_url_for_sutty(data) }
|
||||
- elsif @post.url? key
|
||||
%a{href: @site.get_url_for_sutty(data)}= data
|
||||
%a{ href: @site.get_url_for_sutty(data) }= data
|
||||
- else
|
||||
= data
|
||||
|
|
97
app/views/sites/_form.haml
Normal file
97
app/views/sites/_form.haml
Normal file
|
@ -0,0 +1,97 @@
|
|||
= form_for site, html: { class: form_class(site) } do |f|
|
||||
.form-group
|
||||
%h2= f.label :name
|
||||
%p.lead= t('.help.name')
|
||||
-#
|
||||
El dominio contiene letras y números
|
||||
No puede empezar ni terminar con guiones
|
||||
No puede estar compuesto solo de números
|
||||
|
||||
= f.text_field :name,
|
||||
class: form_control(site, :name),
|
||||
required: true,
|
||||
pattern: '^([a-z0-9][a-z0-9\-]*)?[a-z0-9]$',
|
||||
minlength: 1,
|
||||
maxlength: 63
|
||||
- if invalid? site, :name
|
||||
.invalid-feedback= site.errors.messages[:name].join(', ')
|
||||
|
||||
.form-group
|
||||
%h2= f.label :title
|
||||
%p.lead= t('.help.title')
|
||||
= f.text_field :title, class: form_control(site, :title),
|
||||
required: true
|
||||
- if invalid? site, :title
|
||||
.invalid-feedback= site.errors.messages[:title].join(', ')
|
||||
|
||||
.form-group
|
||||
%h2= f.label :description
|
||||
%p.lead= t('.help.description')
|
||||
= f.text_area :description, class: form_control(site, :description),
|
||||
maxlength: 160, minlength: 50, required: true
|
||||
- if invalid? site, :description
|
||||
.invalid-feedback= site.errors.messages[:description].join(', ')
|
||||
%hr/
|
||||
|
||||
.form-group
|
||||
%h2= t('.design.title')
|
||||
%p.lead= t('.help.design')
|
||||
.row
|
||||
-# Demasiado complejo para un f.collection_radio_buttons
|
||||
- Design.all.each do |design|
|
||||
.col
|
||||
%h3
|
||||
= f.radio_button :design_id, design.id,
|
||||
checked: design.id == site.design_id,
|
||||
disabled: design.disabled
|
||||
= f.label "design_id_#{design.id}", design.name
|
||||
= sanitize_markdown design.description,
|
||||
tags: %w[p a strong em]
|
||||
|
||||
.btn-group{ role: 'group', 'aria-label': t('.design.actions') }
|
||||
- if design.url
|
||||
= link_to t('.design.url'), design.url,
|
||||
target: '_blank', class: 'btn btn-info'
|
||||
- if design.license
|
||||
= link_to t('.design.license'), design.license,
|
||||
target: '_blank', class: 'btn btn-info'
|
||||
%hr/
|
||||
|
||||
.form-group
|
||||
%h2= t('.licencia.title')
|
||||
%p.lead= t('.help.licencia')
|
||||
- Licencia.all.each do |licencia|
|
||||
.row
|
||||
.col
|
||||
%h3
|
||||
= f.radio_button :licencia_id, licencia.id,
|
||||
checked: licencia.id == site.licencia_id
|
||||
= f.label "licencia_id_#{licencia.id}" do
|
||||
= image_tag licencia.icons, alt: licencia.name
|
||||
= licencia.name
|
||||
= sanitize_markdown licencia.description,
|
||||
tags: %w[p a strong em ul ol li h1 h2 h3 h4 h5 h6]
|
||||
|
||||
.btn-group{ role: 'group', 'aria-label': t('.licencia.actions') }
|
||||
= link_to t('.licencia.url'), licencia.url,
|
||||
target: '_blank', class: 'btn btn-info'
|
||||
|
||||
%hr/
|
||||
|
||||
.form-group
|
||||
%h2= t('.privacidad.title')
|
||||
%p.lead= sanitize_markdown t('.help.privacidad'), tags: %w[a]
|
||||
|
||||
%hr/
|
||||
|
||||
.form-group
|
||||
%h2= t('.deploys.title')
|
||||
%p.lead= t('.help.deploys')
|
||||
|
||||
= f.fields_for :deploys do |deploy|
|
||||
= render "deploys/#{deploy.object.type.underscore}",
|
||||
deploy: deploy, site: site
|
||||
%hr/
|
||||
|
||||
.form-group
|
||||
= f.submit submit, class: 'btn btn-success'
|
10
app/views/sites/edit.haml
Normal file
10
app/views/sites/edit.haml
Normal file
|
@ -0,0 +1,10 @@
|
|||
.row
|
||||
.col
|
||||
= render 'layouts/breadcrumb',
|
||||
crumbs: [link_to(t('sites.index'), sites_path),
|
||||
t('.title', site: @site.name)]
|
||||
.row
|
||||
.col
|
||||
%h1= t('.title', site: @site.name)
|
||||
|
||||
= render 'form', site: @site, submit: t('.submit')
|
34
app/views/sites/fetch.haml
Normal file
34
app/views/sites/fetch.haml
Normal file
|
@ -0,0 +1,34 @@
|
|||
.row
|
||||
.col
|
||||
= render 'layouts/breadcrumb',
|
||||
crumbs: [link_to(t('sites.index'), sites_path), t('.title')]
|
||||
.row.justify-content-center
|
||||
.col-md-8#pull
|
||||
%h1= t('.title')
|
||||
%p.lead= sanitize_markdown t('.help.fetch'), tags: %w[em strong a]
|
||||
|
||||
%h2= t('.toc')
|
||||
%ul.toc
|
||||
- @commits.each do |commit|
|
||||
%li= link_to commit.summary, "##{commit.oid}"
|
||||
|
||||
- @commits.each do |commit|
|
||||
.row.justify-content-center
|
||||
.col-md-8{ id: commit.oid }
|
||||
%h1= commit.summary
|
||||
%p.lead= render 'layouts/time', time: commit.time
|
||||
|
||||
-#
|
||||
No hay forma de obtener el cuerpo del commit separado del
|
||||
resumen, cortamos por el primer salto de línea doble y obtenemos
|
||||
todo lo demás
|
||||
= sanitize_markdown commit.message.split("\n\n", 2).last,
|
||||
tags: %w[p a h1 h2 h3 h4 h5 h6 ol ul li strong em]
|
||||
|
||||
%hr
|
||||
|
||||
- unless @commits.empty?
|
||||
.row.justify-content-center
|
||||
.col-md-8
|
||||
= link_to t('.merge.request'), site_pull_path(@site),
|
||||
method: 'post', class: 'btn btn-lg btn-success'
|
|
@ -1,10 +1,13 @@
|
|||
|
||||
.row
|
||||
.col
|
||||
= render 'layouts/breadcrumb', crumbs: [ t('sites.index') ]
|
||||
= render 'layouts/breadcrumb', crumbs: [t('sites.index')]
|
||||
.row
|
||||
.col
|
||||
%h1= t('sites.title')
|
||||
%h1
|
||||
= t('sites.title')
|
||||
- if policy(Site).new?
|
||||
= link_to t('sites.new.title'), new_site_path,
|
||||
class: 'btn btn-info'
|
||||
|
||||
= render 'layouts/help', help: t('help.sites.index')
|
||||
|
||||
|
@ -16,14 +19,15 @@
|
|||
%h2
|
||||
- if policy(site).show?
|
||||
= link_to site.name, site_path(site)
|
||||
- else
|
||||
- else
|
||||
= site.name
|
||||
- if site.invitade? current_usuarie
|
||||
%span.badge.badge-warning{data: { toggle: 'tooltip' },
|
||||
title: t('help.sites.invitade')}
|
||||
%span.badge.badge-warning{ data: { toggle: 'tooltip' },
|
||||
title: t('help.sites.invitade') }
|
||||
= t('.invitade')
|
||||
%br
|
||||
.btn-group{role: 'group', 'aria-label': t('sites.actions')}
|
||||
.btn-group{ role: 'group',
|
||||
'aria-label': t('sites.actions') }
|
||||
- if current_usuarie.rol_for_site(site).temporal
|
||||
= button_to t('sites.invitations.accept'),
|
||||
site_usuaries_accept_invitation_path(site),
|
||||
|
@ -64,7 +68,8 @@
|
|||
type: 'secondary',
|
||||
link: nil
|
||||
- else
|
||||
= form_tag site_enqueue_path(site), method: :post, class: 'form-inline' do
|
||||
= form_tag site_enqueue_path(site),
|
||||
method: :post, class: 'form-inline' do
|
||||
= button_tag type: 'submit',
|
||||
class: 'btn btn-success',
|
||||
title: t('help.sites.enqueue'),
|
||||
|
@ -72,12 +77,9 @@
|
|||
= fa_icon 'building'
|
||||
= t('sites.enqueue')
|
||||
|
||||
- if policy(site).build_log?
|
||||
- if site.failed?
|
||||
%button.btn.btn-danger= t('sites.failed')
|
||||
- if site.build_log?
|
||||
= render 'layouts/btn_with_tooltip',
|
||||
tooltip: t('help.sites.build_log'),
|
||||
text: t('sites.build_log'),
|
||||
type: 'warning',
|
||||
link: site_build_log_path(site)
|
||||
- if policy(site).pull? && site.needs_pull?
|
||||
= render 'layouts/btn_with_tooltip',
|
||||
tooltip: t('help.sites.pull'),
|
||||
text: t('.pull'),
|
||||
type: 'info',
|
||||
link: site_pull_path(site)
|
||||
|
|
9
app/views/sites/new.haml
Normal file
9
app/views/sites/new.haml
Normal file
|
@ -0,0 +1,9 @@
|
|||
.row
|
||||
.col
|
||||
= render 'layouts/breadcrumb',
|
||||
crumbs: [link_to(t('sites.index'), sites_path), t('.title')]
|
||||
.row
|
||||
.col
|
||||
%h1= t('.title')
|
||||
|
||||
= render 'form', site: @site, submit: t('.submit')
|
18
app/views/stats/index.haml
Normal file
18
app/views/stats/index.haml
Normal file
|
@ -0,0 +1,18 @@
|
|||
.row
|
||||
.col
|
||||
= render 'layouts/breadcrumb',
|
||||
crumbs: [link_to(t('sites.index'), sites_path),
|
||||
link_to(@site.name, site_path(@site)), t('.title')]
|
||||
.row
|
||||
.col
|
||||
%h1= t('.title')
|
||||
%p.lead= t('.help')
|
||||
|
||||
%table.table.table-striped.table-condensed
|
||||
%tbody
|
||||
%tr
|
||||
%td= t('.build.average')
|
||||
%td= distance_of_time_in_words_if_more_than_a_minute @build_avg
|
||||
%tr
|
||||
%td= t('.build.maximum')
|
||||
%td= distance_of_time_in_words_if_more_than_a_minute @build_max
|
45
app/workers/deploy_worker.rb
Normal file
45
app/workers/deploy_worker.rb
Normal file
|
@ -0,0 +1,45 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Realiza el deploy de un sitio
|
||||
class DeployWorker
|
||||
include Sidekiq::Worker
|
||||
|
||||
def perform(site)
|
||||
site = Site.find(site)
|
||||
site.update_attribute :status, 'building'
|
||||
# Asegurarse que DeployLocal sea el primero!
|
||||
deployed = { deploy_local: deploy_local(site) }
|
||||
|
||||
# No es opcional
|
||||
unless deployed[:deploy_local]
|
||||
site.update_attribute :status, 'waiting'
|
||||
raise
|
||||
end
|
||||
|
||||
deploy_others site, deployed
|
||||
notify_usuaries site, deployed
|
||||
|
||||
site.update_attribute :status, 'waiting'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def deploy_local(site)
|
||||
site.deploys.find_by(type: 'DeployLocal').deploy
|
||||
end
|
||||
|
||||
def deploy_others(site, deployed)
|
||||
site.deploys.where.not(type: 'DeployLocal').find_each do |d|
|
||||
deployed[d.type.underscore.to_sym] = d.deploy
|
||||
end
|
||||
end
|
||||
|
||||
def notify_usuaries(site, deployed)
|
||||
# TODO: existe site.usuaries_ids?
|
||||
site.usuaries.find_each do |usuarie|
|
||||
DeployMailer.with(usuarie: usuarie.id, site: site.id)
|
||||
.deployed(deployed)
|
||||
.deliver_now
|
||||
end
|
||||
end
|
||||
end
|
29
bin/haml-lint
Executable file
29
bin/haml-lint
Executable file
|
@ -0,0 +1,29 @@
|
|||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
#
|
||||
# This file was generated by Bundler.
|
||||
#
|
||||
# The application 'haml-lint' is installed as part of a gem, and
|
||||
# this file is here to facilitate running it.
|
||||
#
|
||||
|
||||
require "pathname"
|
||||
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
||||
Pathname.new(__FILE__).realpath)
|
||||
|
||||
bundle_binstub = File.expand_path("../bundle", __FILE__)
|
||||
|
||||
if File.file?(bundle_binstub)
|
||||
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
|
||||
load(bundle_binstub)
|
||||
else
|
||||
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
||||
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
||||
end
|
||||
end
|
||||
|
||||
require "rubygems"
|
||||
require "bundler/setup"
|
||||
|
||||
load Gem.bin_path("haml_lint", "haml-lint")
|
|
@ -1,80 +0,0 @@
|
|||
#!/bin/bash
|
||||
# TODO convertir a ruby!
|
||||
set -e
|
||||
|
||||
rails_root="${PWD}"
|
||||
|
||||
# Encontrar todos los sitios únicos con el archivo `.generate`. Esto
|
||||
# significa que la usuaria quiso generar el sitio.
|
||||
find -L ./_sites -mindepth 2 -maxdepth 2 -name .generate \
|
||||
| sed "s/\/\.generate$//" \
|
||||
| while read _path ; do
|
||||
# Como seguimos todos los symlinks y los sitios pueden estar
|
||||
# vinculados entre sí, volvemos a chequear si existe el archivo para
|
||||
# no generarlo dos veces
|
||||
test -f "${_path}/.generate" || continue
|
||||
test -f "${_path}/.generating" && continue
|
||||
|
||||
# Obtenemos las direcciones de correo de las responsables
|
||||
_mail=($(cat "${_path}/.usuarias"))
|
||||
_site="$(echo "${_path}" | xargs basename)"
|
||||
_deploy="${rails_root}/_deploy/${_site}"
|
||||
|
||||
# Entrar al directorio del sitio
|
||||
pushd "${_path}" &>/dev/null
|
||||
|
||||
# Reiniciar el log con la fecha
|
||||
date > build.log
|
||||
|
||||
# Instalar las gemas si no están
|
||||
test -f .bundle/config \
|
||||
|| bundle install --path=/srv/http/gems.kefir.red \
|
||||
>> build.log
|
||||
|
||||
# Actualizar las gemas
|
||||
bundle >> build.log
|
||||
# Instalar los assets
|
||||
test -f yarn.lock \
|
||||
&& yarn >> build.log
|
||||
|
||||
# Crear el sitio con lujo de detalles y guardar un log, pero a la vez
|
||||
# tenerlo en la salida estándar para poder enviar al MAILTO del
|
||||
# cronjob.
|
||||
#
|
||||
# Ya que estamos, eliminamos la ruta donde estamos paradas para no dar
|
||||
# información sobre la servidora.
|
||||
touch .generating
|
||||
# Correr en baja prioridad
|
||||
nice -n 19 \
|
||||
bundle exec \
|
||||
jekyll build --trace --destination "${_deploy}" 2>&1 \
|
||||
| sed -re "s,${_path},,g" \
|
||||
>> "build.log"
|
||||
|
||||
# Acciones posteriores
|
||||
# TODO convertir en un plugin de cada sitio?
|
||||
if test $? -eq 0; then
|
||||
# Si funciona, enviar un mail
|
||||
# TODO enviar un mail más completo y no hardcodear direcciones
|
||||
echo "Everything was good! You can see your changes in https://${_site}" \
|
||||
| mail -b "sysadmin@kefir.red" \
|
||||
-s "${_site}: :)" \
|
||||
${_mail[@]}
|
||||
else
|
||||
echo "There was an error, please check build log at https://sutty.kefir.red/" \
|
||||
| mail -b "sysadmin@kefir.red" \
|
||||
-s "${_site}: :(" \
|
||||
${_mail[@]}
|
||||
date +%s >.failed
|
||||
fi
|
||||
|
||||
# Eliminar el archivo para sacar el sitio de la cola de compilación
|
||||
rm -f .generate .generating
|
||||
# TODO descubrir el grupo según la distro?
|
||||
chgrp -R http "${_deploy}"
|
||||
find "${_deploy}" -type f -print0 | xargs -r -0 chmod 640
|
||||
find "${_deploy}" -type d -print0 | xargs -r -0 chmod 2750
|
||||
|
||||
# Volver al principio para continuar con el siguiente sitio
|
||||
popd &>/dev/null
|
||||
done
|
|
@ -1,11 +1,4 @@
|
|||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
begin
|
||||
load File.expand_path('spring', __dir__)
|
||||
rescue LoadError => e
|
||||
raise unless e.message.include?('spring')
|
||||
end
|
||||
APP_PATH = File.expand_path('../config/application', __dir__)
|
||||
require_relative '../config/boot'
|
||||
require 'rails/commands'
|
||||
|
|
7
bin/rake
7
bin/rake
|
@ -1,11 +1,4 @@
|
|||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
begin
|
||||
load File.expand_path('spring', __dir__)
|
||||
rescue LoadError => e
|
||||
raise unless e.message.include?('spring')
|
||||
end
|
||||
require_relative '../config/boot'
|
||||
require 'rake'
|
||||
Rake.application.run
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'pathname'
|
||||
require 'fileutils'
|
||||
include FileUtils
|
||||
|
||||
# path to your application root.
|
||||
APP_ROOT = Pathname.new File.expand_path('..', __dir__)
|
||||
APP_ROOT = File.expand_path('..', __dir__)
|
||||
|
||||
def system!(*args)
|
||||
system(*args) || abort("\n== Command #{args} failed ==")
|
||||
|
|
32
bin/sidekiq
Executable file
32
bin/sidekiq
Executable file
|
@ -0,0 +1,32 @@
|
|||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
#
|
||||
# This file was generated by Bundler.
|
||||
#
|
||||
# The application 'sidekiq' is installed as part of a gem, and
|
||||
# this file is here to facilitate running it.
|
||||
#
|
||||
|
||||
require 'pathname'
|
||||
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile',
|
||||
Pathname.new(__FILE__).realpath)
|
||||
|
||||
bundle_binstub = File.expand_path('bundle', __dir__)
|
||||
|
||||
if File.file?(bundle_binstub)
|
||||
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
|
||||
load(bundle_binstub)
|
||||
else
|
||||
abort(
|
||||
'Your `bin/bundle` was not generated by Bundler, so this binstub
|
||||
cannot run. Replace `bin/bundle` by running `bundle binstubs
|
||||
bundler --force`, then run this command again.'
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
require 'rubygems'
|
||||
require 'bundler/setup'
|
||||
|
||||
load Gem.bin_path('sidekiq', 'sidekiq')
|
32
bin/sidekiqctl
Executable file
32
bin/sidekiqctl
Executable file
|
@ -0,0 +1,32 @@
|
|||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
#
|
||||
# This file was generated by Bundler.
|
||||
#
|
||||
# The application 'sidekiqctl' is installed as part of a gem, and
|
||||
# this file is here to facilitate running it.
|
||||
#
|
||||
|
||||
require 'pathname'
|
||||
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile',
|
||||
Pathname.new(__FILE__).realpath)
|
||||
|
||||
bundle_binstub = File.expand_path('bundle', __dir__)
|
||||
|
||||
if File.file?(bundle_binstub)
|
||||
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
|
||||
load(bundle_binstub)
|
||||
else
|
||||
abort(
|
||||
'Your `bin/bundle` was not generated by Bundler, so this binstub
|
||||
cannot run. Replace `bin/bundle` by running `bundle binstubs
|
||||
bundler --force`, then run this command again.'
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
require 'rubygems'
|
||||
require 'bundler/setup'
|
||||
|
||||
load Gem.bin_path('sidekiq', 'sidekiqctl')
|
|
@ -1,12 +1,9 @@
|
|||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'pathname'
|
||||
require 'fileutils'
|
||||
include FileUtils
|
||||
|
||||
# path to your application root.
|
||||
APP_ROOT = Pathname.new File.expand_path('..', __dir__)
|
||||
APP_ROOT = File.expand_path('..', __dir__)
|
||||
|
||||
def system!(*args)
|
||||
system(*args) || abort("\n== Command #{args} failed ==")
|
||||
|
@ -20,6 +17,9 @@ chdir APP_ROOT do
|
|||
system! 'gem install bundler --conservative'
|
||||
system('bundle check') || system!('bundle install')
|
||||
|
||||
# Install JavaScript dependencies if using Yarn
|
||||
# system('bin/yarn')
|
||||
|
||||
puts "\n== Updating database =="
|
||||
system! 'bin/rails db:migrate'
|
||||
|
||||
|
|
11
bin/yarn
Executable file
11
bin/yarn
Executable file
|
@ -0,0 +1,11 @@
|
|||
#!/usr/bin/env ruby
|
||||
APP_ROOT = File.expand_path('..', __dir__)
|
||||
Dir.chdir(APP_ROOT) do
|
||||
begin
|
||||
exec "yarnpkg", *ARGV
|
||||
rescue Errno::ENOENT
|
||||
$stderr.puts "Yarn executable was not detected in the system."
|
||||
$stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install"
|
||||
exit 1
|
||||
end
|
||||
end
|
|
@ -19,13 +19,18 @@ require 'rails/test_unit/railtie'
|
|||
Bundler.require(*Rails.groups)
|
||||
|
||||
module Sutty
|
||||
# Sutty!
|
||||
class Application < Rails::Application
|
||||
# Initialize configuration defaults for originally generated Rails version.
|
||||
# Initialize configuration defaults for originally generated Rails
|
||||
# version.
|
||||
config.load_defaults 5.1
|
||||
|
||||
# Settings in config/environments/* take precedence over those specified here.
|
||||
# Application configuration should go into files in config/initializers
|
||||
# -- all .rb files in that directory are automatically loaded.
|
||||
config.action_dispatch.rescue_responses['Pundit::NotAuthorizedError'] = :forbidden
|
||||
# Settings in config/environments/* take precedence over those
|
||||
# specified here. Application configuration should go into files in
|
||||
# config/initializers -- all .rb files in that directory are
|
||||
# automatically loaded.
|
||||
config.action_dispatch
|
||||
.rescue_responses['Pundit::NotAuthorizedError'] = :forbidden
|
||||
config.active_record.sqlite3.represent_boolean_as_integer = true
|
||||
end
|
||||
end
|
||||
|
|
|
@ -22,4 +22,4 @@ test:
|
|||
|
||||
production:
|
||||
<<: *default
|
||||
database: db/production.sqlite3
|
||||
database: data/production.sqlite3
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
Rails.application.configure do
|
||||
# Settings specified here will take precedence over those in config/application.rb.
|
||||
# Settings specified here will take precedence over those in
|
||||
# config/application.rb.
|
||||
|
||||
# In the development environment your application's code is reloaded on
|
||||
# every request. This slows down response time but is perfect for development
|
||||
# since you don't have to restart the web server when you make code changes.
|
||||
# In the development environment your application's code is reloaded
|
||||
# on every request. This slows down response time but is perfect for
|
||||
# development since you don't have to restart the web server when you
|
||||
# make code changes.
|
||||
config.cache_classes = false
|
||||
|
||||
# Do not eager load code on boot.
|
||||
|
@ -18,7 +20,7 @@ Rails.application.configure do
|
|||
if Rails.root.join('tmp/caching-dev.txt').exist?
|
||||
config.action_controller.perform_caching = true
|
||||
|
||||
config.cache_store = :memory_store
|
||||
config.cache_store = :redis_cache_store
|
||||
config.public_file_server.headers = {
|
||||
'Cache-Control' => "public, max-age=#{2.days.seconds.to_i}"
|
||||
}
|
||||
|
@ -39,8 +41,8 @@ Rails.application.configure do
|
|||
# Raise an error on page load if there are pending migrations.
|
||||
config.active_record.migration_error = :page_load
|
||||
|
||||
# Debug mode disables concatenation and preprocessing of assets.
|
||||
# This option may cause significant delays in view rendering with a large
|
||||
# Debug mode disables concatenation and preprocessing of assets. This
|
||||
# option may cause significant delays in view rendering with a large
|
||||
# number of complex assets.
|
||||
config.assets.debug = true
|
||||
|
||||
|
@ -50,8 +52,9 @@ Rails.application.configure do
|
|||
# Raises error for missing translations
|
||||
# config.action_view.raise_on_missing_translations = true
|
||||
|
||||
# Use an evented file watcher to asynchronously detect changes in source code,
|
||||
# routes, locales, etc. This feature depends on the listen gem.
|
||||
# Use an evented file watcher to asynchronously detect changes in
|
||||
# source code, routes, locales, etc. This feature depends on the
|
||||
# listen gem.
|
||||
config.file_watcher = ActiveSupport::EventedFileUpdateChecker
|
||||
|
||||
CarrierWave.configure do |config|
|
||||
|
@ -63,5 +66,6 @@ Rails.application.configure do
|
|||
config.action_mailer.perform_caching = false
|
||||
config.action_mailer.delivery_method = :letter_opener
|
||||
config.action_mailer.perform_deliveries = true
|
||||
config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
|
||||
config.action_mailer.default_url_options = { host: 'localhost',
|
||||
port: 3000 }
|
||||
end
|
||||
|
|
|
@ -56,12 +56,12 @@ Rails.application.configure do
|
|||
config.log_tags = [:request_id]
|
||||
|
||||
# Use a different cache store in production.
|
||||
# config.cache_store = :mem_cache_store
|
||||
config.cache_store = :redis_cache_store
|
||||
|
||||
# Use a real queuing backend for Active Job (and separate queues per
|
||||
# environment)
|
||||
# config.active_job.queue_adapter = :resque
|
||||
# config.active_job.queue_name_prefix = "sutty_#{Rails.env}"
|
||||
config.active_job.queue_adapter = :sidekiq
|
||||
config.active_job.queue_name_prefix = "sutty_#{Rails.env}"
|
||||
config.action_mailer.perform_caching = false
|
||||
|
||||
# Ignore bad email addresses and do not raise email delivery errors.
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'commonmarker'
|
||||
|
||||
module Haml::Filters
|
||||
remove_filter('Markdown') # remove the existing Markdown filter
|
||||
|
||||
module Markdown
|
||||
include Haml::Filters::Base
|
||||
|
||||
def render(text)
|
||||
CommonMarker.render_html(text,
|
||||
[:TABLE_PREFER_STYLE_ATTRIBUTES],
|
||||
%i[table strikethrough autolink tagfilter])
|
||||
end
|
||||
end
|
||||
end
|
3
config/initializers/core_extensions.rb
Normal file
3
config/initializers/core_extensions.rb
Normal file
|
@ -0,0 +1,3 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
String.include CoreExtensions::String::StripTags
|
|
@ -78,7 +78,7 @@ Devise.setup do |config|
|
|||
# `config.http_authenticatable = [:database]` will enable it only for
|
||||
# database authentication. The supported strategies are: :database
|
||||
# = Support basic authentication with authentication key + password
|
||||
# config.http_authenticatable = false
|
||||
config.http_authenticatable = true
|
||||
|
||||
# If 401 status code should be returned for AJAX requests. True by
|
||||
# default.
|
||||
|
@ -127,7 +127,7 @@ Devise.setup do |config|
|
|||
config.stretches = Rails.env.test? ? 1 : 11
|
||||
|
||||
# Set up a pepper to generate the hashed password.
|
||||
config.pepper = ENV['DEVISE_PEPPER']
|
||||
config.pepper = Rails.application.credentials.devise_pepper
|
||||
|
||||
# Send a notification to the original email when the user's email is
|
||||
# changed.
|
||||
|
|
|
@ -7,6 +7,8 @@ ActiveSupport::Inflector.inflections(:en) do |inflect|
|
|||
inflect.singular 'invitades', 'invitade'
|
||||
inflect.plural 'usuarie', 'usuaries'
|
||||
inflect.singular 'usuaries', 'usuarie'
|
||||
inflect.plural 'licencia', 'licencias'
|
||||
inflect.singular 'licencias', 'licencia'
|
||||
inflect.plural 'rol', 'roles'
|
||||
inflect.singular 'roles', 'rol'
|
||||
end
|
||||
|
@ -20,4 +22,6 @@ ActiveSupport::Inflector.inflections(:es) do |inflect|
|
|||
inflect.singular 'usuaries', 'usuarie'
|
||||
inflect.plural 'rol', 'roles'
|
||||
inflect.singular 'roles', 'rol'
|
||||
inflect.plural 'licencia', 'licencias'
|
||||
inflect.singular 'licencias', 'licencia'
|
||||
end
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
Rails.application.configure do
|
||||
config.i18n.available_locales = %i[es en ar]
|
||||
config.i18n.available_locales = %i[es en]
|
||||
config.i18n.default_locale = :es
|
||||
end
|
||||
|
|
97
config/initializers/mobility.rb
Normal file
97
config/initializers/mobility.rb
Normal file
|
@ -0,0 +1,97 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
Mobility.configure do |config|
|
||||
# Sets the default backend to use in models. This can be overridden in
|
||||
# models by passing +backend: ...+ to +translates+.
|
||||
config.default_backend = :key_value
|
||||
|
||||
# By default, Mobility uses the +translates+ class method in models to
|
||||
# describe translated attributes, but you can configure this method to
|
||||
# be whatever you like. This may be useful if using Mobility alongside
|
||||
# another translation gem which uses the same method name.
|
||||
config.accessor_method = :translates
|
||||
|
||||
# To query on translated attributes, you need to append a scope to
|
||||
# your model. The name of this scope is +i18n+ by default, but this
|
||||
# can be changed to something else.
|
||||
config.query_method = :i18n
|
||||
|
||||
# Uncomment and remove (or add) items to (from) this list to
|
||||
# completely disable/enable plugins globally (so they cannot be used
|
||||
# and are never even loaded). Note that if you remove an item from the
|
||||
# list, you will not be able to use the plugin at all, and any options
|
||||
# for the plugin will be ignored by models. (In most cases, you
|
||||
# probably don't want to change this.)
|
||||
#
|
||||
# config.plugins = %i[
|
||||
# query
|
||||
# cache
|
||||
# dirty
|
||||
# fallbacks
|
||||
# presence
|
||||
# default
|
||||
# attribute_methods
|
||||
# fallthrough_accessors
|
||||
# locale_accessors
|
||||
# ]
|
||||
|
||||
# The translation cache is on by default, but you can turn it off by
|
||||
# uncommenting this line. (This may be helpful in debugging.)
|
||||
#
|
||||
# config.default_options[:cache] = false
|
||||
|
||||
# Dirty tracking is disabled by default. Uncomment this line to enable
|
||||
# it. If you enable this, you should also enable +locale_accessors+
|
||||
# by default (see below).
|
||||
#
|
||||
# config.default_options[:dirty] = true
|
||||
|
||||
# No fallbacks are used by default. To define default fallbacks,
|
||||
# uncomment and set the default fallback option value here. A "true"
|
||||
# value will use whatever is defined by +I18n.fallbacks+ (if defined),
|
||||
# or alternatively will fallback to your +I18n.default_locale+.
|
||||
#
|
||||
config.default_options[:fallbacks] = true
|
||||
|
||||
# The Presence plugin converts empty strings to nil when fetching and
|
||||
# setting translations. By default it is on, uncomment this line to
|
||||
# turn it off.
|
||||
#
|
||||
# config.default_options[:presence] = false
|
||||
|
||||
# Set a default value to use if the translation is nil. By default
|
||||
# this is off, uncomment and set a default to use it across all models
|
||||
# (you probably don't want to do that).
|
||||
#
|
||||
# config.default_options[:default] = ...
|
||||
|
||||
# Uncomment to enable locale_accessors by default on models. A true
|
||||
# value will use the locales defined either in
|
||||
# Rails.application.config.i18n.available_locales or
|
||||
# I18n.available_locales. If you want something else, pass an array
|
||||
# of locales instead.
|
||||
#
|
||||
# config.default_options[:locale_accessors] = true
|
||||
|
||||
# Uncomment to enable fallthrough accessors by default on models. This
|
||||
# will allow you to call any method with a suffix like _en or _pt_br,
|
||||
# and Mobility will catch the suffix and convert it into a locale in
|
||||
# +method_missing+. If you don't need this kind of open-ended
|
||||
# fallthrough behavior, it's better to use locale_accessors instead
|
||||
# (which define methods) since method_missing is very slow. (You can
|
||||
# use both fallthrough and locale accessor plugins together without
|
||||
# conflict.)
|
||||
#
|
||||
# Note: The dirty plugin enables fallthrough_accessors by default.
|
||||
#
|
||||
# config.default_options[:fallthrough_accessors] = true
|
||||
|
||||
# You can also include backend-specific default options. For example,
|
||||
# if you want to default to using the text-type translation table with
|
||||
# the KeyValue backend, you can set that as a default by uncommenting
|
||||
# this line, or change it to :string to default to the string-type
|
||||
# translation table instead. (For other backends, this option is
|
||||
# ignored.)
|
||||
#
|
||||
# config.default_options[:type] = :text
|
||||
end
|
13
config/initializers/sidekiq.rb
Normal file
13
config/initializers/sidekiq.rb
Normal file
|
@ -0,0 +1,13 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
Sidekiq.configure_server do |config|
|
||||
config.redis = {
|
||||
url: ENV.fetch('REDIS_SERVER', 'redis://localhost:6379/1')
|
||||
}
|
||||
end
|
||||
|
||||
Sidekiq.configure_client do |config|
|
||||
config.redis = {
|
||||
url: ENV.fetch('REDIS_CLIENT', 'redis://localhost:6379/1')
|
||||
}
|
||||
end
|
|
@ -93,14 +93,14 @@ en:
|
|||
registrations:
|
||||
destroyed: Bye! Your account has been successfully cancelled. We hope to see you again soon.
|
||||
edit:
|
||||
index: 'Back to sites'
|
||||
are_you_sure: Are you sure?
|
||||
cancel_my_account: Cancel my account
|
||||
currently_waiting_confirmation_for_email: 'Currently waiting confirmation for: %{email}'
|
||||
leave_blank_if_you_don_t_want_to_change_it: leave blank if you don't want to change it
|
||||
title: Edit %{resource}
|
||||
unhappy: Unhappy?
|
||||
leave_blank_if_you_don_t_want_to_change_it: Leave empty if you don't want to change it.
|
||||
title: Edit my account
|
||||
update: Update
|
||||
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:
|
||||
sign_up: Sign up
|
||||
signed_up: Welcome! You have signed up successfully.
|
||||
|
@ -126,8 +126,8 @@ en:
|
|||
sign_in_with_provider: Sign in with %{provider}
|
||||
sign_up: Sign up
|
||||
minimum_password_length:
|
||||
one: "(%{count} character minimum)"
|
||||
other: "(%{count} characters minimum)"
|
||||
one: "%{count} character minimum."
|
||||
other: "%{count} characters minimum."
|
||||
unlocks:
|
||||
new:
|
||||
resend_unlock_instructions: Resend unlock instructions
|
||||
|
|
|
@ -93,14 +93,14 @@ es:
|
|||
registrations:
|
||||
destroyed: "¡Adiós! Tu cuenta ha sido cancelada correctamente. Esperamos verte pronto."
|
||||
edit:
|
||||
are_you_sure: "¿Estás segura?"
|
||||
cancel_my_account: Anular mi cuenta
|
||||
index: 'Volver a sitios'
|
||||
are_you_sure: "¿Estás segure?"
|
||||
cancel_my_account: Eliminar mi cuenta
|
||||
currently_waiting_confirmation_for_email: 'Actualmente esperando la confirmacion de: %{email} '
|
||||
leave_blank_if_you_don_t_want_to_change_it: dejar en blanco si no desea cambiarlo
|
||||
title: Editar %{resource}
|
||||
unhappy: "¿Disconforme?"
|
||||
update: Actualizar
|
||||
we_need_your_current_password_to_confirm_your_changes: necesitamos tu contraseña actual para confirmar los cambios
|
||||
leave_blank_if_you_don_t_want_to_change_it: Deja este campo vacío si no deseas cambiarla.
|
||||
title: Editar mi cuenta
|
||||
update: Actualizar mi perfil
|
||||
we_need_your_current_password_to_confirm_your_changes: Necesitamos tu contraseña actual para confirmar los cambios.
|
||||
new:
|
||||
sign_up: Registrarme por primera vez
|
||||
email: O simplemente continuar con tu dirección de correo y contraseña
|
||||
|
@ -110,7 +110,6 @@ es:
|
|||
signed_up_but_unconfirmed: Para recibir actualizaciones, se ha enviado un mensaje con un enlace de confirmación a tu correo electrónico. Abre el enlace para activar tu cuenta.
|
||||
update_needs_confirmation: Has actualizado tu cuenta correctamente, pero es necesario confirmar tu nuevo correo electrónico. Por favor, comprueba tu correo y sigue el enlace de confirmación para finalizar la comprobación del nuevo correo electrónico.
|
||||
updated: Tu cuenta se ha actualizado.
|
||||
updated_but_not_signed_in:
|
||||
sessions:
|
||||
already_signed_out: Sesión finalizada.
|
||||
new:
|
||||
|
@ -129,8 +128,8 @@ es:
|
|||
i_dont_have_account: ¿Nunca te registraste en LUNAR?
|
||||
i_have_account: ¿Ya tenés cuenta?
|
||||
minimum_password_length:
|
||||
one: "(%{count} caracter como mínimo)"
|
||||
other: "(%{count} caracteres como mínimo)"
|
||||
one: "%{count} caracter como mínimo."
|
||||
other: "%{count} caracteres como mínimo."
|
||||
unlocks:
|
||||
new:
|
||||
resend_unlock_instructions: Reenviar instrucciones para desbloquear
|
||||
|
|
|
@ -1,7 +1,46 @@
|
|||
en:
|
||||
es: Castillian Spanish
|
||||
en: English
|
||||
seconds: '%{seconds} seconds'
|
||||
deploy_mailer:
|
||||
deployed:
|
||||
subject: "[Sutty] The site %{site} has been built"
|
||||
hi: "Hi!"
|
||||
explanation: |
|
||||
This e-mail is to notify you that Sutty has built your site and
|
||||
it's available at <https://%{fqdn}>.
|
||||
|
||||
You'll find details bellow.
|
||||
th:
|
||||
type: Type
|
||||
status: Status
|
||||
deploy_local:
|
||||
title: Build the site
|
||||
success: Success!
|
||||
error: Error
|
||||
deploy_zip:
|
||||
title: Build ZIP file
|
||||
success: Available for download
|
||||
error: Error
|
||||
help: You can contact us by replying this e-mail
|
||||
activerecord:
|
||||
models:
|
||||
usuarie: User
|
||||
attributes:
|
||||
usuarie:
|
||||
email: 'E-mail address'
|
||||
password: 'Password'
|
||||
password_confirmation: 'Password confirmation'
|
||||
current_password: 'Current password'
|
||||
lang: 'Main language'
|
||||
site:
|
||||
name: 'Name'
|
||||
errors:
|
||||
models:
|
||||
site:
|
||||
attributes:
|
||||
deploys:
|
||||
deploy_local_presence: 'We need to be build the site!'
|
||||
invitadx:
|
||||
attributes:
|
||||
email:
|
||||
|
@ -18,6 +57,8 @@ en:
|
|||
disordered: "The posts are disordered, this will prevent you from reordering them!"
|
||||
disordered_button: 'Reorder!'
|
||||
layouts:
|
||||
mailer:
|
||||
signature: 'With love, Sutty'
|
||||
breadcrumb:
|
||||
help: Help
|
||||
collaborations:
|
||||
|
@ -25,23 +66,12 @@ en:
|
|||
submit: Register
|
||||
password:
|
||||
incorrect: 'Wrong password, please try again.'
|
||||
invitadxs:
|
||||
index:
|
||||
title: 'Guests'
|
||||
new:
|
||||
email: 'E-Mail'
|
||||
password: 'Password'
|
||||
password_confirmation: 'Repeat password'
|
||||
submit: 'Register'
|
||||
acepta_politicas_de_privacidad: 'I accept the <a href="%{privacy_policy}" target="_blank">Privacy policy</a>.'
|
||||
confirmation:
|
||||
confirmed: 'Your account is confirmed, please log in to continue'
|
||||
show:
|
||||
confirmation_sent: "We've sent a confirmation link to your e-mail address. Please open that link to continue."
|
||||
info:
|
||||
posts:
|
||||
reorder: 'The articles have been reordered!'
|
||||
help:
|
||||
usuarie:
|
||||
edit: Edit my profile
|
||||
category: 'Category'
|
||||
logout: 'Close the session'
|
||||
breadcrumbs: "What you see up here are the bread crumbs for this site. When you enter a new section, you will see the previous ones and also have a path for where you're standing."
|
||||
|
@ -140,7 +170,39 @@ en:
|
|||
logout: 'Log out'
|
||||
lang: 'Language'
|
||||
error: 'There was an error during log in. Did you type your credentials correctly?'
|
||||
deploys:
|
||||
deploy_local:
|
||||
title: 'Host at Sutty'
|
||||
help: |
|
||||
The site will be available at <https://%{fqdn}/>.
|
||||
|
||||
We're working out the details for allowing your own site
|
||||
domains, you can help us!
|
||||
ejemplo: 'example'
|
||||
deploy_zip:
|
||||
title: 'Generate a ZIP file'
|
||||
help: |
|
||||
ZIP files contain and compress all the files of your site. With
|
||||
this option you can download and also share your whole site
|
||||
through the <https://%{fqdn}/%{file}> address, keep it as backup
|
||||
or have an strategy of solidarity hosting, were many people
|
||||
shares a copy of your site.
|
||||
|
||||
It also helps with site archival for historical purposes :)
|
||||
|
||||
ejemplo: 'example'
|
||||
stats:
|
||||
index:
|
||||
title: Statistics
|
||||
help: |
|
||||
Statistics show information about how your site is generated and
|
||||
how many resources it uses.
|
||||
build:
|
||||
average: 'Average building time'
|
||||
maximum: 'Maximum building time'
|
||||
sites:
|
||||
repository:
|
||||
config: 'Changes in config'
|
||||
actions: 'Actions'
|
||||
posts: 'View and edit posts'
|
||||
title: 'Sites'
|
||||
|
@ -152,12 +214,55 @@ en:
|
|||
invitations:
|
||||
accept: 'Accept invitation'
|
||||
reject: 'No, thanks'
|
||||
new:
|
||||
title: 'Create site'
|
||||
submit: 'Create site'
|
||||
edit:
|
||||
title: 'Edit %{site}'
|
||||
submit: 'Save changes'
|
||||
form:
|
||||
help:
|
||||
name: "Your site's name. It can only contain numbers and letters."
|
||||
design: 'Select the design for your site. You can change it later. We add more designs from time to time.'
|
||||
licencia: 'Everything we publish has automatic copyright. This
|
||||
means nobody can use our works without explicit permission. By
|
||||
using licenses, we stablish conditions by which we want to share
|
||||
them.'
|
||||
privacidad: |
|
||||
The [privacy policy](https://sutty.nl/en/privacy-policy/) and
|
||||
[code of conduct](https://sutty.nl/en/code-of-conduct/) inform
|
||||
your visitors about their privacy and expected conduct of the
|
||||
site's community. We suggest you use the same documents Sutty
|
||||
uses. You can modify them as articles after creating the
|
||||
site.
|
||||
deploys: |
|
||||
Sutty allows you to host your site in different places at the
|
||||
same time. This strategy makes your site available even when
|
||||
some of them become unavailable.
|
||||
design:
|
||||
title: 'Design'
|
||||
actions: 'Information about this design'
|
||||
url: 'Demo'
|
||||
licencia: 'Read the license'
|
||||
licencia:
|
||||
title: 'License for the site and everything in it'
|
||||
url: 'Read the license'
|
||||
privacidad:
|
||||
title: 'Privacy policy and code of conduct'
|
||||
deploys:
|
||||
title: 'Where do you want your site to be hosted?'
|
||||
fetch:
|
||||
title: 'Upgrade the site'
|
||||
help:
|
||||
fetch: 'Any changes made to the site are saved into a _git_ repository. Git saves the differences between previous and current versions of files so we can explore them as the history of the project. Also, we can bring and send changes between repositories. In this case, every site managed with Sutty share a common root that we call [skeleton](https://0xacab.org/sutty/skel.sutty.nl). When we upgrade this skeleton, you can explore the changes here and accept them to make your site better.'
|
||||
toc: 'Table of contents'
|
||||
merge:
|
||||
request: 'Upgrade my site with these changes'
|
||||
success: 'Site upgrade has been completed. Your next build will run this upgrade :)'
|
||||
error: "There was an error when we were trying to upgrade your site. This could be due to conflicts that couldn't be solved automatically. We've sent a report of the issue to Sutty's admins so they already know about it. Sorry! :("
|
||||
message: 'Skeleton upgrade'
|
||||
footer:
|
||||
powered_by: 'is developed by'
|
||||
templates:
|
||||
index: 'Templates'
|
||||
edit: 'Edit'
|
||||
save: 'Save'
|
||||
i18n:
|
||||
index: 'Translations'
|
||||
edit: 'Edit texts and translations'
|
||||
|
|
|
@ -1,4 +1,28 @@
|
|||
es:
|
||||
es: Castellano
|
||||
en: Inglés
|
||||
seconds: '%{seconds} segundos'
|
||||
deploy_mailer:
|
||||
deployed:
|
||||
subject: "[Sutty] El sitio %{site} ha sido generado"
|
||||
hi: "¡Hola!"
|
||||
explanation: |
|
||||
Este correo es para notificarte que Sutty ha generado tu sitio y
|
||||
ya está disponible en la dirección <https://%{fqdn}>.
|
||||
|
||||
A continuación encontrarás el detalle de lo que hicimos.
|
||||
th:
|
||||
type: Tipo
|
||||
status: Estado
|
||||
deploy_local:
|
||||
title: Generar el sitio
|
||||
success: ¡Éxito!
|
||||
error: Hubo un error
|
||||
deploy_zip:
|
||||
title: Generar archivo ZIP
|
||||
success: Disponible para descargar
|
||||
error: Hubo un error
|
||||
help: Por cualquier duda, responde este correo para contactarte con nosotres.
|
||||
activerecord:
|
||||
models:
|
||||
usuarie: Usuarie
|
||||
|
@ -7,8 +31,18 @@ es:
|
|||
email: 'Correo electrónico'
|
||||
password: 'Contraseña'
|
||||
password_confirmation: 'Confirmación de contraseña'
|
||||
current_password: 'Contraseña actual'
|
||||
lang: Idioma principal
|
||||
site:
|
||||
name: 'Nombre'
|
||||
title: 'Título'
|
||||
description: 'Descripción'
|
||||
errors:
|
||||
models:
|
||||
site:
|
||||
attributes:
|
||||
deploys:
|
||||
deploy_local_presence: '¡Necesitamos poder generar el sitio!'
|
||||
invitadx:
|
||||
attributes:
|
||||
email:
|
||||
|
@ -25,6 +59,8 @@ es:
|
|||
disordered: 'Los artículos no tienen número de orden, esto impedirá que los puedas reordenar'
|
||||
disordered_button: '¡Reordenar!'
|
||||
layouts:
|
||||
mailer:
|
||||
signature: 'Con cariño, Sutty'
|
||||
breadcrumb:
|
||||
help: Ayuda
|
||||
collaborations:
|
||||
|
@ -36,6 +72,8 @@ es:
|
|||
posts:
|
||||
reorder: "¡Los artículos fueron reordenados!"
|
||||
help:
|
||||
usuarie:
|
||||
edit: Editar mi perfil
|
||||
category: 'Categoría'
|
||||
logout: 'Cierra la sesión'
|
||||
breadcrumbs: 'Lo que ves arriba son las migas de pan de este sitio.
|
||||
|
@ -143,7 +181,39 @@ es:
|
|||
lang: 'Idioma'
|
||||
logout: 'Salir'
|
||||
error: 'Hubo un error al iniciar la sesión. ¿Escribiste bien tus credenciales?'
|
||||
deploys:
|
||||
deploy_local:
|
||||
title: 'Alojar en Sutty'
|
||||
help: |
|
||||
El sitio estará disponible en <https://%{fqdn}/>.
|
||||
|
||||
Estamos desarrollando la posibilidad de agregar tus propios
|
||||
dominios, ¡ayudanos!
|
||||
ejemplo: 'ejemplo'
|
||||
deploy_zip:
|
||||
title: 'Generar un archivo ZIP'
|
||||
help: |
|
||||
Los archivos ZIP contienen y comprimen todos los archivos de tu
|
||||
sitio. Con esta opción podrás descargar y compartir tu sitio
|
||||
entero a través de la dirección <https://%{fqdn}/%{file}> y
|
||||
guardarla como copia de seguridad o una estrategia de
|
||||
alojamiento solidario, donde muchas personas comparten una copia
|
||||
de tu sitio.
|
||||
|
||||
También sirve para archivo histórico :)
|
||||
ejemplo: 'ejemplo'
|
||||
stats:
|
||||
index:
|
||||
title: Estadísticas
|
||||
help: |
|
||||
Las estadísticas visibilizan información sobre cómo se genera y
|
||||
cuántos recursos utiliza tu sitio.
|
||||
build:
|
||||
average: 'Tiempo promedio de generación'
|
||||
maximum: 'Tiempo máximo de generación'
|
||||
sites:
|
||||
repository:
|
||||
config: 'Cambios en la configuración'
|
||||
actions: 'Acciones'
|
||||
posts: 'Ver y editar artículos'
|
||||
title: 'Sitios'
|
||||
|
@ -155,6 +225,57 @@ es:
|
|||
invitations:
|
||||
accept: 'Aceptar la invitación'
|
||||
reject: 'No, gracias'
|
||||
new:
|
||||
title: 'Crear un sitio'
|
||||
submit: 'Crear sitio'
|
||||
edit:
|
||||
title: 'Editar %{site}'
|
||||
submit: 'Guardar cambios'
|
||||
form:
|
||||
help:
|
||||
name: 'El nombre de tu sitio que formará parte de la dirección (ejemplo.sutty.nl). Solo puede contener letras minúsculas, números y guiones.'
|
||||
title: 'El título de tu sitio puede ser lo que quieras.'
|
||||
description: 'La descripción del sitio, que saldrá en buscadores. Entre 50 y 160 caracteres.'
|
||||
design: 'Elegí el diseño que va a tener tu sitio aquí. Podés cambiarlo luego. De tanto en tanto vamos sumando diseños nuevos.'
|
||||
licencia: 'Todo lo que publicamos posee automáticamente derechos
|
||||
de autore. Esto significa que nadie puede hacer uso de nuestras
|
||||
obras sin permiso explícito. Con las licencias establecemos
|
||||
condiciones bajo las que queremos compartir.'
|
||||
privacidad: |
|
||||
Las [políticas de
|
||||
privacidad](https://sutty.nl/es/politica-de-privacidad/) y
|
||||
[código de
|
||||
convivencia](https://sutty.nl/es/codigo-de-convivencia/)
|
||||
informan a les visitantes qué garantías de privacidad vas a
|
||||
darles y con qué códigos se va a autogestionar su comunidad.
|
||||
Sugerimos las mismas que las de Sutty. Una vez creado el
|
||||
sitio, podrás editarlas como artículos.
|
||||
deploys: |
|
||||
Sutty te permite alojar tu sitio en distintos lugares al mismo
|
||||
tiempo. Esta estrategia facilita que el sitio esté disponible
|
||||
aun cuando algunos de los alojamientos no funcionen.
|
||||
design:
|
||||
title: 'Diseño'
|
||||
actions: 'Información sobre este diseño'
|
||||
url: 'Demostración'
|
||||
license: 'Leer la licencia'
|
||||
licencia:
|
||||
title: 'Licencia del sitio y todo lo que publiques'
|
||||
url: 'Leer la licencia'
|
||||
privacidad:
|
||||
title: Políticas de privacidad y código de convivencia
|
||||
deploys:
|
||||
title: '¿Dónde querés alojar tu sitio?'
|
||||
fetch:
|
||||
title: 'Actualizar el sitio'
|
||||
help:
|
||||
fetch: 'Todos los cambios en el sitio se guardan en un repositorio _git_. En git, se guarda la diferencia entre una versión anterior y la actual de todos los archivos y podemos explorar la historia de un proyecto. Además, podemos traer y enviar cambios con otros repositorios. En este caso, todos los sitios gestionados desde Sutty tienen una raíz común, que llamamos [esqueleto](https://0xacab.org/sutty/skel.sutty.nl). Cuando hacemos cambios en el esqueleto para mejorar los sitios, podés explorar los cambios aquí y aceptarlos.'
|
||||
toc: 'Tabla de contenidos'
|
||||
merge:
|
||||
request: 'Incorporar los cambios en mi sitio'
|
||||
success: 'Ya se incorporaron los cambios en el sitio, se aplicarán en la próxima compilación que hagas :)'
|
||||
error: 'Hubo un error al incorporar los cambios en el sitio. Esto puede deberse a conflictos entre cambios que no se pueden resolver automáticamente. Hemos enviado un reporte del problema a les administradores de Sutty para que estén al tanto de la situación. ¡Lo sentimos! :('
|
||||
message: 'Actualización del esqueleto'
|
||||
footer:
|
||||
powered_by: 'es desarrollada por'
|
||||
i18n:
|
||||
|
|
|
@ -11,11 +11,13 @@ Rails.application.routes.draw do
|
|||
# como un objeto válido
|
||||
resources :invitadxs, only: [:create]
|
||||
|
||||
resources :sites, only: %i[index show],
|
||||
constraints: { site_id: %r{[^/]+}, id: %r{[^/]+} } do
|
||||
|
||||
resources :sites, constraints: { site_id: %r{[^/]+}, id: %r{[^/]+} } do
|
||||
get 'public/:type/:basename', to: 'sites#send_public_file'
|
||||
|
||||
# Gestionar actualizaciones del sitio
|
||||
get 'pull', to: 'sites#fetch'
|
||||
post 'pull', to: 'sites#merge'
|
||||
|
||||
# Gestionar usuaries
|
||||
get 'usuaries/invite', to: 'usuaries#invite'
|
||||
post 'usuaries/invite', to: 'usuaries#send_invitations'
|
||||
|
@ -39,7 +41,8 @@ Rails.application.routes.draw do
|
|||
|
||||
# Compilar el sitio
|
||||
post 'enqueue', to: 'sites#enqueue'
|
||||
get 'build_log', to: 'sites#build_log'
|
||||
post 'reorder_posts', to: 'sites#reorder_posts'
|
||||
|
||||
resources :stats, only: [:index]
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
env 'MAILTO', 'sysadmin@kefir.red'
|
||||
job_type :bash, 'cd :path && ./bin/:task'
|
||||
|
||||
every 3.minutes do
|
||||
bash 'jekyll_build_all'
|
||||
end
|
|
@ -1,32 +0,0 @@
|
|||
# Be sure to restart your server when you modify this file.
|
||||
|
||||
# Your secret key is used for verifying the integrity of signed cookies.
|
||||
# If you change this key, all old signed cookies will become invalid!
|
||||
|
||||
# Make sure the secret is at least 30 characters and all random,
|
||||
# no regular words or you'll be exposed to dictionary attacks.
|
||||
# You can use `rails secret` to generate a secure secret key.
|
||||
|
||||
# Make sure the secrets in this file are kept private
|
||||
# if you're sharing your code publicly.
|
||||
|
||||
# Shared secrets are available across all environments.
|
||||
|
||||
# shared:
|
||||
# api_key: a1B2c3D4e5F6
|
||||
|
||||
# Environmental secrets are only available for that specific environment.
|
||||
|
||||
development:
|
||||
secret_key_base: 18809d32b6661e906759535c3de06955d0eb551a83de5639f1ca4f0375bafd9653b818c4b881942e5cd5cc8da265617c9164fdb63b9f491d4481036c3d23e677
|
||||
|
||||
test:
|
||||
secret_key_base: 95f26bd27ca88acb1f0d8d207fa5e60ae7dc56463774990c4acb938110af035690c929f844eaa97cc9a06b67f44631663f40c927b19c706dcccf629143550a2f
|
||||
|
||||
# Do not keep production secrets in the unencrypted secrets file.
|
||||
# Instead, either read values from the environment.
|
||||
# Or, use `bin/rails secrets:setup` to configure encrypted secrets
|
||||
# and move the `production:` environment over there.
|
||||
|
||||
production:
|
||||
secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
|
|
@ -44,7 +44,10 @@ class CreateSitios < ActiveRecord::Migration[5.2]
|
|||
usuarie ||= Usuarie.create(email: email,
|
||||
password: SecureRandom.hex,
|
||||
confirmed_at: Date.today)
|
||||
site.usuaries << usuarie
|
||||
|
||||
sql = "insert into sites_usuaries (site_id, usuarie_id)
|
||||
values (#{site.id}, #{usuarie.id});"
|
||||
ActiveRecord::Base.connection.execute(sql)
|
||||
end
|
||||
|
||||
invitadxs.each do |email|
|
||||
|
@ -52,7 +55,9 @@ class CreateSitios < ActiveRecord::Migration[5.2]
|
|||
usuarie ||= Usuarie.create(email: email,
|
||||
password: SecureRandom.hex,
|
||||
confirmed_at: Date.today)
|
||||
site.invitades << usuarie
|
||||
sql = "insert into invitades_sites (site_id, usuarie_id)
|
||||
values (#{site.id}, #{usuarie.id});"
|
||||
ActiveRecord::Base.connection.execute(sql)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
9
db/migrate/20190711183726_add_unique_to_site_name.rb
Normal file
9
db/migrate/20190711183726_add_unique_to_site_name.rb
Normal file
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Los nombres de los sitios son únicos
|
||||
class AddUniqueToSiteName < ActiveRecord::Migration[5.2]
|
||||
def change
|
||||
remove_index :sites, :name
|
||||
add_index :sites, :name, unique: true
|
||||
end
|
||||
end
|
28
db/migrate/20190712165059_sqlite_boolean.rb
Normal file
28
db/migrate/20190712165059_sqlite_boolean.rb
Normal file
|
@ -0,0 +1,28 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Convertir los valores binarios de sqlite
|
||||
class SqliteBoolean < ActiveRecord::Migration[5.2]
|
||||
def up
|
||||
return unless adapter_name == 'SQLite'
|
||||
|
||||
Usuarie.where("acepta_politicas_de_privacidad = 't'")
|
||||
.update_all(acepta_politicas_de_privacidad: 1)
|
||||
Usuarie.where("acepta_politicas_de_privacidad = 'f'")
|
||||
.update_all(acepta_politicas_de_privacidad: 0)
|
||||
|
||||
change_column :usuaries, :acepta_politicas_de_privacidad, :boolean,
|
||||
default: 0
|
||||
end
|
||||
|
||||
def down
|
||||
return unless adapter_name == 'SQLite'
|
||||
|
||||
Usuarie.where('acepta_politicas_de_privacidad = 1')
|
||||
.update_all(acepta_politicas_de_privacidad: 't')
|
||||
Usuarie.where('acepta_politicas_de_privacidad = 0')
|
||||
.update_all(acepta_politicas_de_privacidad: 'f')
|
||||
|
||||
change_column :usuaries, :acepta_politicas_de_privacidad, :boolean,
|
||||
default: 'f'
|
||||
end
|
||||
end
|
15
db/migrate/20190716195155_create_designs.rb
Normal file
15
db/migrate/20190716195155_create_designs.rb
Normal file
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Crea la tabla de diseños
|
||||
class CreateDesigns < ActiveRecord::Migration[5.2]
|
||||
def change
|
||||
create_table :designs do |t|
|
||||
t.timestamps
|
||||
t.string :name, unique: true
|
||||
t.text :description
|
||||
t.string :gem, unique: true
|
||||
t.string :url
|
||||
t.string :license
|
||||
end
|
||||
end
|
||||
end
|
9
db/migrate/20190716195449_add_lang_to_usuaries.rb
Normal file
9
db/migrate/20190716195449_add_lang_to_usuaries.rb
Normal file
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Agrega la columna de idioma a cada usuarie para que pueda ver el sitio
|
||||
# y escribir artículos en su idioma.
|
||||
class AddLangToUsuaries < ActiveRecord::Migration[5.2]
|
||||
def change
|
||||
add_column :usuaries, :lang, :string, default: 'es'
|
||||
end
|
||||
end
|
16
db/migrate/20190716195811_create_text_translations.rb
Normal file
16
db/migrate/20190716195811_create_text_translations.rb
Normal file
|
@ -0,0 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Tabla de traducción de textos utilizada por Mobility
|
||||
class CreateTextTranslations < ActiveRecord::Migration[5.2]
|
||||
def change
|
||||
create_table :mobility_text_translations do |t|
|
||||
t.string :locale, null: false
|
||||
t.string :key, null: false
|
||||
t.text :value
|
||||
t.references :translatable, polymorphic: true, index: false
|
||||
t.timestamps null: false
|
||||
end
|
||||
add_index :mobility_text_translations, %i[translatable_id translatable_type locale key], unique: true, name: :index_mobility_text_translations_on_keys
|
||||
add_index :mobility_text_translations, %i[translatable_id translatable_type key], name: :index_mobility_text_translations_on_translatable_attribute
|
||||
end
|
||||
end
|
17
db/migrate/20190716195812_create_string_translations.rb
Normal file
17
db/migrate/20190716195812_create_string_translations.rb
Normal file
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Tabla de traducción de cadenas usada por Mobility
|
||||
class CreateStringTranslations < ActiveRecord::Migration[5.2]
|
||||
def change
|
||||
create_table :mobility_string_translations do |t|
|
||||
t.string :locale, null: false
|
||||
t.string :key, null: false
|
||||
t.string :value
|
||||
t.references :translatable, polymorphic: true, index: false
|
||||
t.timestamps null: false
|
||||
end
|
||||
add_index :mobility_string_translations, %i[translatable_id translatable_type locale key], unique: true, name: :index_mobility_string_translations_on_keys
|
||||
add_index :mobility_string_translations, %i[translatable_id translatable_type key], name: :index_mobility_string_translations_on_translatable_attribute
|
||||
add_index :mobility_string_translations, %i[translatable_type key value locale], name: :index_mobility_string_translations_on_query_keys
|
||||
end
|
||||
end
|
8
db/migrate/20190716202024_add_design_to_sites.rb
Normal file
8
db/migrate/20190716202024_add_design_to_sites.rb
Normal file
|
@ -0,0 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Los sitios tienen un diseño
|
||||
class AddDesignToSites < ActiveRecord::Migration[5.2]
|
||||
def change
|
||||
add_belongs_to :sites, :design, index: true
|
||||
end
|
||||
end
|
8
db/migrate/20190717214308_add_disabled_to_designs.rb
Normal file
8
db/migrate/20190717214308_add_disabled_to_designs.rb
Normal file
|
@ -0,0 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Algunos diseños están deshabilitados
|
||||
class AddDisabledToDesigns < ActiveRecord::Migration[5.2]
|
||||
def change
|
||||
add_column :designs, :disabled, :boolean, default: false
|
||||
end
|
||||
end
|
16
db/migrate/20190718185817_create_licencias.rb
Normal file
16
db/migrate/20190718185817_create_licencias.rb
Normal file
|
@ -0,0 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Crea la tabla de licencias
|
||||
class CreateLicencias < ActiveRecord::Migration[5.2]
|
||||
def change
|
||||
create_table :licencias do |t|
|
||||
t.timestamps
|
||||
t.string :name, unique: true
|
||||
t.text :description
|
||||
t.text :deed
|
||||
t.string :url
|
||||
end
|
||||
|
||||
add_belongs_to :sites, :licencia, index: true
|
||||
end
|
||||
end
|
9
db/migrate/20190719221653_add_icons_to_licenses.rb
Normal file
9
db/migrate/20190719221653_add_icons_to_licenses.rb
Normal file
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Agrega íconos a las licencias
|
||||
class AddIconsToLicenses < ActiveRecord::Migration[5.2]
|
||||
def change
|
||||
# XXX: Cambiar por ActiveStorage?
|
||||
add_column :licencias, :icons, :string
|
||||
end
|
||||
end
|
13
db/migrate/20190723220002_create_deploys.rb
Normal file
13
db/migrate/20190723220002_create_deploys.rb
Normal file
|
@ -0,0 +1,13 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Crea la tabla de deploys posibles para un sitio
|
||||
class CreateDeploys < ActiveRecord::Migration[5.2]
|
||||
def change
|
||||
create_table :deploys do |t|
|
||||
t.timestamps
|
||||
t.belongs_to :site, index: true
|
||||
t.string :type, index: true
|
||||
t.text :values
|
||||
end
|
||||
end
|
||||
end
|
15
db/migrate/20190725185427_create_build_stats.rb
Normal file
15
db/migrate/20190725185427_create_build_stats.rb
Normal file
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Crea la tabla de estadísticas de compilación
|
||||
class CreateBuildStats < ActiveRecord::Migration[5.2]
|
||||
def change
|
||||
create_table :build_stats do |t|
|
||||
t.timestamps
|
||||
t.belongs_to :deploy, index: true
|
||||
t.integer :bytes
|
||||
t.float :seconds
|
||||
t.string :action, null: false
|
||||
t.text :log
|
||||
end
|
||||
end
|
||||
end
|
8
db/migrate/20190726003756_add_status_to_site.rb
Normal file
8
db/migrate/20190726003756_add_status_to_site.rb
Normal file
|
@ -0,0 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# El status de un sitio
|
||||
class AddStatusToSite < ActiveRecord::Migration[5.2]
|
||||
def change
|
||||
add_column :sites, :status, :string, default: 'waiting'
|
||||
end
|
||||
end
|
8
db/migrate/20190730211624_add_description_to_site.rb
Normal file
8
db/migrate/20190730211624_add_description_to_site.rb
Normal file
|
@ -0,0 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Agrega la descripción de un sitio
|
||||
class AddDescriptionToSite < ActiveRecord::Migration[5.2]
|
||||
def change
|
||||
add_column :sites, :description, :text
|
||||
end
|
||||
end
|
8
db/migrate/20190730211756_add_title_to_sites.rb
Normal file
8
db/migrate/20190730211756_add_title_to_sites.rb
Normal file
|
@ -0,0 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Agrega el título al sitio
|
||||
class AddTitleToSites < ActiveRecord::Migration[5.2]
|
||||
def change
|
||||
add_column :sites, :title, :string
|
||||
end
|
||||
end
|
79
db/schema.rb
79
db/schema.rb
|
@ -12,7 +12,74 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 20_190_706_002_615) do
|
||||
ActiveRecord::Schema.define(version: 20_190_730_211_756) do
|
||||
create_table 'build_stats', force: :cascade do |t|
|
||||
t.datetime 'created_at', null: false
|
||||
t.datetime 'updated_at', null: false
|
||||
t.integer 'deploy_id'
|
||||
t.integer 'bytes'
|
||||
t.float 'seconds'
|
||||
t.string 'action', null: false
|
||||
t.text 'log'
|
||||
t.index ['deploy_id'], name: 'index_build_stats_on_deploy_id'
|
||||
end
|
||||
|
||||
create_table 'deploys', force: :cascade do |t|
|
||||
t.datetime 'created_at', null: false
|
||||
t.datetime 'updated_at', null: false
|
||||
t.integer 'site_id'
|
||||
t.string 'type'
|
||||
t.text 'values'
|
||||
t.index ['site_id'], name: 'index_deploys_on_site_id'
|
||||
t.index ['type'], name: 'index_deploys_on_type'
|
||||
end
|
||||
|
||||
create_table 'designs', force: :cascade do |t|
|
||||
t.datetime 'created_at', null: false
|
||||
t.datetime 'updated_at', null: false
|
||||
t.string 'name'
|
||||
t.text 'description'
|
||||
t.string 'gem'
|
||||
t.string 'url'
|
||||
t.string 'license'
|
||||
t.boolean 'disabled', default: false
|
||||
end
|
||||
|
||||
create_table 'licencias', force: :cascade do |t|
|
||||
t.datetime 'created_at', null: false
|
||||
t.datetime 'updated_at', null: false
|
||||
t.string 'name'
|
||||
t.text 'description'
|
||||
t.text 'deed'
|
||||
t.string 'url'
|
||||
t.string 'icons'
|
||||
end
|
||||
|
||||
create_table 'mobility_string_translations', force: :cascade do |t|
|
||||
t.string 'locale', null: false
|
||||
t.string 'key', null: false
|
||||
t.string 'value'
|
||||
t.string 'translatable_type'
|
||||
t.integer 'translatable_id'
|
||||
t.datetime 'created_at', null: false
|
||||
t.datetime 'updated_at', null: false
|
||||
t.index %w[translatable_id translatable_type key], name: 'index_mobility_string_translations_on_translatable_attribute'
|
||||
t.index %w[translatable_id translatable_type locale key], name: 'index_mobility_string_translations_on_keys', unique: true
|
||||
t.index %w[translatable_type key value locale], name: 'index_mobility_string_translations_on_query_keys'
|
||||
end
|
||||
|
||||
create_table 'mobility_text_translations', force: :cascade do |t|
|
||||
t.string 'locale', null: false
|
||||
t.string 'key', null: false
|
||||
t.text 'value'
|
||||
t.string 'translatable_type'
|
||||
t.integer 'translatable_id'
|
||||
t.datetime 'created_at', null: false
|
||||
t.datetime 'updated_at', null: false
|
||||
t.index %w[translatable_id translatable_type key], name: 'index_mobility_text_translations_on_translatable_attribute'
|
||||
t.index %w[translatable_id translatable_type locale key], name: 'index_mobility_text_translations_on_keys', unique: true
|
||||
end
|
||||
|
||||
create_table 'roles', force: :cascade do |t|
|
||||
t.datetime 'created_at', null: false
|
||||
t.datetime 'updated_at', null: false
|
||||
|
@ -29,7 +96,14 @@ ActiveRecord::Schema.define(version: 20_190_706_002_615) do
|
|||
t.datetime 'created_at', null: false
|
||||
t.datetime 'updated_at', null: false
|
||||
t.string 'name'
|
||||
t.index ['name'], name: 'index_sites_on_name'
|
||||
t.integer 'design_id'
|
||||
t.integer 'licencia_id'
|
||||
t.string 'status', default: 'waiting'
|
||||
t.text 'description'
|
||||
t.string 'title'
|
||||
t.index ['design_id'], name: 'index_sites_on_design_id'
|
||||
t.index ['licencia_id'], name: 'index_sites_on_licencia_id'
|
||||
t.index ['name'], name: 'index_sites_on_name', unique: true
|
||||
end
|
||||
|
||||
create_table 'usuaries', force: :cascade do |t|
|
||||
|
@ -56,6 +130,7 @@ ActiveRecord::Schema.define(version: 20_190_706_002_615) do
|
|||
t.string 'invited_by_type'
|
||||
t.integer 'invited_by_id'
|
||||
t.integer 'invitations_count', default: 0
|
||||
t.string 'lang', default: 'es'
|
||||
t.index ['confirmation_token'], name: 'index_usuaries_on_confirmation_token', unique: true
|
||||
t.index ['email'], name: 'index_usuaries_on_email', unique: true
|
||||
t.index ['invitation_token'], name: 'index_usuaries_on_invitation_token', unique: true
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue