mirror of
https://0xacab.org/sutty/sutty
synced 2024-11-21 22:36:22 +00:00
Merge branch 'rails' into usar-sutty-editor
This commit is contained in:
commit
b5d21a4c3e
198 changed files with 4143 additions and 1071 deletions
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/credentials.yml.enc
|
||||
|
||||
/public/packs
|
||||
/public/packs-test
|
||||
/public/assets
|
||||
/public/assets-production
|
||||
/public/packs
|
||||
/public/packs-production
|
||||
/node_modules
|
||||
/yarn-error.log
|
||||
yarn-debug.log*
|
||||
|
@ -49,8 +44,6 @@ yarn-debug.log*
|
|||
*.key
|
||||
*.crt
|
||||
|
||||
/public/packs
|
||||
/public/packs-test
|
||||
/node_modules
|
||||
/yarn-error.log
|
||||
yarn-debug.log*
|
||||
|
|
|
@ -20,6 +20,7 @@ pipeline:
|
|||
branch:
|
||||
- "rails"
|
||||
- "panel.sutty.nl"
|
||||
- "17.3.alpine.panel.sutty.nl"
|
||||
event: "push"
|
||||
path:
|
||||
include:
|
||||
|
@ -27,7 +28,7 @@ pipeline:
|
|||
- ".dockerignore"
|
||||
- ".woodpecker.yml"
|
||||
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:
|
||||
- "apk add python2 dotenv openssh-client brotli"
|
||||
- "install -d -m 700 ~/.ssh/"
|
||||
|
@ -51,6 +52,9 @@ pipeline:
|
|||
- "git add public && git commit -m \"ci: assets [skip ci]\""
|
||||
- "git pull 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:
|
||||
- "SSH_KEY"
|
||||
- "KNOWN_HOSTS"
|
||||
|
@ -65,8 +69,15 @@ pipeline:
|
|||
- "app/javascript/**/*"
|
||||
- "package.json"
|
||||
- "yarn.lock"
|
||||
matrix:
|
||||
ALPINE_VERSION: "3.14.10"
|
||||
RUBY_VERSION: "2.7"
|
||||
RUBY_PATCH: "8"
|
||||
matrix:
|
||||
include:
|
||||
- ALPINE_VERSION: "3.17.3"
|
||||
RUBY_VERSION: "3.1"
|
||||
RUBY_PATCH: "4"
|
||||
- ALPINE_VERSION: "3.14.10"
|
||||
RUBY_VERSION: "2.7"
|
||||
RUBY_PATCH: "8"
|
||||
|
|
34
Gemfile
34
Gemfile
|
@ -1,14 +1,13 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
puts 'Usa haini.sh para generar un entorno de trabajo reproducible'
|
||||
source 'https://gems.sutty.nl'
|
||||
source ENV.fetch('GEMS_SOURCE', 'https://17.3.alpine.gems.sutty.nl')
|
||||
|
||||
ruby '~> 2.7'
|
||||
ruby "~> #{ENV.fetch('RUBY_VERSION', '3.1')}"
|
||||
|
||||
gem 'dotenv-rails', require: 'dotenv/rails-now'
|
||||
|
||||
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
|
||||
gem 'rails', '~> 6'
|
||||
gem 'rails', '~> 6.1.0'
|
||||
# Use Puma as the app server
|
||||
gem 'puma'
|
||||
|
||||
|
@ -33,14 +32,14 @@ gem 'turbolinks', '~> 5'
|
|||
gem 'jbuilder', '~> 2.5'
|
||||
# Use ActiveModel has_secure_password
|
||||
gem 'bcrypt', '~> 3.1.7'
|
||||
gem 'safely_block', '~> 0.3.0'
|
||||
gem 'blazer'
|
||||
gem 'chartkick'
|
||||
gem 'commonmarker'
|
||||
gem 'devise'
|
||||
gem 'devise-i18n'
|
||||
gem 'devise_invitable'
|
||||
gem 'distributed-press-api-client', '~> 0.2.3'
|
||||
gem 'njalla-api-client', '~> 0.2.0'
|
||||
gem 'distributed-press-api-client', '~> 0.3.0rc0'
|
||||
gem 'email_address', git: 'https://github.com/fauno/email_address', branch: 'i18n'
|
||||
gem 'exception_notification'
|
||||
gem 'fast_blank'
|
||||
|
@ -51,10 +50,9 @@ gem 'image_processing'
|
|||
gem 'icalendar'
|
||||
gem 'inline_svg'
|
||||
gem 'httparty'
|
||||
gem 'safe_yaml'
|
||||
gem 'jekyll', '~> 4.2'
|
||||
gem 'jekyll-data'
|
||||
gem 'jekyll-commonmark'
|
||||
gem 'safe_yaml', require: false
|
||||
gem 'jekyll', '~> 4.2.0'
|
||||
gem 'jekyll-commonmark', '~> 1.4.0'
|
||||
gem 'jekyll-images'
|
||||
gem 'jekyll-include-cache'
|
||||
gem 'sutty-liquid', '>= 0.7.3'
|
||||
|
@ -65,19 +63,21 @@ gem 'mobility'
|
|||
gem 'pundit'
|
||||
gem 'rails-i18n'
|
||||
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 'rollups', git: 'https://github.com/fauno/rollup.git', branch: 'update'
|
||||
gem 'rubyzip'
|
||||
gem 'rugged'
|
||||
gem 'rugged', '1.5.0.1'
|
||||
gem 'git_clone_url'
|
||||
gem 'concurrent-ruby-ext'
|
||||
gem 'sucker_punch'
|
||||
gem 'que'
|
||||
gem 'symbol-fstring', require: 'fstring/all'
|
||||
gem 'terminal-table'
|
||||
gem 'validates_hostname'
|
||||
gem 'webpacker'
|
||||
gem 'yaml_db', git: 'https://0xacab.org/sutty/yaml_db.git'
|
||||
gem 'kaminari'
|
||||
gem 'device_detector'
|
||||
|
||||
# database
|
||||
gem 'hairtrigger'
|
||||
|
@ -111,7 +111,7 @@ group :development, :test do
|
|||
gem 'pry'
|
||||
# Adds support for Capybara system testing and selenium driver
|
||||
gem 'capybara', '~> 2.13'
|
||||
gem 'selenium-webdriver'
|
||||
gem 'selenium-webdriver', '~> 4.8.0'
|
||||
gem 'sqlite3'
|
||||
end
|
||||
|
||||
|
@ -119,11 +119,11 @@ group :development do
|
|||
gem 'brakeman'
|
||||
gem 'haml-lint', require: false
|
||||
gem 'letter_opener'
|
||||
gem 'listen', '>= 3.0.5', '< 3.2'
|
||||
gem 'listen'
|
||||
gem 'rubocop-rails'
|
||||
gem 'spring'
|
||||
gem 'spring-watcher-listen', '~> 2.0.0'
|
||||
gem 'web-console', '>= 3.3.0'
|
||||
gem 'spring-watcher-listen'
|
||||
gem 'web-console'
|
||||
end
|
||||
|
||||
group :test do
|
||||
|
|
567
Gemfile.lock
567
Gemfile.lock
|
@ -25,86 +25,86 @@ GIT
|
|||
groupdate (>= 5.2)
|
||||
|
||||
GEM
|
||||
remote: https://gems.sutty.nl/
|
||||
remote: https://17.3.alpine.gems.sutty.nl/
|
||||
specs:
|
||||
actioncable (6.1.4.1)
|
||||
actionpack (= 6.1.4.1)
|
||||
activesupport (= 6.1.4.1)
|
||||
actioncable (6.1.7.3)
|
||||
actionpack (= 6.1.7.3)
|
||||
activesupport (= 6.1.7.3)
|
||||
nio4r (~> 2.0)
|
||||
websocket-driver (>= 0.6.1)
|
||||
actionmailbox (6.1.4.1)
|
||||
actionpack (= 6.1.4.1)
|
||||
activejob (= 6.1.4.1)
|
||||
activerecord (= 6.1.4.1)
|
||||
activestorage (= 6.1.4.1)
|
||||
activesupport (= 6.1.4.1)
|
||||
actionmailbox (6.1.7.3)
|
||||
actionpack (= 6.1.7.3)
|
||||
activejob (= 6.1.7.3)
|
||||
activerecord (= 6.1.7.3)
|
||||
activestorage (= 6.1.7.3)
|
||||
activesupport (= 6.1.7.3)
|
||||
mail (>= 2.7.1)
|
||||
actionmailer (6.1.4.1)
|
||||
actionpack (= 6.1.4.1)
|
||||
actionview (= 6.1.4.1)
|
||||
activejob (= 6.1.4.1)
|
||||
activesupport (= 6.1.4.1)
|
||||
actionmailer (6.1.7.3)
|
||||
actionpack (= 6.1.7.3)
|
||||
actionview (= 6.1.7.3)
|
||||
activejob (= 6.1.7.3)
|
||||
activesupport (= 6.1.7.3)
|
||||
mail (~> 2.5, >= 2.5.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
actionpack (6.1.4.1)
|
||||
actionview (= 6.1.4.1)
|
||||
activesupport (= 6.1.4.1)
|
||||
actionpack (6.1.7.3)
|
||||
actionview (= 6.1.7.3)
|
||||
activesupport (= 6.1.7.3)
|
||||
rack (~> 2.0, >= 2.0.9)
|
||||
rack-test (>= 0.6.3)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
||||
actiontext (6.1.4.1)
|
||||
actionpack (= 6.1.4.1)
|
||||
activerecord (= 6.1.4.1)
|
||||
activestorage (= 6.1.4.1)
|
||||
activesupport (= 6.1.4.1)
|
||||
actiontext (6.1.7.3)
|
||||
actionpack (= 6.1.7.3)
|
||||
activerecord (= 6.1.7.3)
|
||||
activestorage (= 6.1.7.3)
|
||||
activesupport (= 6.1.7.3)
|
||||
nokogiri (>= 1.8.5)
|
||||
actionview (6.1.4.1)
|
||||
activesupport (= 6.1.4.1)
|
||||
actionview (6.1.7.3)
|
||||
activesupport (= 6.1.7.3)
|
||||
builder (~> 3.1)
|
||||
erubi (~> 1.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.1, >= 1.2.0)
|
||||
activejob (6.1.4.1)
|
||||
activesupport (= 6.1.4.1)
|
||||
activejob (6.1.7.3)
|
||||
activesupport (= 6.1.7.3)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (6.1.4.1)
|
||||
activesupport (= 6.1.4.1)
|
||||
activerecord (6.1.4.1)
|
||||
activemodel (= 6.1.4.1)
|
||||
activesupport (= 6.1.4.1)
|
||||
activestorage (6.1.4.1)
|
||||
actionpack (= 6.1.4.1)
|
||||
activejob (= 6.1.4.1)
|
||||
activerecord (= 6.1.4.1)
|
||||
activesupport (= 6.1.4.1)
|
||||
marcel (~> 1.0.0)
|
||||
activemodel (6.1.7.3)
|
||||
activesupport (= 6.1.7.3)
|
||||
activerecord (6.1.7.3)
|
||||
activemodel (= 6.1.7.3)
|
||||
activesupport (= 6.1.7.3)
|
||||
activestorage (6.1.7.3)
|
||||
actionpack (= 6.1.7.3)
|
||||
activejob (= 6.1.7.3)
|
||||
activerecord (= 6.1.7.3)
|
||||
activesupport (= 6.1.7.3)
|
||||
marcel (~> 1.0)
|
||||
mini_mime (>= 1.1.0)
|
||||
activesupport (6.1.4.1)
|
||||
activesupport (6.1.7.3)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (>= 1.6, < 2)
|
||||
minitest (>= 5.1)
|
||||
tzinfo (~> 2.0)
|
||||
zeitwerk (~> 2.3)
|
||||
addressable (2.8.0)
|
||||
public_suffix (>= 2.0.2, < 5.0)
|
||||
addressable (2.8.4)
|
||||
public_suffix (>= 2.0.2, < 6.0)
|
||||
ast (2.4.2)
|
||||
autoprefixer-rails (10.3.3.0)
|
||||
autoprefixer-rails (10.4.13.0)
|
||||
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)
|
||||
benchmark-ips (2.9.2)
|
||||
benchmark-ips (2.12.0)
|
||||
bindex (0.8.1-x86_64-linux-musl)
|
||||
blazer (2.4.7)
|
||||
blazer (2.6.5)
|
||||
activerecord (>= 5)
|
||||
chartkick (>= 3.2)
|
||||
railties (>= 5)
|
||||
safely_block (>= 0.1.1)
|
||||
bootstrap (4.6.0)
|
||||
bootstrap (4.6.2)
|
||||
autoprefixer-rails (>= 9.1.0)
|
||||
popper_js (>= 1.14.3, < 2)
|
||||
popper_js (>= 1.16.1, < 2)
|
||||
sassc-rails (>= 2.0.0)
|
||||
brakeman (5.1.2)
|
||||
brakeman (5.4.1)
|
||||
builder (3.2.4)
|
||||
capybara (2.18.0)
|
||||
addressable
|
||||
|
@ -113,25 +113,24 @@ GEM
|
|||
rack (>= 1.0.0)
|
||||
rack-test (>= 0.5.4)
|
||||
xpath (>= 2.0, < 4.0)
|
||||
chartkick (4.1.2)
|
||||
childprocess (4.1.0)
|
||||
chartkick (5.0.2)
|
||||
climate_control (1.2.0)
|
||||
coderay (1.1.3)
|
||||
colorator (1.1.0)
|
||||
commonmarker (0.21.2-x86_64-linux-musl)
|
||||
ruby-enum (~> 0.5)
|
||||
concurrent-ruby (1.1.9)
|
||||
concurrent-ruby-ext (1.1.9-x86_64-linux-musl)
|
||||
concurrent-ruby (= 1.1.9)
|
||||
commonmarker (0.23.10-x86_64-linux-musl)
|
||||
concurrent-ruby (1.2.2)
|
||||
concurrent-ruby-ext (1.2.2-x86_64-linux-musl)
|
||||
concurrent-ruby (= 1.2.2)
|
||||
crass (1.0.6)
|
||||
database_cleaner (2.0.1)
|
||||
database_cleaner-active_record (~> 2.0.0)
|
||||
database_cleaner-active_record (2.0.1)
|
||||
database_cleaner (2.0.2)
|
||||
database_cleaner-active_record (>= 2, < 3)
|
||||
database_cleaner-active_record (2.1.0)
|
||||
activerecord (>= 5.a)
|
||||
database_cleaner-core (~> 2.0.0)
|
||||
database_cleaner-core (2.0.1)
|
||||
dead_end (3.1.0)
|
||||
derailed_benchmarks (2.1.1)
|
||||
date (3.3.3-x86_64-linux-musl)
|
||||
dead_end (4.0.0)
|
||||
derailed_benchmarks (2.1.2)
|
||||
benchmark-ips (~> 2)
|
||||
dead_end
|
||||
get_process_mem (~> 0)
|
||||
|
@ -143,29 +142,30 @@ GEM
|
|||
rake (> 10, < 14)
|
||||
ruby-statistics (>= 2.1)
|
||||
thor (>= 0.19, < 2)
|
||||
devise (4.8.0)
|
||||
device_detector (1.1.1)
|
||||
devise (4.9.2)
|
||||
bcrypt (~> 3.0)
|
||||
orm_adapter (~> 0.1)
|
||||
railties (>= 4.1.0)
|
||||
responders
|
||||
warden (~> 1.2.3)
|
||||
devise-i18n (1.10.1)
|
||||
devise (>= 4.8.0)
|
||||
devise_invitable (2.0.5)
|
||||
devise-i18n (1.11.0)
|
||||
devise (>= 4.9.0)
|
||||
devise_invitable (2.0.8)
|
||||
actionmailer (>= 5.0)
|
||||
devise (>= 4.6)
|
||||
distributed-press-api-client (0.2.2)
|
||||
distributed-press-api-client (0.3.0rc0)
|
||||
addressable (~> 2.3, >= 2.3.0)
|
||||
climate_control
|
||||
dry-schema
|
||||
httparty (~> 0.18)
|
||||
json (~> 2.1, >= 2.1.0)
|
||||
jwt (~> 2.6.0)
|
||||
dotenv (2.7.6)
|
||||
dotenv-rails (2.7.6)
|
||||
dotenv (= 2.7.6)
|
||||
dotenv (2.8.1)
|
||||
dotenv-rails (2.8.1)
|
||||
dotenv (= 2.8.1)
|
||||
railties (>= 3.2)
|
||||
down (5.2.4)
|
||||
down (5.4.1)
|
||||
addressable (~> 2.8)
|
||||
dry-configurable (1.0.1)
|
||||
dry-core (~> 1.0, < 2)
|
||||
|
@ -179,65 +179,68 @@ GEM
|
|||
concurrent-ruby (~> 1.0)
|
||||
dry-core (~> 1.0, < 2)
|
||||
zeitwerk (~> 2.6)
|
||||
dry-schema (1.13.0)
|
||||
dry-schema (1.13.1)
|
||||
concurrent-ruby (~> 1.0)
|
||||
dry-configurable (~> 1.0, >= 1.0.1)
|
||||
dry-core (~> 1.0, < 2)
|
||||
dry-initializer (~> 3.0)
|
||||
dry-logic (>= 1.5, < 2)
|
||||
dry-logic (>= 1.4, < 2)
|
||||
dry-types (>= 1.7, < 2)
|
||||
zeitwerk (~> 2.6)
|
||||
dry-types (1.7.0)
|
||||
dry-types (1.7.1)
|
||||
concurrent-ruby (~> 1.0)
|
||||
dry-core (~> 1.0, < 2)
|
||||
dry-inflector (~> 1.0, < 2)
|
||||
dry-logic (>= 1.4, < 2)
|
||||
dry-core (~> 1.0)
|
||||
dry-inflector (~> 1.0)
|
||||
dry-logic (~> 1.4)
|
||||
zeitwerk (~> 2.6)
|
||||
ed25519 (1.2.4-x86_64-linux-musl)
|
||||
ed25519 (1.3.0-x86_64-linux-musl)
|
||||
em-websocket (0.5.3)
|
||||
eventmachine (>= 0.12.9)
|
||||
http_parser.rb (~> 0)
|
||||
errbase (0.2.1)
|
||||
erubi (1.10.0)
|
||||
errbase (0.2.2)
|
||||
erubi (1.12.0)
|
||||
eventmachine (1.2.7-x86_64-linux-musl)
|
||||
exception_notification (4.4.3)
|
||||
actionmailer (>= 4.0, < 7)
|
||||
activesupport (>= 4.0, < 7)
|
||||
exception_notification (4.5.0)
|
||||
actionmailer (>= 5.2, < 8)
|
||||
activesupport (>= 5.2, < 8)
|
||||
execjs (2.8.1)
|
||||
factory_bot (6.2.0)
|
||||
factory_bot (6.2.1)
|
||||
activesupport (>= 5.0.0)
|
||||
factory_bot_rails (6.2.0)
|
||||
factory_bot (~> 6.2.0)
|
||||
railties (>= 5.0.0)
|
||||
fast_blank (1.0.1-x86_64-linux-musl)
|
||||
fast_jsonparser (0.5.0-x86_64-linux-musl)
|
||||
ffi (1.15.4-x86_64-linux-musl)
|
||||
ffi (1.15.5-x86_64-linux-musl)
|
||||
flamegraph (0.9.5)
|
||||
forwardable-extended (2.6.0)
|
||||
friendly_id (5.4.2)
|
||||
friendly_id (5.5.0)
|
||||
activerecord (>= 4.0.0)
|
||||
get_process_mem (0.2.7)
|
||||
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)
|
||||
groupdate (6.1.0)
|
||||
groupdate (6.2.1)
|
||||
activesupport (>= 5.2)
|
||||
hairtrigger (0.2.24)
|
||||
activerecord (>= 5.0, < 7)
|
||||
hairtrigger (1.0.0)
|
||||
activerecord (>= 6.0, < 8)
|
||||
ruby2ruby (~> 2.4)
|
||||
ruby_parser (~> 3.10)
|
||||
haml (5.2.2)
|
||||
temple (>= 0.8.0)
|
||||
haml (6.1.2-x86_64-linux-musl)
|
||||
temple (>= 0.8.2)
|
||||
thor
|
||||
tilt
|
||||
haml-lint (0.999.999)
|
||||
haml_lint
|
||||
haml_lint (0.37.1)
|
||||
haml (>= 4.0, < 5.3)
|
||||
haml_lint (0.45.0)
|
||||
haml (>= 4.0, < 6.2)
|
||||
parallel (~> 1.10)
|
||||
rainbow
|
||||
rubocop (>= 0.50.0)
|
||||
sysexits (~> 1.1)
|
||||
hamlit (2.15.1-x86_64-linux-musl)
|
||||
hamlit (3.0.3-x86_64-linux-musl)
|
||||
temple (>= 0.8.2)
|
||||
thor
|
||||
tilt
|
||||
|
@ -253,20 +256,21 @@ GEM
|
|||
httparty (0.21.0)
|
||||
mini_mime (>= 1.0.0)
|
||||
multi_xml (>= 0.5.2)
|
||||
i18n (1.8.11)
|
||||
i18n (1.14.1)
|
||||
concurrent-ruby (~> 1.0)
|
||||
icalendar (2.7.1)
|
||||
icalendar (2.8.0)
|
||||
ice_cube (~> 0.16)
|
||||
ice_cube (0.16.4)
|
||||
image_processing (1.12.1)
|
||||
image_processing (1.12.2)
|
||||
mini_magick (>= 4.9.5, < 5)
|
||||
ruby-vips (>= 2.0.17, < 3)
|
||||
inline_svg (1.7.2)
|
||||
inline_svg (1.9.0)
|
||||
activesupport (>= 3.0)
|
||||
nokogiri (>= 1.6)
|
||||
jbuilder (2.11.3)
|
||||
jbuilder (2.11.5)
|
||||
actionview (>= 5.0.0)
|
||||
activesupport (>= 5.0.0)
|
||||
jekyll (4.2.1)
|
||||
jekyll (4.2.2)
|
||||
addressable (~> 2.4)
|
||||
colorator (~> 1.0)
|
||||
em-websocket (~> 0.5)
|
||||
|
@ -281,242 +285,214 @@ GEM
|
|||
rouge (~> 3.0)
|
||||
safe_yaml (~> 1.0)
|
||||
terminal-table (~> 2.0)
|
||||
jekyll-commonmark (1.3.2)
|
||||
commonmarker (~> 0.14, < 0.22)
|
||||
jekyll (>= 3.7, < 5.0)
|
||||
jekyll-data (1.1.2)
|
||||
jekyll (>= 3.3, < 5.0.0)
|
||||
jekyll-dotenv (0.2.0)
|
||||
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-commonmark (1.4.0)
|
||||
commonmarker (~> 0.22)
|
||||
jekyll-images (0.4.1)
|
||||
jekyll (~> 4)
|
||||
ruby-filemagic (~> 0.7)
|
||||
ruby-vips (~> 2)
|
||||
jekyll-include-cache (0.2.1)
|
||||
jekyll (>= 3.7, < 5.0)
|
||||
jekyll-linked-posts (0.4.2)
|
||||
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)
|
||||
jekyll-sass-converter (2.2.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)
|
||||
listen (~> 3.0)
|
||||
jekyll-write-and-commit-changes (0.2.1)
|
||||
jekyll (~> 4)
|
||||
rugged (~> 1)
|
||||
json (2.6.3-x86_64-linux-musl)
|
||||
jwt (2.6.0)
|
||||
kaminari (1.2.1)
|
||||
kaminari (1.2.2)
|
||||
activesupport (>= 4.1.0)
|
||||
kaminari-actionview (= 1.2.1)
|
||||
kaminari-activerecord (= 1.2.1)
|
||||
kaminari-core (= 1.2.1)
|
||||
kaminari-actionview (1.2.1)
|
||||
kaminari-actionview (= 1.2.2)
|
||||
kaminari-activerecord (= 1.2.2)
|
||||
kaminari-core (= 1.2.2)
|
||||
kaminari-actionview (1.2.2)
|
||||
actionview
|
||||
kaminari-core (= 1.2.1)
|
||||
kaminari-activerecord (1.2.1)
|
||||
kaminari-core (= 1.2.2)
|
||||
kaminari-activerecord (1.2.2)
|
||||
activerecord
|
||||
kaminari-core (= 1.2.1)
|
||||
kaminari-core (1.2.1)
|
||||
kramdown (2.3.1)
|
||||
kaminari-core (= 1.2.2)
|
||||
kaminari-core (1.2.2)
|
||||
kramdown (2.4.0)
|
||||
rexml
|
||||
kramdown-parser-gfm (1.1.0)
|
||||
kramdown (~> 2.0)
|
||||
launchy (2.5.0)
|
||||
addressable (~> 2.7)
|
||||
letter_opener (1.7.0)
|
||||
launchy (~> 2.2)
|
||||
liquid (4.0.3)
|
||||
listen (3.1.5)
|
||||
rb-fsevent (~> 0.9, >= 0.9.4)
|
||||
rb-inotify (~> 0.9, >= 0.9.7)
|
||||
ruby_dep (~> 1.2)
|
||||
launchy (2.5.2)
|
||||
addressable (~> 2.8)
|
||||
letter_opener (1.8.1)
|
||||
launchy (>= 2.2, < 3)
|
||||
liquid (4.0.4)
|
||||
listen (3.8.0)
|
||||
rb-fsevent (~> 0.10, >= 0.10.3)
|
||||
rb-inotify (~> 0.9, >= 0.9.10)
|
||||
loaf (0.10.0)
|
||||
railties (>= 3.2)
|
||||
lockbox (0.6.6)
|
||||
lograge (0.11.2)
|
||||
lockbox (1.2.0)
|
||||
lograge (0.12.0)
|
||||
actionpack (>= 4)
|
||||
activesupport (>= 4)
|
||||
railties (>= 4)
|
||||
request_store (~> 1.0)
|
||||
loofah (2.12.0)
|
||||
loofah (2.21.3)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.5.9)
|
||||
mail (2.7.1)
|
||||
nokogiri (>= 1.12.0)
|
||||
mail (2.8.1)
|
||||
mini_mime (>= 0.1.1)
|
||||
net-imap
|
||||
net-pop
|
||||
net-smtp
|
||||
marcel (1.0.2)
|
||||
memory_profiler (1.0.0)
|
||||
memory_profiler (1.0.1)
|
||||
mercenary (0.4.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_magick (4.11.0)
|
||||
mini_magick (4.12.0)
|
||||
mini_mime (1.1.2)
|
||||
mini_portile2 (2.6.1)
|
||||
minitest (5.14.4)
|
||||
mobility (1.2.4)
|
||||
mini_portile2 (2.8.2)
|
||||
minitest (5.18.0)
|
||||
mobility (1.2.9)
|
||||
i18n (>= 0.6.10, < 2)
|
||||
request_store (~> 1.0)
|
||||
multi_xml (0.6.0)
|
||||
net-ssh (6.1.0)
|
||||
netaddr (2.0.5)
|
||||
nio4r (2.5.8-x86_64-linux-musl)
|
||||
nokogiri (1.12.5-x86_64-linux-musl)
|
||||
mini_portile2 (~> 2.6.1)
|
||||
net-imap (0.3.4)
|
||||
date
|
||||
net-protocol
|
||||
net-pop (0.1.2)
|
||||
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)
|
||||
njalla-api-client (0.2.0)
|
||||
dry-schema
|
||||
httparty (~> 0.18)
|
||||
orm_adapter (0.5.0)
|
||||
pairing_heap (3.0.0)
|
||||
parallel (1.21.0)
|
||||
parser (3.0.2.0)
|
||||
pairing_heap (3.0.1)
|
||||
parallel (1.23.0)
|
||||
parser (3.2.2.1)
|
||||
ast (~> 2.4.1)
|
||||
pathutil (0.16.2)
|
||||
forwardable-extended (~> 2.6)
|
||||
pg (1.2.3-x86_64-linux-musl)
|
||||
pg_search (2.3.5)
|
||||
pg (1.5.3-x86_64-linux-musl)
|
||||
pg_search (2.3.6)
|
||||
activerecord (>= 5.2)
|
||||
activesupport (>= 5.2)
|
||||
popper_js (1.16.0)
|
||||
prometheus_exporter (1.0.0)
|
||||
popper_js (1.16.1)
|
||||
prometheus_exporter (2.0.8)
|
||||
webrick
|
||||
pry (0.14.1)
|
||||
pry (0.14.2)
|
||||
coderay (~> 1.1)
|
||||
method_source (~> 1.0)
|
||||
public_suffix (4.0.6)
|
||||
puma (5.5.2-x86_64-linux-musl)
|
||||
public_suffix (5.0.3)
|
||||
puma (6.3.1-x86_64-linux-musl)
|
||||
nio4r (~> 2.0)
|
||||
pundit (2.1.1)
|
||||
pundit (2.3.0)
|
||||
activesupport (>= 3.0.0)
|
||||
racc (1.6.0-x86_64-linux-musl)
|
||||
rack (2.2.3)
|
||||
rack-cors (1.1.1)
|
||||
que (2.2.1)
|
||||
racc (1.7.1-x86_64-linux-musl)
|
||||
rack (2.2.7)
|
||||
rack-cors (2.0.1)
|
||||
rack (>= 2.0.0)
|
||||
rack-mini-profiler (2.3.3)
|
||||
rack-mini-profiler (3.1.0)
|
||||
rack (>= 1.2.0)
|
||||
rack-proxy (0.7.0)
|
||||
rack-proxy (0.7.6)
|
||||
rack
|
||||
rack-test (1.1.0)
|
||||
rack (>= 1.0, < 3)
|
||||
rails (6.1.4.1)
|
||||
actioncable (= 6.1.4.1)
|
||||
actionmailbox (= 6.1.4.1)
|
||||
actionmailer (= 6.1.4.1)
|
||||
actionpack (= 6.1.4.1)
|
||||
actiontext (= 6.1.4.1)
|
||||
actionview (= 6.1.4.1)
|
||||
activejob (= 6.1.4.1)
|
||||
activemodel (= 6.1.4.1)
|
||||
activerecord (= 6.1.4.1)
|
||||
activestorage (= 6.1.4.1)
|
||||
activesupport (= 6.1.4.1)
|
||||
rack-test (2.1.0)
|
||||
rack (>= 1.3)
|
||||
rails (6.1.7.3)
|
||||
actioncable (= 6.1.7.3)
|
||||
actionmailbox (= 6.1.7.3)
|
||||
actionmailer (= 6.1.7.3)
|
||||
actionpack (= 6.1.7.3)
|
||||
actiontext (= 6.1.7.3)
|
||||
actionview (= 6.1.7.3)
|
||||
activejob (= 6.1.7.3)
|
||||
activemodel (= 6.1.7.3)
|
||||
activerecord (= 6.1.7.3)
|
||||
activestorage (= 6.1.7.3)
|
||||
activesupport (= 6.1.7.3)
|
||||
bundler (>= 1.15.0)
|
||||
railties (= 6.1.4.1)
|
||||
railties (= 6.1.7.3)
|
||||
sprockets-rails (>= 2.0.0)
|
||||
rails-dom-testing (2.0.3)
|
||||
activesupport (>= 4.2.0)
|
||||
nokogiri (>= 1.6)
|
||||
rails-html-sanitizer (1.4.2)
|
||||
loofah (~> 2.3)
|
||||
rails-i18n (6.0.0)
|
||||
rails-html-sanitizer (1.5.0)
|
||||
loofah (~> 2.19, >= 2.19.1)
|
||||
rails-i18n (7.0.7)
|
||||
i18n (>= 0.7, < 2)
|
||||
railties (>= 6.0.0, < 7)
|
||||
railties (>= 6.0.0, < 8)
|
||||
rails_warden (0.6.0)
|
||||
warden (>= 1.2.0)
|
||||
railties (6.1.4.1)
|
||||
actionpack (= 6.1.4.1)
|
||||
activesupport (= 6.1.4.1)
|
||||
railties (6.1.7.3)
|
||||
actionpack (= 6.1.7.3)
|
||||
activesupport (= 6.1.7.3)
|
||||
method_source
|
||||
rake (>= 0.13)
|
||||
rake (>= 12.2)
|
||||
thor (~> 1.0)
|
||||
rainbow (3.0.0)
|
||||
rainbow (3.1.1)
|
||||
rake (13.0.6)
|
||||
rb-fsevent (0.11.0)
|
||||
rb-fsevent (0.11.2)
|
||||
rb-inotify (0.10.1)
|
||||
ffi (~> 1.0)
|
||||
redis (4.5.1)
|
||||
redis-actionpack (5.2.0)
|
||||
actionpack (>= 5, < 7)
|
||||
redis (4.8.1)
|
||||
redis-actionpack (5.3.0)
|
||||
actionpack (>= 5, < 8)
|
||||
redis-rack (>= 2.1.0, < 3)
|
||||
redis-store (>= 1.1.0, < 2)
|
||||
redis-activesupport (5.2.1)
|
||||
activesupport (>= 3, < 7)
|
||||
redis-activesupport (5.3.0)
|
||||
activesupport (>= 3, < 8)
|
||||
redis-store (>= 1.3, < 2)
|
||||
redis-rack (2.1.3)
|
||||
redis-rack (2.1.4)
|
||||
rack (>= 2.0.8, < 3)
|
||||
redis-store (>= 1.2, < 2)
|
||||
redis-rails (5.0.2)
|
||||
redis-actionpack (>= 5.0, < 6)
|
||||
redis-activesupport (>= 5.0, < 6)
|
||||
redis-store (>= 1.2, < 2)
|
||||
redis-store (1.9.0)
|
||||
redis (>= 4, < 5)
|
||||
regexp_parser (2.1.1)
|
||||
request_store (1.5.0)
|
||||
redis-store (1.9.2)
|
||||
redis (>= 4, < 6)
|
||||
regexp_parser (2.8.0)
|
||||
request_store (1.5.1)
|
||||
rack (>= 1.4)
|
||||
responders (3.0.1)
|
||||
actionpack (>= 5.0)
|
||||
railties (>= 5.0)
|
||||
responders (3.1.0)
|
||||
actionpack (>= 5.2)
|
||||
railties (>= 5.2)
|
||||
rexml (3.2.5)
|
||||
rgl (0.6.2)
|
||||
rgl (0.6.3)
|
||||
pairing_heap (>= 0.3.0)
|
||||
rexml (~> 3.2, >= 3.2.4)
|
||||
stream (~> 0.5.3)
|
||||
rouge (3.26.1)
|
||||
rubocop (1.23.0)
|
||||
rouge (3.30.0)
|
||||
rubocop (1.42.0)
|
||||
json (~> 2.3)
|
||||
parallel (~> 1.10)
|
||||
parser (>= 3.0.0.0)
|
||||
parser (>= 3.1.2.1)
|
||||
rainbow (>= 2.2.2, < 4.0)
|
||||
regexp_parser (>= 1.8, < 3.0)
|
||||
rexml
|
||||
rubocop-ast (>= 1.12.0, < 2.0)
|
||||
rexml (>= 3.2.5, < 4.0)
|
||||
rubocop-ast (>= 1.24.1, < 2.0)
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (>= 1.4.0, < 3.0)
|
||||
rubocop-ast (1.13.0)
|
||||
parser (>= 3.0.1.1)
|
||||
rubocop-rails (2.12.4)
|
||||
rubocop-ast (1.28.1)
|
||||
parser (>= 3.2.1.0)
|
||||
rubocop-rails (2.19.1)
|
||||
activesupport (>= 4.2.0)
|
||||
rack (>= 1.1)
|
||||
rubocop (>= 1.7.0, < 2.0)
|
||||
ruby-enum (0.9.0)
|
||||
i18n
|
||||
ruby-filemagic (0.7.2-x86_64-linux-musl)
|
||||
ruby-progressbar (1.11.0)
|
||||
ruby-statistics (3.0.0)
|
||||
rubocop (>= 1.33.0, < 2.0)
|
||||
ruby-filemagic (0.7.3-x86_64-linux-musl)
|
||||
ruby-progressbar (1.13.0)
|
||||
ruby-statistics (3.0.2)
|
||||
ruby-vips (2.1.4)
|
||||
ffi (~> 1.12)
|
||||
ruby2ruby (2.4.4)
|
||||
ruby2ruby (2.5.0)
|
||||
ruby_parser (~> 3.1)
|
||||
sexp_processor (~> 4.6)
|
||||
ruby_dep (1.5.0)
|
||||
ruby_parser (3.18.1)
|
||||
ruby_parser (3.20.1)
|
||||
sexp_processor (~> 4.16)
|
||||
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)
|
||||
safely_block (0.3.0)
|
||||
errbase (>= 0.1.1)
|
||||
|
@ -528,59 +504,55 @@ GEM
|
|||
sprockets (> 3.0)
|
||||
sprockets-rails
|
||||
tilt
|
||||
selenium-webdriver (4.1.0)
|
||||
childprocess (>= 0.5, < 5.0)
|
||||
selenium-webdriver (4.8.6)
|
||||
rexml (~> 3.2, >= 3.2.5)
|
||||
rubyzip (>= 1.2.2)
|
||||
rubyzip (>= 1.2.2, < 3.0)
|
||||
websocket (~> 1.0)
|
||||
semantic_range (3.0.0)
|
||||
sexp_processor (4.16.0)
|
||||
sexp_processor (4.17.0)
|
||||
simpleidn (0.2.1)
|
||||
unf (~> 0.1.4)
|
||||
sourcemap (0.1.1)
|
||||
spree-api-client (0.2.4)
|
||||
fast_blank (~> 1)
|
||||
httparty (~> 0.18.0)
|
||||
spring (2.1.1)
|
||||
spring-watcher-listen (2.0.1)
|
||||
spring (4.1.1)
|
||||
spring-watcher-listen (2.1.0)
|
||||
listen (>= 2.7, < 4.0)
|
||||
spring (>= 1.2, < 3.0)
|
||||
sprockets (4.0.2)
|
||||
spring (>= 4)
|
||||
sprockets (4.2.0)
|
||||
concurrent-ruby (~> 1.0)
|
||||
rack (> 1, < 3)
|
||||
sprockets-rails (3.4.1)
|
||||
rack (>= 2.2.4, < 4)
|
||||
sprockets-rails (3.4.2)
|
||||
actionpack (>= 5.2)
|
||||
activesupport (>= 5.2)
|
||||
sprockets (>= 3.0.0)
|
||||
sqlite3 (1.4.2-x86_64-linux-musl)
|
||||
stackprof (0.2.17-x86_64-linux-musl)
|
||||
sqlite3 (1.6.3-x86_64-linux-musl)
|
||||
mini_portile2 (~> 2.8.0)
|
||||
stackprof (0.2.25-x86_64-linux-musl)
|
||||
stream (0.5.5)
|
||||
sucker_punch (3.0.1)
|
||||
concurrent-ruby (~> 1.0)
|
||||
sutty-archives (2.5.4)
|
||||
jekyll (>= 3.6, < 5.0)
|
||||
sutty-liquid (0.7.4)
|
||||
sutty-liquid (0.11.11)
|
||||
fast_blank (~> 1.0)
|
||||
jekyll (~> 4)
|
||||
symbol-fstring (1.0.2-x86_64-linux-musl)
|
||||
sysexits (1.2.0)
|
||||
temple (0.8.2)
|
||||
temple (0.10.1)
|
||||
terminal-table (2.0.0)
|
||||
unicode-display_width (~> 1.1, >= 1.1.1)
|
||||
thor (1.1.0)
|
||||
tilt (2.0.10)
|
||||
timecop (0.9.4)
|
||||
thor (1.2.2)
|
||||
tilt (2.1.0)
|
||||
timecop (0.9.6)
|
||||
timeout (0.3.2)
|
||||
turbolinks (5.2.1)
|
||||
turbolinks-source (~> 5.2)
|
||||
turbolinks-source (5.2.0)
|
||||
tzinfo (2.0.4)
|
||||
tzinfo (2.0.6)
|
||||
concurrent-ruby (~> 1.0)
|
||||
uglifier (4.2.0)
|
||||
execjs (>= 0.3.0, < 3)
|
||||
unf (0.1.4)
|
||||
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)
|
||||
validates_hostname (1.0.11)
|
||||
uri-ssh_git (2.0.0)
|
||||
validates_hostname (1.0.13)
|
||||
activerecord (>= 3.0)
|
||||
activesupport (>= 3.0)
|
||||
warden (1.2.9)
|
||||
|
@ -590,21 +562,21 @@ GEM
|
|||
activemodel (>= 6.0.0)
|
||||
bindex (>= 0.4.0)
|
||||
railties (>= 6.0.0)
|
||||
webpacker (5.4.3)
|
||||
webpacker (5.4.4)
|
||||
activesupport (>= 5.2)
|
||||
rack-proxy (>= 0.6.1)
|
||||
railties (>= 5.2)
|
||||
semantic_range (>= 2.3.0)
|
||||
webrick (1.7.0)
|
||||
websocket-driver (0.7.5-x86_64-linux-musl)
|
||||
webrick (1.8.1)
|
||||
websocket (1.2.9)
|
||||
websocket-driver (0.7.6-x86_64-linux-musl)
|
||||
websocket-extensions (>= 0.1.0)
|
||||
websocket-extensions (0.1.5)
|
||||
xpath (3.2.0)
|
||||
nokogiri (~> 1.8)
|
||||
zeitwerk (2.5.1)
|
||||
zeitwerk (2.6.8)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
x86_64-linux-musl
|
||||
|
||||
DEPENDENCIES
|
||||
|
@ -619,10 +591,11 @@ DEPENDENCIES
|
|||
concurrent-ruby-ext
|
||||
database_cleaner
|
||||
derailed_benchmarks
|
||||
device_detector
|
||||
devise
|
||||
devise-i18n
|
||||
devise_invitable
|
||||
distributed-press-api-client (~> 0.2.3)
|
||||
distributed-press-api-client (~> 0.3.0rc0)
|
||||
dotenv-rails
|
||||
down
|
||||
ed25519
|
||||
|
@ -630,9 +603,10 @@ DEPENDENCIES
|
|||
exception_notification
|
||||
factory_bot_rails
|
||||
fast_blank
|
||||
fast_jsonparser
|
||||
fast_jsonparser (~> 0.5.0)
|
||||
flamegraph
|
||||
friendly_id
|
||||
git_clone_url
|
||||
hairtrigger
|
||||
haml-lint
|
||||
hamlit-rails
|
||||
|
@ -642,14 +616,13 @@ DEPENDENCIES
|
|||
image_processing
|
||||
inline_svg
|
||||
jbuilder (~> 2.5)
|
||||
jekyll (~> 4.2)
|
||||
jekyll-commonmark
|
||||
jekyll-data
|
||||
jekyll (~> 4.2.0)
|
||||
jekyll-commonmark (~> 1.4.0)
|
||||
jekyll-images
|
||||
jekyll-include-cache
|
||||
kaminari
|
||||
letter_opener
|
||||
listen (>= 3.0.5, < 3.2)
|
||||
listen
|
||||
loaf
|
||||
lockbox
|
||||
lograge
|
||||
|
@ -657,7 +630,6 @@ DEPENDENCIES
|
|||
mini_magick
|
||||
mobility
|
||||
net-ssh
|
||||
njalla-api-client
|
||||
nokogiri
|
||||
pg
|
||||
pg_search
|
||||
|
@ -665,27 +637,28 @@ DEPENDENCIES
|
|||
pry
|
||||
puma
|
||||
pundit
|
||||
que
|
||||
rack-cors
|
||||
rack-mini-profiler
|
||||
rails (~> 6)
|
||||
rails (~> 6.1.0)
|
||||
rails-i18n
|
||||
rails_warden
|
||||
redis
|
||||
redis (~> 4.0)
|
||||
redis-rails
|
||||
rgl
|
||||
rollups!
|
||||
rubocop-rails
|
||||
rubyzip
|
||||
rugged
|
||||
rugged (= 1.5.0.1)
|
||||
safe_yaml
|
||||
safely_block (~> 0.3.0)
|
||||
sassc-rails
|
||||
selenium-webdriver
|
||||
selenium-webdriver (~> 4.8.0)
|
||||
sourcemap
|
||||
spring
|
||||
spring-watcher-listen (~> 2.0.0)
|
||||
spring-watcher-listen
|
||||
sqlite3
|
||||
stackprof
|
||||
sucker_punch
|
||||
sutty-liquid (>= 0.7.3)
|
||||
symbol-fstring
|
||||
terminal-table
|
||||
|
@ -693,12 +666,12 @@ DEPENDENCIES
|
|||
turbolinks (~> 5)
|
||||
uglifier (>= 1.3.0)
|
||||
validates_hostname
|
||||
web-console (>= 3.3.0)
|
||||
web-console
|
||||
webpacker
|
||||
yaml_db!
|
||||
|
||||
RUBY VERSION
|
||||
ruby 2.7.1p83
|
||||
ruby 3.1.4p223
|
||||
|
||||
BUNDLED WITH
|
||||
2.2.2
|
||||
2.4.17
|
||||
|
|
20
Makefile
20
Makefile
|
@ -48,8 +48,6 @@ help: always ## Ayuda
|
|||
@echo -e "\nArgumentos:\n"
|
||||
@grep -E "^[a-z\-]+ \?=.*##" Makefile | sed -re "s/(.*) \?=.*##(.*)/\1;\2/" | column -s ";" -t | sed -re "s/^([^ ]+) /\x1B[38;5;197m\1\x1B[0m/"
|
||||
|
||||
assets: public/packs/manifest.json.br ## Compilar los assets
|
||||
|
||||
test: always ## Ejecutar los tests
|
||||
$(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=).
|
||||
$(hain) 'bundle $(args)'
|
||||
|
||||
psql := psql -h $(PG_HOST) -U $(PG_USER) -p $(PG_PORT) -d sutty
|
||||
psql := psql $(DATABASE_URL)
|
||||
copy-table:
|
||||
test -n "$(table)"
|
||||
echo "truncate $(table) $(cascade);" | $(psql)
|
||||
ssh $(delegate) docker exec postgresql pg_dump -U sutty -d sutty -t $(table) | $(psql)
|
||||
|
||||
psql:
|
||||
$(psql)
|
||||
$(hain) $(psql)
|
||||
|
||||
rubocop: ## Yutea el código que está por ser commiteado
|
||||
git status --porcelain \
|
||||
|
@ -110,21 +108,13 @@ save: ## Subir la imagen Docker al nodo delegado
|
|||
date +%F | xargs -I {} git tag -f $(container)-{}
|
||||
@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
|
||||
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) 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
|
||||
# `make test/models/usuarie_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_"
|
||||
distributed_press_tokens_renew: bundle exec rake distributed_press:tokens:renew
|
||||
cleanup: bundle exec rake cleanup:everything
|
||||
emergency_cleanup: bundle exec rake cleanup:everything BEFORE=7
|
||||
stats: bundle exec rake stats:process_all
|
||||
distributed_press_renew_tokens: bundle exec rake distributed_press:tokens:renew
|
||||
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;
|
||||
$jumbotron-bg: transparent;
|
||||
$enable-rounded: false;
|
||||
$form-feedback-valid-color: $cyan;
|
||||
$form-feedback-valid-color: $black;
|
||||
$form-feedback-invalid-color: $magenta;
|
||||
$form-feedback-icon-valid-color: $black;
|
||||
$component-active-bg: $magenta;
|
||||
|
|
9
app/assets/stylesheets/dark.scss
Normal file
9
app/assets/stylesheets/dark.scss
Normal file
|
@ -0,0 +1,9 @@
|
|||
$black: black;
|
||||
$white: white;
|
||||
$cyan: #13fefe;
|
||||
|
||||
:root {
|
||||
--foreground: #{$white};
|
||||
--background: #{$black};
|
||||
--color: #{$cyan};
|
||||
}
|
|
@ -16,12 +16,14 @@ module ActiveStorage
|
|||
end
|
||||
end
|
||||
|
||||
rescue_from ActiveRecord::RecordNotFound, with: :page_not_found
|
||||
|
||||
# Asociar el archivo subido al sitio correspondiente. Cada sitio
|
||||
# tiene su propio servicio de subida de archivos.
|
||||
def update
|
||||
if (token = decode_verified_token)
|
||||
if acceptable_content?(token)
|
||||
blob = ActiveStorage::Blob.find_by_key token[:key]
|
||||
blob = ActiveStorage::Blob.find_by_key! token[:key]
|
||||
site = Site.find_by_name token[:service_name]
|
||||
|
||||
if remote_file?(token)
|
||||
|
@ -59,6 +61,11 @@ module ActiveStorage
|
|||
def remote_file?(token)
|
||||
token[:content_type] == 'sutty/download-from-url'
|
||||
end
|
||||
|
||||
def page_not_found(exception)
|
||||
head :not_found
|
||||
ExceptionNotifier.notify_exception(exception, data: {params: params.to_hash})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -9,10 +9,10 @@ module Api
|
|||
# Generar un stacktrace en segundo plano y enviarlo por correo
|
||||
# solo si la API key es verificable. Del otro lado siempre
|
||||
# respondemos con lo mismo.
|
||||
def create
|
||||
if site&.airbrake_valid? airbrake_token
|
||||
def create
|
||||
if (site&.airbrake_valid? airbrake_token) && !detected_device.bot?
|
||||
BacktraceJob.perform_later site_id: params[:site_id],
|
||||
params: airbrake_params.to_h
|
||||
params: airbrake_params.to_h
|
||||
end
|
||||
|
||||
render status: 201, json: { id: 1, url: '' }
|
||||
|
@ -34,6 +34,11 @@ module Api
|
|||
def airbrake_token
|
||||
@airbrake_token ||= params[:key]
|
||||
end
|
||||
|
||||
# @return [DeviceDetector]
|
||||
def detected_device
|
||||
@detected_device ||= DeviceDetector.new(request.headers['User-Agent'], request.headers)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -9,7 +9,7 @@ module Api
|
|||
|
||||
# Lista de nombres de dominios a emitir certificados
|
||||
def index
|
||||
render json: sites_names + alternative_names + api_names + www_names
|
||||
render json: alternative_names + api_names + www_names
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -18,17 +18,16 @@ module Api
|
|||
name.end_with?('.') ? name[0..-2] : "#{name}.#{Site.domain}"
|
||||
end
|
||||
|
||||
# Nombres de los sitios
|
||||
def sites_names
|
||||
Site.all.order(:name).pluck(:name).map do |name|
|
||||
canonicalize name
|
||||
end
|
||||
def subdomain?(name)
|
||||
name.end_with? ".#{Site.domain}"
|
||||
end
|
||||
|
||||
# Dominios alternativos
|
||||
def alternative_names
|
||||
(DeployAlternativeDomain.all.map(&:hostname) + DeployLocalizedDomain.all.map(&:hostname)).map do |name|
|
||||
canonicalize name
|
||||
end.reject do |name|
|
||||
subdomain? name
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -41,6 +40,8 @@ module Api
|
|||
.or(Site.where(colaboracion_anonima: true))
|
||||
.select("'api.' || name as name").map(&:name).map do |name|
|
||||
canonicalize name
|
||||
end.reject do |name|
|
||||
subdomain? name
|
||||
end
|
||||
end
|
||||
|
||||
|
|
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]
|
||||
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
|
||||
end
|
||||
|
|
|
@ -22,7 +22,12 @@ class BuildStatsController < ApplicationController
|
|||
|
||||
@table = site.deployment_list.map do |deploy|
|
||||
type = deploy.class.name.underscore
|
||||
urls = deploy.respond_to?(:urls) ? deploy.urls : [deploy.url].compact
|
||||
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
|
||||
|
|
|
@ -5,6 +5,7 @@ class EnvController < ActionController::Base
|
|||
|
||||
def index
|
||||
@site = Site.find_by_name('panel')
|
||||
stale? @site
|
||||
|
||||
stale? @site if @site
|
||||
end
|
||||
end
|
||||
|
|
|
@ -24,6 +24,7 @@ class PostsController < ApplicationController
|
|||
|
||||
# Todos los artículos de este sitio para el idioma actual
|
||||
@posts = site.indexed_posts.where(locale: locale)
|
||||
@posts = @posts.page(filter_params.delete(:page)) if site.pagination
|
||||
# De este tipo
|
||||
@posts = @posts.where(layout: filter_params[:layout]) if filter_params[:layout]
|
||||
# Que estén dentro de la categoría
|
||||
|
@ -154,7 +155,7 @@ class PostsController < ApplicationController
|
|||
#
|
||||
# @return [Hash]
|
||||
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?
|
||||
end.transform_keys(&:to_sym)
|
||||
end
|
||||
|
|
|
@ -57,6 +57,7 @@ class SitesController < ApplicationController
|
|||
usuarie: current_usuarie)
|
||||
|
||||
if service.update.valid?
|
||||
flash[:notice] = I18n.t('sites.update.post')
|
||||
redirect_to site_posts_path(site, locale: site.default_locale)
|
||||
else
|
||||
render 'edit'
|
||||
|
|
|
@ -2,11 +2,21 @@
|
|||
|
||||
import { Notifier } from '@airbrake/browser'
|
||||
|
||||
window.airbrake = new Notifier({
|
||||
projectId: window.env.AIRBRAKE_SITE_ID,
|
||||
projectKey: window.env.AIRBRAKE_API_KEY,
|
||||
host: window.env.PANEL_URL
|
||||
})
|
||||
try {
|
||||
window.airbrake = new Notifier({
|
||||
projectId: window.env.AIRBRAKE_PROJECT_ID,
|
||||
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 'regenerator-runtime/runtime'
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
# Base para trabajos
|
||||
class ApplicationJob < ActiveJob::Base
|
||||
include SuckerPunch::Job
|
||||
include Que::ActiveJob::JobExtensions
|
||||
|
||||
private
|
||||
|
||||
|
|
|
@ -6,8 +6,6 @@ class BacktraceJob < ApplicationJob
|
|||
|
||||
EMPTY_SOURCEMAP = { 'mappings' => '' }.freeze
|
||||
|
||||
queue_as :low_priority
|
||||
|
||||
attr_reader :params, :site_id
|
||||
|
||||
def perform(site_id:, params:)
|
||||
|
|
8
app/jobs/cleanup_job.rb
Normal file
8
app/jobs/cleanup_job.rb
Normal file
|
@ -0,0 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Realiza tareas de limpieza en segundo plano
|
||||
class CleanupJob < ApplicationJob
|
||||
def perform(before = nil)
|
||||
CleanupService.new(before: before).cleanup_everything!
|
||||
end
|
||||
end
|
|
@ -51,7 +51,15 @@ class DeployJob < ApplicationJob
|
|||
status = d.deploy(output: @output)
|
||||
seconds = d.build_stats.last.try(:seconds) || 0
|
||||
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
|
||||
status = false
|
||||
seconds ||= 0
|
||||
|
|
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,8 +10,6 @@ class GitlabNotifierJob < ApplicationJob
|
|||
# Variables que vamos a acceder luego
|
||||
attr_reader :exception, :options, :issue_data, :cached
|
||||
|
||||
queue_as :low_priority
|
||||
|
||||
# @param [Exception] la excepción lanzada
|
||||
# @param [Hash] opciones de ExceptionNotifier
|
||||
def perform(exception, **options)
|
||||
|
@ -32,7 +30,7 @@ class GitlabNotifierJob < ApplicationJob
|
|||
count: 1,
|
||||
issue: @issue['iid'],
|
||||
user_agents: [user_agent].compact,
|
||||
params: [request&.filtered_parameters].compact,
|
||||
params: request&.filtered_parameters&.as_json,
|
||||
urls: [url].compact
|
||||
}
|
||||
end
|
||||
|
@ -194,7 +192,7 @@ class GitlabNotifierJob < ApplicationJob
|
|||
```
|
||||
#{request.request_method} #{url}
|
||||
|
||||
#{pp request.filtered_parameters}
|
||||
#{pp request.filtered_parameters.as_json}
|
||||
```
|
||||
|
||||
REQUEST
|
||||
|
|
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
|
|
@ -96,7 +96,7 @@ module ActiveStorage
|
|||
end
|
||||
|
||||
def blob_for(key)
|
||||
ActiveStorage::Blob.find_by(key: key, service_name: name)
|
||||
ActiveStorage::Blob.find_by!(key: key, service_name: name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8,9 +8,9 @@ module ExceptionNotifier
|
|||
# Recibe la excepción y empieza la tarea de notificación en segundo
|
||||
# plano.
|
||||
#
|
||||
# @param [Exception]
|
||||
# @param [Hash]
|
||||
def call(exception, **options)
|
||||
# @param :exception [Exception]
|
||||
# @param :options [Hash]
|
||||
def call(exception, options, &block)
|
||||
case exception
|
||||
when BacktraceJob::BacktraceException
|
||||
GitlabNotifierJob.perform_later(exception, **options)
|
||||
|
|
36
app/lib/jekyll/readers/data_reader_decorator.rb
Normal file
36
app/lib/jekyll/readers/data_reader_decorator.rb
Normal file
|
@ -0,0 +1,36 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Jekyll
|
||||
module Readers
|
||||
# Permite leer datos utilizando rutas absolutas.
|
||||
#
|
||||
# {Jekyll::DataReader} usa {Dir.chdir} con rutas relativas, lo que
|
||||
# en nuestro uso provoca confusiones en el lector de datos.
|
||||
#
|
||||
# Con este módulo, podemos leer todos los archivos usando rutas
|
||||
# absolutas, lo que nos permite reemplazar jekyll-data, que agregaba
|
||||
# código duplicado.
|
||||
module DataReaderDecorator
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
def read_data_to(dir, data)
|
||||
return unless File.directory?(dir) && !@entry_filter.symlink?(dir)
|
||||
|
||||
Dir.glob(File.join(dir, '*')).each do |path|
|
||||
next if @entry_filter.symlink?(path)
|
||||
|
||||
entry = Pathname.new(path).relative_path_from(dir).to_s
|
||||
|
||||
if File.directory?(path)
|
||||
read_data_to(path, data[sanitize_filename(entry)] = {})
|
||||
else
|
||||
key = sanitize_filename(File.basename(entry, ".*"))
|
||||
data[key] = read_data_file(path)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -52,7 +52,7 @@ class DeployMailer < ApplicationMailer
|
|||
t << (row.map do |k, v|
|
||||
case k
|
||||
when :seconds then v[:human]
|
||||
when :urls then url
|
||||
when :urls then url.to_s
|
||||
else v
|
||||
end
|
||||
end)
|
||||
|
|
|
@ -23,6 +23,11 @@ class Deploy < ApplicationRecord
|
|||
raise NotImplementedError
|
||||
end
|
||||
|
||||
# @return [Array]
|
||||
def urls
|
||||
[url].compact
|
||||
end
|
||||
|
||||
def limit
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
@ -55,6 +60,22 @@ class Deploy < ApplicationRecord
|
|||
@gems_dir ||= Rails.root.join('_storage', 'gems', site.name)
|
||||
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
|
||||
# estado.
|
||||
#
|
||||
|
@ -65,22 +86,20 @@ class Deploy < ApplicationRecord
|
|||
lines = []
|
||||
|
||||
time_start
|
||||
Dir.chdir(site.path) do
|
||||
Open3.popen2e(env, cmd, unsetenv_others: true) do |_, o, t|
|
||||
# TODO: Enviar a un websocket para ver el proceso en vivo?
|
||||
Thread.new do
|
||||
o.each do |line|
|
||||
lines << line
|
||||
Open3.popen2e(env, cmd, unsetenv_others: true, chdir: site.path) do |_, o, t|
|
||||
# TODO: Enviar a un websocket para ver el proceso en vivo?
|
||||
Thread.new do
|
||||
o.each do |line|
|
||||
lines << line
|
||||
|
||||
puts line if output
|
||||
end
|
||||
rescue IOError => e
|
||||
lines << e.message
|
||||
puts e.message if output
|
||||
puts line if output
|
||||
end
|
||||
|
||||
r = t.value
|
||||
rescue IOError => e
|
||||
lines << e.message
|
||||
puts e.message if output
|
||||
end
|
||||
|
||||
r = t.value
|
||||
end
|
||||
time_stop
|
||||
|
||||
|
@ -100,6 +119,11 @@ class Deploy < ApplicationRecord
|
|||
@local_env ||= {}
|
||||
end
|
||||
|
||||
# Devuelve opciones para jekyll build
|
||||
#
|
||||
# @return [String,nil]
|
||||
def flags_for_build(**args); end
|
||||
|
||||
# Trae todas las dependencias
|
||||
#
|
||||
# @return [Array]
|
||||
|
@ -109,6 +133,21 @@ class Deploy < ApplicationRecord
|
|||
|
||||
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]
|
||||
# @return [String]
|
||||
def readable_cmd(cmd)
|
||||
|
@ -119,7 +158,14 @@ class Deploy < ApplicationRecord
|
|||
@deploy_local ||= site.deploys.find_by(type: 'DeployLocal')
|
||||
end
|
||||
|
||||
def non_local_deploys
|
||||
@non_local_deploys ||= site.deploys.where.not(type: 'DeployLocal')
|
||||
# Consigue todas las variables de entorno configuradas por otros
|
||||
# deploys.
|
||||
#
|
||||
# @return [Hash]
|
||||
def extra_env
|
||||
@extra_env ||=
|
||||
site.deployment_list.reduce({}) do |extra, deploy|
|
||||
extra.merge deploy.local_env
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'distributed_press/v1/client/site'
|
||||
require 'njalla/v1'
|
||||
|
||||
# Soportar Distributed Press APIv1
|
||||
#
|
||||
|
@ -15,8 +14,8 @@ require 'njalla/v1'
|
|||
class DeployDistributedPress < Deploy
|
||||
store :values, accessors: %i[hostname remote_site_id remote_info], coder: JSON
|
||||
|
||||
before_create :create_remote_site!, :create_njalla_records!
|
||||
before_destroy :delete_remote_site!, :delete_njalla_records!
|
||||
before_create :create_remote_site!
|
||||
before_destroy :delete_remote_site!
|
||||
|
||||
DEPENDENCIES = %i[deploy_local]
|
||||
|
||||
|
@ -31,17 +30,12 @@ class DeployDistributedPress < Deploy
|
|||
time_start
|
||||
|
||||
create_remote_site! if remote_site_id.blank?
|
||||
create_njalla_records!
|
||||
save
|
||||
|
||||
if remote_site_id.blank?
|
||||
raise DeployJob::DeployException, 'El sitio no se creó en Distributed Press'
|
||||
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|
|
||||
stdout = Thread.new(publisher.logger_out) do |io|
|
||||
until io.eof?
|
||||
|
@ -52,7 +46,12 @@ class DeployDistributedPress < Deploy
|
|||
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
|
||||
self.remote_info[:distributed_press] = c.show(publishing_site).to_h
|
||||
|
@ -140,29 +139,6 @@ class DeployDistributedPress < Deploy
|
|||
nil
|
||||
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ó
|
||||
#
|
||||
# @param status [Bool]
|
||||
|
@ -180,31 +156,4 @@ class DeployDistributedPress < Deploy
|
|||
ExceptionNotifier.notify_exception(e, data: { site: site.name })
|
||||
nil
|
||||
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
|
||||
|
|
|
@ -7,6 +7,11 @@ class DeployLocal < Deploy
|
|||
|
||||
before_destroy :remove_destination!
|
||||
|
||||
def git_lfs(output: false)
|
||||
run %(git lfs fetch), output: output
|
||||
run %(git lfs checkout), output: output
|
||||
end
|
||||
|
||||
# Realizamos la construcción del sitio usando Jekyll y un entorno
|
||||
# limpio para no pasarle secretos
|
||||
#
|
||||
|
@ -55,41 +60,33 @@ class DeployLocal < Deploy
|
|||
#
|
||||
# @return [nil]
|
||||
def cleanup!
|
||||
FileUtils.rm_rf(gems_dir)
|
||||
FileUtils.rm_rf(site.bundle_path)
|
||||
FileUtils.rm_rf(yarn_cache_dir)
|
||||
FileUtils.rm_rf(File.join(site.path, 'node_modules'))
|
||||
FileUtils.rm_rf(File.join(site.path, '.sass-cache'))
|
||||
FileUtils.rm_rf(File.join(site.path, '.jekyll-cache'))
|
||||
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
|
||||
|
||||
def mkdir
|
||||
FileUtils.mkdir_p destination
|
||||
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
|
||||
Rails.root.join('_yarn_cache').to_s
|
||||
end
|
||||
|
@ -114,9 +111,11 @@ class DeployLocal < Deploy
|
|||
File.exist? pnpm_lock
|
||||
end
|
||||
|
||||
def git_lfs(output: false)
|
||||
run %(git lfs fetch), output: output
|
||||
run %(git lfs checkout), output: output
|
||||
def pnpm(output: false)
|
||||
return true unless pnpm_lock?
|
||||
|
||||
run %(pnpm config set store-dir "#{pnpm_cache_dir}"), output: output
|
||||
run 'pnpm install --production', output: output
|
||||
end
|
||||
|
||||
def gem(output: false)
|
||||
|
@ -130,19 +129,21 @@ class DeployLocal < Deploy
|
|||
run 'yarn install --production', output: output
|
||||
end
|
||||
|
||||
def pnpm(output: false)
|
||||
return true unless pnpm_lock?
|
||||
|
||||
run %(pnpm config set store-dir "#{pnpm_cache_dir}"), output: output
|
||||
run 'pnpm install --production', output: output
|
||||
end
|
||||
|
||||
def bundle(output: false)
|
||||
run %(bundle install --deployment --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) if site.gemfile_lock_path?
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
# no debería haber espacios ni caracteres especiales, pero por si
|
||||
|
@ -156,17 +157,13 @@ class DeployLocal < Deploy
|
|||
FileUtils.rm_rf destination
|
||||
end
|
||||
|
||||
# Consigue todas las variables de entorno configuradas por otros
|
||||
# deploys.
|
||||
# Genera opciones extra desde los otros deploys
|
||||
#
|
||||
# @deprecated Solo tenía sentido para Distributed Press v0
|
||||
# @return [Hash]
|
||||
def extra_env
|
||||
@extra_env ||=
|
||||
non_local_deploys.reduce({}) do |extra_env, deploy|
|
||||
extra_env.tap do |e|
|
||||
e.merge! deploy.local_env
|
||||
end
|
||||
end
|
||||
# @param :args [Hash]
|
||||
# @return [String]
|
||||
def extra_flags(**args)
|
||||
site.deployment_list.map do |deploy|
|
||||
deploy.flags_for_build(**args)
|
||||
end.compact.join(' ')
|
||||
end
|
||||
end
|
||||
|
|
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
|
||||
|
||||
# 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
|
||||
#
|
||||
# @param [String,Symbol]
|
||||
|
|
|
@ -4,8 +4,12 @@
|
|||
#
|
||||
# Esto es increíblemente difícil de lograr que salga bien!
|
||||
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
|
||||
false
|
||||
!!super
|
||||
end
|
||||
|
||||
# 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
|
||||
|
||||
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
|
||||
|
||||
# Encuentra el filtro
|
||||
|
|
|
@ -12,7 +12,7 @@ class Post
|
|||
DEFAULT_ATTRIBUTES = %i[site document layout].freeze
|
||||
# Otros atributos que no vienen en los metadatos
|
||||
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_reader :attributes, :errors, :layout, :site, :document
|
||||
|
@ -217,6 +217,11 @@ class Post
|
|||
post: self, required: true)
|
||||
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
|
||||
# plantilla
|
||||
def attribute?(mid)
|
||||
|
@ -265,8 +270,9 @@ class Post
|
|||
yaml['layout'] = layout.name.to_s
|
||||
yaml['uuid'] = uuid.value
|
||||
# Y que no se procese liquid
|
||||
yaml['liquid'] = false
|
||||
yaml['render_with_liquid'] = false
|
||||
yaml['usuaries'] = usuaries.map(&:id).uniq
|
||||
yaml['created_at'] = created_at.value
|
||||
yaml['last_modified_at'] = modified_at
|
||||
|
||||
"#{yaml.to_yaml}---\n\n#{body}"
|
||||
|
|
|
@ -14,6 +14,8 @@ class Rol < ApplicationRecord
|
|||
|
||||
validates_inclusion_of :rol, in: ROLES
|
||||
|
||||
before_save :add_token_if_missing!
|
||||
|
||||
def invitade?
|
||||
rol == INVITADE
|
||||
end
|
||||
|
@ -25,4 +27,11 @@ class Rol < ApplicationRecord
|
|||
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
|
||||
|
|
|
@ -10,6 +10,7 @@ class Site < ApplicationRecord
|
|||
include Site::DeployDependencies
|
||||
include Site::BuildStats
|
||||
include Site::LayoutOrdering
|
||||
include Site::SocialDistributedPress
|
||||
include Tienda
|
||||
|
||||
# Cifrar la llave privada que cifra y decifra campos ocultos. Sutty
|
||||
|
@ -18,10 +19,6 @@ class Site < ApplicationRecord
|
|||
# protege de acceso al panel de Sutty!
|
||||
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: {
|
||||
allow_root_label: true
|
||||
}
|
||||
|
@ -212,10 +209,8 @@ class Site < ApplicationRecord
|
|||
# Trae los datos del directorio _data dentro del sitio
|
||||
def data
|
||||
unless jekyll.data.present?
|
||||
run_in_path do
|
||||
jekyll.reader.read_data
|
||||
jekyll.data['layouts'] ||= {}
|
||||
end
|
||||
jekyll.reader.read_data
|
||||
jekyll.data['layouts'] ||= {}
|
||||
end
|
||||
|
||||
jekyll.data
|
||||
|
@ -225,9 +220,7 @@ class Site < ApplicationRecord
|
|||
# colecciones.
|
||||
def collections
|
||||
unless @read
|
||||
run_in_path do
|
||||
jekyll.reader.read_collections
|
||||
end
|
||||
jekyll.reader.read_collections
|
||||
|
||||
@read = true
|
||||
end
|
||||
|
@ -322,9 +315,7 @@ class Site < ApplicationRecord
|
|||
#
|
||||
# @return [Hash]
|
||||
def theme_layouts
|
||||
run_in_path do
|
||||
jekyll.reader.read_layouts
|
||||
end
|
||||
jekyll.reader.read_layouts
|
||||
end
|
||||
|
||||
# Trae todos los valores disponibles para un campo
|
||||
|
@ -375,9 +366,7 @@ class Site < ApplicationRecord
|
|||
begin
|
||||
install_gems
|
||||
|
||||
Jekyll::Site.new(configuration).tap do |site|
|
||||
site.reader = JekyllData::Reader.new(site) if site.theme
|
||||
end
|
||||
Jekyll::Site.new(configuration)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -447,6 +436,10 @@ class Site < ApplicationRecord
|
|||
find_by(name: "#{Site.domain}.")
|
||||
end
|
||||
|
||||
def self.one_at_a_time
|
||||
@@one_at_a_time ||= Thread::Mutex.new
|
||||
end
|
||||
|
||||
def reset
|
||||
@read = false
|
||||
@layouts = nil
|
||||
|
@ -461,6 +454,15 @@ class Site < ApplicationRecord
|
|||
@docs = nil
|
||||
end
|
||||
|
||||
# @return [Pathname]
|
||||
def bundle_path
|
||||
@bundle_path ||= Rails.root.join('_storage', 'gems', name)
|
||||
end
|
||||
|
||||
def gemfile_lock_path?
|
||||
File.exist? gemfile_lock_path
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Asegurarse que el sitio tenga una llave privada
|
||||
|
@ -473,7 +475,10 @@ class Site < ApplicationRecord
|
|||
def clone_skel!
|
||||
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
|
||||
|
||||
# Elimina el directorio del sitio
|
||||
|
@ -497,8 +502,8 @@ class Site < ApplicationRecord
|
|||
config.theme = design.gem unless design.no_theme?
|
||||
config.description = description
|
||||
config.title = title
|
||||
config.url = url(slash: false)
|
||||
config.hostname = hostname
|
||||
config.url ||= url(slash: false)
|
||||
config.hostname ||= hostname
|
||||
config.locales = locales.map(&:to_s)
|
||||
end
|
||||
|
||||
|
@ -553,7 +558,9 @@ class Site < ApplicationRecord
|
|||
end
|
||||
|
||||
def run_in_path(&block)
|
||||
Dir.chdir path, &block
|
||||
Site.one_at_a_time.synchronize do
|
||||
Dir.chdir path, &block
|
||||
end
|
||||
end
|
||||
|
||||
# Instala las gemas cuando es necesario:
|
||||
|
@ -565,26 +572,52 @@ class Site < ApplicationRecord
|
|||
def install_gems
|
||||
return unless persisted?
|
||||
|
||||
deploys.find_by_type('DeployLocal').send(:git_lfs)
|
||||
deploy_local = deploys.find_by_type('DeployLocal')
|
||||
deploy_local.git_lfs
|
||||
|
||||
if !gem_dir? || gemfile_updated? || gemfile_lock_updated?
|
||||
deploys.find_by_type('DeployLocal').send(:bundle)
|
||||
if !gems_installed? || gemfile_updated? || gemfile_lock_updated?
|
||||
deploy_local.bundle
|
||||
touch
|
||||
File.touch(gemfile_path)
|
||||
end
|
||||
end
|
||||
|
||||
def gem_path
|
||||
@gem_path ||=
|
||||
begin
|
||||
ruby_version = Gem::Version.new(RUBY_VERSION)
|
||||
ruby_version.canonical_segments[2] = 0
|
||||
|
||||
bundle_path.join('ruby', ruby_version.canonical_segments.join('.'))
|
||||
end
|
||||
end
|
||||
|
||||
# Detecta si el repositorio de gemas existe
|
||||
def gem_dir?
|
||||
Rails.root.join('_storage', 'gems', name).directory?
|
||||
def gems_installed?
|
||||
gem_path.directory? && !gem_path.empty?
|
||||
end
|
||||
|
||||
# Detecta si el Gemfile fue modificado
|
||||
def gemfile_updated?
|
||||
updated_at < File.mtime(File.join(path, 'Gemfile'))
|
||||
updated_at < File.mtime(gemfile_path)
|
||||
end
|
||||
|
||||
# Detecta si el Gemfile.lock fue modificado
|
||||
def gemfile_path
|
||||
@gemfile_path ||= File.join(path, 'Gemfile')
|
||||
end
|
||||
|
||||
# @return [String]
|
||||
def gemfile_lock_path
|
||||
@gemfile_lock_path ||= File.join(path, 'Gemfile.lock')
|
||||
end
|
||||
|
||||
# Detecta si el Gemfile.lock fue modificado con respecto al sitio o al
|
||||
# Gemfile.
|
||||
def gemfile_lock_updated?
|
||||
updated_at < File.mtime(File.join(path, 'Gemfile.lock'))
|
||||
return false unless gemfile_lock_path?
|
||||
|
||||
[updated_at, File.mtime(File.join(path, 'Gemfile'))].any? do |compare|
|
||||
compare < File.mtime(gemfile_lock_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -21,7 +21,7 @@ class Site
|
|||
# Leer el archivo de configuración y setear los atributos en el
|
||||
# objeto actual, creando los metodos de ostruct
|
||||
def read
|
||||
data = YAML.safe_load(File.read(path))
|
||||
data = YAML.safe_load(File.read(path), permitted_classes: [Time])
|
||||
@hash = data.hash
|
||||
|
||||
data.each do |key, value|
|
||||
|
|
|
@ -37,7 +37,7 @@ class Site
|
|||
|
||||
author = GitAuthor.new email: "sutty@#{Site.domain}", name: 'Sutty'
|
||||
|
||||
repository.commit(file: modified,
|
||||
repository.commit(add: modified,
|
||||
message: I18n.t('sites.find_and_replace'),
|
||||
usuarie: author)
|
||||
end
|
||||
|
|
|
@ -1,22 +1,125 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Indexa todos los artículos de un sitio
|
||||
#
|
||||
# TODO: Hacer opcional
|
||||
class Site
|
||||
# Indexa todos los artículos de un sitio
|
||||
#
|
||||
# TODO: Hacer opcional
|
||||
module Index
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
# TODO: Debería ser un Job?
|
||||
after_create :index_posts!
|
||||
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!
|
||||
Site.transaction do
|
||||
docs.each(&:index!)
|
||||
|
||||
update(last_indexed_commit: repository.head_commit.oid)
|
||||
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
|
||||
|
|
|
@ -29,7 +29,7 @@ class Site
|
|||
|
||||
# Obtiene el origin
|
||||
#
|
||||
# @return [Rugged::Remote]
|
||||
# @return [Rugged::Remote, nil]
|
||||
def origin
|
||||
@origin ||= rugged.remotes.find do |remote|
|
||||
remote.name == 'origin'
|
||||
|
@ -54,7 +54,7 @@ class Site
|
|||
# Incorpora los cambios en el repositorio actual
|
||||
#
|
||||
# @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)
|
||||
|
||||
# No hacemos nada si hay conflictos, pero notificarnos
|
||||
|
@ -69,12 +69,16 @@ class Site
|
|||
.create(rugged, update_ref: 'HEAD',
|
||||
parents: [head_commit, remote_head_commit],
|
||||
tree: merge.write_tree(rugged),
|
||||
message: I18n.t('sites.fetch.merge.message'),
|
||||
message: message,
|
||||
author: author(usuarie), committer: committer)
|
||||
|
||||
# Forzamos el checkout para mover el HEAD al último commit y
|
||||
# escribir los cambios
|
||||
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
|
||||
end
|
||||
|
||||
|
@ -114,14 +118,21 @@ class Site
|
|||
end
|
||||
|
||||
# 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
|
||||
rugged.index.read_tree rugged.head.target.tree
|
||||
|
||||
file.each do |f|
|
||||
remove ? rm(f) : add(f)
|
||||
add.each do |file|
|
||||
rugged.index.add(relativize(file))
|
||||
end
|
||||
|
||||
rm.each do |file|
|
||||
rugged.index.remove(relativize(file))
|
||||
end
|
||||
|
||||
# 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 }
|
||||
end
|
||||
|
||||
def add(file)
|
||||
rugged.index.add(relativize(file))
|
||||
end
|
||||
|
||||
def rm(file)
|
||||
rugged.index.remove(relativize(file))
|
||||
end
|
||||
|
||||
# Garbage collection
|
||||
#
|
||||
# @return [Boolean]
|
||||
def gc
|
||||
env = { 'PATH' => '/usr/bin', 'LANG' => ENV['LANG'], 'HOME' => path }
|
||||
cmd = 'git gc'
|
||||
git_sh("git", "gc")
|
||||
end
|
||||
|
||||
r = nil
|
||||
Dir.chdir(path) do
|
||||
Open3.popen2e(env, cmd, unsetenv_others: true) do |_, _, t|
|
||||
r = t.value
|
||||
end
|
||||
end
|
||||
|
||||
r&.success?
|
||||
# Pushea cambios al repositorio remoto
|
||||
#
|
||||
# @param :remote [Rugged::Remote]
|
||||
# @return [Boolean, nil]
|
||||
def push(remote = origin)
|
||||
remote.push(rugged.head.canonical_name, credentials: credentials_for(remote))
|
||||
git_sh('git', 'lfs', 'push', remote.name, default_branch)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# @deprecated
|
||||
def credentials
|
||||
@credentials ||= credentials_for(origin)
|
||||
end
|
||||
|
||||
# Si Sutty tiene una llave privada de tipo ED25519, devuelve las
|
||||
# credenciales necesarias para trabajar con repositorios remotos.
|
||||
#
|
||||
# @param :remote [Rugged::Remote]
|
||||
# @return [Nil, Rugged::Credentials::SshKey]
|
||||
def credentials
|
||||
def credentials_for(remote)
|
||||
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
|
||||
|
||||
# @return [String]
|
||||
|
@ -192,5 +220,20 @@ class Site
|
|||
def relativize(file)
|
||||
Pathname.new(file).relative_path_from(Pathname.new(path)).to_s
|
||||
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
|
||||
|
|
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
|
|
@ -10,6 +10,7 @@ class Usuarie < ApplicationRecord
|
|||
|
||||
validates_uniqueness_of :email
|
||||
validates_with EmailAddress::ActiveRecordValidator, field: :email
|
||||
validate :locale_available!
|
||||
|
||||
before_create :lang_from_locale!
|
||||
before_update :remove_confirmation_invitation_inconsistencies!
|
||||
|
@ -78,4 +79,15 @@ class Usuarie < ApplicationRecord
|
|||
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
|
||||
|
|
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
|
|
@ -23,12 +23,15 @@ class CleanupService
|
|||
#
|
||||
# @return [nil]
|
||||
def cleanup_older_sites!
|
||||
Site.where('updated_at < ?', before).find_each do |site|
|
||||
Site.where('updated_at < ?', before).order(updated_at: :desc).find_each do |site|
|
||||
next unless File.directory? site.path
|
||||
|
||||
Rails.logger.info "Limpiando dependencias, archivos temporales y repositorio git de #{site.name}"
|
||||
|
||||
site.deploys.find_each(&:cleanup!)
|
||||
|
||||
site.repository.gc
|
||||
lfs_cleanup
|
||||
site.touch
|
||||
end
|
||||
end
|
||||
|
@ -37,11 +40,20 @@ class CleanupService
|
|||
#
|
||||
# @return [nil]
|
||||
def cleanup_newer_sites!
|
||||
Site.where('updated_at >= ?', before).find_each do |site|
|
||||
Site.where('updated_at >= ?', before).order(updated_at: :desc).find_each do |site|
|
||||
next unless File.directory? site.path
|
||||
|
||||
Rails.logger.info "Limpiando repositorio git de #{site.name}"
|
||||
|
||||
site.repository.gc
|
||||
lfs_cleanup
|
||||
site.touch
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def lfs_cleanup
|
||||
site.repository.git_sh("git", "lfs", "prune")
|
||||
site.repository.git_sh("git", "lfs", "dedup")
|
||||
end
|
||||
end
|
||||
|
|
|
@ -22,7 +22,7 @@ class LfsObjectService
|
|||
Site::Writer.new(site: site, file: path, content: pointer).save
|
||||
|
||||
# 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
|
||||
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?
|
||||
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!
|
||||
|
||||
|
@ -34,7 +34,7 @@ PostService = Struct.new(:site, :usuarie, :post, :params, keyword_init: true) do
|
|||
# Los artículos anónimos siempre son borradores
|
||||
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
|
||||
end
|
||||
|
||||
|
@ -42,11 +42,17 @@ PostService = Struct.new(:site, :usuarie, :post, :params, keyword_init: true) do
|
|||
post.usuaries << usuarie
|
||||
params[:post][:draft] = true if site.invitade? usuarie
|
||||
|
||||
# Es importante que el artículo se guarde primero y luego los
|
||||
# relacionados.
|
||||
commit(action: :updated, file: update_related_posts) if post.update(post_params)
|
||||
# Eliminar ("mover") el archivo si cambió de ubicación.
|
||||
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
|
||||
# errores
|
||||
|
@ -56,7 +62,7 @@ PostService = Struct.new(:site, :usuarie, :post, :params, keyword_init: true) do
|
|||
def destroy
|
||||
post.destroy!
|
||||
|
||||
commit(action: :destroyed) if post.destroyed?
|
||||
commit(action: :destroyed, rm: [post.path.absolute]) if post.destroyed?
|
||||
|
||||
post
|
||||
end
|
||||
|
@ -85,17 +91,19 @@ PostService = Struct.new(:site, :usuarie, :post, :params, keyword_init: true) do
|
|||
|
||||
# TODO: Implementar transacciones!
|
||||
posts.save_all(validate: false) &&
|
||||
commit(action: :reorder, file: files)
|
||||
commit(action: :reorder, add: files)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def commit(action:, file: nil)
|
||||
site.repository.commit(file: file || post.path.absolute,
|
||||
def commit(action:, add: [], rm: [])
|
||||
site.repository.commit(add: add,
|
||||
rm: rm,
|
||||
usuarie: usuarie,
|
||||
remove: action == :destroyed,
|
||||
message: I18n.t("post_service.#{action}",
|
||||
title: post&.title&.value))
|
||||
|
||||
GitPushJob.perform_later(site)
|
||||
end
|
||||
|
||||
# Solo permitir cambiar estos atributos de cada articulo
|
||||
|
|
|
@ -33,6 +33,7 @@ SiteService = Struct.new(:site, :usuarie, :params, keyword_init: true) do
|
|||
add_licencias &&
|
||||
add_code_of_conduct &&
|
||||
add_privacy_policy &&
|
||||
site.index_posts! &&
|
||||
deploy
|
||||
end
|
||||
|
||||
|
@ -54,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.
|
||||
def build_deploys
|
||||
Site::DEPLOYS.map { |deploy| "Deploy#{deploy.to_s.camelcase}" }
|
||||
.each do |deploy|
|
||||
next if site.deploys.find_by type: deploy
|
||||
Deploy.subclasses.each do |deploy|
|
||||
next if site.deploys.find_by type: deploy.name
|
||||
|
||||
site.deploys.build type: deploy
|
||||
end
|
||||
|
@ -94,9 +94,11 @@ SiteService = Struct.new(:site, :usuarie, :params, keyword_init: true) do
|
|||
def commit_config(action:)
|
||||
site.repository
|
||||
.commit(usuarie: usuarie,
|
||||
file: site.config.path,
|
||||
add: [site.config.path],
|
||||
message: I18n.t("site_service.#{action}",
|
||||
name: site.name))
|
||||
|
||||
GitPushJob.perform_later(site)
|
||||
end
|
||||
|
||||
def add_role(temporal: true, rol: 'invitade')
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
- row[:urls].each do |url|
|
||||
%tr
|
||||
%th{ scope: 'row' }= row[:title]
|
||||
%td= link_to_if url.present?, url, url, class: 'word-break-all'
|
||||
%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
|
||||
%td= row[:title]
|
||||
%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
|
||||
%time{ datetime: row[:seconds][:machine] }= row[:seconds][:human]
|
||||
%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/
|
15
app/views/env/index.js.haml
vendored
15
app/views/env/index.js.haml
vendored
|
@ -1,7 +1,8 @@
|
|||
= cache @site do
|
||||
:plain
|
||||
window.env = {
|
||||
AIRBRAKE_SITE_ID: #{@site.id},
|
||||
AIRBRAKE_API_KEY: "#{@site.airbrake_api_key}",
|
||||
PANEL_URL: "#{ENV['PANEL_URL']}"
|
||||
}
|
||||
- if @site
|
||||
= cache @site do
|
||||
:plain
|
||||
window.env = {
|
||||
AIRBRAKE_SITE_ID: #{@site.id},
|
||||
AIRBRAKE_API_KEY: "#{@site.airbrake_api_key}",
|
||||
PANEL_URL: "#{ENV['PANEL_URL']}"
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
- flash.each do |type, message|
|
||||
- unless type == 'js'
|
||||
= render 'bootstrap/alert' do
|
||||
= message
|
||||
= sanitize_markdown message, tags: %w[a strong em]
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
%script{ type: 'text/javascript', src: '/env.js' }
|
||||
= csrf_meta_tags
|
||||
= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload'
|
||||
= stylesheet_link_tag 'dark', rel: 'alternate stylesheet', media: 'all', 'data-turbolinks-track': 'reload', title: t('dark')
|
||||
= javascript_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'
|
||||
|
|
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
|
|
@ -84,7 +84,10 @@
|
|||
%button.btn{ data: { action: 'reorder#top' } }= t('posts.reorder.top')
|
||||
%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
|
||||
- dir = @site.data.dig(params[:locale], 'dir')
|
||||
- size = @posts.size
|
||||
|
|
|
@ -46,36 +46,37 @@
|
|||
.invalid-feedback= site.errors.messages[:description].join(', ')
|
||||
%hr/
|
||||
|
||||
.form-group#design_id
|
||||
%h2= t('.design.title')
|
||||
%p.lead= t('.help.design')
|
||||
- if invalid? site, :design_id
|
||||
= render 'bootstrap/alert' do
|
||||
= t('activerecord.errors.models.site.attributes.design_id.layout_incompatible.help',
|
||||
layouts: site.incompatible_layouts.to_sentence)
|
||||
.row.row-cols-1.row-cols-md-2.designs
|
||||
-# Demasiado complejo para un f.collection_radio_buttons
|
||||
- Design.all.order(priority: :desc).each do |design|
|
||||
.design.col.d-flex.flex-column
|
||||
.custom-control.custom-radio
|
||||
= f.radio_button :design_id, design.id,
|
||||
checked: design.id == site.design_id,
|
||||
disabled: design.disabled,
|
||||
required: true, class: 'custom-control-input'
|
||||
= f.label "design_id_#{design.id}", design.name,
|
||||
class: 'custom-control-label'
|
||||
.flex-fill
|
||||
= sanitize_markdown design.description,
|
||||
tags: %w[p a strong em]
|
||||
- unless site.persisted?
|
||||
.form-group#design_id
|
||||
%h2= t('.design.title')
|
||||
%p.lead= t('.help.design')
|
||||
- if invalid? site, :design_id
|
||||
= render 'bootstrap/alert' do
|
||||
= t('activerecord.errors.models.site.attributes.design_id.layout_incompatible.help',
|
||||
layouts: site.incompatible_layouts.to_sentence)
|
||||
.row.row-cols-1.row-cols-md-2.designs
|
||||
-# Demasiado complejo para un f.collection_radio_buttons
|
||||
- Design.all.order(priority: :desc).each do |design|
|
||||
.design.col.d-flex.flex-column
|
||||
.custom-control.custom-radio
|
||||
= f.radio_button :design_id, design.id,
|
||||
checked: design.id == site.design_id,
|
||||
disabled: design.disabled,
|
||||
required: true, class: 'custom-control-input'
|
||||
= f.label "design_id_#{design.id}", design.name,
|
||||
class: 'custom-control-label'
|
||||
.flex-fill
|
||||
= sanitize_markdown design.description,
|
||||
tags: %w[p a strong em]
|
||||
|
||||
.btn-group{ role: 'group', 'aria-label': t('.design.actions') }
|
||||
- if design.url
|
||||
= link_to t('.design.url'), design.url,
|
||||
target: '_blank', class: 'btn'
|
||||
- if design.license
|
||||
= link_to t('.design.license'), design.license,
|
||||
target: '_blank', class: 'btn'
|
||||
%hr/
|
||||
.btn-group{ role: 'group', 'aria-label': t('.design.actions') }
|
||||
- if design.url
|
||||
= link_to t('.design.url'), design.url,
|
||||
target: '_blank', class: 'btn'
|
||||
- if design.license
|
||||
= link_to t('.design.license'), design.license,
|
||||
target: '_blank', class: 'btn'
|
||||
%hr/
|
||||
|
||||
.form-group.licenses#license_id
|
||||
%h2= t('.licencia.title')
|
||||
|
|
|
@ -54,10 +54,4 @@
|
|||
text: t('usuaries.index.title'),
|
||||
type: 'info',
|
||||
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
|
||||
|
|
|
@ -39,6 +39,14 @@ module Sutty
|
|||
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
|
||||
# Load application's model / class decorators
|
||||
Dir.glob(File.join(File.dirname(__FILE__), '..', 'app', '**', '*_decorator.rb')).sort.each do |c|
|
||||
|
|
|
@ -64,11 +64,6 @@ Rails.application.configure do
|
|||
# Use a different cache store in production.
|
||||
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
|
||||
|
||||
# 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.middleware.use ExceptionNotification::Rack, gitlab: {}, ignore_exceptions: (['DeployJob::DeployAlreadyRunningException'] + ExceptionNotifier.ignored_exceptions)
|
||||
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[:protocol] = 'https'
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
String.include CoreExtensions::String::StripTags
|
||||
Jekyll::Document.include CoreExtensions::Jekyll::Document::Path
|
||||
Jekyll::DataReader.include Jekyll::Readers::DataReaderDecorator
|
||||
|
||||
# Definir tags de Liquid que provienen de complementos para que siempre
|
||||
# devuelvan contenido vacío.
|
||||
|
@ -37,6 +38,19 @@ end
|
|||
#
|
||||
# TODO: Aplicar monkey patches en otro lado...
|
||||
module Jekyll
|
||||
Configuration.class_eval do
|
||||
# No agregar colecciones por defecto, solo las que digamos en base a
|
||||
# los idiomas. Esto remueve la colección "posts".
|
||||
#
|
||||
# Las colecciones de idiomas son agregadas por Site.
|
||||
#
|
||||
# @see Site#configuration
|
||||
# @return [Jekyll::Configuration]
|
||||
def add_default_collections
|
||||
self
|
||||
end
|
||||
end
|
||||
|
||||
Site.class_eval do
|
||||
def configure_theme
|
||||
self.theme = nil
|
||||
|
@ -57,9 +71,27 @@ module Jekyll
|
|||
# No necesitamos los archivos estáticos
|
||||
def retrieve_static_files(_, _); end
|
||||
|
||||
# Solo lee los datos
|
||||
# Solo lee los datos, desde la plantilla y luego desde el sitio,
|
||||
# usando rutas absolutas, para evitar el uso confuso de Dir.chdir.
|
||||
#
|
||||
# Reemplaza jekyll-data también!
|
||||
#
|
||||
# @return [Hash]
|
||||
def read_data
|
||||
@site.data = DataReader.new(site).read(site.config['data_dir'])
|
||||
@site.data =
|
||||
begin
|
||||
reader = DataReader.new(site)
|
||||
theme_dir = site.in_theme_dir('_data')
|
||||
data_dir = site.in_source_dir(site.config['data_dir'])
|
||||
|
||||
if theme_dir
|
||||
reader.read_data_to(theme_dir, reader.content)
|
||||
reader.read_data_to(data_dir, reader.content)
|
||||
reader.content
|
||||
else
|
||||
reader.read data_dir
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Lee los layouts
|
||||
|
@ -91,6 +123,15 @@ module Jekyll
|
|||
spec.name == name
|
||||
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.canonical_segments[2] = 0
|
||||
base_path = Rails.root.join('_storage', 'gems', File.basename(site.source), 'ruby',
|
||||
|
@ -114,6 +155,11 @@ module Jekyll
|
|||
private
|
||||
|
||||
def gemspec; end
|
||||
|
||||
# @return [Symbol]
|
||||
def locale
|
||||
@locale ||= (site.config['locale'] || site.config['lang'] || I18n.locale).to_sym
|
||||
end
|
||||
end
|
||||
|
||||
# No necesitamos los archivos de la plantilla
|
||||
|
@ -161,14 +207,3 @@ module PgSearch
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
# JekyllData::Reader del plugin jekyll-data modifica Jekyll::Site#reader
|
||||
# para también leer los datos que vienen en el theme.
|
||||
module JekyllData
|
||||
Reader.class_eval do
|
||||
def read_data
|
||||
super
|
||||
read_theme_data
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
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
|
|
@ -4,5 +4,5 @@ ActiveJob::Serializers.add_serializers ActiveJob::Serializers::ExceptionSerializ
|
|||
|
||||
# Notificar los errores
|
||||
Que.error_notifier = proc do |error, job|
|
||||
ExceptionNotifier.notify_exception(error, data: (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, _, args|
|
||||
ExceptionNotifier.notify_exception(ex, data: args.last)
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
en:
|
||||
dark: Dark
|
||||
dir: ltr
|
||||
en: English
|
||||
es: Castellano
|
||||
|
@ -123,6 +124,10 @@ en:
|
|||
title: Distributed Web
|
||||
success: Success!
|
||||
error: Error
|
||||
deploy_social_distributed_press:
|
||||
title: Fediverse
|
||||
success: Success!
|
||||
error: Error
|
||||
deploy_reindex:
|
||||
title: Reindex
|
||||
success: Success!
|
||||
|
@ -167,6 +172,7 @@ en:
|
|||
usuarie: User
|
||||
licencia: License
|
||||
design: Design
|
||||
indexed_post: Indexed post
|
||||
attributes:
|
||||
usuarie:
|
||||
email: 'E-mail address'
|
||||
|
@ -191,9 +197,14 @@ en:
|
|||
deploys:
|
||||
deploy_local_presence: 'We need to be build the site!'
|
||||
design_id:
|
||||
missing_gem: "Site is configured to use %{theme} theme, but the corresponding gem is missing from Gemfile"
|
||||
layout_incompatible:
|
||||
error: "Design can't be changed because there are posts with incompatible layouts"
|
||||
help: "Your site has posts with layouts only compatible with the current design. If you change it, the site won't work as you expect. If you're trying out designs, you can delete posts in the following incompatible layouts:: %{layouts}."
|
||||
usuarie:
|
||||
attributes:
|
||||
lang:
|
||||
not_available: "This language is not yet available, would you help us by translating Sutty into it?"
|
||||
errors:
|
||||
argument_error: 'Argument `%{argument}` must be an instance of %{class}'
|
||||
unknown_locale: 'Unknown %{locale} locale'
|
||||
|
@ -307,6 +318,18 @@ en:
|
|||
indefinitely.
|
||||
|
||||
[Learn more](https://sutty.nl/learn-more-about-publish-to-dweb-functionality/)
|
||||
deploy_social_distributed_press:
|
||||
title: 'Publish on the Fediverse'
|
||||
help: |
|
||||
By using the ActivityPub protocol, people on the Fediverse
|
||||
([Mastodon](https://joinmastodon.org/servers),
|
||||
[Pixelfed](https://pixelfed.social/site/about), and
|
||||
[others](https://fediverse.party/)) can follow your site,
|
||||
receive news and interact with them.
|
||||
|
||||
There is also the possibility that Sutty advertises your content and/or Fediverse
|
||||
user automatically to attract followers. You can do it with this
|
||||
[form](https://cryptpad.fr/form/#/2/form/view/yp1KZwQjgU2RG-zhdQCyw4M8QhftNCVu8e+IJG2iN7Y/)
|
||||
stats:
|
||||
index:
|
||||
title: Statistics
|
||||
|
@ -407,15 +430,17 @@ en:
|
|||
title: 'Edit %{site}'
|
||||
submit: 'Save changes'
|
||||
btn: 'Configuration'
|
||||
update:
|
||||
post: "Your changes have been saved. **If you enabled a publication method, don't forget to publish changes.**"
|
||||
form:
|
||||
errors:
|
||||
title: There were errors and we couldn't save your changes :(
|
||||
help: Please, look for the invalid fields to fix them
|
||||
help:
|
||||
name: "The name of your site. It can only include numbers and letters."
|
||||
name: "This will be the host name for your site, ie. **example**.sutty.nl. Choose an expression up to 63 characters. It can contain only lowercase letters, numbers and dashes, **and no spaces**. It can't start or end with a dash, or be entirely composed of numbers."
|
||||
title: 'The title can be anything you want'
|
||||
description: 'You site description that appears in search engines. Between 50 and 160 characters.'
|
||||
design: 'Select the design for your site. You can change it later. We add more designs from time to time!'
|
||||
design: 'Select the design for your site. We add more designs from time to time!'
|
||||
licencia: 'Everything we publish has automatic copyright. This
|
||||
means nobody can use our works without explicit permission. By
|
||||
using licenses, we stablish conditions by which we want to share
|
||||
|
@ -466,6 +491,9 @@ en:
|
|||
success: 'Site upgrade has been completed. Your next build will run this upgrade :)'
|
||||
error: "There was an error when trying to upgrade your site. This could be due to conflicts that couldn't be solved automatically. A report of the issue has already been sent to our admins. Sorry for the inconvenience! :("
|
||||
message: 'Skeleton upgrade'
|
||||
webhooks:
|
||||
pull:
|
||||
message: 'Webhooks pull'
|
||||
footer:
|
||||
powered_by: 'is developed by'
|
||||
i18n:
|
||||
|
@ -512,6 +540,8 @@ en:
|
|||
feedback: 'This field cannot be empty!'
|
||||
uuid:
|
||||
label: 'Unique identifier'
|
||||
created_at:
|
||||
label: 'Created at'
|
||||
geo:
|
||||
uri: 'Open in app'
|
||||
osm: 'Open in web map'
|
||||
|
@ -685,7 +715,7 @@ en:
|
|||
new: 'Create'
|
||||
edit: 'Configure'
|
||||
posts:
|
||||
new: 'New %{layout}'
|
||||
new: 'Add %{layout}'
|
||||
edit: 'Editing'
|
||||
usuaries:
|
||||
index: 'Users'
|
||||
|
@ -709,3 +739,5 @@ en:
|
|||
build_stats:
|
||||
index:
|
||||
title: "Publications"
|
||||
indexed_posts:
|
||||
deleted: "Deleted indexed post %{path} from %{site} (records: %{records})"
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
es:
|
||||
dark: Oscuro
|
||||
es: Castellano
|
||||
en: English
|
||||
es-AR: Castellano Rioplatense
|
||||
|
@ -123,6 +124,10 @@ es:
|
|||
title: Web distribuida
|
||||
success: ¡Éxito!
|
||||
error: Hubo un error
|
||||
deploy_social_distributed_press:
|
||||
title: Fediverso
|
||||
success: ¡Éxito!
|
||||
error: Hubo un error
|
||||
deploy_reindex:
|
||||
title: Reindexación
|
||||
success: ¡Éxito!
|
||||
|
@ -167,6 +172,7 @@ es:
|
|||
usuarie: Usuarie
|
||||
licencia: Licencia
|
||||
design: Diseño
|
||||
indexed_post: Artículo indexado
|
||||
attributes:
|
||||
usuarie:
|
||||
email: 'Correo electrónico'
|
||||
|
@ -191,9 +197,14 @@ es:
|
|||
deploys:
|
||||
deploy_local_presence: '¡Necesitamos poder generar el sitio!'
|
||||
design_id:
|
||||
missing_gem: "El sitio usa la plantilla %{theme} pero la gema correspondiente no se encuentra en el Gemfile"
|
||||
layout_incompatible:
|
||||
error: 'No se puede cambiar la plantilla porque hay artículos con formatos incompatibles'
|
||||
help: 'En tu sitio hay artículos que solo son compatibles con el diseño actual, si cambias la plantilla el sitio no funcionará como esperas. Si estás probando plantillas, puedes eliminar los artículos en los formatos incompatibles: %{layouts}.'
|
||||
usuarie:
|
||||
attributes:
|
||||
lang:
|
||||
not_available: "Este idioma todavía no está disponible, ¿nos ayudas a agregarlo y mantenerlo?"
|
||||
errors:
|
||||
argument_error: 'El argumento `%{argument}` debe ser una instancia de %{class}'
|
||||
unknown_locale: 'El idioma %{locale} es desconocido'
|
||||
|
@ -312,6 +323,18 @@ es:
|
|||
copias de tu contenido indefinidamente.
|
||||
|
||||
[Saber más](https://sutty.nl/saber-mas-sobre-publicar-a-la-web-distribuida/)
|
||||
deploy_social_distributed_press:
|
||||
title: 'Publicar al Fediverso'
|
||||
help: |
|
||||
Utilizando el protocolo ActivityPub, otras personas en el
|
||||
Fediverso ([Mastodon](https://joinmastodon.org/servers),
|
||||
[Pixelfed](https://pixelfed.social/site/about) y
|
||||
[otros](https://fediverse.party/)) pueden seguir a tu sitio,
|
||||
recibir novedades e interactuar con ellas.
|
||||
|
||||
También existe la posibilidad de que Sutty anuncie tu contenido y/o usuarie del Fediverse
|
||||
en forma automática para atraer seguidorxs. Podés hacerlo con este
|
||||
[Formulario](https://cryptpad.fr/form/#/2/form/view/XorL4I-nC17rcEwtol3ghsRDsivfg6g5685MK+TFZ-8/)
|
||||
stats:
|
||||
index:
|
||||
title: Estadísticas
|
||||
|
@ -413,15 +436,17 @@ es:
|
|||
title: 'Editar %{site}'
|
||||
submit: 'Guardar cambios'
|
||||
btn: 'Configuración'
|
||||
update:
|
||||
post: "Tus cambios han sido guardados. **Si agregaste un método de publicación, no te olvides de publicar cambios.**"
|
||||
form:
|
||||
errors:
|
||||
title: Hubo errores y no pudimos guardar tus cambios :(
|
||||
help: Por favor, busca los campos marcados como no válidos para resolverlos
|
||||
help:
|
||||
name: 'El nombre de tu sitio que formará parte de la dirección (**ejemplo**.sutty.nl). Solo puede contener letras minúsculas, números y guiones.'
|
||||
name: 'El nombre de tu sitio que formará parte de la dirección (**ejemplo**.sutty.nl). Solo puede contener hasta 63 letras minúsculas, números y guiones, pero **sin espacios**. No puede empezar ni terminar con guión, ni estar compuesto enteramente por números.'
|
||||
title: 'El título de tu sitio puede ser lo que quieras.'
|
||||
description: 'La descripción del sitio, que saldrá en buscadores. Entre 50 y 160 caracteres.'
|
||||
design: 'Elegí el diseño que va a tener tu sitio aquí. Podés cambiarlo luego. De tanto en tanto vamos sumando diseños nuevos.'
|
||||
design: 'Elegí el diseño que va a tener tu sitio aquí. De tanto en tanto vamos sumando diseños nuevos.'
|
||||
licencia: 'Todo lo que publicamos posee automáticamente derechos
|
||||
de autore. Esto significa que nadie puede hacer uso de nuestras
|
||||
obras sin permiso explícito. Con las licencias establecemos
|
||||
|
@ -474,6 +499,9 @@ es:
|
|||
success: 'Ya se incorporaron los cambios en el sitio, se aplicarán en la próxima compilación que hagas :)'
|
||||
error: 'Hubo un error al incorporar los cambios en el sitio. Esto puede deberse a conflictos entre cambios que no se pueden resolver automáticamente. Hemos enviado un reporte del problema a les administradores de Sutty para que estén al tanto de la situación. ¡Lo sentimos! :('
|
||||
message: 'Actualización del esqueleto'
|
||||
webhooks:
|
||||
pull:
|
||||
message: 'Traer los cambios a partir de un evento remoto'
|
||||
footer:
|
||||
powered_by: 'es desarrollada por'
|
||||
i18n:
|
||||
|
@ -520,6 +548,8 @@ es:
|
|||
feedback: '¡Este campo no puede estar vacío!'
|
||||
uuid:
|
||||
label: 'Identificador único'
|
||||
created_at:
|
||||
label: 'Fecha de creación'
|
||||
geo:
|
||||
uri: 'Abrir en aplicación'
|
||||
osm: 'Abrir en mapa web'
|
||||
|
@ -693,7 +723,7 @@ es:
|
|||
new: 'Crear'
|
||||
edit: 'Configurar'
|
||||
posts:
|
||||
new: 'Nuevo %{layout}'
|
||||
new: 'Agregar %{layout}'
|
||||
edit: 'Editando'
|
||||
usuaries:
|
||||
index: 'Usuaries'
|
||||
|
@ -717,3 +747,5 @@ es:
|
|||
build_stats:
|
||||
index:
|
||||
title: "Publicaciones"
|
||||
indexed_posts:
|
||||
deleted: "Eliminado artículo %{path} de %{site} (filas: %{records})"
|
||||
|
|
|
@ -17,6 +17,8 @@ Rails.application.routes.draw do
|
|||
|
||||
get :'contact/cookie', to: 'invitades#contact_cookie'
|
||||
post :'contact/:form', to: 'contact#receive', as: :contact
|
||||
|
||||
post :'webhooks/pull', to: 'webhooks#pull'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
12
db/migrate/20230328231029_create_que_tables.rb
Normal file
12
db/migrate/20230328231029_create_que_tables.rb
Normal file
|
@ -0,0 +1,12 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Que
|
||||
class CreateQueTables < ActiveRecord::Migration[6.1]
|
||||
def up
|
||||
Que.migrate! version: 7
|
||||
end
|
||||
|
||||
def down
|
||||
Que.migrate! version: 0
|
||||
end
|
||||
end
|
8
db/migrate/20230519143500_add_pagination_to_site.rb
Normal file
8
db/migrate/20230519143500_add_pagination_to_site.rb
Normal file
|
@ -0,0 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Agrega la opción de paginación a los sitios
|
||||
class AddPaginationToSite < ActiveRecord::Migration[6.1]
|
||||
def change
|
||||
add_column :sites, :pagination, :boolean, default: false
|
||||
end
|
||||
end
|
12
db/migrate/20230731195050_add_token_to_roles.rb
Normal file
12
db/migrate/20230731195050_add_token_to_roles.rb
Normal file
|
@ -0,0 +1,12 @@
|
|||
class AddTokenToRoles < ActiveRecord::Migration[6.1]
|
||||
def up
|
||||
add_column :roles, :token, :string
|
||||
Rol.find_each do |m|
|
||||
m.update_column( :token, SecureRandom.hex(64) )
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
remove_column :roles, :token
|
||||
end
|
||||
end
|
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Almacena las llaves privadas de cada sitio
|
||||
class AddPrivateKeyPemCiphertextToSites < ActiveRecord::Migration[6.1]
|
||||
# Agrega la columna cifrada
|
||||
def change
|
||||
add_column :sites, :private_key_pem_ciphertext, :text
|
||||
end
|
||||
end
|
20
db/migrate/20230921155401_site_rename_origin_to_upstream.rb
Normal file
20
db/migrate/20230921155401_site_rename_origin_to_upstream.rb
Normal file
|
@ -0,0 +1,20 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Renombrar todos los repositorios que apunten a skel como su origin
|
||||
class SiteRenameOriginToUpstream < ActiveRecord::Migration[6.1]
|
||||
# Renombrar
|
||||
def up
|
||||
Site.find_each do |site|
|
||||
next unless site.repository.origin&.url == ENV['SKEL_SUTTY']
|
||||
|
||||
site.repository.rugged.remotes.rename('origin', 'upstream') do |_|
|
||||
Rails.logger.info "#{site.name}: renamed origin to upstream"
|
||||
end
|
||||
rescue Rugged::Error, Rugged::OSError => e
|
||||
Rails.logger.warn "#{site.name}: #{e.message}"
|
||||
end
|
||||
end
|
||||
|
||||
# No se puede deshacer
|
||||
def down; end
|
||||
end
|
|
@ -0,0 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Almacenar el último commit indexado
|
||||
class AddLastIndexedCommitToSites < ActiveRecord::Migration[6.1]
|
||||
def up
|
||||
add_column :sites, :last_indexed_commit, :string, null: true
|
||||
|
||||
Site.find_each do |site|
|
||||
site.update_columns(last_indexed_commit: site.repository.head_commit.oid)
|
||||
rescue Rugged::Error, Rugged::OSError => e
|
||||
puts "Falló #{site.name}, ignorando: #{e.message}"
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
remove_column :sites, :last_indexed_commit
|
||||
end
|
||||
end
|
400
db/schema.rb
400
db/schema.rb
|
@ -1,400 +0,0 @@
|
|||
# This file is auto-generated from the current state of the database. Instead
|
||||
# of editing this file, please use the migrations feature of Active Record to
|
||||
# incrementally modify your database, and then regenerate this schema definition.
|
||||
#
|
||||
# This file is the source Rails uses to define your schema when running `bin/rails
|
||||
# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
|
||||
# be faster and is potentially less error prone than running all of your
|
||||
# migrations from scratch. Old migrations may fail to apply correctly if those
|
||||
# migrations use external dependencies or application code.
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 2023_04_15_153231) do
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "pg_trgm"
|
||||
enable_extension "pgcrypto"
|
||||
enable_extension "plpgsql"
|
||||
|
||||
create_table "access_logs", id: :uuid, default: nil, force: :cascade do |t|
|
||||
t.string "host"
|
||||
t.float "msec"
|
||||
t.string "server_protocol"
|
||||
t.string "request_method"
|
||||
t.string "request_completion"
|
||||
t.string "uri"
|
||||
t.string "query_string"
|
||||
t.integer "status"
|
||||
t.string "sent_http_content_type"
|
||||
t.string "sent_http_content_encoding"
|
||||
t.string "sent_http_etag"
|
||||
t.string "sent_http_last_modified"
|
||||
t.string "http_accept"
|
||||
t.string "http_accept_encoding"
|
||||
t.string "http_accept_language"
|
||||
t.string "http_pragma"
|
||||
t.string "http_cache_control"
|
||||
t.string "http_if_none_match"
|
||||
t.string "http_dnt"
|
||||
t.string "http_user_agent"
|
||||
t.string "http_origin"
|
||||
t.float "request_time"
|
||||
t.integer "bytes_sent"
|
||||
t.integer "body_bytes_sent"
|
||||
t.integer "request_length"
|
||||
t.string "http_connection"
|
||||
t.string "pipe"
|
||||
t.integer "connection_requests"
|
||||
t.string "geoip2_data_country_name"
|
||||
t.string "geoip2_data_city_name"
|
||||
t.string "ssl_server_name"
|
||||
t.string "ssl_protocol"
|
||||
t.string "ssl_early_data"
|
||||
t.string "ssl_session_reused"
|
||||
t.string "ssl_curves"
|
||||
t.string "ssl_ciphers"
|
||||
t.string "ssl_cipher"
|
||||
t.string "sent_http_x_xss_protection"
|
||||
t.string "sent_http_x_frame_options"
|
||||
t.string "sent_http_x_content_type_options"
|
||||
t.string "sent_http_strict_transport_security"
|
||||
t.string "nginx_version"
|
||||
t.integer "pid"
|
||||
t.string "remote_user"
|
||||
t.boolean "crawler", default: false
|
||||
t.string "http_referer"
|
||||
t.datetime "created_at", precision: 6
|
||||
t.index ["geoip2_data_city_name"], name: "index_access_logs_on_geoip2_data_city_name"
|
||||
t.index ["geoip2_data_country_name"], name: "index_access_logs_on_geoip2_data_country_name"
|
||||
t.index ["host"], name: "index_access_logs_on_host"
|
||||
t.index ["http_origin"], name: "index_access_logs_on_http_origin"
|
||||
t.index ["http_user_agent"], name: "index_access_logs_on_http_user_agent"
|
||||
t.index ["status"], name: "index_access_logs_on_status"
|
||||
t.index ["uri"], name: "index_access_logs_on_uri"
|
||||
end
|
||||
|
||||
create_table "action_text_rich_texts", force: :cascade do |t|
|
||||
t.string "name", null: false
|
||||
t.text "body"
|
||||
t.string "record_type", null: false
|
||||
t.bigint "record_id", null: false
|
||||
t.datetime "created_at", precision: 6, null: false
|
||||
t.datetime "updated_at", precision: 6, null: false
|
||||
t.index ["record_type", "record_id", "name"], name: "index_action_text_rich_texts_uniqueness", unique: true
|
||||
end
|
||||
|
||||
create_table "active_storage_attachments", force: :cascade do |t|
|
||||
t.string "name", null: false
|
||||
t.string "record_type", null: false
|
||||
t.bigint "record_id", null: false
|
||||
t.bigint "blob_id", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id"
|
||||
t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true
|
||||
end
|
||||
|
||||
create_table "active_storage_blobs", force: :cascade do |t|
|
||||
t.string "key", null: false
|
||||
t.string "filename", null: false
|
||||
t.string "content_type"
|
||||
t.text "metadata"
|
||||
t.bigint "byte_size", null: false
|
||||
t.string "checksum", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.string "service_name", null: false
|
||||
t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true
|
||||
end
|
||||
|
||||
create_table "active_storage_variant_records", force: :cascade do |t|
|
||||
t.bigint "blob_id", null: false
|
||||
t.string "variation_digest", null: false
|
||||
t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true
|
||||
end
|
||||
|
||||
create_table "blazer_audits", force: :cascade do |t|
|
||||
t.bigint "user_id"
|
||||
t.bigint "query_id"
|
||||
t.text "statement"
|
||||
t.string "data_source"
|
||||
t.datetime "created_at"
|
||||
t.index ["query_id"], name: "index_blazer_audits_on_query_id"
|
||||
t.index ["user_id"], name: "index_blazer_audits_on_user_id"
|
||||
end
|
||||
|
||||
create_table "blazer_checks", force: :cascade do |t|
|
||||
t.bigint "creator_id"
|
||||
t.bigint "query_id"
|
||||
t.string "state"
|
||||
t.string "schedule"
|
||||
t.text "emails"
|
||||
t.text "slack_channels"
|
||||
t.string "check_type"
|
||||
t.text "message"
|
||||
t.datetime "last_run_at"
|
||||
t.datetime "created_at", precision: 6, null: false
|
||||
t.datetime "updated_at", precision: 6, null: false
|
||||
t.index ["creator_id"], name: "index_blazer_checks_on_creator_id"
|
||||
t.index ["query_id"], name: "index_blazer_checks_on_query_id"
|
||||
end
|
||||
|
||||
create_table "blazer_dashboard_queries", force: :cascade do |t|
|
||||
t.bigint "dashboard_id"
|
||||
t.bigint "query_id"
|
||||
t.integer "position"
|
||||
t.datetime "created_at", precision: 6, null: false
|
||||
t.datetime "updated_at", precision: 6, null: false
|
||||
t.index ["dashboard_id"], name: "index_blazer_dashboard_queries_on_dashboard_id"
|
||||
t.index ["query_id"], name: "index_blazer_dashboard_queries_on_query_id"
|
||||
end
|
||||
|
||||
create_table "blazer_dashboards", force: :cascade do |t|
|
||||
t.bigint "creator_id"
|
||||
t.text "name"
|
||||
t.datetime "created_at", precision: 6, null: false
|
||||
t.datetime "updated_at", precision: 6, null: false
|
||||
t.index ["creator_id"], name: "index_blazer_dashboards_on_creator_id"
|
||||
end
|
||||
|
||||
create_table "blazer_queries", force: :cascade do |t|
|
||||
t.bigint "creator_id"
|
||||
t.string "name"
|
||||
t.text "description"
|
||||
t.text "statement"
|
||||
t.string "data_source"
|
||||
t.datetime "created_at", precision: 6, null: false
|
||||
t.datetime "updated_at", precision: 6, null: false
|
||||
t.index ["creator_id"], name: "index_blazer_queries_on_creator_id"
|
||||
end
|
||||
|
||||
create_table "build_stats", force: :cascade do |t|
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.bigint "deploy_id"
|
||||
t.bigint "bytes"
|
||||
t.float "seconds"
|
||||
t.string "action", null: false
|
||||
t.text "log"
|
||||
t.boolean "status", default: false
|
||||
t.index ["deploy_id"], name: "index_build_stats_on_deploy_id"
|
||||
end
|
||||
|
||||
create_table "csp_reports", id: :uuid, default: nil, force: :cascade do |t|
|
||||
t.datetime "created_at", precision: 6, null: false
|
||||
t.datetime "updated_at", precision: 6, null: false
|
||||
t.string "disposition"
|
||||
t.string "referrer"
|
||||
t.string "blocked_uri"
|
||||
t.string "document_uri"
|
||||
t.string "effective_directive"
|
||||
t.string "original_policy"
|
||||
t.string "script_sample"
|
||||
t.string "status_code"
|
||||
t.string "violated_directive"
|
||||
t.integer "column_number"
|
||||
t.integer "line_number"
|
||||
t.string "source_file"
|
||||
end
|
||||
|
||||
create_table "deploys", force: :cascade do |t|
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.bigint "site_id"
|
||||
t.string "type"
|
||||
t.text "values"
|
||||
t.index ["site_id"], name: "index_deploys_on_site_id"
|
||||
t.index ["type"], name: "index_deploys_on_type"
|
||||
end
|
||||
|
||||
create_table "designs", force: :cascade do |t|
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.string "name"
|
||||
t.text "description"
|
||||
t.string "gem"
|
||||
t.string "url"
|
||||
t.string "license"
|
||||
t.boolean "disabled", default: false
|
||||
t.text "credits"
|
||||
t.string "designer_url"
|
||||
t.integer "priority"
|
||||
end
|
||||
|
||||
create_table "indexed_posts", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
|
||||
t.bigint "site_id"
|
||||
t.datetime "created_at", precision: 6, null: false
|
||||
t.datetime "updated_at", precision: 6, null: false
|
||||
t.string "locale", default: "simple"
|
||||
t.string "layout", null: false
|
||||
t.string "path", null: false
|
||||
t.string "title", default: ""
|
||||
t.jsonb "front_matter", default: "{}"
|
||||
t.string "content", default: ""
|
||||
t.tsvector "indexed_content"
|
||||
t.integer "order", default: 0
|
||||
t.string "dictionary"
|
||||
t.index ["front_matter"], name: "index_indexed_posts_on_front_matter", using: :gin
|
||||
t.index ["indexed_content"], name: "index_indexed_posts_on_indexed_content", using: :gin
|
||||
t.index ["layout"], name: "index_indexed_posts_on_layout"
|
||||
t.index ["locale"], name: "index_indexed_posts_on_locale"
|
||||
t.index ["site_id"], name: "index_indexed_posts_on_site_id"
|
||||
end
|
||||
|
||||
create_table "licencias", force: :cascade do |t|
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.string "name"
|
||||
t.text "description"
|
||||
t.text "deed"
|
||||
t.string "url"
|
||||
t.string "icons"
|
||||
end
|
||||
|
||||
create_table "log_entries", force: :cascade do |t|
|
||||
t.datetime "created_at", precision: 6, null: false
|
||||
t.datetime "updated_at", precision: 6, null: false
|
||||
t.bigint "site_id"
|
||||
t.text "text"
|
||||
t.boolean "sent", default: false
|
||||
t.index ["site_id"], name: "index_log_entries_on_site_id"
|
||||
end
|
||||
|
||||
create_table "maintenances", force: :cascade do |t|
|
||||
t.datetime "created_at", precision: 6, null: false
|
||||
t.datetime "updated_at", precision: 6, null: false
|
||||
t.text "message"
|
||||
t.datetime "estimated_from"
|
||||
t.datetime "estimated_to"
|
||||
t.boolean "are_we_back", default: false
|
||||
end
|
||||
|
||||
create_table "mobility_string_translations", force: :cascade do |t|
|
||||
t.string "locale", null: false
|
||||
t.string "key", null: false
|
||||
t.string "value"
|
||||
t.string "translatable_type"
|
||||
t.bigint "translatable_id"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["translatable_id", "translatable_type", "key"], name: "index_mobility_string_translations_on_translatable_attribute"
|
||||
t.index ["translatable_id", "translatable_type", "locale", "key"], name: "index_mobility_string_translations_on_keys", unique: true
|
||||
t.index ["translatable_type", "key", "value", "locale"], name: "index_mobility_string_translations_on_query_keys"
|
||||
end
|
||||
|
||||
create_table "mobility_text_translations", force: :cascade do |t|
|
||||
t.string "locale", null: false
|
||||
t.string "key", null: false
|
||||
t.text "value"
|
||||
t.string "translatable_type"
|
||||
t.bigint "translatable_id"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["translatable_id", "translatable_type", "key"], name: "index_mobility_text_translations_on_translatable_attribute"
|
||||
t.index ["translatable_id", "translatable_type", "locale", "key"], name: "index_mobility_text_translations_on_keys", unique: true
|
||||
end
|
||||
|
||||
create_table "roles", force: :cascade do |t|
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.bigint "site_id"
|
||||
t.bigint "usuarie_id"
|
||||
t.string "rol"
|
||||
t.boolean "temporal"
|
||||
t.index ["site_id", "usuarie_id"], name: "index_roles_on_site_id_and_usuarie_id", unique: true
|
||||
t.index ["site_id"], name: "index_roles_on_site_id"
|
||||
t.index ["usuarie_id"], name: "index_roles_on_usuarie_id"
|
||||
end
|
||||
|
||||
create_table "rollups", force: :cascade do |t|
|
||||
t.string "name", null: false
|
||||
t.string "interval", null: false
|
||||
t.datetime "time", null: false
|
||||
t.jsonb "dimensions", default: {}, null: false
|
||||
t.float "value"
|
||||
t.index ["name", "interval", "time", "dimensions"], name: "index_rollups_on_name_and_interval_and_time_and_dimensions", unique: true
|
||||
end
|
||||
|
||||
create_table "sites", force: :cascade do |t|
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.string "name"
|
||||
t.bigint "design_id"
|
||||
t.bigint "licencia_id"
|
||||
t.string "status", default: "waiting"
|
||||
t.text "description"
|
||||
t.string "title"
|
||||
t.boolean "colaboracion_anonima", default: false
|
||||
t.boolean "contact", default: false
|
||||
t.string "private_key_ciphertext"
|
||||
t.boolean "acepta_invitades", default: false
|
||||
t.string "tienda_api_key_ciphertext", default: ""
|
||||
t.string "tienda_url", default: ""
|
||||
t.string "api_key_ciphertext"
|
||||
t.index ["design_id"], name: "index_sites_on_design_id"
|
||||
t.index ["licencia_id"], name: "index_sites_on_licencia_id"
|
||||
t.index ["name"], name: "index_sites_on_name", unique: true
|
||||
end
|
||||
|
||||
create_table "stats", force: :cascade do |t|
|
||||
t.datetime "created_at", precision: 6, null: false
|
||||
t.datetime "updated_at", precision: 6, null: false
|
||||
t.bigint "site_id"
|
||||
t.string "name", null: false
|
||||
t.index ["name"], name: "index_stats_on_name", using: :hash
|
||||
t.index ["site_id"], name: "index_stats_on_site_id"
|
||||
end
|
||||
|
||||
create_table "usuaries", force: :cascade do |t|
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.string "email", default: "", null: false
|
||||
t.string "encrypted_password", default: "", null: false
|
||||
t.string "reset_password_token"
|
||||
t.datetime "reset_password_sent_at"
|
||||
t.datetime "remember_created_at"
|
||||
t.string "confirmation_token"
|
||||
t.datetime "confirmed_at"
|
||||
t.datetime "confirmation_sent_at"
|
||||
t.string "unconfirmed_email"
|
||||
t.integer "failed_attempts", default: 0, null: false
|
||||
t.string "unlock_token"
|
||||
t.datetime "locked_at"
|
||||
t.boolean "acepta_politicas_de_privacidad", default: false
|
||||
t.string "invitation_token"
|
||||
t.datetime "invitation_created_at"
|
||||
t.datetime "invitation_sent_at"
|
||||
t.datetime "invitation_accepted_at"
|
||||
t.integer "invitation_limit"
|
||||
t.string "invited_by_type"
|
||||
t.bigint "invited_by_id"
|
||||
t.integer "invitations_count", default: 0
|
||||
t.string "lang", default: "es"
|
||||
t.index ["confirmation_token"], name: "index_usuaries_on_confirmation_token", unique: true
|
||||
t.index ["email"], name: "index_usuaries_on_email", unique: true
|
||||
t.index ["invitation_token"], name: "index_usuaries_on_invitation_token", unique: true
|
||||
t.index ["invitations_count"], name: "index_usuaries_on_invitations_count"
|
||||
t.index ["invited_by_id"], name: "index_usuaries_on_invited_by_id"
|
||||
t.index ["invited_by_type", "invited_by_id"], name: "index_usuaries_on_invited_by"
|
||||
t.index ["reset_password_token"], name: "index_usuaries_on_reset_password_token", unique: true
|
||||
t.index ["unlock_token"], name: "index_usuaries_on_unlock_token", unique: true
|
||||
end
|
||||
|
||||
add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id"
|
||||
add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id"
|
||||
|
||||
create_trigger("indexed_posts_before_insert_update_row_tr", :compatibility => 1).
|
||||
on("indexed_posts").
|
||||
before(:insert, :update) do
|
||||
<<-SQL_ACTIONS
|
||||
new.indexed_content := to_tsvector(('pg_catalog.' || new.dictionary)::regconfig, coalesce(new.title, '') || '
|
||||
' || coalesce(new.content,''));
|
||||
SQL_ACTIONS
|
||||
end
|
||||
|
||||
create_trigger("access_logs_before_insert_row_tr", :compatibility => 1).
|
||||
on("access_logs").
|
||||
before(:insert) do
|
||||
"new.created_at := to_timestamp(new.msec);"
|
||||
end
|
||||
|
||||
end
|
18
db/seeds.rb
18
db/seeds.rb
|
@ -16,12 +16,14 @@ licencias.each do |l|
|
|||
licencia.update l
|
||||
end
|
||||
|
||||
unless Rails.env.test?
|
||||
YAML.safe_load(File.read('db/seeds/sites.yml')).each do |site|
|
||||
site = Site.find_or_create_by name: site['name']
|
||||
|
||||
site.update licencia: Licencia.first, design: Design.first,
|
||||
title: site.name, description: 'x' * 50,
|
||||
deploys: site.deploys.empty? ? [DeployLocal.new] : site.deploys
|
||||
if CodeOfConduct.count.zero?
|
||||
YAML.safe_load(File.read('db/seeds/codes_of_conduct.yml')).each do |coc|
|
||||
CodeOfConduct.new(**coc).save!
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if PrivacyPolicy.count.zero?
|
||||
YAML.safe_load(File.read('db/seeds/privacy_policies.yml')).each do |pp|
|
||||
PrivacyPolicy.new(**pp).save!
|
||||
end
|
||||
end
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
- name_en: 'Minima'
|
||||
name_es: 'Mínima'
|
||||
gem: 'sutty-minima'
|
||||
url: 'https://0xacab.org/sutty/jekyll/minima'
|
||||
url: 'https://minima.sutty.nl/'
|
||||
description_en: "Sutty Minima is based on [Minima](https://jekyll.github.io/minima/), a blog-focused theme for Jekyll."
|
||||
description_es: 'Sutty Mínima es una plantilla para blogs basada en [Mínima](https://jekyll.github.io/minima/).'
|
||||
license: 'https://0xacab.org/sutty/jekyll/minima/-/blob/master/LICENSE.txt'
|
||||
|
@ -75,6 +75,14 @@
|
|||
credits_en: 'This template was made in collaboration with Librenauta in 15 hours!'
|
||||
designer_url: 'https://copiona.com/donaunbit/'
|
||||
priority: '70'
|
||||
- name_en: 'Magazine'
|
||||
name_es: 'Revista'
|
||||
gem: 'compost-jekyll-theme'
|
||||
url: 'https://two.compost.digital/'
|
||||
description_en: 'A theme to create multimedia publications, based in COMPOST magazine'
|
||||
description_es: 'Plantilla para crear publicaciones multimedia, basada en COMPOST magazine'
|
||||
license: 'https://0xacab.org/sutty/jekyll/compost-jekyll-theme/-/blob/no-masters/LICENSE.txt'
|
||||
priority: '40'
|
||||
- name_en: 'Resource toolkit'
|
||||
name_es: 'Recursero'
|
||||
gem: 'recursero-jekyll-theme'
|
||||
|
|
2323
db/structure.sql
Normal file
2323
db/structure.sql
Normal file
File diff suppressed because it is too large
Load diff
|
@ -2,10 +2,9 @@
|
|||
|
||||
namespace :cleanup do
|
||||
desc 'Cleanup sites'
|
||||
task everything: :environment do
|
||||
task everything: :environment do |_, args|
|
||||
before = ENV.fetch('BEFORE', '30').to_i.days.ago
|
||||
service = CleanupService.new(before: before)
|
||||
|
||||
service.cleanup_everything!
|
||||
CleanupJob.perform_later(before)
|
||||
end
|
||||
end
|
||||
|
|
11
monit.conf
11
monit.conf
|
@ -19,7 +19,10 @@ check program stats
|
|||
every "0 1 * * *"
|
||||
if status != 0 then alert
|
||||
|
||||
check program distributed_press_tokens_renew
|
||||
with path "/usr/bin/foreman run -f /srv/Procfile -d /srv distributed_press_tokens_renew" as uid "rails" gid "www-data"
|
||||
every "0 3 * * *"
|
||||
if status != 0 then alert
|
||||
check filesystem root with path /
|
||||
if space usage > 80% for 5 times within 15 cycles then alert
|
||||
if space usage > 90% for 5 cycles then exec "/usr/bin/foreman run -f /srv/Procfile -d /srv emergency_cleanup" as uid "rails" gid "www-data"
|
||||
|
||||
check process que with pidfile /srv/tmp/que.pid
|
||||
start program = "/usr/bin/foreman run -f /srv/Procfile -d /srv que"
|
||||
stop program = "/bin/sh -c 'cat /srv/tmp/que.pid | xargs -r kill'"
|
||||
|
|
BIN
public/assets/.sprockets-manifest-c6294bb290dcb7473076f4de99ce9c00.json
(Stored with Git LFS)
Normal file
BIN
public/assets/.sprockets-manifest-c6294bb290dcb7473076f4de99ce9c00.json
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
public/assets/.sprockets-manifest-c6294bb290dcb7473076f4de99ce9c00.json.br
(Stored with Git LFS)
Normal file
BIN
public/assets/.sprockets-manifest-c6294bb290dcb7473076f4de99ce9c00.json.br
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
public/assets/activestorage-9f749d32771691cb3104d21c1c9926e52c04c48d8730498df4aaa39b0adfb7ba.js
(Stored with Git LFS)
Normal file
BIN
public/assets/activestorage-9f749d32771691cb3104d21c1c9926e52c04c48d8730498df4aaa39b0adfb7ba.js
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
public/assets/activestorage-9f749d32771691cb3104d21c1c9926e52c04c48d8730498df4aaa39b0adfb7ba.js.br
(Stored with Git LFS)
Normal file
BIN
public/assets/activestorage-9f749d32771691cb3104d21c1c9926e52c04c48d8730498df4aaa39b0adfb7ba.js.br
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
public/assets/activestorage-9f749d32771691cb3104d21c1c9926e52c04c48d8730498df4aaa39b0adfb7ba.js.gz
(Stored with Git LFS)
Normal file
BIN
public/assets/activestorage-9f749d32771691cb3104d21c1c9926e52c04c48d8730498df4aaa39b0adfb7ba.js.gz
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
public/assets/application-dc5234b33b6c06db79ab79114f5b85952f35482ce5b97061ce9402b2a8a4cd19.css
(Stored with Git LFS)
Normal file
BIN
public/assets/application-dc5234b33b6c06db79ab79114f5b85952f35482ce5b97061ce9402b2a8a4cd19.css
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
public/assets/application-dc5234b33b6c06db79ab79114f5b85952f35482ce5b97061ce9402b2a8a4cd19.css.br
(Stored with Git LFS)
Normal file
BIN
public/assets/application-dc5234b33b6c06db79ab79114f5b85952f35482ce5b97061ce9402b2a8a4cd19.css.br
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
public/assets/application-dc5234b33b6c06db79ab79114f5b85952f35482ce5b97061ce9402b2a8a4cd19.css.gz
(Stored with Git LFS)
Normal file
BIN
public/assets/application-dc5234b33b6c06db79ab79114f5b85952f35482ce5b97061ce9402b2a8a4cd19.css.gz
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
public/assets/arrows-alt-v-89a34626be855d3b1c3199cd75f62cf6327678eed1e126f74b7cbc6de3502606.svg
(Stored with Git LFS)
Normal file
BIN
public/assets/arrows-alt-v-89a34626be855d3b1c3199cd75f62cf6327678eed1e126f74b7cbc6de3502606.svg
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
public/assets/arrows-alt-v-89a34626be855d3b1c3199cd75f62cf6327678eed1e126f74b7cbc6de3502606.svg.br
(Stored with Git LFS)
Normal file
BIN
public/assets/arrows-alt-v-89a34626be855d3b1c3199cd75f62cf6327678eed1e126f74b7cbc6de3502606.svg.br
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
public/assets/arrows-alt-v-89a34626be855d3b1c3199cd75f62cf6327678eed1e126f74b7cbc6de3502606.svg.gz
(Stored with Git LFS)
Normal file
BIN
public/assets/arrows-alt-v-89a34626be855d3b1c3199cd75f62cf6327678eed1e126f74b7cbc6de3502606.svg.gz
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
public/assets/blazer/application-59c73da0ca1f2fd8dd42765f0a172ae513546920ad9fa0718c15d9b10a4f18dd.css
(Stored with Git LFS)
Normal file
BIN
public/assets/blazer/application-59c73da0ca1f2fd8dd42765f0a172ae513546920ad9fa0718c15d9b10a4f18dd.css
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
public/assets/blazer/application-59c73da0ca1f2fd8dd42765f0a172ae513546920ad9fa0718c15d9b10a4f18dd.css.br
(Stored with Git LFS)
Normal file
BIN
public/assets/blazer/application-59c73da0ca1f2fd8dd42765f0a172ae513546920ad9fa0718c15d9b10a4f18dd.css.br
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
public/assets/blazer/application-59c73da0ca1f2fd8dd42765f0a172ae513546920ad9fa0718c15d9b10a4f18dd.css.gz
(Stored with Git LFS)
Normal file
BIN
public/assets/blazer/application-59c73da0ca1f2fd8dd42765f0a172ae513546920ad9fa0718c15d9b10a4f18dd.css.gz
(Stored with Git LFS)
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue