mirror of
https://0xacab.org/sutty/sutty
synced 2024-05-18 05:40:49 +00:00
Merge branch 'rails' into issue-12854
This commit is contained in:
commit
e827405c12
11
.editorconfig
Normal file
11
.editorconfig
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
charset = utf-8
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
[Makefile]
|
||||||
|
indent_style = tab
|
2
.gitattributes
vendored
Normal file
2
.gitattributes
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
public/assets/** filter=lfs diff=lfs merge=lfs -text
|
||||||
|
public/packs/** filter=lfs diff=lfs merge=lfs -text
|
7
.gitignore
vendored
7
.gitignore
vendored
|
@ -34,12 +34,7 @@
|
||||||
/config/master.key
|
/config/master.key
|
||||||
/config/credentials.yml.enc
|
/config/credentials.yml.enc
|
||||||
|
|
||||||
/public/packs
|
|
||||||
/public/packs-test
|
/public/packs-test
|
||||||
/public/assets
|
|
||||||
/public/assets-production
|
|
||||||
/public/packs
|
|
||||||
/public/packs-production
|
|
||||||
/node_modules
|
/node_modules
|
||||||
/yarn-error.log
|
/yarn-error.log
|
||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
|
@ -49,8 +44,6 @@ yarn-debug.log*
|
||||||
*.key
|
*.key
|
||||||
*.crt
|
*.crt
|
||||||
|
|
||||||
/public/packs
|
|
||||||
/public/packs-test
|
|
||||||
/node_modules
|
/node_modules
|
||||||
/yarn-error.log
|
/yarn-error.log
|
||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
|
|
33
.gitlab-ci.yml
Normal file
33
.gitlab-ci.yml
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
image: "gitea.nulo.in/sutty/panel:3.14.10-2.7.8-panel.sutty.nl"
|
||||||
|
variables:
|
||||||
|
RAILS_ENV: "production"
|
||||||
|
LC_ALL: "C.UTF-8"
|
||||||
|
cache:
|
||||||
|
paths:
|
||||||
|
- "vendor/ruby"
|
||||||
|
assets:
|
||||||
|
stage: "build"
|
||||||
|
rules:
|
||||||
|
- if: "$CI_COMMIT_BRANCH == \"panel.sutty.nl\""
|
||||||
|
- if: "$CI_COMMIT_BRANCH"
|
||||||
|
changes:
|
||||||
|
compare_to: "refs/heads/rails"
|
||||||
|
paths:
|
||||||
|
- "package.json"
|
||||||
|
- "app/javascript/**/*"
|
||||||
|
- "app/assets/**/*"
|
||||||
|
before_script:
|
||||||
|
- "git config --global user.email \"${GIT_USER_EMAIL:-$GITLAB_USER_EMAIL}\""
|
||||||
|
- "git config --global user.name \"${GIT_USER_NAME:-$GITLAB_USER_NAME}\""
|
||||||
|
- "git remote set-url --push origin \"https://${GITLAB_USERNAME}:${GITLAB_TOKEN}@${CI_SERVER_HOST}/${CI_PROJECT_PATH}.git\""
|
||||||
|
- "apk add python2 dotenv brotli"
|
||||||
|
- "mv config/credentials.yml.enc.ci config/credentials.yml.enc"
|
||||||
|
- "cp .env.example .env"
|
||||||
|
- "dotenv bundle install --path=vendor"
|
||||||
|
script:
|
||||||
|
- "dotenv RAILS_ENV=production bundle exec rails webpacker:clobber"
|
||||||
|
- "dotenv RAILS_ENV=production bundle exec rails assets:precompile"
|
||||||
|
- "dotenv RAILS_ENV=production bundle exec rails assets:clean"
|
||||||
|
after_script:
|
||||||
|
- "git add public && git commit -m \"ci: assets [skip ci]\""
|
||||||
|
- "git push -o ci.skip"
|
|
@ -6,7 +6,7 @@ pipeline:
|
||||||
username: "sutty"
|
username: "sutty"
|
||||||
repo: "gitea.nulo.in/sutty/panel"
|
repo: "gitea.nulo.in/sutty/panel"
|
||||||
tags:
|
tags:
|
||||||
- "${ALPINE_VERSION}-${RUBY_VERSION}.${RUBY_PATCH}"
|
- "${ALPINE_VERSION}-${RUBY_VERSION}.${RUBY_PATCH}-${CI_COMMIT_BRANCH}"
|
||||||
- "latest"
|
- "latest"
|
||||||
build_args:
|
build_args:
|
||||||
- "RUBY_VERSION=${RUBY_VERSION}"
|
- "RUBY_VERSION=${RUBY_VERSION}"
|
||||||
|
@ -20,13 +20,15 @@ pipeline:
|
||||||
branch:
|
branch:
|
||||||
- "rails"
|
- "rails"
|
||||||
- "panel.sutty.nl"
|
- "panel.sutty.nl"
|
||||||
|
- "17.3.alpine.panel.sutty.nl"
|
||||||
event: "push"
|
event: "push"
|
||||||
path:
|
path:
|
||||||
include:
|
include:
|
||||||
- "Dockerfile"
|
- "Dockerfile"
|
||||||
- ".dockerignore"
|
- ".dockerignore"
|
||||||
|
- ".woodpecker.yml"
|
||||||
assets:
|
assets:
|
||||||
image: "gitea.nulo.in/sutty/panel:${ALPINE_VERSION}-${RUBY_VERSION}.${RUBY_PATCH}"
|
image: "gitea.nulo.in/sutty/panel:3.14.10-2.7.8"
|
||||||
commands:
|
commands:
|
||||||
- "apk add python2 dotenv openssh-client brotli"
|
- "apk add python2 dotenv openssh-client brotli"
|
||||||
- "install -d -m 700 ~/.ssh/"
|
- "install -d -m 700 ~/.ssh/"
|
||||||
|
@ -50,6 +52,9 @@ pipeline:
|
||||||
- "git add public && git commit -m \"ci: assets [skip ci]\""
|
- "git add public && git commit -m \"ci: assets [skip ci]\""
|
||||||
- "git pull upstream ${CI_COMMIT_BRANCH}"
|
- "git pull upstream ${CI_COMMIT_BRANCH}"
|
||||||
- "git push upstream ${CI_COMMIT_BRANCH}"
|
- "git push upstream ${CI_COMMIT_BRANCH}"
|
||||||
|
environment:
|
||||||
|
- "RUBY_VERSION=${RUBY_VERSION}"
|
||||||
|
- "GEMS_SOURCE=https://14.3.alpine.gems.sutty.nl"
|
||||||
secrets:
|
secrets:
|
||||||
- "SSH_KEY"
|
- "SSH_KEY"
|
||||||
- "KNOWN_HOSTS"
|
- "KNOWN_HOSTS"
|
||||||
|
@ -64,8 +69,15 @@ pipeline:
|
||||||
- "app/javascript/**/*"
|
- "app/javascript/**/*"
|
||||||
- "package.json"
|
- "package.json"
|
||||||
- "yarn.lock"
|
- "yarn.lock"
|
||||||
|
matrix:
|
||||||
|
ALPINE_VERSION: "3.14.10"
|
||||||
|
RUBY_VERSION: "2.7"
|
||||||
|
RUBY_PATCH: "8"
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
|
- ALPINE_VERSION: "3.17.3"
|
||||||
|
RUBY_VERSION: "3.1"
|
||||||
|
RUBY_PATCH: "4"
|
||||||
- ALPINE_VERSION: "3.14.10"
|
- ALPINE_VERSION: "3.14.10"
|
||||||
RUBY_VERSION: "2.7"
|
RUBY_VERSION: "2.7"
|
||||||
RUBY_PATCH: "8"
|
RUBY_PATCH: "8"
|
||||||
|
|
36
Gemfile
36
Gemfile
|
@ -1,17 +1,13 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
if ENV['RAILS_ENV'] != 'production' && ENV['HAIN_ENV'].nil?
|
source ENV.fetch('GEMS_SOURCE', 'https://17.3.alpine.gems.sutty.nl')
|
||||||
puts 'Usa haini.sh para generar un entorno de trabajo reproducible'
|
|
||||||
end
|
|
||||||
|
|
||||||
source 'https://gems.sutty.nl'
|
ruby "~> #{ENV.fetch('RUBY_VERSION', '3.1')}"
|
||||||
|
|
||||||
ruby '~> 2.7'
|
|
||||||
|
|
||||||
gem 'dotenv-rails', require: 'dotenv/rails-now'
|
gem 'dotenv-rails', require: 'dotenv/rails-now'
|
||||||
|
|
||||||
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
|
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
|
||||||
gem 'rails', '~> 6'
|
gem 'rails', '~> 6.1.0'
|
||||||
# Use Puma as the app server
|
# Use Puma as the app server
|
||||||
gem 'puma'
|
gem 'puma'
|
||||||
|
|
||||||
|
@ -36,14 +32,14 @@ gem 'turbolinks', '~> 5'
|
||||||
gem 'jbuilder', '~> 2.5'
|
gem 'jbuilder', '~> 2.5'
|
||||||
# Use ActiveModel has_secure_password
|
# Use ActiveModel has_secure_password
|
||||||
gem 'bcrypt', '~> 3.1.7'
|
gem 'bcrypt', '~> 3.1.7'
|
||||||
|
gem 'safely_block', '~> 0.3.0'
|
||||||
gem 'blazer'
|
gem 'blazer'
|
||||||
gem 'chartkick'
|
gem 'chartkick'
|
||||||
gem 'commonmarker'
|
gem 'commonmarker'
|
||||||
gem 'devise'
|
gem 'devise'
|
||||||
gem 'devise-i18n'
|
gem 'devise-i18n'
|
||||||
gem 'devise_invitable'
|
gem 'devise_invitable'
|
||||||
gem 'distributed-press-api-client', '~> 0.2.3'
|
gem 'distributed-press-api-client', '~> 0.3.0rc0'
|
||||||
gem 'njalla-api-client', '~> 0.2.0'
|
|
||||||
gem 'email_address', git: 'https://github.com/fauno/email_address', branch: 'i18n'
|
gem 'email_address', git: 'https://github.com/fauno/email_address', branch: 'i18n'
|
||||||
gem 'exception_notification'
|
gem 'exception_notification'
|
||||||
gem 'fast_blank'
|
gem 'fast_blank'
|
||||||
|
@ -54,10 +50,10 @@ gem 'image_processing'
|
||||||
gem 'icalendar'
|
gem 'icalendar'
|
||||||
gem 'inline_svg'
|
gem 'inline_svg'
|
||||||
gem 'httparty'
|
gem 'httparty'
|
||||||
gem 'safe_yaml'
|
gem 'safe_yaml', require: false
|
||||||
gem 'jekyll', '~> 4.2'
|
gem 'jekyll', '~> 4.2.0'
|
||||||
gem 'jekyll-data'
|
gem 'jekyll-data'
|
||||||
gem 'jekyll-commonmark'
|
gem 'jekyll-commonmark', '~> 1.4.0'
|
||||||
gem 'jekyll-images'
|
gem 'jekyll-images'
|
||||||
gem 'jekyll-include-cache'
|
gem 'jekyll-include-cache'
|
||||||
gem 'sutty-liquid', '>= 0.7.3'
|
gem 'sutty-liquid', '>= 0.7.3'
|
||||||
|
@ -68,19 +64,21 @@ gem 'mobility'
|
||||||
gem 'pundit'
|
gem 'pundit'
|
||||||
gem 'rails-i18n'
|
gem 'rails-i18n'
|
||||||
gem 'rails_warden'
|
gem 'rails_warden'
|
||||||
gem 'redis', require: %w[redis redis/connection/hiredis]
|
gem 'redis', '~> 4.0', require: %w[redis redis/connection/hiredis]
|
||||||
gem 'redis-rails'
|
gem 'redis-rails'
|
||||||
gem 'rollups', git: 'https://github.com/fauno/rollup.git', branch: 'update'
|
gem 'rollups', git: 'https://github.com/fauno/rollup.git', branch: 'update'
|
||||||
gem 'rubyzip'
|
gem 'rubyzip'
|
||||||
gem 'rugged'
|
gem 'rugged', '1.5.0.1'
|
||||||
|
gem 'git_clone_url'
|
||||||
gem 'concurrent-ruby-ext'
|
gem 'concurrent-ruby-ext'
|
||||||
gem 'sucker_punch'
|
gem 'que'
|
||||||
gem 'symbol-fstring', require: 'fstring/all'
|
gem 'symbol-fstring', require: 'fstring/all'
|
||||||
gem 'terminal-table'
|
gem 'terminal-table'
|
||||||
gem 'validates_hostname'
|
gem 'validates_hostname'
|
||||||
gem 'webpacker'
|
gem 'webpacker'
|
||||||
gem 'yaml_db', git: 'https://0xacab.org/sutty/yaml_db.git'
|
gem 'yaml_db', git: 'https://0xacab.org/sutty/yaml_db.git'
|
||||||
gem 'kaminari'
|
gem 'kaminari'
|
||||||
|
gem 'device_detector'
|
||||||
|
|
||||||
# database
|
# database
|
||||||
gem 'hairtrigger'
|
gem 'hairtrigger'
|
||||||
|
@ -114,7 +112,7 @@ group :development, :test do
|
||||||
gem 'pry'
|
gem 'pry'
|
||||||
# Adds support for Capybara system testing and selenium driver
|
# Adds support for Capybara system testing and selenium driver
|
||||||
gem 'capybara', '~> 2.13'
|
gem 'capybara', '~> 2.13'
|
||||||
gem 'selenium-webdriver'
|
gem 'selenium-webdriver', '~> 4.8.0'
|
||||||
gem 'sqlite3'
|
gem 'sqlite3'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -122,11 +120,11 @@ group :development do
|
||||||
gem 'brakeman'
|
gem 'brakeman'
|
||||||
gem 'haml-lint', require: false
|
gem 'haml-lint', require: false
|
||||||
gem 'letter_opener'
|
gem 'letter_opener'
|
||||||
gem 'listen', '>= 3.0.5', '< 3.2'
|
gem 'listen'
|
||||||
gem 'rubocop-rails'
|
gem 'rubocop-rails'
|
||||||
gem 'spring'
|
gem 'spring'
|
||||||
gem 'spring-watcher-listen', '~> 2.0.0'
|
gem 'spring-watcher-listen'
|
||||||
gem 'web-console', '>= 3.3.0'
|
gem 'web-console'
|
||||||
end
|
end
|
||||||
|
|
||||||
group :test do
|
group :test do
|
||||||
|
|
564
Gemfile.lock
564
Gemfile.lock
|
@ -25,86 +25,86 @@ GIT
|
||||||
groupdate (>= 5.2)
|
groupdate (>= 5.2)
|
||||||
|
|
||||||
GEM
|
GEM
|
||||||
remote: https://gems.sutty.nl/
|
remote: https://17.3.alpine.gems.sutty.nl/
|
||||||
specs:
|
specs:
|
||||||
actioncable (6.1.4.1)
|
actioncable (6.1.7.3)
|
||||||
actionpack (= 6.1.4.1)
|
actionpack (= 6.1.7.3)
|
||||||
activesupport (= 6.1.4.1)
|
activesupport (= 6.1.7.3)
|
||||||
nio4r (~> 2.0)
|
nio4r (~> 2.0)
|
||||||
websocket-driver (>= 0.6.1)
|
websocket-driver (>= 0.6.1)
|
||||||
actionmailbox (6.1.4.1)
|
actionmailbox (6.1.7.3)
|
||||||
actionpack (= 6.1.4.1)
|
actionpack (= 6.1.7.3)
|
||||||
activejob (= 6.1.4.1)
|
activejob (= 6.1.7.3)
|
||||||
activerecord (= 6.1.4.1)
|
activerecord (= 6.1.7.3)
|
||||||
activestorage (= 6.1.4.1)
|
activestorage (= 6.1.7.3)
|
||||||
activesupport (= 6.1.4.1)
|
activesupport (= 6.1.7.3)
|
||||||
mail (>= 2.7.1)
|
mail (>= 2.7.1)
|
||||||
actionmailer (6.1.4.1)
|
actionmailer (6.1.7.3)
|
||||||
actionpack (= 6.1.4.1)
|
actionpack (= 6.1.7.3)
|
||||||
actionview (= 6.1.4.1)
|
actionview (= 6.1.7.3)
|
||||||
activejob (= 6.1.4.1)
|
activejob (= 6.1.7.3)
|
||||||
activesupport (= 6.1.4.1)
|
activesupport (= 6.1.7.3)
|
||||||
mail (~> 2.5, >= 2.5.4)
|
mail (~> 2.5, >= 2.5.4)
|
||||||
rails-dom-testing (~> 2.0)
|
rails-dom-testing (~> 2.0)
|
||||||
actionpack (6.1.4.1)
|
actionpack (6.1.7.3)
|
||||||
actionview (= 6.1.4.1)
|
actionview (= 6.1.7.3)
|
||||||
activesupport (= 6.1.4.1)
|
activesupport (= 6.1.7.3)
|
||||||
rack (~> 2.0, >= 2.0.9)
|
rack (~> 2.0, >= 2.0.9)
|
||||||
rack-test (>= 0.6.3)
|
rack-test (>= 0.6.3)
|
||||||
rails-dom-testing (~> 2.0)
|
rails-dom-testing (~> 2.0)
|
||||||
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
||||||
actiontext (6.1.4.1)
|
actiontext (6.1.7.3)
|
||||||
actionpack (= 6.1.4.1)
|
actionpack (= 6.1.7.3)
|
||||||
activerecord (= 6.1.4.1)
|
activerecord (= 6.1.7.3)
|
||||||
activestorage (= 6.1.4.1)
|
activestorage (= 6.1.7.3)
|
||||||
activesupport (= 6.1.4.1)
|
activesupport (= 6.1.7.3)
|
||||||
nokogiri (>= 1.8.5)
|
nokogiri (>= 1.8.5)
|
||||||
actionview (6.1.4.1)
|
actionview (6.1.7.3)
|
||||||
activesupport (= 6.1.4.1)
|
activesupport (= 6.1.7.3)
|
||||||
builder (~> 3.1)
|
builder (~> 3.1)
|
||||||
erubi (~> 1.4)
|
erubi (~> 1.4)
|
||||||
rails-dom-testing (~> 2.0)
|
rails-dom-testing (~> 2.0)
|
||||||
rails-html-sanitizer (~> 1.1, >= 1.2.0)
|
rails-html-sanitizer (~> 1.1, >= 1.2.0)
|
||||||
activejob (6.1.4.1)
|
activejob (6.1.7.3)
|
||||||
activesupport (= 6.1.4.1)
|
activesupport (= 6.1.7.3)
|
||||||
globalid (>= 0.3.6)
|
globalid (>= 0.3.6)
|
||||||
activemodel (6.1.4.1)
|
activemodel (6.1.7.3)
|
||||||
activesupport (= 6.1.4.1)
|
activesupport (= 6.1.7.3)
|
||||||
activerecord (6.1.4.1)
|
activerecord (6.1.7.3)
|
||||||
activemodel (= 6.1.4.1)
|
activemodel (= 6.1.7.3)
|
||||||
activesupport (= 6.1.4.1)
|
activesupport (= 6.1.7.3)
|
||||||
activestorage (6.1.4.1)
|
activestorage (6.1.7.3)
|
||||||
actionpack (= 6.1.4.1)
|
actionpack (= 6.1.7.3)
|
||||||
activejob (= 6.1.4.1)
|
activejob (= 6.1.7.3)
|
||||||
activerecord (= 6.1.4.1)
|
activerecord (= 6.1.7.3)
|
||||||
activesupport (= 6.1.4.1)
|
activesupport (= 6.1.7.3)
|
||||||
marcel (~> 1.0.0)
|
marcel (~> 1.0)
|
||||||
mini_mime (>= 1.1.0)
|
mini_mime (>= 1.1.0)
|
||||||
activesupport (6.1.4.1)
|
activesupport (6.1.7.3)
|
||||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||||
i18n (>= 1.6, < 2)
|
i18n (>= 1.6, < 2)
|
||||||
minitest (>= 5.1)
|
minitest (>= 5.1)
|
||||||
tzinfo (~> 2.0)
|
tzinfo (~> 2.0)
|
||||||
zeitwerk (~> 2.3)
|
zeitwerk (~> 2.3)
|
||||||
addressable (2.8.0)
|
addressable (2.8.4)
|
||||||
public_suffix (>= 2.0.2, < 5.0)
|
public_suffix (>= 2.0.2, < 6.0)
|
||||||
ast (2.4.2)
|
ast (2.4.2)
|
||||||
autoprefixer-rails (10.3.3.0)
|
autoprefixer-rails (10.4.13.0)
|
||||||
execjs (~> 2)
|
execjs (~> 2)
|
||||||
bcrypt (3.1.16-x86_64-linux-musl)
|
bcrypt (3.1.19-x86_64-linux-musl)
|
||||||
bcrypt_pbkdf (1.1.0-x86_64-linux-musl)
|
bcrypt_pbkdf (1.1.0-x86_64-linux-musl)
|
||||||
benchmark-ips (2.9.2)
|
benchmark-ips (2.12.0)
|
||||||
bindex (0.8.1-x86_64-linux-musl)
|
bindex (0.8.1-x86_64-linux-musl)
|
||||||
blazer (2.4.7)
|
blazer (2.6.5)
|
||||||
activerecord (>= 5)
|
activerecord (>= 5)
|
||||||
chartkick (>= 3.2)
|
chartkick (>= 3.2)
|
||||||
railties (>= 5)
|
railties (>= 5)
|
||||||
safely_block (>= 0.1.1)
|
safely_block (>= 0.1.1)
|
||||||
bootstrap (4.6.0)
|
bootstrap (4.6.2)
|
||||||
autoprefixer-rails (>= 9.1.0)
|
autoprefixer-rails (>= 9.1.0)
|
||||||
popper_js (>= 1.14.3, < 2)
|
popper_js (>= 1.16.1, < 2)
|
||||||
sassc-rails (>= 2.0.0)
|
sassc-rails (>= 2.0.0)
|
||||||
brakeman (5.1.2)
|
brakeman (5.4.1)
|
||||||
builder (3.2.4)
|
builder (3.2.4)
|
||||||
capybara (2.18.0)
|
capybara (2.18.0)
|
||||||
addressable
|
addressable
|
||||||
|
@ -113,25 +113,24 @@ GEM
|
||||||
rack (>= 1.0.0)
|
rack (>= 1.0.0)
|
||||||
rack-test (>= 0.5.4)
|
rack-test (>= 0.5.4)
|
||||||
xpath (>= 2.0, < 4.0)
|
xpath (>= 2.0, < 4.0)
|
||||||
chartkick (4.1.2)
|
chartkick (5.0.2)
|
||||||
childprocess (4.1.0)
|
|
||||||
climate_control (1.2.0)
|
climate_control (1.2.0)
|
||||||
coderay (1.1.3)
|
coderay (1.1.3)
|
||||||
colorator (1.1.0)
|
colorator (1.1.0)
|
||||||
commonmarker (0.21.2-x86_64-linux-musl)
|
commonmarker (0.23.10-x86_64-linux-musl)
|
||||||
ruby-enum (~> 0.5)
|
concurrent-ruby (1.2.2)
|
||||||
concurrent-ruby (1.1.9)
|
concurrent-ruby-ext (1.2.2-x86_64-linux-musl)
|
||||||
concurrent-ruby-ext (1.1.9-x86_64-linux-musl)
|
concurrent-ruby (= 1.2.2)
|
||||||
concurrent-ruby (= 1.1.9)
|
|
||||||
crass (1.0.6)
|
crass (1.0.6)
|
||||||
database_cleaner (2.0.1)
|
database_cleaner (2.0.2)
|
||||||
database_cleaner-active_record (~> 2.0.0)
|
database_cleaner-active_record (>= 2, < 3)
|
||||||
database_cleaner-active_record (2.0.1)
|
database_cleaner-active_record (2.1.0)
|
||||||
activerecord (>= 5.a)
|
activerecord (>= 5.a)
|
||||||
database_cleaner-core (~> 2.0.0)
|
database_cleaner-core (~> 2.0.0)
|
||||||
database_cleaner-core (2.0.1)
|
database_cleaner-core (2.0.1)
|
||||||
dead_end (3.1.0)
|
date (3.3.3-x86_64-linux-musl)
|
||||||
derailed_benchmarks (2.1.1)
|
dead_end (4.0.0)
|
||||||
|
derailed_benchmarks (2.1.2)
|
||||||
benchmark-ips (~> 2)
|
benchmark-ips (~> 2)
|
||||||
dead_end
|
dead_end
|
||||||
get_process_mem (~> 0)
|
get_process_mem (~> 0)
|
||||||
|
@ -143,29 +142,30 @@ GEM
|
||||||
rake (> 10, < 14)
|
rake (> 10, < 14)
|
||||||
ruby-statistics (>= 2.1)
|
ruby-statistics (>= 2.1)
|
||||||
thor (>= 0.19, < 2)
|
thor (>= 0.19, < 2)
|
||||||
devise (4.8.0)
|
device_detector (1.1.1)
|
||||||
|
devise (4.9.2)
|
||||||
bcrypt (~> 3.0)
|
bcrypt (~> 3.0)
|
||||||
orm_adapter (~> 0.1)
|
orm_adapter (~> 0.1)
|
||||||
railties (>= 4.1.0)
|
railties (>= 4.1.0)
|
||||||
responders
|
responders
|
||||||
warden (~> 1.2.3)
|
warden (~> 1.2.3)
|
||||||
devise-i18n (1.10.1)
|
devise-i18n (1.11.0)
|
||||||
devise (>= 4.8.0)
|
devise (>= 4.9.0)
|
||||||
devise_invitable (2.0.5)
|
devise_invitable (2.0.8)
|
||||||
actionmailer (>= 5.0)
|
actionmailer (>= 5.0)
|
||||||
devise (>= 4.6)
|
devise (>= 4.6)
|
||||||
distributed-press-api-client (0.2.2)
|
distributed-press-api-client (0.3.0rc0)
|
||||||
addressable (~> 2.3, >= 2.3.0)
|
addressable (~> 2.3, >= 2.3.0)
|
||||||
climate_control
|
climate_control
|
||||||
dry-schema
|
dry-schema
|
||||||
httparty (~> 0.18)
|
httparty (~> 0.18)
|
||||||
json (~> 2.1, >= 2.1.0)
|
json (~> 2.1, >= 2.1.0)
|
||||||
jwt (~> 2.6.0)
|
jwt (~> 2.6.0)
|
||||||
dotenv (2.7.6)
|
dotenv (2.8.1)
|
||||||
dotenv-rails (2.7.6)
|
dotenv-rails (2.8.1)
|
||||||
dotenv (= 2.7.6)
|
dotenv (= 2.8.1)
|
||||||
railties (>= 3.2)
|
railties (>= 3.2)
|
||||||
down (5.2.4)
|
down (5.4.1)
|
||||||
addressable (~> 2.8)
|
addressable (~> 2.8)
|
||||||
dry-configurable (1.0.1)
|
dry-configurable (1.0.1)
|
||||||
dry-core (~> 1.0, < 2)
|
dry-core (~> 1.0, < 2)
|
||||||
|
@ -179,65 +179,68 @@ GEM
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
dry-core (~> 1.0, < 2)
|
dry-core (~> 1.0, < 2)
|
||||||
zeitwerk (~> 2.6)
|
zeitwerk (~> 2.6)
|
||||||
dry-schema (1.13.0)
|
dry-schema (1.13.1)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
dry-configurable (~> 1.0, >= 1.0.1)
|
dry-configurable (~> 1.0, >= 1.0.1)
|
||||||
dry-core (~> 1.0, < 2)
|
dry-core (~> 1.0, < 2)
|
||||||
dry-initializer (~> 3.0)
|
dry-initializer (~> 3.0)
|
||||||
dry-logic (>= 1.5, < 2)
|
dry-logic (>= 1.4, < 2)
|
||||||
dry-types (>= 1.7, < 2)
|
dry-types (>= 1.7, < 2)
|
||||||
zeitwerk (~> 2.6)
|
zeitwerk (~> 2.6)
|
||||||
dry-types (1.7.0)
|
dry-types (1.7.1)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
dry-core (~> 1.0, < 2)
|
dry-core (~> 1.0)
|
||||||
dry-inflector (~> 1.0, < 2)
|
dry-inflector (~> 1.0)
|
||||||
dry-logic (>= 1.4, < 2)
|
dry-logic (~> 1.4)
|
||||||
zeitwerk (~> 2.6)
|
zeitwerk (~> 2.6)
|
||||||
ed25519 (1.2.4-x86_64-linux-musl)
|
ed25519 (1.3.0-x86_64-linux-musl)
|
||||||
em-websocket (0.5.3)
|
em-websocket (0.5.3)
|
||||||
eventmachine (>= 0.12.9)
|
eventmachine (>= 0.12.9)
|
||||||
http_parser.rb (~> 0)
|
http_parser.rb (~> 0)
|
||||||
errbase (0.2.1)
|
errbase (0.2.2)
|
||||||
erubi (1.10.0)
|
erubi (1.12.0)
|
||||||
eventmachine (1.2.7-x86_64-linux-musl)
|
eventmachine (1.2.7-x86_64-linux-musl)
|
||||||
exception_notification (4.4.3)
|
exception_notification (4.5.0)
|
||||||
actionmailer (>= 4.0, < 7)
|
actionmailer (>= 5.2, < 8)
|
||||||
activesupport (>= 4.0, < 7)
|
activesupport (>= 5.2, < 8)
|
||||||
execjs (2.8.1)
|
execjs (2.8.1)
|
||||||
factory_bot (6.2.0)
|
factory_bot (6.2.1)
|
||||||
activesupport (>= 5.0.0)
|
activesupport (>= 5.0.0)
|
||||||
factory_bot_rails (6.2.0)
|
factory_bot_rails (6.2.0)
|
||||||
factory_bot (~> 6.2.0)
|
factory_bot (~> 6.2.0)
|
||||||
railties (>= 5.0.0)
|
railties (>= 5.0.0)
|
||||||
fast_blank (1.0.1-x86_64-linux-musl)
|
fast_blank (1.0.1-x86_64-linux-musl)
|
||||||
fast_jsonparser (0.5.0-x86_64-linux-musl)
|
fast_jsonparser (0.5.0-x86_64-linux-musl)
|
||||||
ffi (1.15.4-x86_64-linux-musl)
|
ffi (1.15.5-x86_64-linux-musl)
|
||||||
flamegraph (0.9.5)
|
flamegraph (0.9.5)
|
||||||
forwardable-extended (2.6.0)
|
forwardable-extended (2.6.0)
|
||||||
friendly_id (5.4.2)
|
friendly_id (5.5.0)
|
||||||
activerecord (>= 4.0.0)
|
activerecord (>= 4.0.0)
|
||||||
get_process_mem (0.2.7)
|
get_process_mem (0.2.7)
|
||||||
ffi (~> 1.0)
|
ffi (~> 1.0)
|
||||||
globalid (0.6.0)
|
git_clone_url (2.0.0)
|
||||||
|
uri-ssh_git (>= 2.0)
|
||||||
|
globalid (1.1.0)
|
||||||
activesupport (>= 5.0)
|
activesupport (>= 5.0)
|
||||||
groupdate (6.1.0)
|
groupdate (6.2.1)
|
||||||
activesupport (>= 5.2)
|
activesupport (>= 5.2)
|
||||||
hairtrigger (0.2.24)
|
hairtrigger (1.0.0)
|
||||||
activerecord (>= 5.0, < 7)
|
activerecord (>= 6.0, < 8)
|
||||||
ruby2ruby (~> 2.4)
|
ruby2ruby (~> 2.4)
|
||||||
ruby_parser (~> 3.10)
|
ruby_parser (~> 3.10)
|
||||||
haml (5.2.2)
|
haml (6.1.2-x86_64-linux-musl)
|
||||||
temple (>= 0.8.0)
|
temple (>= 0.8.2)
|
||||||
|
thor
|
||||||
tilt
|
tilt
|
||||||
haml-lint (0.999.999)
|
haml-lint (0.999.999)
|
||||||
haml_lint
|
haml_lint
|
||||||
haml_lint (0.37.1)
|
haml_lint (0.45.0)
|
||||||
haml (>= 4.0, < 5.3)
|
haml (>= 4.0, < 6.2)
|
||||||
parallel (~> 1.10)
|
parallel (~> 1.10)
|
||||||
rainbow
|
rainbow
|
||||||
rubocop (>= 0.50.0)
|
rubocop (>= 0.50.0)
|
||||||
sysexits (~> 1.1)
|
sysexits (~> 1.1)
|
||||||
hamlit (2.15.1-x86_64-linux-musl)
|
hamlit (3.0.3-x86_64-linux-musl)
|
||||||
temple (>= 0.8.2)
|
temple (>= 0.8.2)
|
||||||
thor
|
thor
|
||||||
tilt
|
tilt
|
||||||
|
@ -253,20 +256,21 @@ GEM
|
||||||
httparty (0.21.0)
|
httparty (0.21.0)
|
||||||
mini_mime (>= 1.0.0)
|
mini_mime (>= 1.0.0)
|
||||||
multi_xml (>= 0.5.2)
|
multi_xml (>= 0.5.2)
|
||||||
i18n (1.8.11)
|
i18n (1.14.1)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
icalendar (2.7.1)
|
icalendar (2.8.0)
|
||||||
ice_cube (~> 0.16)
|
ice_cube (~> 0.16)
|
||||||
ice_cube (0.16.4)
|
ice_cube (0.16.4)
|
||||||
image_processing (1.12.1)
|
image_processing (1.12.2)
|
||||||
mini_magick (>= 4.9.5, < 5)
|
mini_magick (>= 4.9.5, < 5)
|
||||||
ruby-vips (>= 2.0.17, < 3)
|
ruby-vips (>= 2.0.17, < 3)
|
||||||
inline_svg (1.7.2)
|
inline_svg (1.9.0)
|
||||||
activesupport (>= 3.0)
|
activesupport (>= 3.0)
|
||||||
nokogiri (>= 1.6)
|
nokogiri (>= 1.6)
|
||||||
jbuilder (2.11.3)
|
jbuilder (2.11.5)
|
||||||
|
actionview (>= 5.0.0)
|
||||||
activesupport (>= 5.0.0)
|
activesupport (>= 5.0.0)
|
||||||
jekyll (4.2.1)
|
jekyll (4.2.2)
|
||||||
addressable (~> 2.4)
|
addressable (~> 2.4)
|
||||||
colorator (~> 1.0)
|
colorator (~> 1.0)
|
||||||
em-websocket (~> 0.5)
|
em-websocket (~> 0.5)
|
||||||
|
@ -281,242 +285,216 @@ GEM
|
||||||
rouge (~> 3.0)
|
rouge (~> 3.0)
|
||||||
safe_yaml (~> 1.0)
|
safe_yaml (~> 1.0)
|
||||||
terminal-table (~> 2.0)
|
terminal-table (~> 2.0)
|
||||||
jekyll-commonmark (1.3.2)
|
jekyll-commonmark (1.4.0)
|
||||||
commonmarker (~> 0.14, < 0.22)
|
commonmarker (~> 0.22)
|
||||||
jekyll (>= 3.7, < 5.0)
|
|
||||||
jekyll-data (1.1.2)
|
jekyll-data (1.1.2)
|
||||||
jekyll (>= 3.3, < 5.0.0)
|
jekyll (>= 3.3, < 5.0.0)
|
||||||
jekyll-dotenv (0.2.0)
|
jekyll-images (0.4.1)
|
||||||
dotenv (~> 2.7)
|
|
||||||
jekyll (~> 4)
|
|
||||||
jekyll-feed (0.15.1)
|
|
||||||
jekyll (>= 3.7, < 5.0)
|
|
||||||
jekyll-hardlinks (0.1.2)
|
|
||||||
jekyll (~> 4)
|
|
||||||
jekyll-ignore-layouts (0.1.2)
|
|
||||||
jekyll (~> 4)
|
|
||||||
jekyll-images (0.3.2)
|
|
||||||
jekyll (~> 4)
|
jekyll (~> 4)
|
||||||
ruby-filemagic (~> 0.7)
|
ruby-filemagic (~> 0.7)
|
||||||
ruby-vips (~> 2)
|
ruby-vips (~> 2)
|
||||||
jekyll-include-cache (0.2.1)
|
jekyll-include-cache (0.2.1)
|
||||||
jekyll (>= 3.7, < 5.0)
|
jekyll (>= 3.7, < 5.0)
|
||||||
jekyll-linked-posts (0.4.2)
|
jekyll-sass-converter (2.2.0)
|
||||||
jekyll (~> 4)
|
|
||||||
jekyll-locales (0.1.13)
|
|
||||||
jekyll-lunr (0.3.0)
|
|
||||||
loofah (~> 2.4)
|
|
||||||
jekyll-order (0.1.4)
|
|
||||||
jekyll-relative-urls (0.0.6)
|
|
||||||
jekyll (~> 4)
|
|
||||||
jekyll-sass-converter (2.1.0)
|
|
||||||
sassc (> 2.0.1, < 3.0)
|
sassc (> 2.0.1, < 3.0)
|
||||||
jekyll-seo-tag (2.7.1)
|
|
||||||
jekyll (>= 3.8, < 5.0)
|
|
||||||
jekyll-spree-client (0.1.19)
|
|
||||||
fast_blank (~> 1)
|
|
||||||
spree-api-client (>= 0.2.4)
|
|
||||||
jekyll-turbolinks (0.0.5)
|
|
||||||
jekyll (~> 4)
|
|
||||||
turbolinks-source (~> 5)
|
|
||||||
jekyll-unique-urls (0.1.1)
|
|
||||||
jekyll (~> 4)
|
|
||||||
jekyll-watch (2.2.1)
|
jekyll-watch (2.2.1)
|
||||||
listen (~> 3.0)
|
listen (~> 3.0)
|
||||||
jekyll-write-and-commit-changes (0.2.1)
|
json (2.6.3-x86_64-linux-musl)
|
||||||
jekyll (~> 4)
|
|
||||||
rugged (~> 1)
|
|
||||||
jwt (2.6.0)
|
jwt (2.6.0)
|
||||||
kaminari (1.2.1)
|
kaminari (1.2.2)
|
||||||
activesupport (>= 4.1.0)
|
activesupport (>= 4.1.0)
|
||||||
kaminari-actionview (= 1.2.1)
|
kaminari-actionview (= 1.2.2)
|
||||||
kaminari-activerecord (= 1.2.1)
|
kaminari-activerecord (= 1.2.2)
|
||||||
kaminari-core (= 1.2.1)
|
kaminari-core (= 1.2.2)
|
||||||
kaminari-actionview (1.2.1)
|
kaminari-actionview (1.2.2)
|
||||||
actionview
|
actionview
|
||||||
kaminari-core (= 1.2.1)
|
kaminari-core (= 1.2.2)
|
||||||
kaminari-activerecord (1.2.1)
|
kaminari-activerecord (1.2.2)
|
||||||
activerecord
|
activerecord
|
||||||
kaminari-core (= 1.2.1)
|
kaminari-core (= 1.2.2)
|
||||||
kaminari-core (1.2.1)
|
kaminari-core (1.2.2)
|
||||||
kramdown (2.3.1)
|
kramdown (2.4.0)
|
||||||
rexml
|
rexml
|
||||||
kramdown-parser-gfm (1.1.0)
|
kramdown-parser-gfm (1.1.0)
|
||||||
kramdown (~> 2.0)
|
kramdown (~> 2.0)
|
||||||
launchy (2.5.0)
|
launchy (2.5.2)
|
||||||
addressable (~> 2.7)
|
addressable (~> 2.8)
|
||||||
letter_opener (1.7.0)
|
letter_opener (1.8.1)
|
||||||
launchy (~> 2.2)
|
launchy (>= 2.2, < 3)
|
||||||
liquid (4.0.3)
|
liquid (4.0.4)
|
||||||
listen (3.1.5)
|
listen (3.8.0)
|
||||||
rb-fsevent (~> 0.9, >= 0.9.4)
|
rb-fsevent (~> 0.10, >= 0.10.3)
|
||||||
rb-inotify (~> 0.9, >= 0.9.7)
|
rb-inotify (~> 0.9, >= 0.9.10)
|
||||||
ruby_dep (~> 1.2)
|
|
||||||
loaf (0.10.0)
|
loaf (0.10.0)
|
||||||
railties (>= 3.2)
|
railties (>= 3.2)
|
||||||
lockbox (0.6.6)
|
lockbox (1.2.0)
|
||||||
lograge (0.11.2)
|
lograge (0.12.0)
|
||||||
actionpack (>= 4)
|
actionpack (>= 4)
|
||||||
activesupport (>= 4)
|
activesupport (>= 4)
|
||||||
railties (>= 4)
|
railties (>= 4)
|
||||||
request_store (~> 1.0)
|
request_store (~> 1.0)
|
||||||
loofah (2.12.0)
|
loofah (2.21.3)
|
||||||
crass (~> 1.0.2)
|
crass (~> 1.0.2)
|
||||||
nokogiri (>= 1.5.9)
|
nokogiri (>= 1.12.0)
|
||||||
mail (2.7.1)
|
mail (2.8.1)
|
||||||
mini_mime (>= 0.1.1)
|
mini_mime (>= 0.1.1)
|
||||||
|
net-imap
|
||||||
|
net-pop
|
||||||
|
net-smtp
|
||||||
marcel (1.0.2)
|
marcel (1.0.2)
|
||||||
memory_profiler (1.0.0)
|
memory_profiler (1.0.1)
|
||||||
mercenary (0.4.0)
|
mercenary (0.4.0)
|
||||||
method_source (1.0.0)
|
method_source (1.0.0)
|
||||||
mime-types (3.4.1)
|
|
||||||
mime-types-data (~> 3.2015)
|
|
||||||
mime-types-data (3.2021.1115)
|
|
||||||
mini_histogram (0.3.1)
|
mini_histogram (0.3.1)
|
||||||
mini_magick (4.11.0)
|
mini_magick (4.12.0)
|
||||||
mini_mime (1.1.2)
|
mini_mime (1.1.2)
|
||||||
mini_portile2 (2.6.1)
|
mini_portile2 (2.8.2)
|
||||||
minitest (5.14.4)
|
minitest (5.18.0)
|
||||||
mobility (1.2.4)
|
mobility (1.2.9)
|
||||||
i18n (>= 0.6.10, < 2)
|
i18n (>= 0.6.10, < 2)
|
||||||
request_store (~> 1.0)
|
request_store (~> 1.0)
|
||||||
multi_xml (0.6.0)
|
multi_xml (0.6.0)
|
||||||
net-ssh (6.1.0)
|
net-imap (0.3.4)
|
||||||
netaddr (2.0.5)
|
date
|
||||||
nio4r (2.5.8-x86_64-linux-musl)
|
net-protocol
|
||||||
nokogiri (1.12.5-x86_64-linux-musl)
|
net-pop (0.1.2)
|
||||||
mini_portile2 (~> 2.6.1)
|
net-protocol
|
||||||
|
net-protocol (0.2.1)
|
||||||
|
timeout
|
||||||
|
net-smtp (0.3.3)
|
||||||
|
net-protocol
|
||||||
|
net-ssh (7.1.0)
|
||||||
|
netaddr (2.0.6)
|
||||||
|
nio4r (2.5.9-x86_64-linux-musl)
|
||||||
|
nokogiri (1.15.4-x86_64-linux-musl)
|
||||||
|
mini_portile2 (~> 2.8.2)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
njalla-api-client (0.2.0)
|
|
||||||
dry-schema
|
|
||||||
httparty (~> 0.18)
|
|
||||||
orm_adapter (0.5.0)
|
orm_adapter (0.5.0)
|
||||||
pairing_heap (3.0.0)
|
pairing_heap (3.0.1)
|
||||||
parallel (1.21.0)
|
parallel (1.23.0)
|
||||||
parser (3.0.2.0)
|
parser (3.2.2.1)
|
||||||
ast (~> 2.4.1)
|
ast (~> 2.4.1)
|
||||||
pathutil (0.16.2)
|
pathutil (0.16.2)
|
||||||
forwardable-extended (~> 2.6)
|
forwardable-extended (~> 2.6)
|
||||||
pg (1.2.3-x86_64-linux-musl)
|
pg (1.5.3-x86_64-linux-musl)
|
||||||
pg_search (2.3.5)
|
pg_search (2.3.6)
|
||||||
activerecord (>= 5.2)
|
activerecord (>= 5.2)
|
||||||
activesupport (>= 5.2)
|
activesupport (>= 5.2)
|
||||||
popper_js (1.16.0)
|
popper_js (1.16.1)
|
||||||
prometheus_exporter (1.0.0)
|
prometheus_exporter (2.0.8)
|
||||||
webrick
|
webrick
|
||||||
pry (0.14.1)
|
pry (0.14.2)
|
||||||
coderay (~> 1.1)
|
coderay (~> 1.1)
|
||||||
method_source (~> 1.0)
|
method_source (~> 1.0)
|
||||||
public_suffix (4.0.6)
|
public_suffix (5.0.3)
|
||||||
puma (5.5.2-x86_64-linux-musl)
|
puma (6.3.1-x86_64-linux-musl)
|
||||||
nio4r (~> 2.0)
|
nio4r (~> 2.0)
|
||||||
pundit (2.1.1)
|
pundit (2.3.0)
|
||||||
activesupport (>= 3.0.0)
|
activesupport (>= 3.0.0)
|
||||||
racc (1.6.0-x86_64-linux-musl)
|
que (2.2.1)
|
||||||
rack (2.2.3)
|
racc (1.7.1-x86_64-linux-musl)
|
||||||
rack-cors (1.1.1)
|
rack (2.2.7)
|
||||||
|
rack-cors (2.0.1)
|
||||||
rack (>= 2.0.0)
|
rack (>= 2.0.0)
|
||||||
rack-mini-profiler (2.3.3)
|
rack-mini-profiler (3.1.0)
|
||||||
rack (>= 1.2.0)
|
rack (>= 1.2.0)
|
||||||
rack-proxy (0.7.0)
|
rack-proxy (0.7.6)
|
||||||
rack
|
rack
|
||||||
rack-test (1.1.0)
|
rack-test (2.1.0)
|
||||||
rack (>= 1.0, < 3)
|
rack (>= 1.3)
|
||||||
rails (6.1.4.1)
|
rails (6.1.7.3)
|
||||||
actioncable (= 6.1.4.1)
|
actioncable (= 6.1.7.3)
|
||||||
actionmailbox (= 6.1.4.1)
|
actionmailbox (= 6.1.7.3)
|
||||||
actionmailer (= 6.1.4.1)
|
actionmailer (= 6.1.7.3)
|
||||||
actionpack (= 6.1.4.1)
|
actionpack (= 6.1.7.3)
|
||||||
actiontext (= 6.1.4.1)
|
actiontext (= 6.1.7.3)
|
||||||
actionview (= 6.1.4.1)
|
actionview (= 6.1.7.3)
|
||||||
activejob (= 6.1.4.1)
|
activejob (= 6.1.7.3)
|
||||||
activemodel (= 6.1.4.1)
|
activemodel (= 6.1.7.3)
|
||||||
activerecord (= 6.1.4.1)
|
activerecord (= 6.1.7.3)
|
||||||
activestorage (= 6.1.4.1)
|
activestorage (= 6.1.7.3)
|
||||||
activesupport (= 6.1.4.1)
|
activesupport (= 6.1.7.3)
|
||||||
bundler (>= 1.15.0)
|
bundler (>= 1.15.0)
|
||||||
railties (= 6.1.4.1)
|
railties (= 6.1.7.3)
|
||||||
sprockets-rails (>= 2.0.0)
|
sprockets-rails (>= 2.0.0)
|
||||||
rails-dom-testing (2.0.3)
|
rails-dom-testing (2.0.3)
|
||||||
activesupport (>= 4.2.0)
|
activesupport (>= 4.2.0)
|
||||||
nokogiri (>= 1.6)
|
nokogiri (>= 1.6)
|
||||||
rails-html-sanitizer (1.4.2)
|
rails-html-sanitizer (1.5.0)
|
||||||
loofah (~> 2.3)
|
loofah (~> 2.19, >= 2.19.1)
|
||||||
rails-i18n (6.0.0)
|
rails-i18n (7.0.7)
|
||||||
i18n (>= 0.7, < 2)
|
i18n (>= 0.7, < 2)
|
||||||
railties (>= 6.0.0, < 7)
|
railties (>= 6.0.0, < 8)
|
||||||
rails_warden (0.6.0)
|
rails_warden (0.6.0)
|
||||||
warden (>= 1.2.0)
|
warden (>= 1.2.0)
|
||||||
railties (6.1.4.1)
|
railties (6.1.7.3)
|
||||||
actionpack (= 6.1.4.1)
|
actionpack (= 6.1.7.3)
|
||||||
activesupport (= 6.1.4.1)
|
activesupport (= 6.1.7.3)
|
||||||
method_source
|
method_source
|
||||||
rake (>= 0.13)
|
rake (>= 12.2)
|
||||||
thor (~> 1.0)
|
thor (~> 1.0)
|
||||||
rainbow (3.0.0)
|
rainbow (3.1.1)
|
||||||
rake (13.0.6)
|
rake (13.0.6)
|
||||||
rb-fsevent (0.11.0)
|
rb-fsevent (0.11.2)
|
||||||
rb-inotify (0.10.1)
|
rb-inotify (0.10.1)
|
||||||
ffi (~> 1.0)
|
ffi (~> 1.0)
|
||||||
redis (4.5.1)
|
redis (4.8.1)
|
||||||
redis-actionpack (5.2.0)
|
redis-actionpack (5.3.0)
|
||||||
actionpack (>= 5, < 7)
|
actionpack (>= 5, < 8)
|
||||||
redis-rack (>= 2.1.0, < 3)
|
redis-rack (>= 2.1.0, < 3)
|
||||||
redis-store (>= 1.1.0, < 2)
|
redis-store (>= 1.1.0, < 2)
|
||||||
redis-activesupport (5.2.1)
|
redis-activesupport (5.3.0)
|
||||||
activesupport (>= 3, < 7)
|
activesupport (>= 3, < 8)
|
||||||
redis-store (>= 1.3, < 2)
|
redis-store (>= 1.3, < 2)
|
||||||
redis-rack (2.1.3)
|
redis-rack (2.1.4)
|
||||||
rack (>= 2.0.8, < 3)
|
rack (>= 2.0.8, < 3)
|
||||||
redis-store (>= 1.2, < 2)
|
redis-store (>= 1.2, < 2)
|
||||||
redis-rails (5.0.2)
|
redis-rails (5.0.2)
|
||||||
redis-actionpack (>= 5.0, < 6)
|
redis-actionpack (>= 5.0, < 6)
|
||||||
redis-activesupport (>= 5.0, < 6)
|
redis-activesupport (>= 5.0, < 6)
|
||||||
redis-store (>= 1.2, < 2)
|
redis-store (>= 1.2, < 2)
|
||||||
redis-store (1.9.0)
|
redis-store (1.9.2)
|
||||||
redis (>= 4, < 5)
|
redis (>= 4, < 6)
|
||||||
regexp_parser (2.1.1)
|
regexp_parser (2.8.0)
|
||||||
request_store (1.5.0)
|
request_store (1.5.1)
|
||||||
rack (>= 1.4)
|
rack (>= 1.4)
|
||||||
responders (3.0.1)
|
responders (3.1.0)
|
||||||
actionpack (>= 5.0)
|
actionpack (>= 5.2)
|
||||||
railties (>= 5.0)
|
railties (>= 5.2)
|
||||||
rexml (3.2.5)
|
rexml (3.2.5)
|
||||||
rgl (0.6.2)
|
rgl (0.6.3)
|
||||||
pairing_heap (>= 0.3.0)
|
pairing_heap (>= 0.3.0)
|
||||||
rexml (~> 3.2, >= 3.2.4)
|
rexml (~> 3.2, >= 3.2.4)
|
||||||
stream (~> 0.5.3)
|
stream (~> 0.5.3)
|
||||||
rouge (3.26.1)
|
rouge (3.30.0)
|
||||||
rubocop (1.23.0)
|
rubocop (1.42.0)
|
||||||
|
json (~> 2.3)
|
||||||
parallel (~> 1.10)
|
parallel (~> 1.10)
|
||||||
parser (>= 3.0.0.0)
|
parser (>= 3.1.2.1)
|
||||||
rainbow (>= 2.2.2, < 4.0)
|
rainbow (>= 2.2.2, < 4.0)
|
||||||
regexp_parser (>= 1.8, < 3.0)
|
regexp_parser (>= 1.8, < 3.0)
|
||||||
rexml
|
rexml (>= 3.2.5, < 4.0)
|
||||||
rubocop-ast (>= 1.12.0, < 2.0)
|
rubocop-ast (>= 1.24.1, < 2.0)
|
||||||
ruby-progressbar (~> 1.7)
|
ruby-progressbar (~> 1.7)
|
||||||
unicode-display_width (>= 1.4.0, < 3.0)
|
unicode-display_width (>= 1.4.0, < 3.0)
|
||||||
rubocop-ast (1.13.0)
|
rubocop-ast (1.28.1)
|
||||||
parser (>= 3.0.1.1)
|
parser (>= 3.2.1.0)
|
||||||
rubocop-rails (2.12.4)
|
rubocop-rails (2.19.1)
|
||||||
activesupport (>= 4.2.0)
|
activesupport (>= 4.2.0)
|
||||||
rack (>= 1.1)
|
rack (>= 1.1)
|
||||||
rubocop (>= 1.7.0, < 2.0)
|
rubocop (>= 1.33.0, < 2.0)
|
||||||
ruby-enum (0.9.0)
|
ruby-filemagic (0.7.3-x86_64-linux-musl)
|
||||||
i18n
|
ruby-progressbar (1.13.0)
|
||||||
ruby-filemagic (0.7.2-x86_64-linux-musl)
|
ruby-statistics (3.0.2)
|
||||||
ruby-progressbar (1.11.0)
|
|
||||||
ruby-statistics (3.0.0)
|
|
||||||
ruby-vips (2.1.4)
|
ruby-vips (2.1.4)
|
||||||
ffi (~> 1.12)
|
ffi (~> 1.12)
|
||||||
ruby2ruby (2.4.4)
|
ruby2ruby (2.5.0)
|
||||||
ruby_parser (~> 3.1)
|
ruby_parser (~> 3.1)
|
||||||
sexp_processor (~> 4.6)
|
sexp_processor (~> 4.6)
|
||||||
ruby_dep (1.5.0)
|
ruby_parser (3.20.1)
|
||||||
ruby_parser (3.18.1)
|
|
||||||
sexp_processor (~> 4.16)
|
sexp_processor (~> 4.16)
|
||||||
rubyzip (2.3.2)
|
rubyzip (2.3.2)
|
||||||
rugged (1.2.0-x86_64-linux-musl)
|
rugged (1.5.0.1-x86_64-linux-musl)
|
||||||
safe_yaml (1.0.6)
|
safe_yaml (1.0.6)
|
||||||
safely_block (0.3.0)
|
safely_block (0.3.0)
|
||||||
errbase (>= 0.1.1)
|
errbase (>= 0.1.1)
|
||||||
|
@ -528,59 +506,55 @@ GEM
|
||||||
sprockets (> 3.0)
|
sprockets (> 3.0)
|
||||||
sprockets-rails
|
sprockets-rails
|
||||||
tilt
|
tilt
|
||||||
selenium-webdriver (4.1.0)
|
selenium-webdriver (4.8.6)
|
||||||
childprocess (>= 0.5, < 5.0)
|
|
||||||
rexml (~> 3.2, >= 3.2.5)
|
rexml (~> 3.2, >= 3.2.5)
|
||||||
rubyzip (>= 1.2.2)
|
rubyzip (>= 1.2.2, < 3.0)
|
||||||
|
websocket (~> 1.0)
|
||||||
semantic_range (3.0.0)
|
semantic_range (3.0.0)
|
||||||
sexp_processor (4.16.0)
|
sexp_processor (4.17.0)
|
||||||
simpleidn (0.2.1)
|
simpleidn (0.2.1)
|
||||||
unf (~> 0.1.4)
|
unf (~> 0.1.4)
|
||||||
sourcemap (0.1.1)
|
sourcemap (0.1.1)
|
||||||
spree-api-client (0.2.4)
|
spring (4.1.1)
|
||||||
fast_blank (~> 1)
|
spring-watcher-listen (2.1.0)
|
||||||
httparty (~> 0.18.0)
|
|
||||||
spring (2.1.1)
|
|
||||||
spring-watcher-listen (2.0.1)
|
|
||||||
listen (>= 2.7, < 4.0)
|
listen (>= 2.7, < 4.0)
|
||||||
spring (>= 1.2, < 3.0)
|
spring (>= 4)
|
||||||
sprockets (4.0.2)
|
sprockets (4.2.0)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
rack (> 1, < 3)
|
rack (>= 2.2.4, < 4)
|
||||||
sprockets-rails (3.4.1)
|
sprockets-rails (3.4.2)
|
||||||
actionpack (>= 5.2)
|
actionpack (>= 5.2)
|
||||||
activesupport (>= 5.2)
|
activesupport (>= 5.2)
|
||||||
sprockets (>= 3.0.0)
|
sprockets (>= 3.0.0)
|
||||||
sqlite3 (1.4.2-x86_64-linux-musl)
|
sqlite3 (1.6.3-x86_64-linux-musl)
|
||||||
stackprof (0.2.17-x86_64-linux-musl)
|
mini_portile2 (~> 2.8.0)
|
||||||
|
stackprof (0.2.25-x86_64-linux-musl)
|
||||||
stream (0.5.5)
|
stream (0.5.5)
|
||||||
sucker_punch (3.0.1)
|
sutty-liquid (0.11.11)
|
||||||
concurrent-ruby (~> 1.0)
|
|
||||||
sutty-archives (2.5.4)
|
|
||||||
jekyll (>= 3.6, < 5.0)
|
|
||||||
sutty-liquid (0.7.4)
|
|
||||||
fast_blank (~> 1.0)
|
fast_blank (~> 1.0)
|
||||||
jekyll (~> 4)
|
jekyll (~> 4)
|
||||||
symbol-fstring (1.0.2-x86_64-linux-musl)
|
symbol-fstring (1.0.2-x86_64-linux-musl)
|
||||||
sysexits (1.2.0)
|
sysexits (1.2.0)
|
||||||
temple (0.8.2)
|
temple (0.10.1)
|
||||||
terminal-table (2.0.0)
|
terminal-table (2.0.0)
|
||||||
unicode-display_width (~> 1.1, >= 1.1.1)
|
unicode-display_width (~> 1.1, >= 1.1.1)
|
||||||
thor (1.1.0)
|
thor (1.2.2)
|
||||||
tilt (2.0.10)
|
tilt (2.1.0)
|
||||||
timecop (0.9.4)
|
timecop (0.9.6)
|
||||||
|
timeout (0.3.2)
|
||||||
turbolinks (5.2.1)
|
turbolinks (5.2.1)
|
||||||
turbolinks-source (~> 5.2)
|
turbolinks-source (~> 5.2)
|
||||||
turbolinks-source (5.2.0)
|
turbolinks-source (5.2.0)
|
||||||
tzinfo (2.0.4)
|
tzinfo (2.0.6)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
uglifier (4.2.0)
|
uglifier (4.2.0)
|
||||||
execjs (>= 0.3.0, < 3)
|
execjs (>= 0.3.0, < 3)
|
||||||
unf (0.1.4)
|
unf (0.1.4)
|
||||||
unf_ext
|
unf_ext
|
||||||
unf_ext (0.0.8-x86_64-linux-musl)
|
unf_ext (0.0.8.2-x86_64-linux-musl)
|
||||||
unicode-display_width (1.8.0)
|
unicode-display_width (1.8.0)
|
||||||
validates_hostname (1.0.11)
|
uri-ssh_git (2.0.0)
|
||||||
|
validates_hostname (1.0.13)
|
||||||
activerecord (>= 3.0)
|
activerecord (>= 3.0)
|
||||||
activesupport (>= 3.0)
|
activesupport (>= 3.0)
|
||||||
warden (1.2.9)
|
warden (1.2.9)
|
||||||
|
@ -590,21 +564,21 @@ GEM
|
||||||
activemodel (>= 6.0.0)
|
activemodel (>= 6.0.0)
|
||||||
bindex (>= 0.4.0)
|
bindex (>= 0.4.0)
|
||||||
railties (>= 6.0.0)
|
railties (>= 6.0.0)
|
||||||
webpacker (5.4.3)
|
webpacker (5.4.4)
|
||||||
activesupport (>= 5.2)
|
activesupport (>= 5.2)
|
||||||
rack-proxy (>= 0.6.1)
|
rack-proxy (>= 0.6.1)
|
||||||
railties (>= 5.2)
|
railties (>= 5.2)
|
||||||
semantic_range (>= 2.3.0)
|
semantic_range (>= 2.3.0)
|
||||||
webrick (1.7.0)
|
webrick (1.8.1)
|
||||||
websocket-driver (0.7.5-x86_64-linux-musl)
|
websocket (1.2.9)
|
||||||
|
websocket-driver (0.7.6-x86_64-linux-musl)
|
||||||
websocket-extensions (>= 0.1.0)
|
websocket-extensions (>= 0.1.0)
|
||||||
websocket-extensions (0.1.5)
|
websocket-extensions (0.1.5)
|
||||||
xpath (3.2.0)
|
xpath (3.2.0)
|
||||||
nokogiri (~> 1.8)
|
nokogiri (~> 1.8)
|
||||||
zeitwerk (2.5.1)
|
zeitwerk (2.6.8)
|
||||||
|
|
||||||
PLATFORMS
|
PLATFORMS
|
||||||
ruby
|
|
||||||
x86_64-linux-musl
|
x86_64-linux-musl
|
||||||
|
|
||||||
DEPENDENCIES
|
DEPENDENCIES
|
||||||
|
@ -619,10 +593,11 @@ DEPENDENCIES
|
||||||
concurrent-ruby-ext
|
concurrent-ruby-ext
|
||||||
database_cleaner
|
database_cleaner
|
||||||
derailed_benchmarks
|
derailed_benchmarks
|
||||||
|
device_detector
|
||||||
devise
|
devise
|
||||||
devise-i18n
|
devise-i18n
|
||||||
devise_invitable
|
devise_invitable
|
||||||
distributed-press-api-client (~> 0.2.3)
|
distributed-press-api-client (~> 0.3.0rc0)
|
||||||
dotenv-rails
|
dotenv-rails
|
||||||
down
|
down
|
||||||
ed25519
|
ed25519
|
||||||
|
@ -630,9 +605,10 @@ DEPENDENCIES
|
||||||
exception_notification
|
exception_notification
|
||||||
factory_bot_rails
|
factory_bot_rails
|
||||||
fast_blank
|
fast_blank
|
||||||
fast_jsonparser
|
fast_jsonparser (~> 0.5.0)
|
||||||
flamegraph
|
flamegraph
|
||||||
friendly_id
|
friendly_id
|
||||||
|
git_clone_url
|
||||||
hairtrigger
|
hairtrigger
|
||||||
haml-lint
|
haml-lint
|
||||||
hamlit-rails
|
hamlit-rails
|
||||||
|
@ -642,14 +618,14 @@ DEPENDENCIES
|
||||||
image_processing
|
image_processing
|
||||||
inline_svg
|
inline_svg
|
||||||
jbuilder (~> 2.5)
|
jbuilder (~> 2.5)
|
||||||
jekyll (~> 4.2)
|
jekyll (~> 4.2.0)
|
||||||
jekyll-commonmark
|
jekyll-commonmark (~> 1.4.0)
|
||||||
jekyll-data
|
jekyll-data
|
||||||
jekyll-images
|
jekyll-images
|
||||||
jekyll-include-cache
|
jekyll-include-cache
|
||||||
kaminari
|
kaminari
|
||||||
letter_opener
|
letter_opener
|
||||||
listen (>= 3.0.5, < 3.2)
|
listen
|
||||||
loaf
|
loaf
|
||||||
lockbox
|
lockbox
|
||||||
lograge
|
lograge
|
||||||
|
@ -657,7 +633,6 @@ DEPENDENCIES
|
||||||
mini_magick
|
mini_magick
|
||||||
mobility
|
mobility
|
||||||
net-ssh
|
net-ssh
|
||||||
njalla-api-client
|
|
||||||
nokogiri
|
nokogiri
|
||||||
pg
|
pg
|
||||||
pg_search
|
pg_search
|
||||||
|
@ -665,27 +640,28 @@ DEPENDENCIES
|
||||||
pry
|
pry
|
||||||
puma
|
puma
|
||||||
pundit
|
pundit
|
||||||
|
que
|
||||||
rack-cors
|
rack-cors
|
||||||
rack-mini-profiler
|
rack-mini-profiler
|
||||||
rails (~> 6)
|
rails (~> 6.1.0)
|
||||||
rails-i18n
|
rails-i18n
|
||||||
rails_warden
|
rails_warden
|
||||||
redis
|
redis (~> 4.0)
|
||||||
redis-rails
|
redis-rails
|
||||||
rgl
|
rgl
|
||||||
rollups!
|
rollups!
|
||||||
rubocop-rails
|
rubocop-rails
|
||||||
rubyzip
|
rubyzip
|
||||||
rugged
|
rugged (= 1.5.0.1)
|
||||||
safe_yaml
|
safe_yaml
|
||||||
|
safely_block (~> 0.3.0)
|
||||||
sassc-rails
|
sassc-rails
|
||||||
selenium-webdriver
|
selenium-webdriver (~> 4.8.0)
|
||||||
sourcemap
|
sourcemap
|
||||||
spring
|
spring
|
||||||
spring-watcher-listen (~> 2.0.0)
|
spring-watcher-listen
|
||||||
sqlite3
|
sqlite3
|
||||||
stackprof
|
stackprof
|
||||||
sucker_punch
|
|
||||||
sutty-liquid (>= 0.7.3)
|
sutty-liquid (>= 0.7.3)
|
||||||
symbol-fstring
|
symbol-fstring
|
||||||
terminal-table
|
terminal-table
|
||||||
|
@ -693,12 +669,12 @@ DEPENDENCIES
|
||||||
turbolinks (~> 5)
|
turbolinks (~> 5)
|
||||||
uglifier (>= 1.3.0)
|
uglifier (>= 1.3.0)
|
||||||
validates_hostname
|
validates_hostname
|
||||||
web-console (>= 3.3.0)
|
web-console
|
||||||
webpacker
|
webpacker
|
||||||
yaml_db!
|
yaml_db!
|
||||||
|
|
||||||
RUBY VERSION
|
RUBY VERSION
|
||||||
ruby 2.7.1p83
|
ruby 3.1.4p223
|
||||||
|
|
||||||
BUNDLED WITH
|
BUNDLED WITH
|
||||||
2.2.2
|
2.4.17
|
||||||
|
|
20
Makefile
20
Makefile
|
@ -48,8 +48,6 @@ help: always ## Ayuda
|
||||||
@echo -e "\nArgumentos:\n"
|
@echo -e "\nArgumentos:\n"
|
||||||
@grep -E "^[a-z\-]+ \?=.*##" Makefile | sed -re "s/(.*) \?=.*##(.*)/\1;\2/" | column -s ";" -t | sed -re "s/^([^ ]+) /\x1B[38;5;197m\1\x1B[0m/"
|
@grep -E "^[a-z\-]+ \?=.*##" Makefile | sed -re "s/(.*) \?=.*##(.*)/\1;\2/" | column -s ";" -t | sed -re "s/^([^ ]+) /\x1B[38;5;197m\1\x1B[0m/"
|
||||||
|
|
||||||
assets: public/packs/manifest.json.br ## Compilar los assets
|
|
||||||
|
|
||||||
test: always ## Ejecutar los tests
|
test: always ## Ejecutar los tests
|
||||||
$(MAKE) rake args="test RAILS_ENV=test $(args)"
|
$(MAKE) rake args="test RAILS_ENV=test $(args)"
|
||||||
|
|
||||||
|
@ -71,14 +69,14 @@ rake: ## Corre rake dentro del entorno de desarrollo (pasar argumentos con args=
|
||||||
bundle: ## Corre bundle dentro del entorno de desarrollo (pasar argumentos con args=).
|
bundle: ## Corre bundle dentro del entorno de desarrollo (pasar argumentos con args=).
|
||||||
$(hain) 'bundle $(args)'
|
$(hain) 'bundle $(args)'
|
||||||
|
|
||||||
psql := psql -h $(PG_HOST) -U $(PG_USER) -p $(PG_PORT) -d sutty
|
psql := psql $(DATABASE_URL)
|
||||||
copy-table:
|
copy-table:
|
||||||
test -n "$(table)"
|
test -n "$(table)"
|
||||||
echo "truncate $(table) $(cascade);" | $(psql)
|
echo "truncate $(table) $(cascade);" | $(psql)
|
||||||
ssh $(delegate) docker exec postgresql pg_dump -U sutty -d sutty -t $(table) | $(psql)
|
ssh $(delegate) docker exec postgresql pg_dump -U sutty -d sutty -t $(table) | $(psql)
|
||||||
|
|
||||||
psql:
|
psql:
|
||||||
$(psql)
|
$(hain) $(psql)
|
||||||
|
|
||||||
rubocop: ## Yutea el código que está por ser commiteado
|
rubocop: ## Yutea el código que está por ser commiteado
|
||||||
git status --porcelain \
|
git status --porcelain \
|
||||||
|
@ -110,21 +108,13 @@ save: ## Subir la imagen Docker al nodo delegado
|
||||||
date +%F | xargs -I {} git tag -f $(container)-{}
|
date +%F | xargs -I {} git tag -f $(container)-{}
|
||||||
@echo -e "\a"
|
@echo -e "\a"
|
||||||
|
|
||||||
ota-js: assets ## Actualizar Javascript en el nodo delegado
|
|
||||||
rsync -avi --delete-after --chown 1000:82 public/ root@$(delegate):/srv/sutty/srv/http/data/_$(public)/
|
|
||||||
ssh root@$(delegate) docker exec $(container) sh -c "cat /srv/http/tmp/puma.pid | xargs -r kill -USR2"
|
|
||||||
|
|
||||||
ota: ## Actualizar Rails en el nodo delegado
|
ota: ## Actualizar Rails en el nodo delegado
|
||||||
ssh $(delegate) git -C /srv/sutty/srv/http/panel.sutty.nl pull ; true
|
git push
|
||||||
|
ssh $(delegate) git -C /srv/sutty/srv/http/panel.sutty.nl pull
|
||||||
|
ssh $(delegate) git -C /srv/sutty/srv/http/panel.sutty.nl lfs prune
|
||||||
ssh $(delegate) chown -R 1000:82 /srv/sutty/srv/http/panel.sutty.nl
|
ssh $(delegate) chown -R 1000:82 /srv/sutty/srv/http/panel.sutty.nl
|
||||||
ssh $(delegate) docker exec $(container) rails reload
|
ssh $(delegate) docker exec $(container) rails reload
|
||||||
|
|
||||||
# Todos los archivos de assets. Si alguno cambia, se van a recompilar
|
|
||||||
# los assets que luego se suben al nodo delegado.
|
|
||||||
assets := package.json yarn.lock $(shell find app/assets/ app/javascript/ -type f)
|
|
||||||
public/packs/manifest.json.br: $(assets)
|
|
||||||
$(hain) 'PANEL_URL=https://panel.sutty.nl RAILS_ENV=production NODE_ENV=production bundle exec rake assets:precompile assets:clean'
|
|
||||||
|
|
||||||
# Correr un test en particular por ejemplo
|
# Correr un test en particular por ejemplo
|
||||||
# `make test/models/usuarie_test.rb`
|
# `make test/models/usuarie_test.rb`
|
||||||
tests := $(shell find test/ -name "*_test.rb")
|
tests := $(shell find test/ -name "*_test.rb")
|
||||||
|
|
3
Procfile
3
Procfile
|
@ -7,5 +7,6 @@ blazer: bundle exec rake blazer:send_failing_checks
|
||||||
prometheus: bundle exec prometheus_exporter -b 0.0.0.0 --prefix "sutty_"
|
prometheus: bundle exec prometheus_exporter -b 0.0.0.0 --prefix "sutty_"
|
||||||
distributed_press_tokens_renew: bundle exec rake distributed_press:tokens:renew
|
distributed_press_tokens_renew: bundle exec rake distributed_press:tokens:renew
|
||||||
cleanup: bundle exec rake cleanup:everything
|
cleanup: bundle exec rake cleanup:everything
|
||||||
stats: bundle exec rake stats:process_all
|
|
||||||
emergency_cleanup: bundle exec rake cleanup:everything BEFORE=7
|
emergency_cleanup: bundle exec rake cleanup:everything BEFORE=7
|
||||||
|
stats: bundle exec rake stats:process_all
|
||||||
|
que: daemonize -c /srv/ -p /srv/tmp/que.pid -u rails /usr/local/bin/syslogize bundle exec que
|
||||||
|
|
|
@ -16,7 +16,7 @@ $primary: $magenta;
|
||||||
$secondary: $black;
|
$secondary: $black;
|
||||||
$jumbotron-bg: transparent;
|
$jumbotron-bg: transparent;
|
||||||
$enable-rounded: false;
|
$enable-rounded: false;
|
||||||
$form-feedback-valid-color: $cyan;
|
$form-feedback-valid-color: $black;
|
||||||
$form-feedback-invalid-color: $magenta;
|
$form-feedback-invalid-color: $magenta;
|
||||||
$form-feedback-icon-valid-color: $black;
|
$form-feedback-icon-valid-color: $black;
|
||||||
$component-active-bg: $magenta;
|
$component-active-bg: $magenta;
|
||||||
|
@ -29,6 +29,11 @@ $sizes: (
|
||||||
"70ch": 70ch,
|
"70ch": 70ch,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
background-color: var(--foreground);
|
||||||
|
color: var(--background);
|
||||||
|
}
|
||||||
|
|
||||||
@import "bootstrap";
|
@import "bootstrap";
|
||||||
@import "editor";
|
@import "editor";
|
||||||
|
|
||||||
|
@ -204,8 +209,6 @@ svg {
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn {
|
.btn {
|
||||||
background-color: var(--foreground);
|
|
||||||
color: var(--background);
|
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
margin-right: 0.3rem;
|
margin-right: 0.3rem;
|
||||||
|
@ -383,6 +386,9 @@ $bezier: cubic-bezier(0.75, 0, 0.25, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.word-break-all { word-break: break-all !important; }
|
||||||
|
.hyphens { hyphens: auto; }
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Modificadores de Bootstrap que no tienen versión responsive.
|
* Modificadores de Bootstrap que no tienen versión responsive.
|
||||||
*/
|
*/
|
||||||
|
@ -405,6 +411,8 @@ $bezier: cubic-bezier(0.75, 0, 0.25, 1);
|
||||||
.text-#{$grid-breakpoint}-right { text-align: right !important; }
|
.text-#{$grid-breakpoint}-right { text-align: right !important; }
|
||||||
.text-#{$grid-breakpoint}-center { text-align: center !important; }
|
.text-#{$grid-breakpoint}-center { text-align: center !important; }
|
||||||
|
|
||||||
|
.word-break-#{$grid-breakpoint}-all { word-break: break-all !important; }
|
||||||
|
|
||||||
// posición
|
// posición
|
||||||
@each $position in $positions {
|
@each $position in $positions {
|
||||||
.position-#{$grid-breakpoint}-#{$position} { position: $position !important; }
|
.position-#{$grid-breakpoint}-#{$position} { position: $position !important; }
|
||||||
|
|
|
@ -18,7 +18,7 @@ module Api
|
||||||
|
|
||||||
# Si todo salió bien, enviar los correos y redirigir al sitio.
|
# Si todo salió bien, enviar los correos y redirigir al sitio.
|
||||||
# El sitio nos dice a dónde tenemos que ir.
|
# El sitio nos dice a dónde tenemos que ir.
|
||||||
ContactJob.perform_async site.id,
|
ContactJob.perform_later site.id,
|
||||||
params[:form],
|
params[:form],
|
||||||
contact_params.to_h.symbolize_keys,
|
contact_params.to_h.symbolize_keys,
|
||||||
params[:redirect]
|
params[:redirect]
|
||||||
|
|
|
@ -9,10 +9,10 @@ module Api
|
||||||
# Generar un stacktrace en segundo plano y enviarlo por correo
|
# Generar un stacktrace en segundo plano y enviarlo por correo
|
||||||
# solo si la API key es verificable. Del otro lado siempre
|
# solo si la API key es verificable. Del otro lado siempre
|
||||||
# respondemos con lo mismo.
|
# respondemos con lo mismo.
|
||||||
def create
|
def create
|
||||||
if site&.airbrake_valid? airbrake_token
|
if (site&.airbrake_valid? airbrake_token) && !detected_device.bot?
|
||||||
BacktraceJob.perform_later site_id: params[:site_id],
|
BacktraceJob.perform_later site_id: params[:site_id],
|
||||||
params: airbrake_params.to_h
|
params: airbrake_params.to_h
|
||||||
end
|
end
|
||||||
|
|
||||||
render status: 201, json: { id: 1, url: '' }
|
render status: 201, json: { id: 1, url: '' }
|
||||||
|
@ -34,6 +34,11 @@ module Api
|
||||||
def airbrake_token
|
def airbrake_token
|
||||||
@airbrake_token ||= params[:key]
|
@airbrake_token ||= params[:key]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# @return [DeviceDetector]
|
||||||
|
def detected_device
|
||||||
|
@detected_device ||= DeviceDetector.new(request.headers['User-Agent'], request.headers)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -12,31 +12,6 @@ module Api
|
||||||
render json: sites_names + alternative_names + api_names + www_names
|
render json: sites_names + alternative_names + api_names + www_names
|
||||||
end
|
end
|
||||||
|
|
||||||
# Sitios con hidden service de Tor
|
|
||||||
#
|
|
||||||
# @return [Array] lista de nombres de sitios sin onion aun
|
|
||||||
def hidden_services
|
|
||||||
render json: DeployHiddenService.where(values: nil).includes(:site).pluck(:name)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Tor va a enviar el onion junto con el nombre del sitio y tenemos
|
|
||||||
# que guardarlo en su deploy_hidden_service.
|
|
||||||
#
|
|
||||||
# @params [String] name
|
|
||||||
# @params [String] onion
|
|
||||||
def add_onion
|
|
||||||
site = Site.find_by(name: params[:name])
|
|
||||||
|
|
||||||
if site
|
|
||||||
usuarie = GitAuthor.new email: "tor@#{Site.domain}", name: 'Tor'
|
|
||||||
service = SiteService.new site: site, usuarie: usuarie,
|
|
||||||
params: params
|
|
||||||
service.add_onion
|
|
||||||
end
|
|
||||||
|
|
||||||
head :ok
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def canonicalize(name)
|
def canonicalize(name)
|
||||||
|
|
77
app/controllers/api/v1/webhooks_controller.rb
Normal file
77
app/controllers/api/v1/webhooks_controller.rb
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Api
|
||||||
|
module V1
|
||||||
|
# Recibe webhooks y lanza un PullJob
|
||||||
|
class WebhooksController < BaseController
|
||||||
|
# responde con forbidden si falla la validación del token
|
||||||
|
rescue_from ActiveRecord::RecordNotFound, with: :platforms_answer
|
||||||
|
|
||||||
|
# Trae los cambios a partir de un post de Webhooks:
|
||||||
|
# (Gitlab, Github, Gitea, etc)
|
||||||
|
#
|
||||||
|
# @return [nil]
|
||||||
|
def pull
|
||||||
|
message = I18n.with_locale(site.default_locale) do
|
||||||
|
I18n.t('webhooks.pull.message')
|
||||||
|
end
|
||||||
|
|
||||||
|
GitPullJob.perform_later(site, usuarie, message)
|
||||||
|
head :ok
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# encuentra el sitio a partir de la url
|
||||||
|
def site
|
||||||
|
@site ||= Site.find_by_name!(params[:site_id])
|
||||||
|
end
|
||||||
|
|
||||||
|
# valida el token que envía la plataforma del webhook
|
||||||
|
#
|
||||||
|
# @return [String]
|
||||||
|
def token
|
||||||
|
@token ||=
|
||||||
|
begin
|
||||||
|
# Gitlab
|
||||||
|
if request.headers['X-Gitlab-Token'].present?
|
||||||
|
request.headers['X-Gitlab-Token']
|
||||||
|
# Github
|
||||||
|
elsif request.headers['X-Hub-Signature-256'].present?
|
||||||
|
token_from_signature(request.headers['X-Hub-Signature-256'], 'sha256=')
|
||||||
|
# Gitea
|
||||||
|
elsif request.headers['X-Gitea-Signature'].present?
|
||||||
|
token_from_signature(request.headers['X-Gitea-Signature'])
|
||||||
|
else
|
||||||
|
raise ActiveRecord::RecordNotFound, 'proveedor no soportado'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# valida token a partir de firma de webhook
|
||||||
|
#
|
||||||
|
# @return [String, Boolean]
|
||||||
|
def token_from_signature(signature, prepend = '')
|
||||||
|
payload = request.body.read
|
||||||
|
site.roles.where(temporal: false, rol: 'usuarie').pluck(:token).find do |token|
|
||||||
|
new_signature = prepend + OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), token, payload)
|
||||||
|
ActiveSupport::SecurityUtils.secure_compare(new_signature, signature.to_s)
|
||||||
|
end.tap do |t|
|
||||||
|
raise ActiveRecord::RecordNotFound, 'token no encontrado' if t.nil?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# encuentra le usuarie
|
||||||
|
def usuarie
|
||||||
|
@usuarie ||= site.roles.find_by!(temporal: false, rol: 'usuarie', token: token).usuarie
|
||||||
|
end
|
||||||
|
|
||||||
|
# respuesta de error a plataformas
|
||||||
|
def platforms_answer(exception)
|
||||||
|
ExceptionNotifier.notify_exception(exception, data: { headers: request.headers.to_h }
|
||||||
|
|
||||||
|
head :forbidden
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -59,7 +59,11 @@ class ApplicationController < ActionController::Base
|
||||||
#
|
#
|
||||||
# @return [String,Symbol]
|
# @return [String,Symbol]
|
||||||
def current_locale
|
def current_locale
|
||||||
session[:locale] = params[:change_locale_to] if params[:change_locale_to].present?
|
locale = params[:change_locale_to]
|
||||||
|
|
||||||
|
if locale.present? && I18n.locale_available?(locale)
|
||||||
|
session[:locale] = params[:change_locale_to]
|
||||||
|
end
|
||||||
|
|
||||||
session[:locale] || current_usuarie&.lang || I18n.locale
|
session[:locale] || current_usuarie&.lang || I18n.locale
|
||||||
end
|
end
|
||||||
|
@ -91,6 +95,10 @@ class ApplicationController < ActionController::Base
|
||||||
breadcrumb 'stats.index', root_path, match: :exact
|
breadcrumb 'stats.index', root_path, match: :exact
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def site
|
||||||
|
@site ||= find_site
|
||||||
|
end
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
def configure_permitted_parameters
|
def configure_permitted_parameters
|
||||||
|
|
46
app/controllers/build_stats_controller.rb
Normal file
46
app/controllers/build_stats_controller.rb
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# La lista de estados de compilación, por ahora solo mostramos el último
|
||||||
|
# estado.
|
||||||
|
class BuildStatsController < ApplicationController
|
||||||
|
include ActionView::Helpers::NumberHelper
|
||||||
|
include ActionView::Helpers::DateHelper
|
||||||
|
|
||||||
|
before_action :authenticate_usuarie!
|
||||||
|
|
||||||
|
breadcrumb -> { current_usuarie.email }, :edit_usuarie_registration_path
|
||||||
|
breadcrumb 'sites.index', :sites_path, match: :exact
|
||||||
|
breadcrumb -> { site.title }, -> { site_posts_path(site, locale: locale) }, match: :exact
|
||||||
|
|
||||||
|
def index
|
||||||
|
authorize SiteBuildStat.new(site)
|
||||||
|
breadcrumb I18n.t('build_stats.index.title'), ''
|
||||||
|
|
||||||
|
@headers = %w[type url seconds size].map do |header|
|
||||||
|
t("deploy_mailer.deployed.th.#{header}")
|
||||||
|
end
|
||||||
|
|
||||||
|
@table = site.deployment_list.map do |deploy|
|
||||||
|
type = deploy.class.name.underscore
|
||||||
|
urls = deploy.urls.map do |url|
|
||||||
|
URI.parse(url)
|
||||||
|
rescue URI::Error
|
||||||
|
nil
|
||||||
|
end.compact
|
||||||
|
|
||||||
|
urls = [nil] if urls.empty?
|
||||||
|
build_stat = deploy.build_stats.where(status: true).last
|
||||||
|
seconds = build_stat&.seconds || 0
|
||||||
|
|
||||||
|
{
|
||||||
|
title: t("deploy_mailer.deployed.#{type}.title"),
|
||||||
|
urls: urls,
|
||||||
|
seconds: {
|
||||||
|
human: distance_of_time_in_words(seconds),
|
||||||
|
machine: "PT#{seconds}S"
|
||||||
|
},
|
||||||
|
size: number_to_human_size(build_stat&.bytes || 0, precision: 2)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -5,6 +5,7 @@ class EnvController < ActionController::Base
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@site = Site.find_by_name('panel')
|
@site = Site.find_by_name('panel')
|
||||||
stale? @site
|
|
||||||
|
stale? @site if @site
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -24,6 +24,7 @@ class PostsController < ApplicationController
|
||||||
|
|
||||||
# Todos los artículos de este sitio para el idioma actual
|
# Todos los artículos de este sitio para el idioma actual
|
||||||
@posts = site.indexed_posts.where(locale: locale)
|
@posts = site.indexed_posts.where(locale: locale)
|
||||||
|
@posts = @posts.page(filter_params.delete(:page)) if site.pagination
|
||||||
# De este tipo
|
# De este tipo
|
||||||
@posts = @posts.where(layout: filter_params[:layout]) if filter_params[:layout]
|
@posts = @posts.where(layout: filter_params[:layout]) if filter_params[:layout]
|
||||||
# Que estén dentro de la categoría
|
# Que estén dentro de la categoría
|
||||||
|
@ -154,15 +155,11 @@ class PostsController < ApplicationController
|
||||||
#
|
#
|
||||||
# @return [Hash]
|
# @return [Hash]
|
||||||
def filter_params
|
def filter_params
|
||||||
@filter_params ||= params.permit(:q, :category, :layout).to_hash.select do |_, v|
|
@filter_params ||= params.permit(:q, :category, :layout, :page).to_hash.select do |_, v|
|
||||||
v.present?
|
v.present?
|
||||||
end.transform_keys(&:to_sym)
|
end.transform_keys(&:to_sym)
|
||||||
end
|
end
|
||||||
|
|
||||||
def site
|
|
||||||
@site ||= find_site
|
|
||||||
end
|
|
||||||
|
|
||||||
def post
|
def post
|
||||||
@post ||= site.posts(lang: locale).find(params[:post_id] || params[:id])
|
@post ||= site.posts(lang: locale).find(params[:post_id] || params[:id])
|
||||||
end
|
end
|
||||||
|
|
|
@ -10,7 +10,7 @@ class SitesController < ApplicationController
|
||||||
# Ver un listado de sitios
|
# Ver un listado de sitios
|
||||||
def index
|
def index
|
||||||
authorize Site
|
authorize Site
|
||||||
@sites = current_usuarie.sites.order(:title)
|
@sites = current_usuarie.sites.order(updated_at: :desc)
|
||||||
|
|
||||||
fresh_when @sites
|
fresh_when @sites
|
||||||
end
|
end
|
||||||
|
@ -57,6 +57,7 @@ class SitesController < ApplicationController
|
||||||
usuarie: current_usuarie)
|
usuarie: current_usuarie)
|
||||||
|
|
||||||
if service.update.valid?
|
if service.update.valid?
|
||||||
|
flash[:notice] = I18n.t('sites.update.post')
|
||||||
redirect_to site_posts_path(site, locale: site.default_locale)
|
redirect_to site_posts_path(site, locale: site.default_locale)
|
||||||
else
|
else
|
||||||
render 'edit'
|
render 'edit'
|
||||||
|
|
|
@ -47,7 +47,7 @@ class UsuariesController < ApplicationController
|
||||||
@usuarie = Usuarie.find(params[:usuarie_id])
|
@usuarie = Usuarie.find(params[:usuarie_id])
|
||||||
|
|
||||||
if @site.usuaries.count > 1
|
if @site.usuaries.count > 1
|
||||||
@usuarie.rol_for_site(@site).update_attribute :rol, 'invitade'
|
@usuarie.rol_for_site(@site).update_attribute :rol, Rol::INVITADE
|
||||||
else
|
else
|
||||||
flash[:warning] = I18n.t('usuaries.index.demote.denied')
|
flash[:warning] = I18n.t('usuaries.index.demote.denied')
|
||||||
end
|
end
|
||||||
|
@ -61,7 +61,7 @@ class UsuariesController < ApplicationController
|
||||||
authorize SiteUsuarie.new(@site, current_usuarie)
|
authorize SiteUsuarie.new(@site, current_usuarie)
|
||||||
|
|
||||||
@usuarie = Usuarie.find(params[:usuarie_id])
|
@usuarie = Usuarie.find(params[:usuarie_id])
|
||||||
@usuarie.rol_for_site(@site).update_attribute :rol, 'usuarie'
|
@usuarie.rol_for_site(@site).update_attribute :rol, Rol::USUARIE
|
||||||
|
|
||||||
redirect_to site_usuaries_path
|
redirect_to site_usuaries_path
|
||||||
end
|
end
|
||||||
|
@ -72,6 +72,8 @@ class UsuariesController < ApplicationController
|
||||||
site_usuarie = SiteUsuarie.new(@site, current_usuarie)
|
site_usuarie = SiteUsuarie.new(@site, current_usuarie)
|
||||||
authorize site_usuarie
|
authorize site_usuarie
|
||||||
|
|
||||||
|
params[:invite_as] = invite_as
|
||||||
|
|
||||||
@policy = policy(site_usuarie)
|
@policy = policy(site_usuarie)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -81,27 +83,33 @@ class UsuariesController < ApplicationController
|
||||||
authorize SiteUsuarie.new(@site, current_usuarie)
|
authorize SiteUsuarie.new(@site, current_usuarie)
|
||||||
|
|
||||||
# Enviar la invitación si es necesario y agregar al sitio
|
# Enviar la invitación si es necesario y agregar al sitio
|
||||||
invitaciones.each do |invitacion|
|
invitaciones.each do |address|
|
||||||
# Si la cuenta no existe, envía una invitación por correo, sino,
|
next if Usuarie.where(id: @site.roles.pluck(:usuarie_id)).find_by_email(address)
|
||||||
# no se envía nada
|
|
||||||
#
|
|
||||||
# TODO: Enviar invitación igual! Podemos no usar el Mailer de
|
|
||||||
# DeviseInvitations y usar uno propio que contenga texto y se
|
|
||||||
# envíe de todas formas.
|
|
||||||
usuarie = Usuarie.invite! email: invitacion.address,
|
|
||||||
skip_invitation: true
|
|
||||||
|
|
||||||
# No invitar al sitio si ya estaba en la lista!
|
Usuarie.transaction do
|
||||||
#
|
usuarie = Usuarie.find_by_email(address)
|
||||||
# XXX: En este caso no estamos enviando ninguna invitación
|
usuarie ||= Usuarie.invite!({ email: address, skip_invitation: true }).tap do |u|
|
||||||
next if usuarie.sites.exists? @site.id
|
u.send :generate_invitation_token!
|
||||||
|
end
|
||||||
|
|
||||||
@site.roles << Rol.create(usuarie: usuarie, site: @site,
|
role = @site.roles.create(usuarie: usuarie, temporal: true, rol: invited_as)
|
||||||
temporal: true, rol: invited_as)
|
|
||||||
|
|
||||||
# Invitamos después de crear el rol para que el correo de
|
# XXX: La invitación tiene que ser enviada luego de crear el rol
|
||||||
# invitación pueda recibir el sitio.
|
if role.persisted?
|
||||||
usuarie.deliver_invitation
|
# Si es una cuenta manual que no está confirmada aun,
|
||||||
|
# aprovechar para reconfirmarla.
|
||||||
|
if !usuarie.confirmed? && !usuarie.created_by_invite?
|
||||||
|
usuarie.confirmation_token = nil
|
||||||
|
usuarie.send :generate_confirmation_token!
|
||||||
|
end
|
||||||
|
|
||||||
|
usuarie.deliver_invitation
|
||||||
|
else
|
||||||
|
raise ArgumentError, role.errors.full_messages
|
||||||
|
end
|
||||||
|
rescue ArgumentError => e
|
||||||
|
ExceptionNotifier.notify_exception(e, data: { site: @site.name, address: address })
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
redirect_to site_usuaries_path(@site)
|
redirect_to site_usuaries_path(@site)
|
||||||
|
@ -142,6 +150,8 @@ class UsuariesController < ApplicationController
|
||||||
private
|
private
|
||||||
|
|
||||||
# Traer todas las invitaciones que al menos tengan usuarie y dominio
|
# Traer todas las invitaciones que al menos tengan usuarie y dominio
|
||||||
|
#
|
||||||
|
# @return [Array]
|
||||||
def invitaciones
|
def invitaciones
|
||||||
# XXX: Podríamos usar EmailAddress pero hace chequeos más lentos
|
# XXX: Podríamos usar EmailAddress pero hace chequeos más lentos
|
||||||
params[:invitaciones]&.tr("\r", '')&.split("\n")&.map do |m|
|
params[:invitaciones]&.tr("\r", '')&.split("\n")&.map do |m|
|
||||||
|
@ -150,17 +160,19 @@ class UsuariesController < ApplicationController
|
||||||
nil
|
nil
|
||||||
end.compact.select do |m|
|
end.compact.select do |m|
|
||||||
m.local && m.domain
|
m.local && m.domain
|
||||||
end
|
end.map(&:address)
|
||||||
end
|
end
|
||||||
|
|
||||||
# El tipo de invitación que tenemos que enviar, si alguien mandó
|
# El tipo de invitación que tenemos que enviar, si alguien mandó
|
||||||
# cualquier cosa, usamos el privilegio menor.
|
# cualquier cosa, usamos el privilegio menor.
|
||||||
|
#
|
||||||
|
# @return [String]
|
||||||
def invited_as
|
def invited_as
|
||||||
if Rol::ROLES.include?(params[:invited_as])
|
Rol.role?(params[:invited_as]) ? params[:invited_as] : Rol::INVITADE
|
||||||
params[:invited_as]
|
end
|
||||||
else
|
|
||||||
'invitade'
|
def invite_as
|
||||||
end
|
Rol.role?(params[:invite_as]&.singularize) ? params[:invite_as] : Rol::INVITADE.pluralize
|
||||||
end
|
end
|
||||||
|
|
||||||
def site
|
def site
|
||||||
|
|
|
@ -2,11 +2,21 @@
|
||||||
|
|
||||||
import { Notifier } from '@airbrake/browser'
|
import { Notifier } from '@airbrake/browser'
|
||||||
|
|
||||||
window.airbrake = new Notifier({
|
try {
|
||||||
projectId: window.env.AIRBRAKE_SITE_ID,
|
window.airbrake = new Notifier({
|
||||||
projectKey: window.env.AIRBRAKE_API_KEY,
|
projectId: window.env.AIRBRAKE_PROJECT_ID,
|
||||||
host: window.env.PANEL_URL
|
projectKey: window.env.AIRBRAKE_PROJECT_KEY,
|
||||||
})
|
host: window.env.PANEL_URL
|
||||||
|
});
|
||||||
|
|
||||||
|
console.originalError = console.error;
|
||||||
|
console.error = (...e) => {
|
||||||
|
window.airbrake.notify(e.join(" "));
|
||||||
|
return console.originalError(...e);
|
||||||
|
};
|
||||||
|
} catch(e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
|
||||||
import 'core-js/stable'
|
import 'core-js/stable'
|
||||||
import 'regenerator-runtime/runtime'
|
import 'regenerator-runtime/runtime'
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
# Base para trabajos
|
# Base para trabajos
|
||||||
class ApplicationJob < ActiveJob::Base
|
class ApplicationJob < ActiveJob::Base
|
||||||
include SuckerPunch::Job
|
include Que::ActiveJob::JobExtensions
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
|
|
@ -6,8 +6,6 @@ class BacktraceJob < ApplicationJob
|
||||||
|
|
||||||
EMPTY_SOURCEMAP = { 'mappings' => '' }.freeze
|
EMPTY_SOURCEMAP = { 'mappings' => '' }.freeze
|
||||||
|
|
||||||
queue_as :low_priority
|
|
||||||
|
|
||||||
attr_reader :params, :site_id
|
attr_reader :params, :site_id
|
||||||
|
|
||||||
def perform(site_id:, params:)
|
def perform(site_id:, params:)
|
||||||
|
|
|
@ -4,9 +4,21 @@
|
||||||
class DeployJob < ApplicationJob
|
class DeployJob < ApplicationJob
|
||||||
class DeployException < StandardError; end
|
class DeployException < StandardError; end
|
||||||
class DeployTimedOutException < DeployException; end
|
class DeployTimedOutException < DeployException; end
|
||||||
|
class DeployAlreadyRunningException < DeployException; end
|
||||||
|
|
||||||
discard_on ActiveRecord::RecordNotFound
|
discard_on ActiveRecord::RecordNotFound
|
||||||
|
|
||||||
|
# Lanzar lo antes posible
|
||||||
|
self.priority = 10
|
||||||
|
|
||||||
|
def handle_error(error)
|
||||||
|
case error
|
||||||
|
when DeployAlreadyRunningException then retry_in 1.minute
|
||||||
|
when DeployTimedOutException then expire
|
||||||
|
else super
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# rubocop:disable Metrics/MethodLength
|
# rubocop:disable Metrics/MethodLength
|
||||||
def perform(site, notify: true, time: Time.now, output: false)
|
def perform(site, notify: true, time: Time.now, output: false)
|
||||||
@output = output
|
@output = output
|
||||||
|
@ -20,14 +32,14 @@ class DeployJob < ApplicationJob
|
||||||
# Como el trabajo actual se aplaza al siguiente, arrastrar la
|
# Como el trabajo actual se aplaza al siguiente, arrastrar la
|
||||||
# hora original para poder ir haciendo timeouts.
|
# hora original para poder ir haciendo timeouts.
|
||||||
if @site.building?
|
if @site.building?
|
||||||
|
notify = false
|
||||||
|
|
||||||
if 10.minutes.ago >= time
|
if 10.minutes.ago >= time
|
||||||
notify = false
|
|
||||||
raise DeployTimedOutException,
|
raise DeployTimedOutException,
|
||||||
"#{@site.name} la tarea estuvo más de 10 minutos esperando, volviendo al estado original"
|
"#{@site.name} la tarea estuvo más de 10 minutos esperando, volviendo al estado original"
|
||||||
|
else
|
||||||
|
raise DeployAlreadyRunningException
|
||||||
end
|
end
|
||||||
|
|
||||||
DeployJob.perform_in(60, site, notify: notify, time: time, output: output)
|
|
||||||
return
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@deployed = {}
|
@deployed = {}
|
||||||
|
@ -39,7 +51,15 @@ class DeployJob < ApplicationJob
|
||||||
status = d.deploy(output: @output)
|
status = d.deploy(output: @output)
|
||||||
seconds = d.build_stats.last.try(:seconds) || 0
|
seconds = d.build_stats.last.try(:seconds) || 0
|
||||||
size = d.size
|
size = d.size
|
||||||
urls = d.respond_to?(:urls) ? d.urls : [d.url].compact
|
urls = d.urls.map do |url|
|
||||||
|
URI.parse url
|
||||||
|
rescue URI::Error
|
||||||
|
nil
|
||||||
|
end.compact
|
||||||
|
|
||||||
|
if d == @site.deployment_list.last && !status
|
||||||
|
raise DeployException, 'Falló la compilación'
|
||||||
|
end
|
||||||
rescue StandardError => e
|
rescue StandardError => e
|
||||||
status = false
|
status = false
|
||||||
seconds ||= 0
|
seconds ||= 0
|
||||||
|
@ -67,8 +87,6 @@ class DeployJob < ApplicationJob
|
||||||
t << ([type.to_s] + row.values)
|
t << ([type.to_s] + row.values)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
rescue DeployTimedOutException => e
|
|
||||||
notify_exception e
|
|
||||||
ensure
|
ensure
|
||||||
if @site.present?
|
if @site.present?
|
||||||
@site.update status: 'waiting'
|
@site.update status: 'waiting'
|
||||||
|
|
16
app/jobs/git_pull_job.rb
Normal file
16
app/jobs/git_pull_job.rb
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# Permite traer los cambios desde webhooks
|
||||||
|
|
||||||
|
class GitPullJob < ApplicationJob
|
||||||
|
# @param :site [Site]
|
||||||
|
# @param :usuarie [Usuarie]
|
||||||
|
# @return [nil]
|
||||||
|
def perform(site, usuarie)
|
||||||
|
return unless site.repository.origin
|
||||||
|
return unless site.repository.fetch.positive?
|
||||||
|
|
||||||
|
site.repository.merge(usuarie)
|
||||||
|
site.reindex_changes!
|
||||||
|
end
|
||||||
|
end
|
11
app/jobs/git_push_job.rb
Normal file
11
app/jobs/git_push_job.rb
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# Permite pushear los cambios cada vez que se
|
||||||
|
# hacen commits en un sitio
|
||||||
|
class GitPushJob < ApplicationJob
|
||||||
|
# @param :site [Site]
|
||||||
|
# @return [nil]
|
||||||
|
def perform(site)
|
||||||
|
site.repository.push if site.repository.origin
|
||||||
|
end
|
||||||
|
end
|
|
@ -10,13 +10,11 @@ class GitlabNotifierJob < ApplicationJob
|
||||||
# Variables que vamos a acceder luego
|
# Variables que vamos a acceder luego
|
||||||
attr_reader :exception, :options, :issue_data, :cached
|
attr_reader :exception, :options, :issue_data, :cached
|
||||||
|
|
||||||
queue_as :low_priority
|
|
||||||
|
|
||||||
# @param [Exception] la excepción lanzada
|
# @param [Exception] la excepción lanzada
|
||||||
# @param [Hash] opciones de ExceptionNotifier
|
# @param [Hash] opciones de ExceptionNotifier
|
||||||
def perform(exception, **options)
|
def perform(exception, **options)
|
||||||
@exception = exception
|
@exception = exception
|
||||||
@options = options
|
@options = fix_options options
|
||||||
@issue_data = { count: 1 }
|
@issue_data = { count: 1 }
|
||||||
# Necesitamos saber si el issue ya existía
|
# Necesitamos saber si el issue ya existía
|
||||||
@cached = false
|
@cached = false
|
||||||
|
@ -37,7 +35,7 @@ class GitlabNotifierJob < ApplicationJob
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
unless @issue['iid']
|
if @issue['iid'].blank? && issue_data[:issue].blank?
|
||||||
Rails.cache.delete(cache_key)
|
Rails.cache.delete(cache_key)
|
||||||
raise GitlabNotifierError, @issue.dig('message', 'title')&.join(', ')
|
raise GitlabNotifierError, @issue.dig('message', 'title')&.join(', ')
|
||||||
end
|
end
|
||||||
|
@ -61,9 +59,9 @@ class GitlabNotifierJob < ApplicationJob
|
||||||
Rails.cache.write(cache_key, issue_data)
|
Rails.cache.write(cache_key, issue_data)
|
||||||
# Si este trabajo genera una excepción va a entrar en un loop, así que
|
# Si este trabajo genera una excepción va a entrar en un loop, así que
|
||||||
# la notificamos por correo
|
# la notificamos por correo
|
||||||
rescue Exception => e
|
rescue StandardError => e
|
||||||
email_notification.call(e)
|
email_notification.call(e, data: @issue)
|
||||||
email_notification.call(exception, options)
|
email_notification.call(exception, data: @options)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
@ -84,10 +82,15 @@ class GitlabNotifierJob < ApplicationJob
|
||||||
exception.class.name,
|
exception.class.name,
|
||||||
Digest::SHA1.hexdigest(exception.message),
|
Digest::SHA1.hexdigest(exception.message),
|
||||||
Digest::SHA1.hexdigest(backtrace&.first.to_s),
|
Digest::SHA1.hexdigest(backtrace&.first.to_s),
|
||||||
Digest::SHA1.hexdigest(options.dig(:data, :params, 'errors').to_s)
|
Digest::SHA1.hexdigest(errors.to_s)
|
||||||
].join('/')
|
].join('/')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# @return [Array]
|
||||||
|
def errors
|
||||||
|
options.dig(:data, :params, 'errors') || []
|
||||||
|
end
|
||||||
|
|
||||||
# Define si es una excepción de javascript o local
|
# Define si es una excepción de javascript o local
|
||||||
#
|
#
|
||||||
# @see BacktraceJob
|
# @see BacktraceJob
|
||||||
|
@ -126,6 +129,7 @@ class GitlabNotifierJob < ApplicationJob
|
||||||
# @return [String]
|
# @return [String]
|
||||||
def body
|
def body
|
||||||
@body ||= ''.dup.tap do |b|
|
@body ||= ''.dup.tap do |b|
|
||||||
|
b << log_section
|
||||||
b << request_section
|
b << request_section
|
||||||
b << javascript_footer
|
b << javascript_footer
|
||||||
b << data_section
|
b << data_section
|
||||||
|
@ -162,14 +166,16 @@ class GitlabNotifierJob < ApplicationJob
|
||||||
|
|
||||||
# @return [String]
|
# @return [String]
|
||||||
def log_section
|
def log_section
|
||||||
return '' unless options[:log]
|
return '' unless options.dig(:data, :log)
|
||||||
|
|
||||||
<<~LOG
|
<<~LOG
|
||||||
# Log
|
|
||||||
|
|
||||||
```
|
# Build log
|
||||||
#{options[:log]}
|
|
||||||
```
|
```
|
||||||
|
#{options[:data].delete(:log)}
|
||||||
|
```
|
||||||
|
|
||||||
LOG
|
LOG
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -257,8 +263,8 @@ class GitlabNotifierJob < ApplicationJob
|
||||||
|
|
||||||
## Data
|
## Data
|
||||||
|
|
||||||
```
|
```yaml
|
||||||
#{pp options[:data]}
|
#{options[:data].to_yaml}
|
||||||
```
|
```
|
||||||
|
|
||||||
DATA
|
DATA
|
||||||
|
@ -279,4 +285,16 @@ class GitlabNotifierJob < ApplicationJob
|
||||||
def url
|
def url
|
||||||
@url ||= request&.url || options.dig(:data, :params, 'context', 'url')
|
@url ||= request&.url || options.dig(:data, :params, 'context', 'url')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Define llaves necesarias
|
||||||
|
#
|
||||||
|
# @param :options [Hash]
|
||||||
|
# @return [Hash]
|
||||||
|
def fix_options(options)
|
||||||
|
options = { data: options } unless options.is_a? Hash
|
||||||
|
options[:data] ||= {}
|
||||||
|
options[:data][:params] ||= {}
|
||||||
|
|
||||||
|
options
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
# bundle exec rails c
|
# bundle exec rails c
|
||||||
# m = Maintenance.create message_en: 'reason', message_es: 'razón',
|
# m = Maintenance.create message_en: 'reason', message_es: 'razón',
|
||||||
# estimated_from: Time.now, estimated_to: Time.now + 1.hour
|
# estimated_from: Time.now, estimated_to: Time.now + 1.hour
|
||||||
# MaintenanceJob.perform_async(maintenance_id: m.id)
|
# MaintenanceJob.perform_later(maintenance_id: m.id)
|
||||||
#
|
#
|
||||||
# Lo mismo para salir de mantenimiento, agregando el atributo
|
# Lo mismo para salir de mantenimiento, agregando el atributo
|
||||||
# are_we_back: true al crear el Maintenance.
|
# are_we_back: true al crear el Maintenance.
|
||||||
|
|
|
@ -7,7 +7,7 @@ class RenewDistributedPressTokensJob < ApplicationJob
|
||||||
# detener la tarea si algo pasa.
|
# detener la tarea si algo pasa.
|
||||||
def perform
|
def perform
|
||||||
DistributedPressPublisher.with_about_to_expire_tokens.find_each do |publisher|
|
DistributedPressPublisher.with_about_to_expire_tokens.find_each do |publisher|
|
||||||
publisher.touch
|
publisher.save
|
||||||
rescue DistributedPress::V1::Error => e
|
rescue DistributedPress::V1::Error => e
|
||||||
data = { instance: publisher.instance, expires_at: publisher.client.token.expires_at }
|
data = { instance: publisher.instance, expires_at: publisher.client.token.expires_at }
|
||||||
|
|
||||||
|
|
22
app/lib/active_job/serializers/exception_serializer.rb
Normal file
22
app/lib/active_job/serializers/exception_serializer.rb
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'json/add/exception'
|
||||||
|
|
||||||
|
module ActiveJob
|
||||||
|
module Serializers
|
||||||
|
class ExceptionSerializer < ObjectSerializer # :nodoc:
|
||||||
|
def serialize(ex)
|
||||||
|
super('value' => { 'class' => ex.class.name, 'exception' => ex.as_json })
|
||||||
|
end
|
||||||
|
|
||||||
|
def deserialize(hash)
|
||||||
|
hash.dig('value', 'class').constantize.json_create(hash.dig('value', 'exception'))
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
def klass
|
||||||
|
Exception
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -8,10 +8,15 @@ module ExceptionNotifier
|
||||||
# Recibe la excepción y empieza la tarea de notificación en segundo
|
# Recibe la excepción y empieza la tarea de notificación en segundo
|
||||||
# plano.
|
# plano.
|
||||||
#
|
#
|
||||||
# @param [Exception]
|
# @param :exception [Exception]
|
||||||
# @param [Hash]
|
# @param :options [Hash]
|
||||||
def call(exception, **options)
|
def call(exception, options, &block)
|
||||||
GitlabNotifierJob.perform_async(exception, **options)
|
case exception
|
||||||
|
when BacktraceJob::BacktraceException
|
||||||
|
GitlabNotifierJob.perform_later(exception, **options)
|
||||||
|
else
|
||||||
|
GitlabNotifierJob.perform_now(exception, **options)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
13
app/lib/hidden_service_client.rb
Normal file
13
app/lib/hidden_service_client.rb
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'httparty'
|
||||||
|
|
||||||
|
class HiddenServiceClient
|
||||||
|
include HTTParty
|
||||||
|
|
||||||
|
base_uri ENV.fetch('HIDDEN_SERVICE', 'http://tor:3000')
|
||||||
|
|
||||||
|
def create(name)
|
||||||
|
self.class.get("/#{name}").body
|
||||||
|
end
|
||||||
|
end
|
|
@ -52,7 +52,7 @@ class DeployMailer < ApplicationMailer
|
||||||
t << (row.map do |k, v|
|
t << (row.map do |k, v|
|
||||||
case k
|
case k
|
||||||
when :seconds then v[:human]
|
when :seconds then v[:human]
|
||||||
when :urls then url
|
when :urls then url.to_s
|
||||||
else v
|
else v
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
26
app/models/concerns/usuarie/consent.rb
Normal file
26
app/models/concerns/usuarie/consent.rb
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Usuarie
|
||||||
|
# Gestiona los campos de consentimiento
|
||||||
|
module Consent
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
included do
|
||||||
|
CONSENT_FIELDS = %i[privacy_policy_accepted terms_of_service_accepted code_of_conduct_accepted available_for_feedback_accepted]
|
||||||
|
|
||||||
|
CONSENT_FIELDS.each do |field|
|
||||||
|
attribute field, :boolean
|
||||||
|
end
|
||||||
|
|
||||||
|
before_save :update_consent_fields!
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def update_consent_fields!
|
||||||
|
CONSENT_FIELDS.each do |field|
|
||||||
|
send(:"#{field}_at=", Time.now) if send(field).present?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -23,6 +23,11 @@ class Deploy < ApplicationRecord
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# @return [Array]
|
||||||
|
def urls
|
||||||
|
[url].compact
|
||||||
|
end
|
||||||
|
|
||||||
def limit
|
def limit
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
end
|
end
|
||||||
|
@ -55,6 +60,22 @@ class Deploy < ApplicationRecord
|
||||||
@gems_dir ||= Rails.root.join('_storage', 'gems', site.name)
|
@gems_dir ||= Rails.root.join('_storage', 'gems', site.name)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Un entorno que solo tiene lo que necesitamos
|
||||||
|
#
|
||||||
|
# @return [Hash]
|
||||||
|
def env
|
||||||
|
# XXX: This doesn't support Windows paths :B
|
||||||
|
paths = [File.dirname(`which bundle`), '/usr/local/bin', '/usr/bin', '/bin']
|
||||||
|
|
||||||
|
# Las variables de entorno extra no pueden superponerse al local.
|
||||||
|
extra_env.merge({
|
||||||
|
'HOME' => home_dir,
|
||||||
|
'PATH' => paths.join(':'),
|
||||||
|
'JEKYLL_ENV' => Rails.env,
|
||||||
|
'LANG' => ENV['LANG'],
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
# Corre un comando, lo registra en la base de datos y devuelve el
|
# Corre un comando, lo registra en la base de datos y devuelve el
|
||||||
# estado.
|
# estado.
|
||||||
#
|
#
|
||||||
|
@ -65,22 +86,20 @@ class Deploy < ApplicationRecord
|
||||||
lines = []
|
lines = []
|
||||||
|
|
||||||
time_start
|
time_start
|
||||||
Dir.chdir(site.path) do
|
Open3.popen2e(env, cmd, unsetenv_others: true, chdir: site.path) do |_, o, t|
|
||||||
Open3.popen2e(env, cmd, unsetenv_others: true) do |_, o, t|
|
# TODO: Enviar a un websocket para ver el proceso en vivo?
|
||||||
# TODO: Enviar a un websocket para ver el proceso en vivo?
|
Thread.new do
|
||||||
Thread.new do
|
o.each do |line|
|
||||||
o.each do |line|
|
lines << line
|
||||||
lines << line
|
|
||||||
|
|
||||||
puts line if output
|
puts line if output
|
||||||
end
|
|
||||||
rescue IOError => e
|
|
||||||
lines << e.message
|
|
||||||
puts e.message if output
|
|
||||||
end
|
end
|
||||||
|
rescue IOError => e
|
||||||
r = t.value
|
lines << e.message
|
||||||
|
puts e.message if output
|
||||||
end
|
end
|
||||||
|
|
||||||
|
r = t.value
|
||||||
end
|
end
|
||||||
time_stop
|
time_stop
|
||||||
|
|
||||||
|
@ -100,6 +119,11 @@ class Deploy < ApplicationRecord
|
||||||
@local_env ||= {}
|
@local_env ||= {}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Devuelve opciones para jekyll build
|
||||||
|
#
|
||||||
|
# @return [String,nil]
|
||||||
|
def flags_for_build(**args); end
|
||||||
|
|
||||||
# Trae todas las dependencias
|
# Trae todas las dependencias
|
||||||
#
|
#
|
||||||
# @return [Array]
|
# @return [Array]
|
||||||
|
@ -109,6 +133,21 @@ class Deploy < ApplicationRecord
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
# Escribe el contenido en un archivo temporal y ejecuta el bloque
|
||||||
|
# provisto con el archivo como parámetro
|
||||||
|
#
|
||||||
|
# @param :content [String]
|
||||||
|
def with_tempfile(content, &block)
|
||||||
|
Tempfile.create(SecureRandom.hex) do |file|
|
||||||
|
file.write content.to_s
|
||||||
|
file.rewind
|
||||||
|
file.close
|
||||||
|
|
||||||
|
# @yieldparam :file [File]
|
||||||
|
yield file
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# @param [String]
|
# @param [String]
|
||||||
# @return [String]
|
# @return [String]
|
||||||
def readable_cmd(cmd)
|
def readable_cmd(cmd)
|
||||||
|
@ -119,7 +158,14 @@ class Deploy < ApplicationRecord
|
||||||
@deploy_local ||= site.deploys.find_by(type: 'DeployLocal')
|
@deploy_local ||= site.deploys.find_by(type: 'DeployLocal')
|
||||||
end
|
end
|
||||||
|
|
||||||
def non_local_deploys
|
# Consigue todas las variables de entorno configuradas por otros
|
||||||
@non_local_deploys ||= site.deploys.where.not(type: 'DeployLocal')
|
# deploys.
|
||||||
|
#
|
||||||
|
# @return [Hash]
|
||||||
|
def extra_env
|
||||||
|
@extra_env ||=
|
||||||
|
site.deployment_list.reduce({}) do |extra, deploy|
|
||||||
|
extra.merge deploy.local_env
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require 'distributed_press/v1/client/site'
|
require 'distributed_press/v1/client/site'
|
||||||
require 'njalla/v1'
|
|
||||||
|
|
||||||
# Soportar Distributed Press APIv1
|
# Soportar Distributed Press APIv1
|
||||||
#
|
#
|
||||||
|
@ -15,8 +14,8 @@ require 'njalla/v1'
|
||||||
class DeployDistributedPress < Deploy
|
class DeployDistributedPress < Deploy
|
||||||
store :values, accessors: %i[hostname remote_site_id remote_info], coder: JSON
|
store :values, accessors: %i[hostname remote_site_id remote_info], coder: JSON
|
||||||
|
|
||||||
before_create :create_remote_site!, :create_njalla_records!
|
before_create :create_remote_site!
|
||||||
before_destroy :delete_remote_site!, :delete_njalla_records!
|
before_destroy :delete_remote_site!
|
||||||
|
|
||||||
DEPENDENCIES = %i[deploy_local]
|
DEPENDENCIES = %i[deploy_local]
|
||||||
|
|
||||||
|
@ -31,17 +30,12 @@ class DeployDistributedPress < Deploy
|
||||||
time_start
|
time_start
|
||||||
|
|
||||||
create_remote_site! if remote_site_id.blank?
|
create_remote_site! if remote_site_id.blank?
|
||||||
create_njalla_records!
|
|
||||||
save
|
save
|
||||||
|
|
||||||
if remote_site_id.blank?
|
if remote_site_id.blank?
|
||||||
raise DeployJob::DeployException, 'El sitio no se creó en Distributed Press'
|
raise DeployJob::DeployException, 'El sitio no se creó en Distributed Press'
|
||||||
end
|
end
|
||||||
|
|
||||||
if create_njalla_records? && remote_info[:njalla].blank?
|
|
||||||
raise DeployJob::DeployException, 'No se pudieron crear los registros necesarios en Njalla'
|
|
||||||
end
|
|
||||||
|
|
||||||
site_client.tap do |c|
|
site_client.tap do |c|
|
||||||
stdout = Thread.new(publisher.logger_out) do |io|
|
stdout = Thread.new(publisher.logger_out) do |io|
|
||||||
until io.eof?
|
until io.eof?
|
||||||
|
@ -52,7 +46,12 @@ class DeployDistributedPress < Deploy
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
status = c.publish(publishing_site, deploy_local.destination)
|
begin
|
||||||
|
status = c.publish(publishing_site, deploy_local.destination)
|
||||||
|
rescue DistributedPress::V1::Error => e
|
||||||
|
ExceptionNotifier.notify_exception(e, data: { site: site.name })
|
||||||
|
status = false
|
||||||
|
end
|
||||||
|
|
||||||
if status
|
if status
|
||||||
self.remote_info[:distributed_press] = c.show(publishing_site).to_h
|
self.remote_info[:distributed_press] = c.show(publishing_site).to_h
|
||||||
|
@ -80,25 +79,18 @@ class DeployDistributedPress < Deploy
|
||||||
|
|
||||||
# Devuelve las URLs de todos los protocolos
|
# Devuelve las URLs de todos los protocolos
|
||||||
def urls
|
def urls
|
||||||
protocol_urls + gateway_urls
|
gateway_urls
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
# @return [Array]
|
||||||
def gateway_urls
|
def gateway_urls
|
||||||
remote_info.dig(:distributed_press, :links).values.map do |protocol|
|
remote_info.dig(:distributed_press, :links)&.values&.map do |protocol|
|
||||||
[ protocol[:link], protocol[:gateway] ]
|
[ protocol[:link], protocol[:gateway] ]
|
||||||
end.flatten.compact.select do |link|
|
end&.flatten&.compact&.select do |link|
|
||||||
link.include? '://'
|
link.include? '://'
|
||||||
end
|
end || []
|
||||||
end
|
|
||||||
|
|
||||||
def protocol_urls
|
|
||||||
remote_info.dig(:distributed_press, :protocols).select do |_, enabled|
|
|
||||||
enabled
|
|
||||||
end.map do |protocol, _|
|
|
||||||
"#{protocol}://#{site.hostname}"
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# El cliente de la API
|
# El cliente de la API
|
||||||
|
@ -147,29 +139,6 @@ class DeployDistributedPress < Deploy
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
|
|
||||||
# Crea los registros en Njalla
|
|
||||||
#
|
|
||||||
# XXX: Esto depende de nuestro DNS actual, cuando lo migremos hay
|
|
||||||
# que eliminarlo.
|
|
||||||
#
|
|
||||||
# @return [nil]
|
|
||||||
def create_njalla_records!
|
|
||||||
return unless create_njalla_records?
|
|
||||||
|
|
||||||
self.remote_info ||= {}
|
|
||||||
self.remote_info[:njalla] ||= {}
|
|
||||||
self.remote_info[:njalla][:a] ||= njalla.add_record(name: site.name, type: 'CNAME', content: "#{Site.domain}.").to_h
|
|
||||||
self.remote_info[:njalla][:cname] ||= njalla.add_record(name: "www.#{site.name}", type: 'CNAME', content: "#{Site.domain}.").to_h
|
|
||||||
self.remote_info[:njalla][:ns] ||= njalla.add_record(name: "_dnslink.#{site.name}", type: 'NS', content: "#{publisher.hostname}.").to_h
|
|
||||||
|
|
||||||
nil
|
|
||||||
rescue HTTParty::Error => e
|
|
||||||
ExceptionNotifier.notify_exception(e, data: { site: site.name })
|
|
||||||
self.remote_info.delete :njalla
|
|
||||||
ensure
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
|
|
||||||
# Registra lo que sucedió
|
# Registra lo que sucedió
|
||||||
#
|
#
|
||||||
# @param status [Bool]
|
# @param status [Bool]
|
||||||
|
@ -187,31 +156,4 @@ class DeployDistributedPress < Deploy
|
||||||
ExceptionNotifier.notify_exception(e, data: { site: site.name })
|
ExceptionNotifier.notify_exception(e, data: { site: site.name })
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
|
|
||||||
def delete_njalla_records!
|
|
||||||
return unless create_njalla_records?
|
|
||||||
|
|
||||||
%w[a ns cname].each do |type|
|
|
||||||
next if (id = remote_info.dig('njalla', type, 'id')).blank?
|
|
||||||
|
|
||||||
njalla.remove_record(id: id.to_i)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Actualizar registros en Njalla
|
|
||||||
#
|
|
||||||
# @return [Njalla::V1::Domain]
|
|
||||||
def njalla
|
|
||||||
@njalla ||=
|
|
||||||
begin
|
|
||||||
client = Njalla::V1::Client.new(token: Rails.application.credentials.njalla)
|
|
||||||
|
|
||||||
Njalla::V1::Domain.new(domain: Site.domain, client: client)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Detecta si tenemos que crear registros en Njalla
|
|
||||||
def create_njalla_records?
|
|
||||||
!site.name.end_with?('.')
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -27,8 +27,4 @@ class DeployFullRsync < DeployRsync
|
||||||
|
|
||||||
result.present? && result.all?
|
result.present? && result.all?
|
||||||
end
|
end
|
||||||
|
|
||||||
def url
|
|
||||||
"https://#{user_host.last}/"
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,8 +2,16 @@
|
||||||
|
|
||||||
# Genera una versión onion
|
# Genera una versión onion
|
||||||
class DeployHiddenService < DeployWww
|
class DeployHiddenService < DeployWww
|
||||||
|
store :values, accessors: %i[onion], coder: JSON
|
||||||
|
|
||||||
|
before_create :create_hidden_service!
|
||||||
|
|
||||||
|
ONION_RE = /\A[a-z0-9]{56}\.onion\z/.freeze
|
||||||
|
|
||||||
def fqdn
|
def fqdn
|
||||||
values[:onion].tap do |onion|
|
create_hidden_service! if onion.blank?
|
||||||
|
|
||||||
|
onion.tap do |onion|
|
||||||
raise ArgumentError, 'Aun no se generó la dirección .onion' if onion.blank?
|
raise ArgumentError, 'Aun no se generó la dirección .onion' if onion.blank?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -11,4 +19,19 @@ class DeployHiddenService < DeployWww
|
||||||
def url
|
def url
|
||||||
"http://#{fqdn}"
|
"http://#{fqdn}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def create_hidden_service!
|
||||||
|
onion_address = HiddenServiceClient.new.create(site.name)
|
||||||
|
|
||||||
|
if ONION_RE =~ onion_address
|
||||||
|
self.onion = onion_address
|
||||||
|
|
||||||
|
usuarie = GitAuthor.new email: "tor@#{Site.domain}", name: 'Tor'
|
||||||
|
params = { onion: onion_address, deploy: self }
|
||||||
|
|
||||||
|
SiteService.new(site: site, usuarie: usuarie, params: params).add_onion
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -14,6 +14,7 @@ class DeployLocal < Deploy
|
||||||
# Sutty
|
# Sutty
|
||||||
def deploy(output: false)
|
def deploy(output: false)
|
||||||
return false unless mkdir
|
return false unless mkdir
|
||||||
|
return false unless git_lfs(output: output)
|
||||||
return false unless yarn(output: output)
|
return false unless yarn(output: output)
|
||||||
return false unless pnpm(output: output)
|
return false unless pnpm(output: output)
|
||||||
return false unless bundle(output: output)
|
return false unless bundle(output: output)
|
||||||
|
@ -61,34 +62,26 @@ class DeployLocal < Deploy
|
||||||
FileUtils.rm_rf(File.join(site.path, '.jekyll-cache'))
|
FileUtils.rm_rf(File.join(site.path, '.jekyll-cache'))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Opciones necesarias para la compilación del sitio
|
||||||
|
#
|
||||||
|
# @return [Hash]
|
||||||
|
def local_env
|
||||||
|
@local_env ||= {
|
||||||
|
'SPREE_API_KEY' => site.tienda_api_key,
|
||||||
|
'SPREE_URL' => site.tienda_url,
|
||||||
|
'AIRBRAKE_PROJECT_ID' => site.id.to_s,
|
||||||
|
'AIRBRAKE_PROJECT_KEY' => site.airbrake_api_key,
|
||||||
|
'YARN_CACHE_FOLDER' => yarn_cache_dir,
|
||||||
|
'GEMS_SOURCE' => ENV['GEMS_SOURCE']
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def mkdir
|
def mkdir
|
||||||
FileUtils.mkdir_p destination
|
FileUtils.mkdir_p destination
|
||||||
end
|
end
|
||||||
|
|
||||||
# Un entorno que solo tiene lo que necesitamos
|
|
||||||
#
|
|
||||||
# @return [Hash]
|
|
||||||
def env
|
|
||||||
# XXX: This doesn't support Windows paths :B
|
|
||||||
paths = [File.dirname(`which bundle`), '/usr/local/bin', '/usr/bin', '/bin']
|
|
||||||
|
|
||||||
# Las variables de entorno extra no pueden superponerse al local.
|
|
||||||
extra_env.merge({
|
|
||||||
'HOME' => home_dir,
|
|
||||||
'PATH' => paths.join(':'),
|
|
||||||
'SPREE_API_KEY' => site.tienda_api_key,
|
|
||||||
'SPREE_URL' => site.tienda_url,
|
|
||||||
'AIRBRAKE_PROJECT_ID' => site.id.to_s,
|
|
||||||
'AIRBRAKE_PROJECT_KEY' => site.airbrake_api_key,
|
|
||||||
'JEKYLL_ENV' => Rails.env,
|
|
||||||
'LANG' => ENV['LANG'],
|
|
||||||
'YARN_CACHE_FOLDER' => yarn_cache_dir,
|
|
||||||
'GEMS_SOURCE' => ENV['GEMS_SOURCE']
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
def yarn_cache_dir
|
def yarn_cache_dir
|
||||||
Rails.root.join('_yarn_cache').to_s
|
Rails.root.join('_yarn_cache').to_s
|
||||||
end
|
end
|
||||||
|
@ -113,18 +106,15 @@ class DeployLocal < Deploy
|
||||||
File.exist? pnpm_lock
|
File.exist? pnpm_lock
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def git_lfs(output: false)
|
||||||
|
run %(git lfs fetch), output: output
|
||||||
|
run %(git lfs checkout), output: output
|
||||||
|
end
|
||||||
|
|
||||||
def gem(output: false)
|
def gem(output: false)
|
||||||
run %(gem install bundler --no-document), output: output
|
run %(gem install bundler --no-document), output: output
|
||||||
end
|
end
|
||||||
|
|
||||||
def pnpm_lock
|
|
||||||
File.join(site.path, 'pnpm-lock.yaml')
|
|
||||||
end
|
|
||||||
|
|
||||||
def pnpm_lock?
|
|
||||||
File.exist? pnpm_lock
|
|
||||||
end
|
|
||||||
|
|
||||||
# Corre yarn dentro del repositorio
|
# Corre yarn dentro del repositorio
|
||||||
def yarn(output: false)
|
def yarn(output: false)
|
||||||
return true unless yarn_lock?
|
return true unless yarn_lock?
|
||||||
|
@ -140,11 +130,20 @@ class DeployLocal < Deploy
|
||||||
end
|
end
|
||||||
|
|
||||||
def bundle(output: false)
|
def bundle(output: false)
|
||||||
run %(bundle install --no-cache --path="#{gems_dir}" --clean --without test development), output: output
|
run %(bundle config set --local clean 'true'), output: output
|
||||||
|
run %(bundle config set --local deployment 'true'), output: output
|
||||||
|
run %(bundle config set --local path '#{gems_dir}'), output: output
|
||||||
|
run %(bundle config set --local without 'test development'), output: output
|
||||||
|
run %(bundle config set --local cache_all 'false'), output: output
|
||||||
|
run %(bundle install), output: output
|
||||||
end
|
end
|
||||||
|
|
||||||
def jekyll_build(output: false)
|
def jekyll_build(output: false)
|
||||||
run %(bundle exec jekyll build --trace --profile --destination "#{escaped_destination}"), output: output
|
with_tempfile(site.private_key_pem) do |file|
|
||||||
|
flags = extra_flags(private_key: file)
|
||||||
|
|
||||||
|
run %(bundle exec jekyll build --trace --profile #{flags} --destination "#{escaped_destination}"), output: output
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# no debería haber espacios ni caracteres especiales, pero por si
|
# no debería haber espacios ni caracteres especiales, pero por si
|
||||||
|
@ -158,17 +157,13 @@ class DeployLocal < Deploy
|
||||||
FileUtils.rm_rf destination
|
FileUtils.rm_rf destination
|
||||||
end
|
end
|
||||||
|
|
||||||
# Consigue todas las variables de entorno configuradas por otros
|
# Genera opciones extra desde los otros deploys
|
||||||
# deploys.
|
|
||||||
#
|
#
|
||||||
# @deprecated Solo tenía sentido para Distributed Press v0
|
# @param :args [Hash]
|
||||||
# @return [Hash]
|
# @return [String]
|
||||||
def extra_env
|
def extra_flags(**args)
|
||||||
@extra_env ||=
|
site.deployment_list.map do |deploy|
|
||||||
non_local_deploys.reduce({}) do |extra_env, deploy|
|
deploy.flags_for_build(**args)
|
||||||
extra_env.tap do |e|
|
end.compact.join(' ')
|
||||||
e.merge! deploy.local_env
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -38,6 +38,7 @@ class DeployRsync < Deploy
|
||||||
#
|
#
|
||||||
# @return [Boolean]
|
# @return [Boolean]
|
||||||
def ssh?
|
def ssh?
|
||||||
|
return true if destination.start_with? 'rsync://'
|
||||||
user, host = user_host
|
user, host = user_host
|
||||||
ssh_available = false
|
ssh_available = false
|
||||||
|
|
||||||
|
|
55
app/models/deploy_social_distributed_press.rb
Normal file
55
app/models/deploy_social_distributed_press.rb
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'distributed_press/v1/social/client'
|
||||||
|
|
||||||
|
# Publicar novedades al Fediverso
|
||||||
|
class DeploySocialDistributedPress < Deploy
|
||||||
|
# Solo luego de publicar remotamente
|
||||||
|
DEPENDENCIES = %i[deploy_distributed_press deploy_rsync deploy_full_rsync]
|
||||||
|
|
||||||
|
# Envía las notificaciones
|
||||||
|
def deploy(output: false)
|
||||||
|
with_tempfile(site.private_key_pem) do |file|
|
||||||
|
key = Shellwords.escape file.path
|
||||||
|
dest = Shellwords.escape destination
|
||||||
|
|
||||||
|
run %(bundle exec jekyll notify --trace --key #{key} --destination "#{dest}"), output: output
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Igual que DeployLocal
|
||||||
|
#
|
||||||
|
# @return [String]
|
||||||
|
def destination
|
||||||
|
File.join(Rails.root, '_deploy', site.hostname)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Solo uno
|
||||||
|
#
|
||||||
|
# @return [Integer]
|
||||||
|
def limit
|
||||||
|
1
|
||||||
|
end
|
||||||
|
|
||||||
|
# Espacio ocupado, pero no podemos calcularlo
|
||||||
|
#
|
||||||
|
# @return [Integer]
|
||||||
|
def size
|
||||||
|
0
|
||||||
|
end
|
||||||
|
|
||||||
|
# El perfil de actor
|
||||||
|
#
|
||||||
|
# @return [String,nil]
|
||||||
|
def url
|
||||||
|
site.data.dig('activity_pub', 'actor')
|
||||||
|
end
|
||||||
|
|
||||||
|
# Genera la opción de llave privada para jekyll build
|
||||||
|
#
|
||||||
|
# @params :args [Hash]
|
||||||
|
# @return [String]
|
||||||
|
def flags_for_build(**args)
|
||||||
|
"--key #{Shellwords.escape args[:private_key].path}"
|
||||||
|
end
|
||||||
|
end
|
|
@ -36,6 +36,15 @@ class IndexedPost < ApplicationRecord
|
||||||
|
|
||||||
belongs_to :site
|
belongs_to :site
|
||||||
|
|
||||||
|
# Encuentra el post original
|
||||||
|
#
|
||||||
|
# @return [nil,Post]
|
||||||
|
def post
|
||||||
|
return if post_id.blank?
|
||||||
|
|
||||||
|
@post ||= site.posts(lang: locale).find(post_id, uuid: true)
|
||||||
|
end
|
||||||
|
|
||||||
# Convertir locale a direccionario de PG
|
# Convertir locale a direccionario de PG
|
||||||
#
|
#
|
||||||
# @param [String,Symbol]
|
# @param [String,Symbol]
|
||||||
|
|
|
@ -9,6 +9,13 @@ Layout = Struct.new(:site, :name, :meta, :metadata, keyword_init: true) do
|
||||||
name.to_s
|
name.to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Obtiene todos los layouts (schemas) dependientes de este.
|
||||||
|
#
|
||||||
|
# @return [Array]
|
||||||
|
def schemas
|
||||||
|
@schemas ||= site.layouts.to_h.slice(*site.schema_organization[name]).values
|
||||||
|
end
|
||||||
|
|
||||||
def attributes
|
def attributes
|
||||||
@attributes ||= metadata.keys.map(&:to_sym)
|
@attributes ||= metadata.keys.map(&:to_sym)
|
||||||
end
|
end
|
||||||
|
|
|
@ -11,7 +11,7 @@ class LogEntry < ApplicationRecord
|
||||||
def resend
|
def resend
|
||||||
return if sent
|
return if sent
|
||||||
|
|
||||||
ContactJob.perform_async site_id, params[:form], params
|
ContactJob.perform_later site_id, params[:form], params
|
||||||
end
|
end
|
||||||
|
|
||||||
def params
|
def params
|
||||||
|
|
|
@ -4,8 +4,12 @@
|
||||||
#
|
#
|
||||||
# Esto es increíblemente difícil de lograr que salga bien!
|
# Esto es increíblemente difícil de lograr que salga bien!
|
||||||
class MetadataBoolean < MetadataTemplate
|
class MetadataBoolean < MetadataTemplate
|
||||||
|
# El valor por defecto es una versión booleana de lo que diga (o no
|
||||||
|
# diga) el esquema
|
||||||
|
#
|
||||||
|
# @return [Boolean]
|
||||||
def default_value
|
def default_value
|
||||||
false
|
!!super
|
||||||
end
|
end
|
||||||
|
|
||||||
# Los checkboxes son especiales porque la especificación de HTML
|
# Los checkboxes son especiales porque la especificación de HTML
|
||||||
|
|
19
app/models/metadata_created_at.rb
Normal file
19
app/models/metadata_created_at.rb
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# Fecha y hora de creación
|
||||||
|
class MetadataCreatedAt < MetadataTemplate
|
||||||
|
# Por defecto la hora actual, pero por retrocompatibilidad, queremos
|
||||||
|
# la fecha de publicación
|
||||||
|
def default_value
|
||||||
|
if post.date.value.to_date < Time.now.to_date
|
||||||
|
post.date.value
|
||||||
|
else
|
||||||
|
Time.now
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Nunca cambia
|
||||||
|
def value=(new_value)
|
||||||
|
value
|
||||||
|
end
|
||||||
|
end
|
|
@ -34,7 +34,7 @@ class MetadataRelatedPosts < MetadataArray
|
||||||
end
|
end
|
||||||
|
|
||||||
def title(post)
|
def title(post)
|
||||||
"#{post&.title&.value || post&.slug&.value} (#{post.layout.humanized_name})"
|
"#{post&.title&.value || post&.slug&.value} #{post&.date&.value.strftime('%F')} (#{post.layout.humanized_name})"
|
||||||
end
|
end
|
||||||
|
|
||||||
# Encuentra el filtro
|
# Encuentra el filtro
|
||||||
|
|
|
@ -202,7 +202,7 @@ MetadataTemplate = Struct.new(:site, :document, :name, :label, :type,
|
||||||
|
|
||||||
def allowed_attributes
|
def allowed_attributes
|
||||||
@allowed_attributes ||= %w[style href src alt controls data-align data-multimedia data-multimedia-inner id
|
@allowed_attributes ||= %w[style href src alt controls data-align data-multimedia data-multimedia-inner id
|
||||||
name].freeze
|
name start].freeze
|
||||||
end
|
end
|
||||||
|
|
||||||
def allowed_tags
|
def allowed_tags
|
||||||
|
|
|
@ -12,7 +12,7 @@ class Post
|
||||||
DEFAULT_ATTRIBUTES = %i[site document layout].freeze
|
DEFAULT_ATTRIBUTES = %i[site document layout].freeze
|
||||||
# Otros atributos que no vienen en los metadatos
|
# Otros atributos que no vienen en los metadatos
|
||||||
PRIVATE_ATTRIBUTES = %i[path slug attributes errors].freeze
|
PRIVATE_ATTRIBUTES = %i[path slug attributes errors].freeze
|
||||||
PUBLIC_ATTRIBUTES = %i[lang date uuid].freeze
|
PUBLIC_ATTRIBUTES = %i[lang date uuid created_at].freeze
|
||||||
ATTR_SUFFIXES = %w[? =].freeze
|
ATTR_SUFFIXES = %w[? =].freeze
|
||||||
|
|
||||||
attr_reader :attributes, :errors, :layout, :site, :document
|
attr_reader :attributes, :errors, :layout, :site, :document
|
||||||
|
@ -217,6 +217,11 @@ class Post
|
||||||
post: self, required: true)
|
post: self, required: true)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# La fecha de creación inmodificable del post
|
||||||
|
def created_at
|
||||||
|
@metadata[:created_at] ||= MetadataCreatedAt.new(document: document, site: site, layout: layout, name: :created_at, type: :created_at, post: self, required: true)
|
||||||
|
end
|
||||||
|
|
||||||
# Detecta si es un atributo válido o no, a partir de la tabla de la
|
# Detecta si es un atributo válido o no, a partir de la tabla de la
|
||||||
# plantilla
|
# plantilla
|
||||||
def attribute?(mid)
|
def attribute?(mid)
|
||||||
|
@ -267,6 +272,7 @@ class Post
|
||||||
# Y que no se procese liquid
|
# Y que no se procese liquid
|
||||||
yaml['liquid'] = false
|
yaml['liquid'] = false
|
||||||
yaml['usuaries'] = usuaries.map(&:id).uniq
|
yaml['usuaries'] = usuaries.map(&:id).uniq
|
||||||
|
yaml['created_at'] = created_at.value
|
||||||
yaml['last_modified_at'] = modified_at
|
yaml['last_modified_at'] = modified_at
|
||||||
|
|
||||||
"#{yaml.to_yaml}---\n\n#{body}"
|
"#{yaml.to_yaml}---\n\n#{body}"
|
||||||
|
|
|
@ -14,6 +14,8 @@ class Rol < ApplicationRecord
|
||||||
|
|
||||||
validates_inclusion_of :rol, in: ROLES
|
validates_inclusion_of :rol, in: ROLES
|
||||||
|
|
||||||
|
before_save :add_token_if_missing!
|
||||||
|
|
||||||
def invitade?
|
def invitade?
|
||||||
rol == INVITADE
|
rol == INVITADE
|
||||||
end
|
end
|
||||||
|
@ -21,4 +23,15 @@ class Rol < ApplicationRecord
|
||||||
def usuarie?
|
def usuarie?
|
||||||
rol == USUARIE
|
rol == USUARIE
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.role?(rol)
|
||||||
|
ROLES.include? rol
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Asegurarse que tenga un token
|
||||||
|
def add_token_if_missing!
|
||||||
|
self.token ||= SecureRandom.hex(64)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -9,6 +9,8 @@ class Site < ApplicationRecord
|
||||||
include Site::Api
|
include Site::Api
|
||||||
include Site::DeployDependencies
|
include Site::DeployDependencies
|
||||||
include Site::BuildStats
|
include Site::BuildStats
|
||||||
|
include Site::LayoutOrdering
|
||||||
|
include Site::SocialDistributedPress
|
||||||
include Tienda
|
include Tienda
|
||||||
|
|
||||||
# Cifrar la llave privada que cifra y decifra campos ocultos. Sutty
|
# Cifrar la llave privada que cifra y decifra campos ocultos. Sutty
|
||||||
|
@ -17,10 +19,6 @@ class Site < ApplicationRecord
|
||||||
# protege de acceso al panel de Sutty!
|
# protege de acceso al panel de Sutty!
|
||||||
encrypts :private_key
|
encrypts :private_key
|
||||||
|
|
||||||
# TODO: Hacer que los diferentes tipos de deploy se auto registren
|
|
||||||
# @see app/services/site_service.rb
|
|
||||||
DEPLOYS = %i[local private www zip hidden_service distributed_press].freeze
|
|
||||||
|
|
||||||
validates :name, uniqueness: true, hostname: {
|
validates :name, uniqueness: true, hostname: {
|
||||||
allow_root_label: true
|
allow_root_label: true
|
||||||
}
|
}
|
||||||
|
@ -446,6 +444,10 @@ class Site < ApplicationRecord
|
||||||
find_by(name: "#{Site.domain}.")
|
find_by(name: "#{Site.domain}.")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.one_at_a_time
|
||||||
|
@@one_at_a_time ||= Thread::Mutex.new
|
||||||
|
end
|
||||||
|
|
||||||
def reset
|
def reset
|
||||||
@read = false
|
@read = false
|
||||||
@layouts = nil
|
@layouts = nil
|
||||||
|
@ -472,7 +474,10 @@ class Site < ApplicationRecord
|
||||||
def clone_skel!
|
def clone_skel!
|
||||||
return if jekyll?
|
return if jekyll?
|
||||||
|
|
||||||
Rugged::Repository.clone_at ENV['SKEL_SUTTY'], path
|
Rugged::Repository.clone_at(ENV['SKEL_SUTTY'], path, checkout_branch: design.gem)
|
||||||
|
|
||||||
|
# Necesita un bloque
|
||||||
|
repository.rugged.remotes.rename('origin', 'upstream') {}
|
||||||
end
|
end
|
||||||
|
|
||||||
# Elimina el directorio del sitio
|
# Elimina el directorio del sitio
|
||||||
|
@ -496,8 +501,8 @@ class Site < ApplicationRecord
|
||||||
config.theme = design.gem unless design.no_theme?
|
config.theme = design.gem unless design.no_theme?
|
||||||
config.description = description
|
config.description = description
|
||||||
config.title = title
|
config.title = title
|
||||||
config.url = url(slash: false)
|
config.url ||= url(slash: false)
|
||||||
config.hostname = hostname
|
config.hostname ||= hostname
|
||||||
config.locales = locales.map(&:to_s)
|
config.locales = locales.map(&:to_s)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -552,7 +557,9 @@ class Site < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def run_in_path(&block)
|
def run_in_path(&block)
|
||||||
Dir.chdir path, &block
|
Site.one_at_a_time.synchronize do
|
||||||
|
Dir.chdir path, &block
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Instala las gemas cuando es necesario:
|
# Instala las gemas cuando es necesario:
|
||||||
|
@ -564,6 +571,8 @@ class Site < ApplicationRecord
|
||||||
def install_gems
|
def install_gems
|
||||||
return unless persisted?
|
return unless persisted?
|
||||||
|
|
||||||
|
deploys.find_by_type('DeployLocal').send(:git_lfs)
|
||||||
|
|
||||||
if !gem_dir? || gemfile_updated? || gemfile_lock_updated?
|
if !gem_dir? || gemfile_updated? || gemfile_lock_updated?
|
||||||
deploys.find_by_type('DeployLocal').send(:bundle)
|
deploys.find_by_type('DeployLocal').send(:bundle)
|
||||||
touch
|
touch
|
||||||
|
|
|
@ -40,6 +40,72 @@ class Site
|
||||||
def not_published_yet?
|
def not_published_yet?
|
||||||
build_stats.jekyll.where(status: true).count.zero?
|
build_stats.jekyll.where(status: true).count.zero?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Cambios posibles luego de la última publicación exitosa:
|
||||||
|
#
|
||||||
|
# * Artículos modificados
|
||||||
|
# * Configuración modificada
|
||||||
|
# * Métodos de publicación añadidos
|
||||||
|
#
|
||||||
|
# @return [Boolean]
|
||||||
|
def awaiting_publication?
|
||||||
|
waiting? && (post_pending? || deploy_pending? || configuration_pending?)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Se modificaron artículos después de publicar el sitio por última
|
||||||
|
# vez
|
||||||
|
#
|
||||||
|
# @return [Boolean]
|
||||||
|
def post_pending?
|
||||||
|
last_indexed_post_time > last_publication_time
|
||||||
|
end
|
||||||
|
|
||||||
|
# Se modificó el sitio después de publicarlo por última vez
|
||||||
|
#
|
||||||
|
# @return [Boolean]
|
||||||
|
def deploy_pending?
|
||||||
|
last_deploy_time > last_publication_time
|
||||||
|
end
|
||||||
|
|
||||||
|
# Se modificó la configuración del sitio
|
||||||
|
#
|
||||||
|
# @return [Boolean]
|
||||||
|
def configuration_pending?
|
||||||
|
last_configuration_time > last_publication_time
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Encuentra la fecha del último artículo modificado. Si no hay
|
||||||
|
# ninguno, devuelve la fecha de modificación del sitio.
|
||||||
|
#
|
||||||
|
# @return [Time]
|
||||||
|
def last_indexed_post_time
|
||||||
|
indexed_posts.order(updated_at: :desc).select(:updated_at).first&.updated_at || updated_at
|
||||||
|
end
|
||||||
|
|
||||||
|
# Encuentra la fecha de última modificación de los métodos de
|
||||||
|
# publicación.
|
||||||
|
#
|
||||||
|
# @return [Time]
|
||||||
|
def last_deploy_time
|
||||||
|
deploys.order(created_at: :desc).select(:created_at).first&.created_at || updated_at
|
||||||
|
end
|
||||||
|
|
||||||
|
# Encuentra la fecha de última publicación exitosa, si no hay
|
||||||
|
# ninguno, devuelve la fecha de modificación del sitio.
|
||||||
|
#
|
||||||
|
# @return [Time]
|
||||||
|
def last_publication_time
|
||||||
|
build_stats.jekyll.where(status: true).order(created_at: :desc).select(:created_at).first&.created_at || updated_at
|
||||||
|
end
|
||||||
|
|
||||||
|
# Fecha de última modificación de la configuración
|
||||||
|
#
|
||||||
|
# @return [Time]
|
||||||
|
def last_configuration_time
|
||||||
|
File.mtime(config.path)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -37,7 +37,7 @@ class Site
|
||||||
|
|
||||||
author = GitAuthor.new email: "sutty@#{Site.domain}", name: 'Sutty'
|
author = GitAuthor.new email: "sutty@#{Site.domain}", name: 'Sutty'
|
||||||
|
|
||||||
repository.commit(file: modified,
|
repository.commit(add: modified,
|
||||||
message: I18n.t('sites.find_and_replace'),
|
message: I18n.t('sites.find_and_replace'),
|
||||||
usuarie: author)
|
usuarie: author)
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,22 +1,125 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
# Indexa todos los artículos de un sitio
|
|
||||||
#
|
|
||||||
# TODO: Hacer opcional
|
|
||||||
class Site
|
class Site
|
||||||
|
# Indexa todos los artículos de un sitio
|
||||||
|
#
|
||||||
|
# TODO: Hacer opcional
|
||||||
module Index
|
module Index
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
included do
|
included do
|
||||||
# TODO: Debería ser un Job?
|
|
||||||
after_create :index_posts!
|
|
||||||
has_many :indexed_posts, dependent: :destroy
|
has_many :indexed_posts, dependent: :destroy
|
||||||
|
|
||||||
|
MODIFIED_STATUSES = %i[added modified].freeze
|
||||||
|
DELETED_STATUSES = %i[deleted].freeze
|
||||||
|
LOCALE_FROM_PATH = /\A_/.freeze
|
||||||
|
|
||||||
def index_posts!
|
def index_posts!
|
||||||
Site.transaction do
|
Site.transaction do
|
||||||
docs.each(&:index!)
|
docs.each(&:index!)
|
||||||
|
|
||||||
|
update(last_indexed_commit: repository.head_commit.oid)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Encuentra los artículos modificados entre dos commits y los
|
||||||
|
# reindexa.
|
||||||
|
def reindex_changes!
|
||||||
|
return unless reindexable?
|
||||||
|
|
||||||
|
Site.transaction do
|
||||||
|
remove_deleted_posts!
|
||||||
|
reindex_modified_posts!
|
||||||
|
|
||||||
|
update(last_indexed_commit: repository.head_commit.oid)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# No hacer nada si el repositorio no cambió o no hubo cambios
|
||||||
|
# necesarios
|
||||||
|
def reindexable?
|
||||||
|
return false if last_indexed_commit.blank?
|
||||||
|
return false if last_indexed_commit == repository.head_commit.oid
|
||||||
|
|
||||||
|
!indexable_posts.empty?
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Trae el último commit indexado desde el repositorio
|
||||||
|
#
|
||||||
|
# @return [Rugged::Commit]
|
||||||
|
def indexed_commit
|
||||||
|
@indexed_commit ||= repository.rugged.lookup(last_indexed_commit)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Calcula la diferencia entre el último commit indexado y el
|
||||||
|
# actual
|
||||||
|
#
|
||||||
|
# XXX: Esto no tiene en cuenta modificaciones en la historia como
|
||||||
|
# cambio de ramas, reverts y etc, solo asume que se mueve hacia
|
||||||
|
# adelante en la misma rama o las dos ramas están relacionadas.
|
||||||
|
#
|
||||||
|
# @return [Rugged::Diff]
|
||||||
|
def diff_with_head
|
||||||
|
@diff_with_head ||= indexed_commit.diff(repository.head_commit)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Obtiene todos los archivos a reindexar
|
||||||
|
#
|
||||||
|
# @return [Array<Rugged::Delta>]
|
||||||
|
def indexable_posts
|
||||||
|
@indexable_posts ||=
|
||||||
|
diff_with_head.each_delta.select do |delta|
|
||||||
|
locales.any? do |locale|
|
||||||
|
delta.old_file[:path].start_with? "_#{locale}/"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Elimina los artículos eliminados o que cambiaron de ubicación
|
||||||
|
# del índice
|
||||||
|
def remove_deleted_posts!
|
||||||
|
indexable_posts.select do |delta|
|
||||||
|
DELETED_STATUSES.include? delta.status
|
||||||
|
end.each do |delta|
|
||||||
|
locale, path = locale_and_path_from(delta.old_file[:path])
|
||||||
|
|
||||||
|
indexed_posts.destroy_by(locale: locale, path: path).tap do |destroyed_posts|
|
||||||
|
next unless destroyed_posts.empty?
|
||||||
|
|
||||||
|
Rails.logger.info I18n.t('indexed_posts.deleted', site: name, path: path, records: destroyed_posts.count)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Reindexa artículos que cambiaron de ubicación, se agregaron
|
||||||
|
# o fueron modificados
|
||||||
|
def reindex_modified_posts!
|
||||||
|
indexable_posts.select do |delta|
|
||||||
|
MODIFIED_STATUSES.include? delta.status
|
||||||
|
end.each do |delta|
|
||||||
|
locale, path = locale_and_path_from(delta.new_file[:path])
|
||||||
|
|
||||||
|
posts(lang: locale).find(path).index!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Obtiene el idioma y la ruta del post a partir de la ubicación en
|
||||||
|
# el disco.
|
||||||
|
#
|
||||||
|
# Las rutas vienen en ASCII-9BIT desde Rugged, pero en realidad
|
||||||
|
# son UTF-8
|
||||||
|
#
|
||||||
|
# @return [Array<String>]
|
||||||
|
def locale_and_path_from(path)
|
||||||
|
locale, path = path.force_encoding('utf-8').split(File::SEPARATOR, 2)
|
||||||
|
|
||||||
|
[
|
||||||
|
locale.sub(LOCALE_FROM_PATH, ''),
|
||||||
|
File.basename(path, '.*')
|
||||||
|
]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
38
app/models/site/layout_ordering.rb
Normal file
38
app/models/site/layout_ordering.rb
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Site
|
||||||
|
# Obtiene un listado de layouts (schemas)
|
||||||
|
module LayoutOrdering
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
included do
|
||||||
|
|
||||||
|
# Obtiene o genera un listado de layouts (schemas) con sus
|
||||||
|
# dependencias, para poder generar un árbol.
|
||||||
|
#
|
||||||
|
# Por defecto, si el sitio no lo soporta, se obtienen los layouts
|
||||||
|
# ordenados alfabéticamente por traducción.
|
||||||
|
#
|
||||||
|
# @return [Hash]
|
||||||
|
def schema_organization
|
||||||
|
@schema_organization ||=
|
||||||
|
begin
|
||||||
|
schema_organization = data.dig('schema', 'organization')
|
||||||
|
schema_organization&.symbolize_keys!
|
||||||
|
schema_organization&.transform_values! do |ary|
|
||||||
|
ary.map(&:to_sym)
|
||||||
|
end
|
||||||
|
|
||||||
|
schema_organization ||
|
||||||
|
begin
|
||||||
|
layouts = self.layouts.sort_by(&:humanized_name).map(&:name)
|
||||||
|
Hash[layouts.zip([].fill([], 0, layouts.size))]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# TODO: Deprecar cuando renombremos layouts a schemas
|
||||||
|
alias layout_organization schema_organization
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -29,7 +29,7 @@ class Site
|
||||||
|
|
||||||
# Obtiene el origin
|
# Obtiene el origin
|
||||||
#
|
#
|
||||||
# @return [Rugged::Remote]
|
# @return [Rugged::Remote, nil]
|
||||||
def origin
|
def origin
|
||||||
@origin ||= rugged.remotes.find do |remote|
|
@origin ||= rugged.remotes.find do |remote|
|
||||||
remote.name == 'origin'
|
remote.name == 'origin'
|
||||||
|
@ -54,7 +54,7 @@ class Site
|
||||||
# Incorpora los cambios en el repositorio actual
|
# Incorpora los cambios en el repositorio actual
|
||||||
#
|
#
|
||||||
# @return [Rugged::Commit]
|
# @return [Rugged::Commit]
|
||||||
def merge(usuarie)
|
def merge(usuarie, message = I18n.t('sites.fetch.merge.message'))
|
||||||
merge = rugged.merge_commits(head_commit, remote_head_commit)
|
merge = rugged.merge_commits(head_commit, remote_head_commit)
|
||||||
|
|
||||||
# No hacemos nada si hay conflictos, pero notificarnos
|
# No hacemos nada si hay conflictos, pero notificarnos
|
||||||
|
@ -69,12 +69,16 @@ class Site
|
||||||
.create(rugged, update_ref: 'HEAD',
|
.create(rugged, update_ref: 'HEAD',
|
||||||
parents: [head_commit, remote_head_commit],
|
parents: [head_commit, remote_head_commit],
|
||||||
tree: merge.write_tree(rugged),
|
tree: merge.write_tree(rugged),
|
||||||
message: I18n.t('sites.fetch.merge.message'),
|
message: message,
|
||||||
author: author(usuarie), committer: committer)
|
author: author(usuarie), committer: committer)
|
||||||
|
|
||||||
# Forzamos el checkout para mover el HEAD al último commit y
|
# Forzamos el checkout para mover el HEAD al último commit y
|
||||||
# escribir los cambios
|
# escribir los cambios
|
||||||
rugged.checkout 'HEAD', strategy: :force
|
rugged.checkout 'HEAD', strategy: :force
|
||||||
|
|
||||||
|
git_sh("git", "lfs", "fetch", "origin", default_branch)
|
||||||
|
# reemplaza los pointers por los archivos correspondientes
|
||||||
|
git_sh("git", "lfs", "checkout")
|
||||||
commit
|
commit
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -114,14 +118,21 @@ class Site
|
||||||
end
|
end
|
||||||
|
|
||||||
# Guarda los cambios en git
|
# Guarda los cambios en git
|
||||||
def commit(file:, usuarie:, message:, remove: false)
|
#
|
||||||
file = [file] unless file.respond_to? :each
|
# @param :add [Array] Archivos a agregar
|
||||||
|
# @param :rm [Array] Archivos a eliminar
|
||||||
|
# @param :usuarie [Usuarie] Quién hace el commit
|
||||||
|
# @param :message [String] Mensaje
|
||||||
|
def commit(add: [], rm: [], usuarie:, message:)
|
||||||
# Cargar el árbol actual
|
# Cargar el árbol actual
|
||||||
rugged.index.read_tree rugged.head.target.tree
|
rugged.index.read_tree rugged.head.target.tree
|
||||||
|
|
||||||
file.each do |f|
|
add.each do |file|
|
||||||
remove ? rm(f) : add(f)
|
rugged.index.add(relativize(file))
|
||||||
|
end
|
||||||
|
|
||||||
|
rm.each do |file|
|
||||||
|
rugged.index.remove(relativize(file))
|
||||||
end
|
end
|
||||||
|
|
||||||
# Escribir los cambios para que el repositorio se vea tal cual
|
# Escribir los cambios para que el repositorio se vea tal cual
|
||||||
|
@ -142,41 +153,58 @@ class Site
|
||||||
{ name: 'Sutty', email: "sutty@#{Site.domain}", time: Time.now }
|
{ name: 'Sutty', email: "sutty@#{Site.domain}", time: Time.now }
|
||||||
end
|
end
|
||||||
|
|
||||||
def add(file)
|
|
||||||
rugged.index.add(relativize(file))
|
|
||||||
end
|
|
||||||
|
|
||||||
def rm(file)
|
|
||||||
rugged.index.remove(relativize(file))
|
|
||||||
end
|
|
||||||
|
|
||||||
# Garbage collection
|
# Garbage collection
|
||||||
#
|
#
|
||||||
# @return [Boolean]
|
# @return [Boolean]
|
||||||
def gc
|
def gc
|
||||||
env = { 'PATH' => '/usr/bin', 'LANG' => ENV['LANG'], 'HOME' => path }
|
git_sh("git", "gc")
|
||||||
cmd = 'git gc'
|
end
|
||||||
|
|
||||||
r = nil
|
# Pushea cambios al repositorio remoto
|
||||||
Dir.chdir(path) do
|
#
|
||||||
Open3.popen2e(env, cmd, unsetenv_others: true) do |_, _, t|
|
# @param :remote [Rugged::Remote]
|
||||||
r = t.value
|
# @return [Boolean, nil]
|
||||||
end
|
def push(remote = origin)
|
||||||
end
|
remote.push(rugged.head.canonical_name, credentials: credentials_for(remote))
|
||||||
|
git_sh('git', 'lfs', 'push', remote.name, default_branch)
|
||||||
r&.success?
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
# @deprecated
|
||||||
|
def credentials
|
||||||
|
@credentials ||= credentials_for(origin)
|
||||||
|
end
|
||||||
|
|
||||||
# Si Sutty tiene una llave privada de tipo ED25519, devuelve las
|
# Si Sutty tiene una llave privada de tipo ED25519, devuelve las
|
||||||
# credenciales necesarias para trabajar con repositorios remotos.
|
# credenciales necesarias para trabajar con repositorios remotos.
|
||||||
#
|
#
|
||||||
|
# @param :remote [Rugged::Remote]
|
||||||
# @return [Nil, Rugged::Credentials::SshKey]
|
# @return [Nil, Rugged::Credentials::SshKey]
|
||||||
def credentials
|
def credentials_for(remote)
|
||||||
return unless File.exist? private_key
|
return unless File.exist? private_key
|
||||||
|
|
||||||
@credentials ||= Rugged::Credentials::SshKey.new username: 'git', publickey: public_key, privatekey: private_key
|
Rugged::Credentials::SshKey.new username: username_for(remote), publickey: public_key, privatekey: private_key
|
||||||
|
end
|
||||||
|
|
||||||
|
# Obtiene el nombre de usuario para el repositorio remoto, por
|
||||||
|
# defecto git
|
||||||
|
#
|
||||||
|
# @param :remote [Rugged::Remote]
|
||||||
|
# @return [String]
|
||||||
|
def username_for(remote)
|
||||||
|
username = parse_url(remote.url)&.user if remote.respond_to? :url
|
||||||
|
|
||||||
|
username || 'git'
|
||||||
|
end
|
||||||
|
|
||||||
|
# @param :url [String]
|
||||||
|
# @return [URI, nil]
|
||||||
|
def parse_url(url)
|
||||||
|
GitCloneUrl.parse(url)
|
||||||
|
rescue URI::Error => e
|
||||||
|
ExceptionNotifier.notify_exception(e, data: { path: path, url: url })
|
||||||
|
nil
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [String]
|
# @return [String]
|
||||||
|
@ -192,5 +220,20 @@ class Site
|
||||||
def relativize(file)
|
def relativize(file)
|
||||||
Pathname.new(file).relative_path_from(Pathname.new(path)).to_s
|
Pathname.new(file).relative_path_from(Pathname.new(path)).to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Ejecuta un comando de git
|
||||||
|
#
|
||||||
|
# @param :args [Array]
|
||||||
|
# @return [Boolean]
|
||||||
|
def git_sh(*args)
|
||||||
|
env = { 'PATH' => '/usr/bin', 'LANG' => ENV['LANG'], 'HOME' => path }
|
||||||
|
|
||||||
|
r = nil
|
||||||
|
Open3.popen2e(env, *args, unsetenv_others: true, chdir: path) do |_, _, t|
|
||||||
|
r = t.value
|
||||||
|
end
|
||||||
|
|
||||||
|
r&.success?
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
23
app/models/site/social_distributed_press.rb
Normal file
23
app/models/site/social_distributed_press.rb
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Site
|
||||||
|
# Agrega soporte para Social Distributed Press en los sitios
|
||||||
|
module SocialDistributedPress
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
included do
|
||||||
|
encrypts :private_key_pem
|
||||||
|
|
||||||
|
before_save :generate_private_key_pem!, unless: :private_key_pem?
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Genera la llave privada y la almacena
|
||||||
|
#
|
||||||
|
# @return [nil]
|
||||||
|
def generate_private_key_pem!
|
||||||
|
self.private_key_pem ||= DistributedPress::V1::Social::Client.new(public_key_url: nil, key_size: 2048).private_key.export
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
3
app/models/site_build_stat.rb
Normal file
3
app/models/site_build_stat.rb
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
SiteBuildStat = Struct.new(:site)
|
|
@ -2,14 +2,19 @@
|
||||||
|
|
||||||
# Usuarie de la plataforma
|
# Usuarie de la plataforma
|
||||||
class Usuarie < ApplicationRecord
|
class Usuarie < ApplicationRecord
|
||||||
|
include Usuarie::Consent
|
||||||
|
|
||||||
devise :invitable, :database_authenticatable,
|
devise :invitable, :database_authenticatable,
|
||||||
:recoverable, :rememberable, :validatable,
|
:recoverable, :rememberable, :validatable,
|
||||||
:confirmable, :lockable, :registerable
|
:confirmable, :lockable, :registerable
|
||||||
|
|
||||||
validates_uniqueness_of :email
|
validates_uniqueness_of :email
|
||||||
validates_with EmailAddress::ActiveRecordValidator, field: :email
|
validates_with EmailAddress::ActiveRecordValidator, field: :email
|
||||||
|
validate :locale_available!
|
||||||
|
|
||||||
before_create :lang_from_locale!
|
before_create :lang_from_locale!
|
||||||
|
before_update :remove_confirmation_invitation_inconsistencies!
|
||||||
|
before_update :accept_invitation_after_confirmation!
|
||||||
|
|
||||||
has_many :roles
|
has_many :roles
|
||||||
has_many :sites, through: :roles
|
has_many :sites, through: :roles
|
||||||
|
@ -47,9 +52,42 @@ class Usuarie < ApplicationRecord
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Les usuaries necesitan link de invitación si no tenían cuenta
|
||||||
|
# y todavía no aceptaron la invitación anterior.
|
||||||
|
def needs_invitation_link?
|
||||||
|
created_by_invite? && !invitation_accepted?
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def lang_from_locale!
|
def lang_from_locale!
|
||||||
self.lang = I18n.locale.to_s
|
self.lang = I18n.locale.to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# El invitation_token solo es necesario cuando fue creade por otre
|
||||||
|
# usuarie. De lo contrario lo que queremos es un proceso de
|
||||||
|
# confirmación.
|
||||||
|
def remove_confirmation_invitation_inconsistencies!
|
||||||
|
self.invitation_token = nil unless created_by_invite?
|
||||||
|
end
|
||||||
|
|
||||||
|
# Si le usuarie (re)confirma su cuenta con una invitación pendiente,
|
||||||
|
# considerarla aceptada también.
|
||||||
|
def accept_invitation_after_confirmation!
|
||||||
|
if confirmed?
|
||||||
|
self.invitation_token = nil
|
||||||
|
self.invitation_accepted_at ||= Time.now.utc
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Muestra un error si el idioma no está disponible al cambiar el
|
||||||
|
# idioma de la cuenta.
|
||||||
|
#
|
||||||
|
# @return [nil]
|
||||||
|
def locale_available!
|
||||||
|
return if I18n.locale_available? self.lang
|
||||||
|
|
||||||
|
errors.add(:lang, I18n.t('activerecord.errors.models.usuarie.attributes.lang.not_available'))
|
||||||
|
nil
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
66
app/policies/indexed_post_policy.rb
Normal file
66
app/policies/indexed_post_policy.rb
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# Política de acceso a artículos
|
||||||
|
class IndexedPostPolicy
|
||||||
|
attr_reader :indexed_post, :usuarie, :site
|
||||||
|
|
||||||
|
def initialize(usuarie, indexed_post)
|
||||||
|
@usuarie = usuarie
|
||||||
|
@indexed_post = indexed_post
|
||||||
|
@site = indexed_post.site
|
||||||
|
end
|
||||||
|
|
||||||
|
def index?
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
# Les invitades solo pueden ver sus propios posts
|
||||||
|
def show?
|
||||||
|
site.usuarie?(usuarie) || site.indexed_posts.by_usuarie(usuarie.id).find_by_post_id(indexed_post.post_id).present?
|
||||||
|
end
|
||||||
|
|
||||||
|
def preview?
|
||||||
|
show?
|
||||||
|
end
|
||||||
|
|
||||||
|
def new?
|
||||||
|
create?
|
||||||
|
end
|
||||||
|
|
||||||
|
def create?
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
def edit?
|
||||||
|
update?
|
||||||
|
end
|
||||||
|
|
||||||
|
# Les invitades solo pueden modificar sus propios artículos
|
||||||
|
def update?
|
||||||
|
show?
|
||||||
|
end
|
||||||
|
|
||||||
|
# Solo las usuarias pueden eliminar artículos. Les invitades pueden
|
||||||
|
# borrar sus propios artículos
|
||||||
|
def destroy?
|
||||||
|
update?
|
||||||
|
end
|
||||||
|
|
||||||
|
# Las usuarias pueden ver todos los posts
|
||||||
|
#
|
||||||
|
# Les invitades solo pueden ver sus propios posts
|
||||||
|
class Scope
|
||||||
|
attr_reader :usuarie, :scope
|
||||||
|
|
||||||
|
def initialize(usuarie, scope)
|
||||||
|
@usuarie = usuarie
|
||||||
|
@scope = scope
|
||||||
|
end
|
||||||
|
|
||||||
|
def resolve
|
||||||
|
return scope if scope&.first&.site&.usuarie? usuarie
|
||||||
|
|
||||||
|
scope.by_usuarie(usuarie.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
16
app/policies/site_build_stat_policy.rb
Normal file
16
app/policies/site_build_stat_policy.rb
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# Quiénes pueden ver estados de compilación de un sitio
|
||||||
|
class SiteBuildStatPolicy
|
||||||
|
attr_reader :site_build_stat, :usuarie
|
||||||
|
|
||||||
|
def initialize(usuarie, site_build_stat)
|
||||||
|
@usuarie = usuarie
|
||||||
|
@site_build_stat = site_build_stat
|
||||||
|
end
|
||||||
|
|
||||||
|
# Todes les usuaries e invitades de este sitio
|
||||||
|
def index?
|
||||||
|
site_build_stat.site.usuarie?(usuarie) || site_build_stat.site.invitade?(usuarie)
|
||||||
|
end
|
||||||
|
end
|
|
@ -22,7 +22,7 @@ class LfsObjectService
|
||||||
Site::Writer.new(site: site, file: path, content: pointer).save
|
Site::Writer.new(site: site, file: path, content: pointer).save
|
||||||
|
|
||||||
# Commitear el pointer
|
# Commitear el pointer
|
||||||
site.repository.commit(file: path, usuarie: author, message: File.basename(path))
|
site.repository.commit(add: [path], usuarie: author, message: File.basename(path))
|
||||||
|
|
||||||
# Eliminar el pointer
|
# Eliminar el pointer
|
||||||
FileUtils.rm(path)
|
FileUtils.rm(path)
|
||||||
|
|
|
@ -16,7 +16,7 @@ PostService = Struct.new(:site, :usuarie, :post, :params, keyword_init: true) do
|
||||||
post.slug.value = p[:slug] if p[:slug].present?
|
post.slug.value = p[:slug] if p[:slug].present?
|
||||||
end
|
end
|
||||||
|
|
||||||
commit(action: :created, file: update_related_posts) if post.update(post_params)
|
commit(action: :created, add: update_related_posts) if post.update(post_params)
|
||||||
|
|
||||||
update_site_license!
|
update_site_license!
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ PostService = Struct.new(:site, :usuarie, :post, :params, keyword_init: true) do
|
||||||
# Los artículos anónimos siempre son borradores
|
# Los artículos anónimos siempre son borradores
|
||||||
params[:draft] = true
|
params[:draft] = true
|
||||||
|
|
||||||
commit(action: :created) if post.update(anon_post_params)
|
commit(action: :created, add: [post.path.absolute]) if post.update(anon_post_params)
|
||||||
post
|
post
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -42,11 +42,17 @@ PostService = Struct.new(:site, :usuarie, :post, :params, keyword_init: true) do
|
||||||
post.usuaries << usuarie
|
post.usuaries << usuarie
|
||||||
params[:post][:draft] = true if site.invitade? usuarie
|
params[:post][:draft] = true if site.invitade? usuarie
|
||||||
|
|
||||||
# Es importante que el artículo se guarde primero y luego los
|
# Eliminar ("mover") el archivo si cambió de ubicación.
|
||||||
# relacionados.
|
if post.update(post_params)
|
||||||
commit(action: :updated, file: update_related_posts) if post.update(post_params)
|
rm = []
|
||||||
|
rm << post.path.value_was if post.path.changed?
|
||||||
|
|
||||||
update_site_license!
|
# Es importante que el artículo se guarde primero y luego los
|
||||||
|
# relacionados.
|
||||||
|
commit(action: :updated, add: update_related_posts, rm: rm)
|
||||||
|
|
||||||
|
update_site_license!
|
||||||
|
end
|
||||||
|
|
||||||
# Devolver el post aunque no se haya salvado para poder rescatar los
|
# Devolver el post aunque no se haya salvado para poder rescatar los
|
||||||
# errores
|
# errores
|
||||||
|
@ -56,7 +62,7 @@ PostService = Struct.new(:site, :usuarie, :post, :params, keyword_init: true) do
|
||||||
def destroy
|
def destroy
|
||||||
post.destroy!
|
post.destroy!
|
||||||
|
|
||||||
commit(action: :destroyed) if post.destroyed?
|
commit(action: :destroyed, rm: [post.path.absolute]) if post.destroyed?
|
||||||
|
|
||||||
post
|
post
|
||||||
end
|
end
|
||||||
|
@ -85,17 +91,19 @@ PostService = Struct.new(:site, :usuarie, :post, :params, keyword_init: true) do
|
||||||
|
|
||||||
# TODO: Implementar transacciones!
|
# TODO: Implementar transacciones!
|
||||||
posts.save_all(validate: false) &&
|
posts.save_all(validate: false) &&
|
||||||
commit(action: :reorder, file: files)
|
commit(action: :reorder, add: files)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def commit(action:, file: nil)
|
def commit(action:, add: [], rm: [])
|
||||||
site.repository.commit(file: file || post.path.absolute,
|
site.repository.commit(add: add,
|
||||||
|
rm: rm,
|
||||||
usuarie: usuarie,
|
usuarie: usuarie,
|
||||||
remove: action == :destroyed,
|
|
||||||
message: I18n.t("post_service.#{action}",
|
message: I18n.t("post_service.#{action}",
|
||||||
title: post&.title&.value))
|
title: post&.title&.value))
|
||||||
|
|
||||||
|
GitPushJob.perform_later(site)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Solo permitir cambiar estos atributos de cada articulo
|
# Solo permitir cambiar estos atributos de cada articulo
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
SiteService = Struct.new(:site, :usuarie, :params, keyword_init: true) do
|
SiteService = Struct.new(:site, :usuarie, :params, keyword_init: true) do
|
||||||
def deploy
|
def deploy
|
||||||
site.enqueue!
|
site.enqueue!
|
||||||
DeployJob.perform_async site.id
|
DeployJob.perform_later site.id
|
||||||
end
|
end
|
||||||
|
|
||||||
# Crea un sitio, agrega un rol nuevo y guarda los cambios a la
|
# Crea un sitio, agrega un rol nuevo y guarda los cambios a la
|
||||||
|
@ -15,7 +15,8 @@ SiteService = Struct.new(:site, :usuarie, :params, keyword_init: true) do
|
||||||
|
|
||||||
add_role temporal: false, rol: 'usuarie'
|
add_role temporal: false, rol: 'usuarie'
|
||||||
site.deploys.build type: 'DeployLocal'
|
site.deploys.build type: 'DeployLocal'
|
||||||
sync_nodes
|
# Los sitios de testing no se sincronizan
|
||||||
|
sync_nodes unless site.name.end_with? '.testing'
|
||||||
|
|
||||||
I18n.with_locale(usuarie.lang.to_sym || I18n.default_locale) do
|
I18n.with_locale(usuarie.lang.to_sym || I18n.default_locale) do
|
||||||
# No se puede llamar a site.config antes de save porque el sitio
|
# No se puede llamar a site.config antes de save porque el sitio
|
||||||
|
@ -32,6 +33,7 @@ SiteService = Struct.new(:site, :usuarie, :params, keyword_init: true) do
|
||||||
add_licencias &&
|
add_licencias &&
|
||||||
add_code_of_conduct &&
|
add_code_of_conduct &&
|
||||||
add_privacy_policy &&
|
add_privacy_policy &&
|
||||||
|
site.index_posts! &&
|
||||||
deploy
|
deploy
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -53,9 +55,8 @@ SiteService = Struct.new(:site, :usuarie, :params, keyword_init: true) do
|
||||||
|
|
||||||
# Genera los Deploy necesarios para el sitio a menos que ya los tenga.
|
# Genera los Deploy necesarios para el sitio a menos que ya los tenga.
|
||||||
def build_deploys
|
def build_deploys
|
||||||
Site::DEPLOYS.map { |deploy| "Deploy#{deploy.to_s.camelcase}" }
|
Deploy.subclasses.each do |deploy|
|
||||||
.each do |deploy|
|
next if site.deploys.find_by type: deploy.name
|
||||||
next if site.deploys.find_by type: deploy
|
|
||||||
|
|
||||||
site.deploys.build type: deploy
|
site.deploys.build type: deploy
|
||||||
end
|
end
|
||||||
|
@ -64,14 +65,11 @@ SiteService = Struct.new(:site, :usuarie, :params, keyword_init: true) do
|
||||||
# Agregar una dirección oculta de Tor al DeployHiddenService y a la
|
# Agregar una dirección oculta de Tor al DeployHiddenService y a la
|
||||||
# configuración del Site.
|
# configuración del Site.
|
||||||
def add_onion
|
def add_onion
|
||||||
onion = params[:onion].strip
|
onion = params[:onion]
|
||||||
deploy = DeployHiddenService.find_by(site: site)
|
deploy = params[:deploy]
|
||||||
|
|
||||||
return false unless !onion.blank? && deploy
|
return false unless !onion.blank? && deploy
|
||||||
|
|
||||||
deploy.values[:onion] = onion
|
|
||||||
deploy.save
|
|
||||||
|
|
||||||
site.config['onion-location'] = onion
|
site.config['onion-location'] = onion
|
||||||
site.config.write
|
site.config.write
|
||||||
|
|
||||||
|
@ -96,9 +94,11 @@ SiteService = Struct.new(:site, :usuarie, :params, keyword_init: true) do
|
||||||
def commit_config(action:)
|
def commit_config(action:)
|
||||||
site.repository
|
site.repository
|
||||||
.commit(usuarie: usuarie,
|
.commit(usuarie: usuarie,
|
||||||
file: site.config.path,
|
add: [site.config.path],
|
||||||
message: I18n.t("site_service.#{action}",
|
message: I18n.t("site_service.#{action}",
|
||||||
name: site.name))
|
name: site.name))
|
||||||
|
|
||||||
|
GitPushJob.perform_later(site)
|
||||||
end
|
end
|
||||||
|
|
||||||
def add_role(temporal: true, rol: 'invitade')
|
def add_role(temporal: true, rol: 'invitade')
|
||||||
|
@ -218,7 +218,7 @@ SiteService = Struct.new(:site, :usuarie, :params, keyword_init: true) do
|
||||||
# Crea los deploys necesarios para sincronizar a otros nodos de Sutty
|
# Crea los deploys necesarios para sincronizar a otros nodos de Sutty
|
||||||
def sync_nodes
|
def sync_nodes
|
||||||
Rails.application.nodes.each do |node|
|
Rails.application.nodes.each do |node|
|
||||||
site.deploys.build(type: 'DeployFullRsync', destination: "sutty@#{node}:")
|
site.deploys.build(type: 'DeployFullRsync', destination: "rsync://rsyncd.#{node}/deploys/", hostname: node)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
6
app/views/bootstrap/_custom_checkbox.haml
Normal file
6
app/views/bootstrap/_custom_checkbox.haml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
- help_id = "#{id}_help"
|
||||||
|
|
||||||
|
.custom-control.custom-checkbox
|
||||||
|
%input.custom-control-input{ id: id, type: 'checkbox', name: name, value: value, required: required }
|
||||||
|
%label.custom-control-label{ for: id, aria: { describedby: help_id } }= content
|
||||||
|
%small.form-text.text-muted{ id: help_id }= yield
|
20
app/views/build_stats/index.haml
Normal file
20
app/views/build_stats/index.haml
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
%main.row
|
||||||
|
%aside.menu.col-md-3
|
||||||
|
= render 'sites/header', site: @site
|
||||||
|
.col
|
||||||
|
%h1= t('.title')
|
||||||
|
|
||||||
|
%table.table
|
||||||
|
%thead
|
||||||
|
%tr
|
||||||
|
- @headers.each do |header|
|
||||||
|
%th{ scope: 'col' }= header
|
||||||
|
%tbody
|
||||||
|
- @table.each do |row|
|
||||||
|
- row[:urls].each do |url|
|
||||||
|
%tr
|
||||||
|
%th{ scope: 'row' }= row[:title]
|
||||||
|
%td= link_to_if (url.present? && url.scheme.present?), url.to_s, url.to_s, class: 'word-break-all'
|
||||||
|
%td
|
||||||
|
%time{ datetime: row[:seconds][:machine] }= row[:seconds][:human]
|
||||||
|
%td= row[:size]
|
|
@ -13,7 +13,7 @@
|
||||||
%tr
|
%tr
|
||||||
%td= row[:title]
|
%td= row[:title]
|
||||||
%td= row[:status]
|
%td= row[:status]
|
||||||
%td= link_to_if url.present?, url, url
|
%td= link_to_if (url.present? && url.scheme.present?), url.to_s, url.to_s
|
||||||
%td
|
%td
|
||||||
%time{ datetime: row[:seconds][:machine] }= row[:seconds][:human]
|
%time{ datetime: row[:seconds][:machine] }= row[:seconds][:human]
|
||||||
%td= row[:size]
|
%td= row[:size]
|
||||||
|
|
21
app/views/deploys/_deploy_social_distributed_press.haml
Normal file
21
app/views/deploys/_deploy_social_distributed_press.haml
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
-# Publicar a la web distribuida
|
||||||
|
|
||||||
|
.row
|
||||||
|
.col
|
||||||
|
= deploy.hidden_field :id
|
||||||
|
= deploy.hidden_field :type
|
||||||
|
.custom-control.custom-switch
|
||||||
|
-#
|
||||||
|
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?, class: 'custom-control-input' },
|
||||||
|
'0', '1'
|
||||||
|
= deploy.label :_destroy, class: 'custom-control-label' do
|
||||||
|
%h3= t('.title')
|
||||||
|
= sanitize_markdown t('.help'),
|
||||||
|
tags: %w[p strong em a]
|
||||||
|
|
||||||
|
|
||||||
|
%hr/
|
|
@ -1,4 +1,4 @@
|
||||||
- site = @resource.sites.last
|
- site = @resource.roles.where(temporal: true).last&.site
|
||||||
|
|
||||||
%p= t('devise.mailer.invitation_instructions.hello',
|
%p= t('devise.mailer.invitation_instructions.hello',
|
||||||
email: @resource.email)
|
email: @resource.email)
|
||||||
|
@ -8,12 +8,17 @@
|
||||||
%h1= site.title
|
%h1= site.title
|
||||||
%p= site.description
|
%p= site.description
|
||||||
|
|
||||||
%p= link_to t('devise.mailer.invitation_instructions.accept'),
|
- if @resource.needs_invitation_link?
|
||||||
accept_invitation_url(@resource, invitation_token: @token, change_locale_to: @resource.lang)
|
%p= link_to t('devise.mailer.invitation_instructions.accept'),
|
||||||
|
accept_invitation_url(@resource, invitation_token: @token, change_locale_to: @resource.lang)
|
||||||
|
|
||||||
- if @resource.invitation_due_at
|
- if @resource.invitation_due_at
|
||||||
%p= t('devise.mailer.invitation_instructions.accept_until',
|
%p= t('devise.mailer.invitation_instructions.accept_until',
|
||||||
due_date: l(@resource.invitation_due_at,
|
due_date: l(@resource.invitation_due_at,
|
||||||
format: :'devise.mailer.invitation_instructions.accept_until_format'))
|
format: :'devise.mailer.invitation_instructions.accept_until_format'))
|
||||||
|
|
||||||
%p= t('devise.mailer.invitation_instructions.ignore')
|
%p= t('devise.mailer.invitation_instructions.ignore')
|
||||||
|
- elsif !@resource.confirmed? && @resource.confirmation_token
|
||||||
|
= confirmation_url(@resource, confirmation_token: @resource.confirmation_token, change_locale_to: @resource.lang)
|
||||||
|
- else
|
||||||
|
%p= link_to t('devise.mailer.invitation_instructions.sign_in'), root_url
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
- site = @resource.sites.last
|
- site = @resource.roles.where(temporal: true).last&.site
|
||||||
|
|
||||||
= t('devise.mailer.invitation_instructions.hello', email: @resource.email)
|
= t('devise.mailer.invitation_instructions.hello', email: @resource.email)
|
||||||
\
|
\
|
||||||
|
@ -9,11 +9,17 @@
|
||||||
\
|
\
|
||||||
= site.description
|
= site.description
|
||||||
\
|
\
|
||||||
= accept_invitation_url(@resource, invitation_token: @token, change_locale_to: @resource.lang)
|
- if @resource.needs_invitation_link?
|
||||||
\
|
= accept_invitation_url(@resource, invitation_token: @token, change_locale_to: @resource.lang)
|
||||||
- if @resource.invitation_due_at
|
\
|
||||||
= t('devise.mailer.invitation_instructions.accept_until',
|
- if @resource.invitation_due_at
|
||||||
due_date: l(@resource.invitation_due_at,
|
= t('devise.mailer.invitation_instructions.accept_until',
|
||||||
format: :'devise.mailer.invitation_instructions.accept_until_format'))
|
due_date: l(@resource.invitation_due_at,
|
||||||
\
|
format: :'devise.mailer.invitation_instructions.accept_until_format'))
|
||||||
= t('devise.mailer.invitation_instructions.ignore')
|
\
|
||||||
|
= t('devise.mailer.invitation_instructions.ignore')
|
||||||
|
- elsif !@resource.confirmed? && @resource.confirmation_token
|
||||||
|
= confirmation_url(@resource, confirmation_token: @resource.confirmation_token, change_locale_to: @resource.lang)
|
||||||
|
- else
|
||||||
|
= root_url(change_locale_to: @resource.lang)
|
||||||
|
= t('devise.mailer.invitation_instructions.sign_in')
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
= render 'devise/shared/error_messages', resource: resource
|
= render 'devise/shared/error_messages', resource: resource
|
||||||
|
|
||||||
.row.align-items-center.justify-content-center.full-height
|
.row.align-items-center.justify-content-center.full-height
|
||||||
.col-md-5.align-self-center
|
.col-md-6.align-self-center
|
||||||
%h2= t('.sign_up')
|
%h2= t('.sign_up')
|
||||||
%p= t('.help')
|
%p= t('.help')
|
||||||
|
|
||||||
|
@ -39,6 +39,21 @@
|
||||||
min: @minimum_password_length,
|
min: @minimum_password_length,
|
||||||
aria: { describedby: 'minimum-password-length' },
|
aria: { describedby: 'minimum-password-length' },
|
||||||
placeholder: t("#{password}_confirmation")
|
placeholder: t("#{password}_confirmation")
|
||||||
|
|
||||||
|
.form-group
|
||||||
|
- Usuarie::CONSENT_FIELDS.each do |field|
|
||||||
|
- required = t(".#{field}.required", default: '').present?
|
||||||
|
- id = "usuarie_#{field}"
|
||||||
|
- name = "usuarie[#{field}]"
|
||||||
|
- content = t(".#{field}.label")
|
||||||
|
- href = t(".#{field}.href", default: '')
|
||||||
|
- help_content = t(".#{field}.help")
|
||||||
|
= render 'bootstrap/custom_checkbox', id: id, name: name, content: content, required: required, value: "1" do
|
||||||
|
- if href.present?
|
||||||
|
= link_to help_content, href, target: '_blank', rel: 'noopener'
|
||||||
|
- else
|
||||||
|
= help_content
|
||||||
|
|
||||||
.actions
|
.actions
|
||||||
= f.submit t('.sign_up'),
|
= f.submit t('.sign_up'),
|
||||||
class: 'btn btn-lg btn-block'
|
class: 'btn btn-lg btn-block'
|
||||||
|
|
15
app/views/env/index.js.haml
vendored
15
app/views/env/index.js.haml
vendored
|
@ -1,7 +1,8 @@
|
||||||
= cache @site do
|
- if @site
|
||||||
:plain
|
= cache @site do
|
||||||
window.env = {
|
:plain
|
||||||
AIRBRAKE_SITE_ID: #{@site.id},
|
window.env = {
|
||||||
AIRBRAKE_API_KEY: "#{@site.airbrake_api_key}",
|
AIRBRAKE_SITE_ID: #{@site.id},
|
||||||
PANEL_URL: "#{ENV['PANEL_URL']}"
|
AIRBRAKE_API_KEY: "#{@site.airbrake_api_key}",
|
||||||
}
|
PANEL_URL: "#{ENV['PANEL_URL']}"
|
||||||
|
}
|
||||||
|
|
|
@ -19,10 +19,15 @@
|
||||||
= link_to t('.tienda'), @site.tienda_url,
|
= link_to t('.tienda'), @site.tienda_url,
|
||||||
role: 'button', class: 'btn'
|
role: 'button', class: 'btn'
|
||||||
|
|
||||||
|
%li.nav-item
|
||||||
|
= link_to t('.contact_us'), t('.contact_us_href'),
|
||||||
|
class: 'btn', rel: 'me', target: '_blank'
|
||||||
|
|
||||||
%li.nav-item
|
%li.nav-item
|
||||||
= link_to t('.logout'), main_app.destroy_usuarie_session_path,
|
= link_to t('.logout'), main_app.destroy_usuarie_session_path,
|
||||||
method: :delete, role: 'button', class: 'btn'
|
method: :delete, role: 'button', class: 'btn'
|
||||||
- else
|
- else
|
||||||
|
- params.permit!
|
||||||
- I18n.available_locales.each do |locale|
|
- I18n.available_locales.each do |locale|
|
||||||
- next if locale == I18n.locale
|
- next if locale == I18n.locale
|
||||||
= link_to t(locale), "?change_locale_to=#{locale}"
|
= link_to t("switch_locale.#{locale}"), params.to_h.merge(change_locale_to: locale)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
- flash.each do |type, message|
|
- flash.each do |type, message|
|
||||||
- unless type == 'js'
|
- unless type == 'js'
|
||||||
= render 'bootstrap/alert' do
|
= render 'bootstrap/alert' do
|
||||||
= message
|
= sanitize_markdown message, tags: %w[a strong em]
|
||||||
|
|
7
app/views/layouts/_link_rel_alternate.haml
Normal file
7
app/views/layouts/_link_rel_alternate.haml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
- unless current_usuarie
|
||||||
|
- params.permit!
|
||||||
|
- I18n.available_locales.each do |locale|
|
||||||
|
- url = url_for(**params.to_h.merge(change_locale_to: locale), only_path: false)
|
||||||
|
- if locale == I18n.default_locale
|
||||||
|
%link{ rel: 'alternate', hreflang: 'x-default', href: url }
|
||||||
|
%link{ rel: 'alternate', hreflang: locale, href: url }
|
|
@ -4,7 +4,7 @@
|
||||||
%meta{ charset: 'UTF-8' }/
|
%meta{ charset: 'UTF-8' }/
|
||||||
%meta{ content: 'text/html; charset=UTF-8',
|
%meta{ content: 'text/html; charset=UTF-8',
|
||||||
'http-equiv': 'Content-Type' }/
|
'http-equiv': 'Content-Type' }/
|
||||||
%meta{ name: 'color-scheme', content: 'light dark' }/
|
%meta{ name: 'color-scheme', content: 'light' }/
|
||||||
%meta{ name: 'viewport',
|
%meta{ name: 'viewport',
|
||||||
content: 'width=device-width, initial-scale=1.0' }/
|
content: 'width=device-width, initial-scale=1.0' }/
|
||||||
%meta{ name: 'referrer', content: 'same-origin' }/
|
%meta{ name: 'referrer', content: 'same-origin' }/
|
||||||
|
@ -17,6 +17,7 @@
|
||||||
= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload'
|
= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload'
|
||||||
= stylesheet_pack_tag 'application', 'data-turbolinks-track': 'reload'
|
= stylesheet_pack_tag 'application', 'data-turbolinks-track': 'reload'
|
||||||
= favicon_link_tag 'sutty_cuadrada.png', rel: 'apple-touch-icon', type: 'image/png'
|
= favicon_link_tag 'sutty_cuadrada.png', rel: 'apple-touch-icon', type: 'image/png'
|
||||||
|
= render 'layouts/link_rel_alternate'
|
||||||
|
|
||||||
%body{ class: yield(:body) }
|
%body{ class: yield(:body) }
|
||||||
.container-fluid#sutty
|
.container-fluid#sutty
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
|
- invalid_help = site.config.fetch('invalid_help', t('.invalid_help'))
|
||||||
|
- sending_help = site.config.fetch('sending_help', t('.sending_help'))
|
||||||
.form-group
|
.form-group
|
||||||
= submit_tag t('.save'), class: 'btn submit-post'
|
= submit_tag t('.save'), class: 'btn submit-post'
|
||||||
= render 'bootstrap/alert', class: 'invalid-help d-none' do
|
= render 'bootstrap/alert', class: 'invalid-help d-none' do
|
||||||
= site.config.fetch('invalid_help', t('.invalid_help'))
|
= invalid_help
|
||||||
= render 'bootstrap/alert', class: 'sending-help d-none' do
|
= render 'bootstrap/alert', class: 'sending-help d-none' do
|
||||||
= site.config.fetch('sending_help', t('.sending_help'))
|
= sending_help
|
||||||
|
|
4
app/views/posts/attribute_ro/_created_at.haml
Normal file
4
app/views/posts/attribute_ro/_created_at.haml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
%tr{ id: attribute }
|
||||||
|
%th= post_label_t(attribute, post: post)
|
||||||
|
%td{ dir: dir, lang: locale }
|
||||||
|
%time{ datetime: metadata.value.xmlschema }= l metadata.value
|
1
app/views/posts/attributes/_created_at.haml
Normal file
1
app/views/posts/attributes/_created_at.haml
Normal file
|
@ -0,0 +1 @@
|
||||||
|
-# nada
|
|
@ -22,7 +22,7 @@
|
||||||
= file_field(*field_name_for(base, attribute, :path),
|
= file_field(*field_name_for(base, attribute, :path),
|
||||||
**field_options(attribute, metadata, required: (metadata.required && !metadata.path?)),
|
**field_options(attribute, metadata, required: (metadata.required && !metadata.path?)),
|
||||||
class: "custom-file-input #{invalid(post, attribute)}",
|
class: "custom-file-input #{invalid(post, attribute)}",
|
||||||
accept: 'image/*', data: { preview: "#{attribute}-preview" })
|
accept: ActiveStorage.web_image_content_types.join(','), data: { preview: "#{attribute}-preview" })
|
||||||
= label_tag "#{base}_#{attribute}_path",
|
= label_tag "#{base}_#{attribute}_path",
|
||||||
post_label_t(attribute, :path, post: post), class: 'custom-file-label'
|
post_label_t(attribute, :path, post: post), class: 'custom-file-label'
|
||||||
= render 'posts/attribute_feedback',
|
= render 'posts/attribute_feedback',
|
||||||
|
|
|
@ -1,21 +1,18 @@
|
||||||
%main.row
|
%main.row
|
||||||
%aside.menu.col-md-3
|
%aside.menu.col-md-3
|
||||||
%h1= @site.title
|
= render 'sites/header', site: @site
|
||||||
%p.lead= @site.description
|
|
||||||
- cache_if @usuarie, [@site, I18n.locale] do
|
= render 'sites/status', site: @site
|
||||||
= render 'sites/status', site: @site
|
|
||||||
|
= render 'sites/build', site: @site, class: 'btn-block'
|
||||||
|
|
||||||
%h3= t('posts.new')
|
%h3= t('posts.new')
|
||||||
%table.mb-3
|
%table.table.table-sm.mb-3
|
||||||
- @site.layouts.sort_by(&:humanized_name).each do |layout|
|
%tbody
|
||||||
- next if layout.hidden?
|
- @site.schema_organization.each do |schema, _|
|
||||||
%tr
|
- schema = @site.layouts[schema]
|
||||||
%th= layout.humanized_name
|
- next if schema.hidden?
|
||||||
%td.pl-3= link_to t('posts.add'), new_site_post_path(@site, layout: layout.value), class: 'btn btn-secondary btn-sm'
|
= render 'schemas/row', site: @site, schema: schema, filter: @filter_params
|
||||||
- if @filter_params[:layout] == layout.name.to_s
|
|
||||||
%td= link_to t('posts.remove_filter'), site_posts_path(@site, **@filter_params.merge(layout: nil)), class: 'btn btn-primary btn-sm'
|
|
||||||
- else
|
|
||||||
%td= link_to t('posts.filter'), site_posts_path(@site, **@filter_params.merge(layout: layout.value)), class: 'btn btn-secondary btn-sm'
|
|
||||||
|
|
||||||
- if policy(@site_stat).index?
|
- if policy(@site_stat).index?
|
||||||
= link_to t('stats.index.title'), site_stats_path(@site), class: 'btn'
|
= link_to t('stats.index.title'), site_stats_path(@site), class: 'btn'
|
||||||
|
@ -33,8 +30,6 @@
|
||||||
type: 'info',
|
type: 'info',
|
||||||
link: site_usuaries_path(@site)
|
link: site_usuaries_path(@site)
|
||||||
|
|
||||||
= render 'sites/build', site: @site
|
|
||||||
|
|
||||||
- if @site.design.credits
|
- if @site.design.credits
|
||||||
= render 'bootstrap/alert' do
|
= render 'bootstrap/alert' do
|
||||||
= sanitize_markdown @site.design.credits
|
= sanitize_markdown @site.design.credits
|
||||||
|
@ -49,7 +44,8 @@
|
||||||
- next if param == 'q'
|
- next if param == 'q'
|
||||||
%input{ type: 'hidden', name: param, value: value }
|
%input{ type: 'hidden', name: param, value: value }
|
||||||
.form-group.flex-grow-0.m-0
|
.form-group.flex-grow-0.m-0
|
||||||
%input.form-control.border.border-magenta{ type: 'search', placeholder: 'Buscar', name: 'q', value: @filter_params[:q] }
|
%label.sr-only{for: 'q'}= t('.search')
|
||||||
|
%input#q.form-control.border.border-magenta{ type: 'search', placeholder: t('.search'), name: 'q', value: @filter_params[:q] }
|
||||||
%input.sr-only{ type: 'submit' }
|
%input.sr-only{ type: 'submit' }
|
||||||
|
|
||||||
- if @site.locales.size > 1
|
- if @site.locales.size > 1
|
||||||
|
@ -88,7 +84,10 @@
|
||||||
%button.btn{ data: { action: 'reorder#top' } }= t('posts.reorder.top')
|
%button.btn{ data: { action: 'reorder#top' } }= t('posts.reorder.top')
|
||||||
%button.btn{ data: { action: 'reorder#bottom' } }= t('posts.reorder.bottom')
|
%button.btn{ data: { action: 'reorder#bottom' } }= t('posts.reorder.bottom')
|
||||||
|
|
||||||
%div
|
- if @site.pagination
|
||||||
|
%div
|
||||||
|
= link_to_prev_page @posts, t('posts.prev'), class: 'btn'
|
||||||
|
= link_to_next_page @posts, t('posts.next'), class: 'btn'
|
||||||
%tbody
|
%tbody
|
||||||
- dir = @site.data.dig(params[:locale], 'dir')
|
- dir = @site.data.dig(params[:locale], 'dir')
|
||||||
- size = @posts.size
|
- size = @posts.size
|
||||||
|
|
1
app/views/schemas/_add.haml
Normal file
1
app/views/schemas/_add.haml
Normal file
|
@ -0,0 +1 @@
|
||||||
|
= link_to t('.add'), new_site_post_path(site, layout: schema.value), class: 'btn btn-secondary btn-sm m-0'
|
4
app/views/schemas/_filter.haml
Normal file
4
app/views/schemas/_filter.haml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
- if filter[:layout] == schema.name.to_s
|
||||||
|
= link_to t('.remove'), site_posts_path(site, **filter.merge(layout: nil)), class: 'btn btn-primary btn-sm m-0'
|
||||||
|
- else
|
||||||
|
= link_to t('.filter'), site_posts_path(site, **filter.merge(layout: schema.value)), class: 'btn btn-secondary btn-sm m-0'
|
13
app/views/schemas/_row.haml
Normal file
13
app/views/schemas/_row.haml
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
%tr
|
||||||
|
%th.w-100{ scope: 'row' }
|
||||||
|
- if local_assigns[:parent_schema]
|
||||||
|
%span.text-muted —
|
||||||
|
= schema.humanized_name
|
||||||
|
%td.px-0.text-nowrap
|
||||||
|
= render 'schemas/add', **local_assigns
|
||||||
|
= render 'schemas/filter', **local_assigns
|
||||||
|
|
||||||
|
-# XXX: Solo un nivel de recursividad
|
||||||
|
- unless local_assigns[:parent_schema]
|
||||||
|
- schema.schemas.each do |s|
|
||||||
|
= render 'schemas/row', schema: s, site: site, filter: filter, parent_schema: schema
|
|
@ -3,7 +3,7 @@
|
||||||
method: :post,
|
method: :post,
|
||||||
class: 'form-inline inline' do
|
class: 'form-inline inline' do
|
||||||
= submit_tag site.enqueued? ? t('sites.enqueued') : t('sites.enqueue'),
|
= submit_tag site.enqueued? ? t('sites.enqueued') : t('sites.enqueue'),
|
||||||
class: 'btn no-border-radius',
|
class: "btn no-border-radius #{local_assigns[:class]}",
|
||||||
title: site.enqueued? ? t('help.sites.enqueued') : t('help.sites.enqueue'),
|
title: site.enqueued? ? t('help.sites.enqueued') : t('help.sites.enqueue'),
|
||||||
data: { disable_with: t('sites.enqueued') },
|
data: { disable_with: t('sites.enqueued') },
|
||||||
disabled: site.enqueued?
|
disabled: site.enqueued?
|
||||||
|
|
|
@ -46,36 +46,37 @@
|
||||||
.invalid-feedback= site.errors.messages[:description].join(', ')
|
.invalid-feedback= site.errors.messages[:description].join(', ')
|
||||||
%hr/
|
%hr/
|
||||||
|
|
||||||
.form-group#design_id
|
- unless site.persisted?
|
||||||
%h2= t('.design.title')
|
.form-group#design_id
|
||||||
%p.lead= t('.help.design')
|
%h2= t('.design.title')
|
||||||
- if invalid? site, :design_id
|
%p.lead= t('.help.design')
|
||||||
= render 'bootstrap/alert' do
|
- if invalid? site, :design_id
|
||||||
= t('activerecord.errors.models.site.attributes.design_id.layout_incompatible.help',
|
= render 'bootstrap/alert' do
|
||||||
layouts: site.incompatible_layouts.to_sentence)
|
= t('activerecord.errors.models.site.attributes.design_id.layout_incompatible.help',
|
||||||
.row.row-cols-1.row-cols-md-2.designs
|
layouts: site.incompatible_layouts.to_sentence)
|
||||||
-# Demasiado complejo para un f.collection_radio_buttons
|
.row.row-cols-1.row-cols-md-2.designs
|
||||||
- Design.all.find_each do |design|
|
-# Demasiado complejo para un f.collection_radio_buttons
|
||||||
.design.col.d-flex.flex-column
|
- Design.all.order(priority: :desc).each do |design|
|
||||||
.custom-control.custom-radio
|
.design.col.d-flex.flex-column
|
||||||
= f.radio_button :design_id, design.id,
|
.custom-control.custom-radio
|
||||||
checked: design.id == site.design_id,
|
= f.radio_button :design_id, design.id,
|
||||||
disabled: design.disabled,
|
checked: design.id == site.design_id,
|
||||||
required: true, class: 'custom-control-input'
|
disabled: design.disabled,
|
||||||
= f.label "design_id_#{design.id}", design.name,
|
required: true, class: 'custom-control-input'
|
||||||
class: 'custom-control-label'
|
= f.label "design_id_#{design.id}", design.name,
|
||||||
.flex-fill
|
class: 'custom-control-label'
|
||||||
= sanitize_markdown design.description,
|
.flex-fill
|
||||||
tags: %w[p a strong em]
|
= sanitize_markdown design.description,
|
||||||
|
tags: %w[p a strong em]
|
||||||
|
|
||||||
.btn-group{ role: 'group', 'aria-label': t('.design.actions') }
|
.btn-group{ role: 'group', 'aria-label': t('.design.actions') }
|
||||||
- if design.url
|
- if design.url
|
||||||
= link_to t('.design.url'), design.url,
|
= link_to t('.design.url'), design.url,
|
||||||
target: '_blank', class: 'btn'
|
target: '_blank', class: 'btn'
|
||||||
- if design.license
|
- if design.license
|
||||||
= link_to t('.design.license'), design.license,
|
= link_to t('.design.license'), design.license,
|
||||||
target: '_blank', class: 'btn'
|
target: '_blank', class: 'btn'
|
||||||
%hr/
|
%hr/
|
||||||
|
|
||||||
.form-group.licenses#license_id
|
.form-group.licenses#license_id
|
||||||
%h2= t('.licencia.title')
|
%h2= t('.licencia.title')
|
||||||
|
|
3
app/views/sites/_header.haml
Normal file
3
app/views/sites/_header.haml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
.hyphens{ lang: site.default_locale }
|
||||||
|
%h1= site.title
|
||||||
|
%p.lead= site.description
|
|
@ -1,7 +1,9 @@
|
||||||
- link = nil
|
- link = nil
|
||||||
- if site.not_published_yet?
|
- if site.not_published_yet?
|
||||||
- message = t('.not_published_yet')
|
- message = t('.not_published_yet')
|
||||||
- if site.building?
|
- elsif site.awaiting_publication?
|
||||||
|
- message = t('.awaiting_publication')
|
||||||
|
- elsif site.building?
|
||||||
- if site.average_publication_time_calculable?
|
- if site.average_publication_time_calculable?
|
||||||
- average_building_time = site.average_publication_time
|
- average_building_time = site.average_publication_time
|
||||||
- elsif !site.similar_sites?
|
- elsif !site.similar_sites?
|
||||||
|
@ -16,4 +18,4 @@
|
||||||
- link = true
|
- link = true
|
||||||
|
|
||||||
= render 'bootstrap/alert' do
|
= render 'bootstrap/alert' do
|
||||||
= link_to_if link, message.html_safe, site.url, class: 'alert-link'
|
= link_to_if link, message.html_safe, site_build_stats_path(site), class: 'alert-link'
|
||||||
|
|
|
@ -54,10 +54,4 @@
|
||||||
text: t('usuaries.index.title'),
|
text: t('usuaries.index.title'),
|
||||||
type: 'info',
|
type: 'info',
|
||||||
link: site_usuaries_path(site)
|
link: site_usuaries_path(site)
|
||||||
- if policy(site).pull? && site.repository.needs_pull?
|
|
||||||
= render 'layouts/btn_with_tooltip',
|
|
||||||
tooltip: t('help.sites.pull'),
|
|
||||||
text: t('.pull'),
|
|
||||||
type: 'info',
|
|
||||||
link: site_pull_path(site)
|
|
||||||
= render 'sites/build', site: site
|
= render 'sites/build', site: site
|
||||||
|
|
|
@ -27,6 +27,8 @@
|
||||||
%p.lead= t('.urls.description')
|
%p.lead= t('.urls.description')
|
||||||
%form{ method: 'get', action: '#custom-urls' }
|
%form{ method: 'get', action: '#custom-urls' }
|
||||||
%input{ type: 'hidden', name: 'interval', value: @interval }
|
%input{ type: 'hidden', name: 'interval', value: @interval }
|
||||||
|
%input{ type: 'hidden', name: 'period_start', value: params[:period_start] }
|
||||||
|
%input{ type: 'hidden', name: 'period_end', value: params[:period_end] }
|
||||||
.form-group
|
.form-group
|
||||||
%label{ for: 'urls' }= t('.urls.label')
|
%label{ for: 'urls' }= t('.urls.label')
|
||||||
%textarea#urls.form-control{ name: 'urls', autocomplete: 'on', required: true, rows: @normalized_urls.size + 1, aria_describedby: 'help-urls' }= @normalized_urls.join("\n")
|
%textarea#urls.form-control{ name: 'urls', autocomplete: 'on', required: true, rows: @normalized_urls.size + 1, aria_describedby: 'help-urls' }= @normalized_urls.join("\n")
|
||||||
|
|
|
@ -37,6 +37,15 @@ module Sutty
|
||||||
.rescue_responses['Pundit::NotAuthorizedError'] = :forbidden
|
.rescue_responses['Pundit::NotAuthorizedError'] = :forbidden
|
||||||
|
|
||||||
config.active_storage.variant_processor = :vips
|
config.active_storage.variant_processor = :vips
|
||||||
|
config.active_storage.web_image_content_types << 'image/webp'
|
||||||
|
|
||||||
|
# Que
|
||||||
|
config.action_mailer.deliver_later_queue_name = :default
|
||||||
|
config.active_storage.queues.analysis = :default
|
||||||
|
config.active_storage.queues.purge = :default
|
||||||
|
config.active_job.queue_adapter = :que
|
||||||
|
|
||||||
|
config.active_record.schema_format = :sql
|
||||||
|
|
||||||
config.to_prepare do
|
config.to_prepare do
|
||||||
# Load application's model / class decorators
|
# Load application's model / class decorators
|
||||||
|
|
|
@ -64,11 +64,6 @@ Rails.application.configure do
|
||||||
# Use a different cache store in production.
|
# Use a different cache store in production.
|
||||||
config.cache_store = :redis_cache_store, { url: ENV['REDIS_SERVER'] }
|
config.cache_store = :redis_cache_store, { url: ENV['REDIS_SERVER'] }
|
||||||
|
|
||||||
# Use a real queuing backend for Active Job (and separate queues per
|
|
||||||
# environment)
|
|
||||||
config.active_job.queue_adapter = :sucker_punch
|
|
||||||
config.active_job.queue_name_prefix = "sutty_#{Rails.env}"
|
|
||||||
|
|
||||||
config.action_mailer.perform_caching = false
|
config.action_mailer.perform_caching = false
|
||||||
|
|
||||||
# Ignore bad email addresses and do not raise email delivery errors.
|
# Ignore bad email addresses and do not raise email delivery errors.
|
||||||
|
@ -147,7 +142,7 @@ Rails.application.configure do
|
||||||
}
|
}
|
||||||
config.action_mailer.default_options = { from: ENV.fetch('DEFAULT_FROM', "noreply@sutty.nl") }
|
config.action_mailer.default_options = { from: ENV.fetch('DEFAULT_FROM', "noreply@sutty.nl") }
|
||||||
|
|
||||||
config.middleware.use ExceptionNotification::Rack, gitlab: {}
|
config.middleware.use ExceptionNotification::Rack, gitlab: {}, ignore_exceptions: ['DeployJob::DeployAlreadyRunningException']
|
||||||
|
|
||||||
Rails.application.routes.default_url_options[:host] = "panel.#{ENV.fetch('SUTTY', 'sutty.nl')}"
|
Rails.application.routes.default_url_options[:host] = "panel.#{ENV.fetch('SUTTY', 'sutty.nl')}"
|
||||||
Rails.application.routes.default_url_options[:protocol] = 'https'
|
Rails.application.routes.default_url_options[:protocol] = 'https'
|
||||||
|
|
|
@ -91,6 +91,15 @@ module Jekyll
|
||||||
spec.name == name
|
spec.name == name
|
||||||
end
|
end
|
||||||
|
|
||||||
|
unless spec
|
||||||
|
I18n.with_locale(locale) do
|
||||||
|
raise Jekyll::Errors::InvalidThemeName, I18n.t('activerecord.errors.models.site.attributes.design_id.missing_gem', theme: name)
|
||||||
|
rescue Jekyll::Errors::InvalidThemeName => e
|
||||||
|
ExceptionNotifier.notify_exception(e, data: { theme: name, site: File.basename(site.source) })
|
||||||
|
raise
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
ruby_version = Gem::Version.new(RUBY_VERSION)
|
ruby_version = Gem::Version.new(RUBY_VERSION)
|
||||||
ruby_version.canonical_segments[2] = 0
|
ruby_version.canonical_segments[2] = 0
|
||||||
base_path = Rails.root.join('_storage', 'gems', File.basename(site.source), 'ruby',
|
base_path = Rails.root.join('_storage', 'gems', File.basename(site.source), 'ruby',
|
||||||
|
@ -114,6 +123,11 @@ module Jekyll
|
||||||
private
|
private
|
||||||
|
|
||||||
def gemspec; end
|
def gemspec; end
|
||||||
|
|
||||||
|
# @return [Symbol]
|
||||||
|
def locale
|
||||||
|
@locale ||= (site.config['locale'] || site.config['lang'] || I18n.locale).to_sym
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# No necesitamos los archivos de la plantilla
|
# No necesitamos los archivos de la plantilla
|
||||||
|
|
3
config/initializers/device_detector.rb
Normal file
3
config/initializers/device_detector.rb
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
DeviceDetector.configure do |config|
|
||||||
|
config.max_cache_keys = 5_000 # to check if not too much
|
||||||
|
end
|
8
config/initializers/que.rb
Normal file
8
config/initializers/que.rb
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
ActiveJob::Serializers.add_serializers ActiveJob::Serializers::ExceptionSerializer
|
||||||
|
|
||||||
|
# Notificar los errores
|
||||||
|
Que.error_notifier = proc do |error, job|
|
||||||
|
ExceptionNotifier.notify_exception(error, data: (job.dup || {}))
|
||||||
|
end
|
|
@ -1,6 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
# Enviar una notificación cuando falla una tarea
|
|
||||||
SuckerPunch.exception_handler = lambda { |ex, _klass, _args|
|
|
||||||
ExceptionNotifier.notify_exception(ex)
|
|
||||||
}
|
|
|
@ -104,6 +104,25 @@ en:
|
||||||
new:
|
new:
|
||||||
sign_up: Sign up
|
sign_up: Sign up
|
||||||
help: We only ask for an e-mail address and a password. The password is safely stored, no one else besides you knows it! You'll also receive an e-mail to confirm your account.
|
help: We only ask for an e-mail address and a password. The password is safely stored, no one else besides you knows it! You'll also receive an e-mail to confirm your account.
|
||||||
|
privacy_policy_accepted:
|
||||||
|
label: "I understand and accept the privacy policy"
|
||||||
|
help: "Read privacy policy"
|
||||||
|
href: "https://sutty.nl/en/privacy-policy/"
|
||||||
|
required: true
|
||||||
|
terms_of_service_accepted:
|
||||||
|
label: "My sites won't promote hate speech"
|
||||||
|
help: "Read terms of service"
|
||||||
|
href: "https://sutty.nl/en/terms-of-service/"
|
||||||
|
required: true
|
||||||
|
code_of_conduct_accepted:
|
||||||
|
label: "I want a more inclusive Internet"
|
||||||
|
help: "Read codes for sharing"
|
||||||
|
href: "https://sutty.nl/en/code-of-conduct/"
|
||||||
|
required: true
|
||||||
|
available_for_feedback_accepted:
|
||||||
|
label: "I'm available to provide feedback"
|
||||||
|
help: "We may contact you occasionaly"
|
||||||
|
required: false
|
||||||
signed_up: "A message with a confirmation link has been sent to your email address. Please follow the link to activate your account."
|
signed_up: "A message with a confirmation link has been sent to your email address. Please follow the link to activate your account."
|
||||||
signed_up_but_inactive: You have signed up successfully. However, we could not sign you in because your account is not yet activated.
|
signed_up_but_inactive: You have signed up successfully. However, we could not sign you in because your account is not yet activated.
|
||||||
signed_up_but_locked: You have signed up successfully. However, we could not sign you in because your account is locked.
|
signed_up_but_locked: You have signed up successfully. However, we could not sign you in because your account is locked.
|
||||||
|
|
|
@ -104,6 +104,24 @@ es:
|
||||||
new:
|
new:
|
||||||
sign_up: Registrarme
|
sign_up: Registrarme
|
||||||
help: Para registrarte solo pedimos una dirección de correo y una contraseña. La contraseña se almacena de forma segura, ¡nadie más que vos la sabe! Recibirás un correo de confirmación de cuenta.
|
help: Para registrarte solo pedimos una dirección de correo y una contraseña. La contraseña se almacena de forma segura, ¡nadie más que vos la sabe! Recibirás un correo de confirmación de cuenta.
|
||||||
|
privacy_policy_accepted:
|
||||||
|
label: "Comprendo y acepto la política de privacidad"
|
||||||
|
help: "Leer política de privacidad"
|
||||||
|
href: "https://sutty.nl/politica-de-privacidad/"
|
||||||
|
required: "true"
|
||||||
|
terms_of_service_accepted:
|
||||||
|
label: "Mis sitios no promueven el discurso de odio"
|
||||||
|
help: "Leer términos de servicio"
|
||||||
|
href: "https://sutty.nl/terminos-de-servicio/"
|
||||||
|
required: "true"
|
||||||
|
code_of_conduct_accepted:
|
||||||
|
label: "Quiero una Internet más inclusiva"
|
||||||
|
help: "Leer códigos para compartir"
|
||||||
|
href: "https://sutty.nl/codigo-de-convivencia/"
|
||||||
|
required: "true"
|
||||||
|
available_for_feedback_accepted:
|
||||||
|
label: "Estoy disponible para ofrecer retroalimentación"
|
||||||
|
help: "Te contactaremos ocasionalmente"
|
||||||
signed_up: "Hemos enviado un mensaje con un enlace de confirmación a tu correo electrónico. Por favor, abrí el enlace para terminar de activar tu cuenta."
|
signed_up: "Hemos enviado un mensaje con un enlace de confirmación a tu correo electrónico. Por favor, abrí el enlace para terminar de activar tu cuenta."
|
||||||
signed_up_but_inactive: Tu cuenta ha sido creada correctamente. Sin embargo, no hemos podido iniciar la sesión porque tu cuenta aún no está activada.
|
signed_up_but_inactive: Tu cuenta ha sido creada correctamente. Sin embargo, no hemos podido iniciar la sesión porque tu cuenta aún no está activada.
|
||||||
signed_up_but_locked: Tu cuenta ha sido creada correctamente. Sin embargo, no hemos podido iniciar la sesión porque que tu cuenta está bloqueada.
|
signed_up_but_locked: Tu cuenta ha sido creada correctamente. Sin embargo, no hemos podido iniciar la sesión porque que tu cuenta está bloqueada.
|
||||||
|
|
|
@ -23,6 +23,7 @@ en:
|
||||||
accept: "Accept invitation"
|
accept: "Accept invitation"
|
||||||
accept_until: "This invitation will be due in %{due_date}."
|
accept_until: "This invitation will be due in %{due_date}."
|
||||||
ignore: "If you don't want to accept the invitation, please ignore this email. Your account won't be created until you access the link above and set your password."
|
ignore: "If you don't want to accept the invitation, please ignore this email. Your account won't be created until you access the link above and set your password."
|
||||||
|
sign_in: "Sign in to your account to accept or decline the invitation."
|
||||||
time:
|
time:
|
||||||
formats:
|
formats:
|
||||||
devise:
|
devise:
|
||||||
|
|
|
@ -22,7 +22,8 @@ es:
|
||||||
someone_invited_you: "Alguien te ha invitado a colaborar en %{url}, podés aceptar la invitación con el enlace a continuación."
|
someone_invited_you: "Alguien te ha invitado a colaborar en %{url}, podés aceptar la invitación con el enlace a continuación."
|
||||||
accept: "Aceptar la invitación"
|
accept: "Aceptar la invitación"
|
||||||
accept_until: "La invitación vencerá el %{due_date}."
|
accept_until: "La invitación vencerá el %{due_date}."
|
||||||
ignore: "Si no querés aceptar la invitación, por favor ignora este correo. Tu cuenta no será creada hasta que aceptes la invitación y configures una contraseña."
|
ignore: "Si no querés aceptar la invitación, por favor ignorá este correo. Tu cuenta no será creada hasta que aceptes la invitación y configures una contraseña."
|
||||||
|
sign_in: "Iniciá sesión con tu cuenta para aceptar o rechazar la invitación."
|
||||||
time:
|
time:
|
||||||
formats:
|
formats:
|
||||||
devise:
|
devise:
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue