mirror of
https://0xacab.org/sutty/sutty
synced 2024-11-22 15:46:21 +00:00
Merge branch 'rails' of 0xacab.org:sutty/sutty into issue-7537
This commit is contained in:
commit
8cd0048109
148 changed files with 6035 additions and 3028 deletions
|
@ -1,8 +1,8 @@
|
||||||
|
NODE_OPTIONS=--openssl-legacy-provider
|
||||||
# pwgen -1 32
|
# pwgen -1 32
|
||||||
RAILS_MASTER_KEY=11111111111111111111111111111111
|
RAILS_MASTER_KEY=11111111111111111111111111111111
|
||||||
RAILS_GROUPS=assets
|
RAILS_GROUPS=assets
|
||||||
DELEGATE=athshe.sutty.nl
|
DELEGATE=panel.sutty.nl
|
||||||
HAINISH=../haini.sh/haini.sh
|
|
||||||
DATABASE_URL=postgres://suttier@postgresql.sutty.local/sutty
|
DATABASE_URL=postgres://suttier@postgresql.sutty.local/sutty
|
||||||
RAILS_ENV=development
|
RAILS_ENV=development
|
||||||
IMAP_SERVER=
|
IMAP_SERVER=
|
||||||
|
@ -37,3 +37,5 @@ AIRBRAKE_API_KEY=
|
||||||
GITLAB_URI=https://0xacab.org
|
GITLAB_URI=https://0xacab.org
|
||||||
GITLAB_PROJECT=
|
GITLAB_PROJECT=
|
||||||
GITLAB_TOKEN=
|
GITLAB_TOKEN=
|
||||||
|
PGVER=15
|
||||||
|
PGPID=/run/postgresql.pid
|
1
.env.development
Normal file
1
.env.development
Normal file
|
@ -0,0 +1 @@
|
||||||
|
HAINISH=../haini.sh/haini.sh
|
6
.gitignore
vendored
6
.gitignore
vendored
|
@ -28,7 +28,7 @@
|
||||||
/data/*
|
/data/*
|
||||||
/_storage/*
|
/_storage/*
|
||||||
|
|
||||||
.env*
|
.env.*
|
||||||
|
|
||||||
# Ignore master key for decrypting credentials and more.
|
# Ignore master key for decrypting credentials and more.
|
||||||
/config/master.key
|
/config/master.key
|
||||||
|
@ -48,3 +48,7 @@ yarn-debug.log*
|
||||||
/yarn-error.log
|
/yarn-error.log
|
||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
.yarn-integrity
|
.yarn-integrity
|
||||||
|
|
||||||
|
/.task
|
||||||
|
/.yardoc
|
||||||
|
/public/doc/
|
||||||
|
|
111
.gitlab-ci.yml
111
.gitlab-ci.yml
|
@ -1,33 +1,104 @@
|
||||||
image: "gitea.nulo.in/sutty/panel:3.14.10-2.7.8-panel.sutty.nl"
|
.apk-add: &apk-add
|
||||||
|
- "apk add go-task diffutils gitlab_ci_log_section"
|
||||||
|
.disable-hainish: &disable-hainish
|
||||||
|
- "rm -f .env.development"
|
||||||
|
.cache-ruby: &cache-ruby
|
||||||
|
- paths:
|
||||||
|
- "vendor/ruby"
|
||||||
|
- ".bundle"
|
||||||
|
.cache-node: &cache-node
|
||||||
|
- paths:
|
||||||
|
- "node_modules"
|
||||||
|
.cache-task: &cache-task
|
||||||
|
- paths:
|
||||||
|
- ".task"
|
||||||
|
image: "registry.0xacab.org/sutty/sutty:3.17.3-3.1.4-rails"
|
||||||
variables:
|
variables:
|
||||||
RAILS_ENV: "production"
|
RAILS_ENV: "production"
|
||||||
LC_ALL: "C.UTF-8"
|
LC_ALL: "C.UTF-8"
|
||||||
|
HAINISH: ""
|
||||||
cache:
|
cache:
|
||||||
paths:
|
|
||||||
- "vendor/ruby"
|
|
||||||
assets:
|
assets:
|
||||||
stage: "build"
|
stage: "deploy"
|
||||||
rules:
|
only:
|
||||||
- if: "$CI_COMMIT_BRANCH == \"panel.sutty.nl\""
|
- "rails"
|
||||||
- if: "$CI_COMMIT_BRANCH"
|
- "17.3.alpine.panel.sutty.nl"
|
||||||
changes:
|
except:
|
||||||
compare_to: "refs/heads/rails"
|
- "schedules"
|
||||||
paths:
|
cache:
|
||||||
- "package.json"
|
- *cache-ruby
|
||||||
- "app/javascript/**/*"
|
- *cache-node
|
||||||
- "app/assets/**/*"
|
- *cache-task
|
||||||
before_script:
|
before_script:
|
||||||
|
- "gitlab_ci_log_section --name git --header=\"Configuring git\""
|
||||||
- "git config --global user.email \"${GIT_USER_EMAIL:-$GITLAB_USER_EMAIL}\""
|
- "git config --global user.email \"${GIT_USER_EMAIL:-$GITLAB_USER_EMAIL}\""
|
||||||
- "git config --global user.name \"${GIT_USER_NAME:-$GITLAB_USER_NAME}\""
|
- "git config --global user.name \"${GIT_USER_NAME:-$GITLAB_USER_NAME}\""
|
||||||
- "git remote set-url --push origin \"https://${GITLAB_USERNAME}:${GITLAB_TOKEN}@${CI_SERVER_HOST}/${CI_PROJECT_PATH}.git\""
|
- "git remote set-url --push origin \"https://${GITLAB_USERNAME}:${GITLAB_TOKEN}@${CI_SERVER_HOST}/${CI_PROJECT_PATH}.git\""
|
||||||
- "apk add python2 dotenv brotli"
|
- "gitlab_ci_log_section --name git --end"
|
||||||
- "mv config/credentials.yml.enc.ci config/credentials.yml.enc"
|
- "gitlab_ci_log_section --name apk --header=\"Installing dependencies\""
|
||||||
- "cp .env.example .env"
|
- "apk add brotli"
|
||||||
- "dotenv bundle install --path=vendor"
|
- *apk-add
|
||||||
|
- *disable-hainish
|
||||||
|
- "gitlab_ci_log_section --name apk --end"
|
||||||
script:
|
script:
|
||||||
- "dotenv RAILS_ENV=production bundle exec rails webpacker:clobber"
|
- "gitlab_ci_log_section --name assets --header=\"Building\""
|
||||||
- "dotenv RAILS_ENV=production bundle exec rails assets:precompile"
|
- "go-task assets"
|
||||||
- "dotenv RAILS_ENV=production bundle exec rails assets:clean"
|
|
||||||
after_script:
|
after_script:
|
||||||
- "git add public && git commit -m \"ci: assets [skip ci]\""
|
- "git add public && git commit -m \"ci: assets [skip ci]\""
|
||||||
- "git push -o ci.skip"
|
- "git push -o ci.skip"
|
||||||
|
gem-audit:
|
||||||
|
stage: "test"
|
||||||
|
only:
|
||||||
|
- "schedules"
|
||||||
|
cache:
|
||||||
|
- *cache-ruby
|
||||||
|
before_script:
|
||||||
|
- *apk-add
|
||||||
|
- *disable-hainish
|
||||||
|
script:
|
||||||
|
- "go-task gem-audit"
|
||||||
|
node-audit:
|
||||||
|
stage: "test"
|
||||||
|
only:
|
||||||
|
- "schedules"
|
||||||
|
cache:
|
||||||
|
- *cache-node
|
||||||
|
before_script:
|
||||||
|
- *apk-add
|
||||||
|
- *disable-hainish
|
||||||
|
script:
|
||||||
|
- "apk add go-task"
|
||||||
|
- "go-task node-audit"
|
||||||
|
brakeman:
|
||||||
|
stage: "test"
|
||||||
|
cache:
|
||||||
|
- *cache-ruby
|
||||||
|
rules:
|
||||||
|
- if: "$CI_PIPELINE_SOURCE == 'merge_request_event'"
|
||||||
|
before_script:
|
||||||
|
- *apk-add
|
||||||
|
- *disable-hainish
|
||||||
|
script:
|
||||||
|
- "go-task bundle -- exec brakeman"
|
||||||
|
rubocop:
|
||||||
|
stage: "test"
|
||||||
|
cache:
|
||||||
|
- *cache-ruby
|
||||||
|
rules:
|
||||||
|
- if: "$CI_PIPELINE_SOURCE == 'merge_request_event'"
|
||||||
|
before_script:
|
||||||
|
- *apk-add
|
||||||
|
- *disable-hainish
|
||||||
|
script:
|
||||||
|
- "./bin/modified_files | ./bin/with_extension rb | xargs -r go-task bundle -- exec rubocop"
|
||||||
|
haml:
|
||||||
|
stage: "test"
|
||||||
|
cache:
|
||||||
|
- *cache-ruby
|
||||||
|
rules:
|
||||||
|
- if: "$CI_PIPELINE_SOURCE == 'merge_request_event'"
|
||||||
|
before_script:
|
||||||
|
- *apk-add
|
||||||
|
- *disable-hainish
|
||||||
|
script:
|
||||||
|
- "./bin/modified_files | ./bin/with_extension haml | xargs -r go-task bundle -- exec haml-lint"
|
||||||
|
|
|
@ -19,7 +19,6 @@ pipeline:
|
||||||
when:
|
when:
|
||||||
branch:
|
branch:
|
||||||
- "rails"
|
- "rails"
|
||||||
- "panel.sutty.nl"
|
|
||||||
- "17.3.alpine.panel.sutty.nl"
|
- "17.3.alpine.panel.sutty.nl"
|
||||||
event: "push"
|
event: "push"
|
||||||
path:
|
path:
|
||||||
|
@ -27,57 +26,8 @@ pipeline:
|
||||||
- "Dockerfile"
|
- "Dockerfile"
|
||||||
- ".dockerignore"
|
- ".dockerignore"
|
||||||
- ".woodpecker.yml"
|
- ".woodpecker.yml"
|
||||||
assets:
|
|
||||||
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/"
|
|
||||||
- "echo \"$${KNOWN_HOSTS}\" | base64 -d >> ~/.ssh/known_hosts"
|
|
||||||
- "chmod 600 ~/.ssh/known_hosts"
|
|
||||||
- "eval $(ssh-agent -s)"
|
|
||||||
- "echo \"$${SSH_KEY}\" | base64 -d | ssh-add -"
|
|
||||||
- "ssh $${ORIGIN%:*}"
|
|
||||||
- "git config user.name Woodpecker"
|
|
||||||
- "git config user.email ci@sutty.coop.ar"
|
|
||||||
- "git remote add upstream $${ORIGIN}"
|
|
||||||
- "git checkout -B ${CI_COMMIT_BRANCH}"
|
|
||||||
- "mv config/credentials.yml.enc.ci config/credentials.yml.enc"
|
|
||||||
- "yarn"
|
|
||||||
- "cp .env.example .env"
|
|
||||||
- "dotenv bundle install --path=vendor"
|
|
||||||
- "dotenv RAILS_ENV=production bundle exec rails webpacker:clobber"
|
|
||||||
- "dotenv RAILS_ENV=production bundle exec rails assets:precompile"
|
|
||||||
- "dotenv RAILS_ENV=production bundle exec rails assets:clean"
|
|
||||||
- "find public -type f -print0 | xargs -r0 brotli -k9f"
|
|
||||||
- "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"
|
|
||||||
- "ORIGIN"
|
|
||||||
when:
|
|
||||||
branch:
|
|
||||||
- "rails"
|
|
||||||
- "panel.sutty.nl"
|
|
||||||
path:
|
|
||||||
include:
|
|
||||||
- "app/assets/**/*"
|
|
||||||
- "app/javascript/**/*"
|
|
||||||
- "package.json"
|
|
||||||
- "yarn.lock"
|
|
||||||
matrix:
|
|
||||||
ALPINE_VERSION: "3.14.10"
|
|
||||||
RUBY_VERSION: "2.7"
|
|
||||||
RUBY_PATCH: "8"
|
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- ALPINE_VERSION: "3.17.3"
|
- ALPINE_VERSION: "3.17.3"
|
||||||
RUBY_VERSION: "3.1"
|
RUBY_VERSION: "3.1"
|
||||||
RUBY_PATCH: "4"
|
RUBY_PATCH: "4"
|
||||||
- ALPINE_VERSION: "3.14.10"
|
|
||||||
RUBY_VERSION: "2.7"
|
|
||||||
RUBY_PATCH: "8"
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
ARG RUBY_VERSION=2.7
|
ARG RUBY_VERSION=3.1
|
||||||
ARG RUBY_PATCH=6
|
ARG RUBY_PATCH=4
|
||||||
ARG ALPINE_VERSION=3.13.10
|
ARG ALPINE_VERSION=3.17.3
|
||||||
ARG BASE_IMAGE=registry.nulo.in/sutty/rails
|
ARG BASE_IMAGE=registry.nulo.in/sutty/rails
|
||||||
FROM ${BASE_IMAGE}:${ALPINE_VERSION}-${RUBY_VERSION}.${RUBY_PATCH}
|
FROM ${BASE_IMAGE}:${ALPINE_VERSION}-${RUBY_VERSION}.${RUBY_PATCH}
|
||||||
ARG PANDOC_VERSION=2.18
|
ARG PANDOC_VERSION=2.18
|
||||||
|
|
7
Gemfile
7
Gemfile
|
@ -4,8 +4,6 @@ source ENV.fetch('GEMS_SOURCE', 'https://17.3.alpine.gems.sutty.nl')
|
||||||
|
|
||||||
ruby "~> #{ENV.fetch('RUBY_VERSION', '3.1')}"
|
ruby "~> #{ENV.fetch('RUBY_VERSION', '3.1')}"
|
||||||
|
|
||||||
gem 'dotenv-rails', require: 'dotenv/rails-now'
|
|
||||||
|
|
||||||
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
|
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
|
||||||
gem 'rails', '~> 6.1.0'
|
gem 'rails', '~> 6.1.0'
|
||||||
# Use Puma as the app server
|
# Use Puma as the app server
|
||||||
|
@ -40,7 +38,6 @@ gem 'devise'
|
||||||
gem 'devise-i18n'
|
gem 'devise-i18n'
|
||||||
gem 'devise_invitable'
|
gem 'devise_invitable'
|
||||||
gem 'distributed-press-api-client', '~> 0.3.0rc0'
|
gem 'distributed-press-api-client', '~> 0.3.0rc0'
|
||||||
gem 'njalla-api-client', '~> 0.2.0'
|
|
||||||
gem 'email_address', git: 'https://github.com/fauno/email_address', branch: 'i18n'
|
gem 'email_address', git: 'https://github.com/fauno/email_address', branch: 'i18n'
|
||||||
gem 'exception_notification'
|
gem 'exception_notification'
|
||||||
gem 'fast_blank'
|
gem 'fast_blank'
|
||||||
|
@ -53,7 +50,6 @@ gem 'inline_svg'
|
||||||
gem 'httparty'
|
gem 'httparty'
|
||||||
gem 'safe_yaml', require: false
|
gem 'safe_yaml', require: false
|
||||||
gem 'jekyll', '~> 4.2.0'
|
gem 'jekyll', '~> 4.2.0'
|
||||||
gem 'jekyll-data'
|
|
||||||
gem 'jekyll-commonmark', '~> 1.4.0'
|
gem 'jekyll-commonmark', '~> 1.4.0'
|
||||||
gem 'jekyll-images'
|
gem 'jekyll-images'
|
||||||
gem 'jekyll-include-cache'
|
gem 'jekyll-include-cache'
|
||||||
|
@ -110,6 +106,7 @@ end
|
||||||
|
|
||||||
group :development, :test do
|
group :development, :test do
|
||||||
gem 'derailed_benchmarks'
|
gem 'derailed_benchmarks'
|
||||||
|
gem 'dotenv-rails'
|
||||||
gem 'pry'
|
gem 'pry'
|
||||||
# Adds support for Capybara system testing and selenium driver
|
# Adds support for Capybara system testing and selenium driver
|
||||||
gem 'capybara', '~> 2.13'
|
gem 'capybara', '~> 2.13'
|
||||||
|
@ -118,7 +115,9 @@ group :development, :test do
|
||||||
end
|
end
|
||||||
|
|
||||||
group :development do
|
group :development do
|
||||||
|
gem 'yard'
|
||||||
gem 'brakeman'
|
gem 'brakeman'
|
||||||
|
gem 'bundler-audit'
|
||||||
gem 'haml-lint', require: false
|
gem 'haml-lint', require: false
|
||||||
gem 'letter_opener'
|
gem 'letter_opener'
|
||||||
gem 'listen'
|
gem 'listen'
|
||||||
|
|
15
Gemfile.lock
15
Gemfile.lock
|
@ -106,6 +106,9 @@ GEM
|
||||||
sassc-rails (>= 2.0.0)
|
sassc-rails (>= 2.0.0)
|
||||||
brakeman (5.4.1)
|
brakeman (5.4.1)
|
||||||
builder (3.2.4)
|
builder (3.2.4)
|
||||||
|
bundler-audit (0.9.1)
|
||||||
|
bundler (>= 1.2.0, < 3)
|
||||||
|
thor (~> 1.0)
|
||||||
capybara (2.18.0)
|
capybara (2.18.0)
|
||||||
addressable
|
addressable
|
||||||
mini_mime (>= 0.1.3)
|
mini_mime (>= 0.1.3)
|
||||||
|
@ -287,8 +290,6 @@ GEM
|
||||||
terminal-table (~> 2.0)
|
terminal-table (~> 2.0)
|
||||||
jekyll-commonmark (1.4.0)
|
jekyll-commonmark (1.4.0)
|
||||||
commonmarker (~> 0.22)
|
commonmarker (~> 0.22)
|
||||||
jekyll-data (1.1.2)
|
|
||||||
jekyll (>= 3.3, < 5.0.0)
|
|
||||||
jekyll-images (0.4.1)
|
jekyll-images (0.4.1)
|
||||||
jekyll (~> 4)
|
jekyll (~> 4)
|
||||||
ruby-filemagic (~> 0.7)
|
ruby-filemagic (~> 0.7)
|
||||||
|
@ -366,9 +367,6 @@ GEM
|
||||||
net-ssh (7.1.0)
|
net-ssh (7.1.0)
|
||||||
netaddr (2.0.6)
|
netaddr (2.0.6)
|
||||||
nio4r (2.5.9-x86_64-linux-musl)
|
nio4r (2.5.9-x86_64-linux-musl)
|
||||||
njalla-api-client (0.2.0)
|
|
||||||
dry-schema
|
|
||||||
httparty (~> 0.18)
|
|
||||||
nokogiri (1.15.4-x86_64-linux-musl)
|
nokogiri (1.15.4-x86_64-linux-musl)
|
||||||
mini_portile2 (~> 2.8.2)
|
mini_portile2 (~> 2.8.2)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
|
@ -541,7 +539,7 @@ GEM
|
||||||
temple (0.10.1)
|
temple (0.10.1)
|
||||||
terminal-table (2.0.0)
|
terminal-table (2.0.0)
|
||||||
unicode-display_width (~> 1.1, >= 1.1.1)
|
unicode-display_width (~> 1.1, >= 1.1.1)
|
||||||
thor (1.2.2)
|
thor (1.3.0)
|
||||||
tilt (2.1.0)
|
tilt (2.1.0)
|
||||||
timecop (0.9.6)
|
timecop (0.9.6)
|
||||||
timeout (0.3.2)
|
timeout (0.3.2)
|
||||||
|
@ -579,6 +577,7 @@ GEM
|
||||||
websocket-extensions (0.1.5)
|
websocket-extensions (0.1.5)
|
||||||
xpath (3.2.0)
|
xpath (3.2.0)
|
||||||
nokogiri (~> 1.8)
|
nokogiri (~> 1.8)
|
||||||
|
yard (0.9.34)
|
||||||
zeitwerk (2.6.8)
|
zeitwerk (2.6.8)
|
||||||
|
|
||||||
PLATFORMS
|
PLATFORMS
|
||||||
|
@ -590,6 +589,7 @@ DEPENDENCIES
|
||||||
blazer
|
blazer
|
||||||
bootstrap (~> 4)
|
bootstrap (~> 4)
|
||||||
brakeman
|
brakeman
|
||||||
|
bundler-audit
|
||||||
capybara (~> 2.13)
|
capybara (~> 2.13)
|
||||||
chartkick
|
chartkick
|
||||||
commonmarker
|
commonmarker
|
||||||
|
@ -623,7 +623,6 @@ DEPENDENCIES
|
||||||
jbuilder (~> 2.5)
|
jbuilder (~> 2.5)
|
||||||
jekyll (~> 4.2.0)
|
jekyll (~> 4.2.0)
|
||||||
jekyll-commonmark (~> 1.4.0)
|
jekyll-commonmark (~> 1.4.0)
|
||||||
jekyll-data
|
|
||||||
jekyll-images
|
jekyll-images
|
||||||
jekyll-include-cache
|
jekyll-include-cache
|
||||||
kaminari
|
kaminari
|
||||||
|
@ -636,7 +635,6 @@ DEPENDENCIES
|
||||||
mini_magick
|
mini_magick
|
||||||
mobility
|
mobility
|
||||||
net-ssh
|
net-ssh
|
||||||
njalla-api-client (~> 0.2.0)
|
|
||||||
nokogiri
|
nokogiri
|
||||||
pg
|
pg
|
||||||
pg_search
|
pg_search
|
||||||
|
@ -676,6 +674,7 @@ DEPENDENCIES
|
||||||
web-console
|
web-console
|
||||||
webpacker
|
webpacker
|
||||||
yaml_db!
|
yaml_db!
|
||||||
|
yard
|
||||||
|
|
||||||
RUBY VERSION
|
RUBY VERSION
|
||||||
ruby 3.1.4p223
|
ruby 3.1.4p223
|
||||||
|
|
140
Makefile
140
Makefile
|
@ -1,140 +0,0 @@
|
||||||
SHELL := /bin/bash
|
|
||||||
.DEFAULT_GOAL := help
|
|
||||||
|
|
||||||
# Copiar el archivo de configuración y avisar cuando hay que
|
|
||||||
# actualizarlo.
|
|
||||||
.env: .env.example
|
|
||||||
@test -f $@ || cp -v $< $@
|
|
||||||
@test -f $@ && echo "Revisa $@ para actualizarlo con respecto a $<"
|
|
||||||
@test -f $@ && diff -auN --color $@ $<
|
|
||||||
|
|
||||||
include .env
|
|
||||||
|
|
||||||
export
|
|
||||||
|
|
||||||
# XXX: El espacio antes del comentario cuenta como espacio
|
|
||||||
args ?=## Argumentos para Hain
|
|
||||||
commit ?= origin/rails## Commit desde el que actualizar
|
|
||||||
env ?= staging## Entorno del nodo delegado
|
|
||||||
sutty ?= $(SUTTY)## Dirección local
|
|
||||||
delegate ?= $(DELEGATE)## Cambia el nodo delegado
|
|
||||||
hain ?= ENV_FILE=.env $(HAINISH)## Ubicación de Hainish
|
|
||||||
|
|
||||||
# El nodo delegado tiene dos entornos, production y staging.
|
|
||||||
# Dependiendo del entorno que elijamos, se van a generar los assets y el
|
|
||||||
# contenedor y subirse a un servidor u otro. No utilizamos CI/CD (aún).
|
|
||||||
#
|
|
||||||
# Production es el entorno de panel.sutty.nl
|
|
||||||
ifeq ($(env),production)
|
|
||||||
container ?= panel
|
|
||||||
## TODO: Cambiar a otra cosa
|
|
||||||
branch ?= rails
|
|
||||||
public ?= public
|
|
||||||
endif
|
|
||||||
|
|
||||||
# Staging es el entorno de panel.staging.sutty.nl
|
|
||||||
ifeq ($(env),staging)
|
|
||||||
container := staging
|
|
||||||
branch := staging
|
|
||||||
public := staging
|
|
||||||
endif
|
|
||||||
|
|
||||||
help: always ## Ayuda
|
|
||||||
@echo -e "Sutty\n" | sed -re "s/^.*/\x1B[38;5;197m&\x1B[0m/"
|
|
||||||
@echo -e "Servidor: https://panel.$(SUTTY_WITH_PORT)/\n"
|
|
||||||
@echo -e "Uso: make TAREA args=\"ARGUMENTOS\"\n"
|
|
||||||
@echo -e "Tareas:\n"
|
|
||||||
@grep -E "^[a-z\-]+:.*##" Makefile | sed -re "s/(.*):.*##(.*)/\1;\2/" | column -s ";" -t | sed -re "s/^([^ ]+) /\x1B[38;5;197m\1\x1B[0m/"
|
|
||||||
@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/"
|
|
||||||
|
|
||||||
test: always ## Ejecutar los tests
|
|
||||||
$(MAKE) rake args="test RAILS_ENV=test $(args)"
|
|
||||||
|
|
||||||
postgresql: /etc/hosts ## Iniciar la base de datos
|
|
||||||
pgrep postgres >/dev/null || $(hain) postgresql
|
|
||||||
|
|
||||||
serve-js: /etc/hosts node_modules ## Iniciar el servidor de desarrollo de Javascript
|
|
||||||
$(hain) 'bundle exec ./bin/webpack-dev-server'
|
|
||||||
|
|
||||||
serve: /etc/hosts postgresql Gemfile.lock ## Iniciar el servidor de desarrollo de Rails
|
|
||||||
$(MAKE) rails args=server
|
|
||||||
|
|
||||||
rails: ## Corre rails dentro del entorno de desarrollo (pasar argumentos con args=).
|
|
||||||
$(MAKE) bundle args="exec rails $(args)"
|
|
||||||
|
|
||||||
rake: ## Corre rake dentro del entorno de desarrollo (pasar argumentos con args=).
|
|
||||||
$(MAKE) bundle args="exec rake $(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
|
|
||||||
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)
|
|
||||||
|
|
||||||
rubocop: ## Yutea el código que está por ser commiteado
|
|
||||||
git status --porcelain \
|
|
||||||
| grep -E "^(A|M)" \
|
|
||||||
| sed "s/^...//" \
|
|
||||||
| grep ".rb$$" \
|
|
||||||
| ../haini.sh/haini.sh "xargs -r ./bin/rubocop --auto-correct"
|
|
||||||
|
|
||||||
audit: ## Encuentra dependencias con vulnerabilidades
|
|
||||||
$(hain) 'gem install bundler-audit'
|
|
||||||
$(hain) 'bundle audit --update'
|
|
||||||
|
|
||||||
brakeman: ## Busca posibles vulnerabilidades en Sutty
|
|
||||||
$(MAKE) bundle args='exec brakeman'
|
|
||||||
|
|
||||||
yarn: ## Tareas de yarn
|
|
||||||
$(hain) 'yarn $(args)'
|
|
||||||
|
|
||||||
clean: ## Limpieza
|
|
||||||
rm -rf _sites/test-* _deploy/test-* log/*.log tmp/cache tmp/letter_opener tmp/miniprofiler tmp/storage
|
|
||||||
|
|
||||||
build: Gemfile.lock ## Generar la imagen Docker
|
|
||||||
time docker build --build-arg="BRANCH=$(branch)" --build-arg="RAILS_MASTER_KEY=`cat config/master.key`" -t sutty/$(container) .
|
|
||||||
docker tag sutty/$(container):latest sutty:keep
|
|
||||||
@echo -e "\a"
|
|
||||||
|
|
||||||
save: ## Subir la imagen Docker al nodo delegado
|
|
||||||
time docker save sutty/$(container):latest | ssh root@$(delegate) docker load
|
|
||||||
date +%F | xargs -I {} git tag -f $(container)-{}
|
|
||||||
@echo -e "\a"
|
|
||||||
|
|
||||||
ota: ## Actualizar Rails en el nodo delegado
|
|
||||||
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
|
|
||||||
|
|
||||||
# Correr un test en particular por ejemplo
|
|
||||||
# `make test/models/usuarie_test.rb`
|
|
||||||
tests := $(shell find test/ -name "*_test.rb")
|
|
||||||
$(tests): always
|
|
||||||
$(MAKE) test args="TEST=$@"
|
|
||||||
|
|
||||||
# Agrega las direcciones locales al sistema
|
|
||||||
/etc/hosts: always
|
|
||||||
@echo "Chequeando si es necesario agregar el dominio local $(SUTTY)"
|
|
||||||
@grep -q " $(SUTTY)$$" $@ || echo -e "127.0.0.1 $(SUTTY)\n::1 $(SUTTY)" | sudo tee -a $@
|
|
||||||
@grep -q " api.$(SUTTY)$$" $@ || echo -e "127.0.0.1 api.$(SUTTY)\n::1 api.$(SUTTY)" | sudo tee -a $@
|
|
||||||
@grep -q " panel.$(SUTTY)$$" $@ || echo -e "127.0.0.1 panel.$(SUTTY)\n::1 panel.$(SUTTY)" | sudo tee -a $@
|
|
||||||
@grep -q " postgresql.$(SUTTY)$$" $@ || echo -e "127.0.0.1 postgresql.$(SUTTY)\n::1 postgresql.$(SUTTY)" | sudo tee -a $@
|
|
||||||
|
|
||||||
# Instala las dependencias de Javascript
|
|
||||||
node_modules: package.json
|
|
||||||
$(MAKE) yarn
|
|
||||||
|
|
||||||
# Instala las dependencias de Rails
|
|
||||||
Gemfile.lock: Gemfile
|
|
||||||
$(MAKE) bundle args=install
|
|
||||||
|
|
||||||
.PHONY: always
|
|
1
Procfile
1
Procfile
|
@ -7,5 +7,6 @@ blazer: bundle exec rake blazer:send_failing_checks
|
||||||
prometheus: bundle exec prometheus_exporter -b 0.0.0.0 --prefix "sutty_"
|
prometheus: bundle exec prometheus_exporter -b 0.0.0.0 --prefix "sutty_"
|
||||||
distributed_press_tokens_renew: bundle exec rake distributed_press:tokens:renew
|
distributed_press_tokens_renew: bundle exec rake distributed_press:tokens:renew
|
||||||
cleanup: bundle exec rake cleanup:everything
|
cleanup: bundle exec rake cleanup:everything
|
||||||
|
emergency_cleanup: bundle exec rake cleanup:everything BEFORE=7
|
||||||
stats: bundle exec rake stats:process_all
|
stats: bundle exec rake stats:process_all
|
||||||
que: daemonize -c /srv/ -p /srv/tmp/que.pid -u rails /usr/local/bin/syslogize bundle exec que
|
que: daemonize -c /srv/ -p /srv/tmp/que.pid -u rails /usr/local/bin/syslogize bundle exec que
|
||||||
|
|
51
README.md
51
README.md
|
@ -17,14 +17,32 @@ Para más información visita el [sitio de Sutty](https://sutty.nl/).
|
||||||
|
|
||||||
### Desarrollar
|
### Desarrollar
|
||||||
|
|
||||||
Todas las tareas se gestionan con `make`, por favor instala GNU Make
|
Para facilitar la gestión de dependencias y entorno de desarrollo,
|
||||||
antes de comenzar.
|
instala [haini.sh](https://0xacab.org/sutty/haini.sh)
|
||||||
|
|
||||||
|
|
||||||
|
Todas las tareas se gestionan con `go-task`. [Instrucciones de
|
||||||
|
instalación (en inglés)](https://taskfile.dev/installation/)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
make help
|
go-task
|
||||||
```
|
```
|
||||||
|
|
||||||
[Leer la documentación](https://docs.sutty.nl/)
|
### Variables de entorno
|
||||||
|
|
||||||
|
Las variables de entorno por defecto se encuentran en el archivo `.env`.
|
||||||
|
Para modificar las opciones, crear o modificar el archivo `.env.local`
|
||||||
|
con valores distintos.
|
||||||
|
|
||||||
|
### Documentación
|
||||||
|
|
||||||
|
Para navegar la documentación del código usando YARD:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go-task doc serve
|
||||||
|
```
|
||||||
|
|
||||||
|
Y luego navegar a <https://panel.sutty.local:3000/doc/>
|
||||||
|
|
||||||
## English
|
## English
|
||||||
|
|
||||||
|
@ -39,10 +57,29 @@ For more information, visit [Sutty's website](https://sutty.nl/en/).
|
||||||
|
|
||||||
### Development
|
### Development
|
||||||
|
|
||||||
Every task is run via `make`, please install GNU Make before developing.
|
|
||||||
|
To facilitate dependencies and dev environment, install
|
||||||
|
[haini.sh](https://0xacab.org/sutty/haini.sh)
|
||||||
|
|
||||||
|
Every task is run via `go-task`. [Installation
|
||||||
|
instructions](https://taskfile.dev/installation/).
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
make help
|
go-task
|
||||||
```
|
```
|
||||||
|
|
||||||
[Read the documentation](https://docs.sutty.nl/en/)
|
### Environment variables
|
||||||
|
|
||||||
|
Default env vars are store on `.env`. For local options, copy them to
|
||||||
|
`.env.local`.
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
To browse documentation using YARD:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go-task doc serve
|
||||||
|
```
|
||||||
|
|
||||||
|
And then open <https://panel.sutty.local:3000/doc/>
|
||||||
|
|
||||||
|
|
185
Taskfile.yaml
Normal file
185
Taskfile.yaml
Normal file
|
@ -0,0 +1,185 @@
|
||||||
|
---
|
||||||
|
version: "3"
|
||||||
|
vars:
|
||||||
|
CURRENT_BRANCH:
|
||||||
|
sh: "git rev-parse --abbrev-ref HEAD"
|
||||||
|
shopt:
|
||||||
|
- "globstar"
|
||||||
|
dotenv:
|
||||||
|
- ".env.development"
|
||||||
|
- ".env"
|
||||||
|
- ".env.local"
|
||||||
|
- ".env.development.local"
|
||||||
|
tasks:
|
||||||
|
credentials:
|
||||||
|
desc: "Generate credentials file"
|
||||||
|
cmds:
|
||||||
|
- "cp --no-clobber config/credentials.yml.enc.ci config/credentials.yml.enc"
|
||||||
|
sources:
|
||||||
|
- "config/credentials.yml.enc.ci"
|
||||||
|
generates:
|
||||||
|
- "config/credentials.yml.enc"
|
||||||
|
gems:
|
||||||
|
desc: "Install gems"
|
||||||
|
deps:
|
||||||
|
- "credentials"
|
||||||
|
cmds:
|
||||||
|
- "{{.HAINISH}} bundle config set --local path './vendor'"
|
||||||
|
- "{{.HAINISH}} bundle install"
|
||||||
|
sources:
|
||||||
|
- "Gemfile"
|
||||||
|
generates:
|
||||||
|
- "Gemfile.lock"
|
||||||
|
status:
|
||||||
|
- "test -d vendor/ruby"
|
||||||
|
clean:
|
||||||
|
desc: "Clean"
|
||||||
|
cmds:
|
||||||
|
- "rm -rf _sites/test-* _deploy/test-* log/*.log tmp/cache tmp/letter_opener tmp/miniprofiler tmp/storage"
|
||||||
|
node-modules:
|
||||||
|
desc: "Install Node modules"
|
||||||
|
cmds:
|
||||||
|
- "{{.HAINISH}} yarn"
|
||||||
|
sources:
|
||||||
|
- "package.json"
|
||||||
|
- "yarn.lock"
|
||||||
|
status:
|
||||||
|
- "test -d node_modules"
|
||||||
|
assets:
|
||||||
|
desc: "Generate assets"
|
||||||
|
deps:
|
||||||
|
- "node-modules"
|
||||||
|
- "gems"
|
||||||
|
cmds:
|
||||||
|
- "git lfs fetch"
|
||||||
|
- "git lfs checkout"
|
||||||
|
- task: "rails"
|
||||||
|
vars:
|
||||||
|
CLI_ARGS: "webpacker:clobber RAILS_ENV=production"
|
||||||
|
- task: "rails"
|
||||||
|
vars:
|
||||||
|
CLI_ARGS: "assets:precompile RAILS_ENV=production"
|
||||||
|
- task: "rails"
|
||||||
|
vars:
|
||||||
|
CLI_ARGS: "assets:clean RAILS_ENV=production"
|
||||||
|
sources:
|
||||||
|
- "package.json"
|
||||||
|
- "yarn.lock"
|
||||||
|
- "app/assets/**/*"
|
||||||
|
- "app/javascript/**/*"
|
||||||
|
generates:
|
||||||
|
- "public/packs/manifest.json"
|
||||||
|
hosts:
|
||||||
|
desc: "Local DNS resolution for hostnames"
|
||||||
|
interactive: true
|
||||||
|
cmds:
|
||||||
|
- "echo -e \"127.0.0.1 panel.{{.SUTTY}} api.{{.SUTTY}} postgresql.{{.SUTTY}}\" | sudo tee -a /etc/hosts"
|
||||||
|
- "echo -e \"::1 panel.{{.SUTTY}} api.{{.SUTTY}} postgresql.{{.SUTTY}}\" | sudo tee -a /etc/hosts"
|
||||||
|
status:
|
||||||
|
- "grep -q \" panel.{{.SUTTY}} \" /etc/hosts"
|
||||||
|
database-init:
|
||||||
|
desc: "Database install"
|
||||||
|
cmds:
|
||||||
|
- "{{.HAINISH}} /usr/bin/initdb --locale en_US.utf8 -E UTF8 -D /var/lib/postgresql/{{.PGVER}}/data"
|
||||||
|
- "echo \"host all all samenet trust\" >> ../hain/var/lib/postgresql/{{.PGVER}}/data/pg_hba.conf"
|
||||||
|
- "echo \"listen_addresses = '*'\" >> ../hain/var/lib/postgresql/{{.PGVER}}/data/postgresql.conf"
|
||||||
|
- "echo \"external_pid_file = '{{.PGPID}}'\" >> ../hain/var/lib/postgresql/{{.PGVER}}/data/postgresql.conf"
|
||||||
|
- "install -dm755 ../hain/run/postgresql"
|
||||||
|
status:
|
||||||
|
- "test -d ../hain/var/lib/postgresql/{{.PGVER}}/data"
|
||||||
|
- "test -f ../hain/var/lib/postgresql/{{.PGVER}}/data/postgresql.conf"
|
||||||
|
database:
|
||||||
|
desc: "Database"
|
||||||
|
deps:
|
||||||
|
- "database-init"
|
||||||
|
cmds:
|
||||||
|
- "{{.HAINISH}} daemonize -c /var/lib/postgresql/{{.PGVER}}/data /usr/bin/postgres -D /var/lib/postgresql/{{.PGVER}}/data"
|
||||||
|
status:
|
||||||
|
- "test -f ../hain{{.PGPID}}"
|
||||||
|
- "pgrep -F ../hain{{.PGPID}}"
|
||||||
|
prepare:
|
||||||
|
desc: "Create database or run pending migrations"
|
||||||
|
deps:
|
||||||
|
- "database"
|
||||||
|
cmds:
|
||||||
|
- task: "rails"
|
||||||
|
vars:
|
||||||
|
CLI_ARGS: "db:prepare"
|
||||||
|
serve:
|
||||||
|
desc: "Run Rails development server"
|
||||||
|
deps:
|
||||||
|
- "prepare"
|
||||||
|
- "gems"
|
||||||
|
cmds:
|
||||||
|
- ": == Development server running at https://panel.{{.SUTTY_WITH_PORT}} =="
|
||||||
|
- task: "rails"
|
||||||
|
vars:
|
||||||
|
CLI_ARGS: "server"
|
||||||
|
status:
|
||||||
|
- "test -f tmp/pids/server.pid"
|
||||||
|
- "pgrep -F tmp/pids/server.pid"
|
||||||
|
yarn:
|
||||||
|
desc: "Yarn. Call with: go-task yarn -- arguments"
|
||||||
|
deps:
|
||||||
|
- "node-modules"
|
||||||
|
cmds:
|
||||||
|
- "{{.HAINISH}} yarn {{.CLI_ARGS}}"
|
||||||
|
- defer:
|
||||||
|
task: "notify"
|
||||||
|
bundle:
|
||||||
|
desc: "Bundle. Call with: go-task bundle -- arguments"
|
||||||
|
interactive: true
|
||||||
|
deps:
|
||||||
|
- "gems"
|
||||||
|
cmds:
|
||||||
|
- "{{.HAINISH}} bundle {{.CLI_ARGS}}"
|
||||||
|
- defer:
|
||||||
|
task: "notify"
|
||||||
|
rails:
|
||||||
|
desc: "Rails. Call with: go-task rails -- arguments"
|
||||||
|
cmds:
|
||||||
|
- task: "bundle"
|
||||||
|
vars:
|
||||||
|
CLI_ARGS: "exec rails {{.CLI_ARGS}}"
|
||||||
|
console:
|
||||||
|
desc: "Rails console"
|
||||||
|
interactive: true
|
||||||
|
cmds:
|
||||||
|
- task: "rails"
|
||||||
|
vars:
|
||||||
|
CLI_ARGS: "console"
|
||||||
|
doc:
|
||||||
|
desc: "Build documentation"
|
||||||
|
deps:
|
||||||
|
- "gems"
|
||||||
|
cmds:
|
||||||
|
- task: "bundle"
|
||||||
|
vars:
|
||||||
|
CLI_ARGS: "exec yardoc -o public/doc app lib config db"
|
||||||
|
gem-audit:
|
||||||
|
desc: "Audit Gem dependencies"
|
||||||
|
deps:
|
||||||
|
- "gems"
|
||||||
|
- "bundler-audit"
|
||||||
|
cmds:
|
||||||
|
- task: "bundle"
|
||||||
|
vars:
|
||||||
|
CLI_ARGS: "audit --update"
|
||||||
|
node-audit:
|
||||||
|
desc: "Audit Node dependencies"
|
||||||
|
deps:
|
||||||
|
- "node-modules"
|
||||||
|
cmds:
|
||||||
|
- task: "yarn"
|
||||||
|
vars:
|
||||||
|
CLI_ARGS: "audit"
|
||||||
|
notify:
|
||||||
|
internal: true
|
||||||
|
cmds:
|
||||||
|
- "echo -e \"\a\""
|
||||||
|
bundler-audit:
|
||||||
|
internal: true
|
||||||
|
cmds:
|
||||||
|
- "{{.HAINISH}} gem install bundler-audit"
|
||||||
|
status:
|
||||||
|
- "test -f ../hain/usr/bin/bundler-audit"
|
|
@ -29,11 +29,6 @@ $sizes: (
|
||||||
"70ch": 70ch,
|
"70ch": 70ch,
|
||||||
);
|
);
|
||||||
|
|
||||||
.btn {
|
|
||||||
background-color: var(--foreground);
|
|
||||||
color: var(--background);
|
|
||||||
}
|
|
||||||
|
|
||||||
@import "bootstrap";
|
@import "bootstrap";
|
||||||
@import "editor";
|
@import "editor";
|
||||||
|
|
||||||
|
@ -195,7 +190,7 @@ fieldset {
|
||||||
|
|
||||||
&[type=button] {
|
&[type=button] {
|
||||||
@extend .btn;
|
@extend .btn;
|
||||||
@extend .btn-info;
|
@extend .btn-secondary;
|
||||||
@extend .m-0;
|
@extend .m-0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -209,8 +204,6 @@ svg {
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn {
|
.btn {
|
||||||
border: none;
|
|
||||||
border-radius: 0;
|
|
||||||
margin-right: 0.3rem;
|
margin-right: 0.3rem;
|
||||||
margin-bottom: 0.3rem;
|
margin-bottom: 0.3rem;
|
||||||
|
|
||||||
|
@ -246,7 +239,7 @@ svg {
|
||||||
color: $magenta;
|
color: $magenta;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn {
|
.btn-secondary {
|
||||||
background-color: $white;
|
background-color: $white;
|
||||||
color: $black;
|
color: $black;
|
||||||
border: none;
|
border: none;
|
||||||
|
@ -525,3 +518,43 @@ $bezier: cubic-bezier(0.75, 0, 0.25, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@import "new_editor";
|
||||||
|
|
||||||
|
.new-editor {
|
||||||
|
.editor {
|
||||||
|
table {
|
||||||
|
@extend .table;
|
||||||
|
@extend .table-responsive;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
p { min-height: $font-size-base * $line-height-base; }
|
||||||
|
h1 { min-height: $h1-font-size * $headings-line-height; }
|
||||||
|
h2 { min-height: $h2-font-size * $headings-line-height; }
|
||||||
|
h3 { min-height: $h3-font-size * $headings-line-height; }
|
||||||
|
h4 { min-height: $h4-font-size * $headings-line-height; }
|
||||||
|
h5 { min-height: $h5-font-size * $headings-line-height; }
|
||||||
|
h6 { min-height: $h6-font-size * $headings-line-height; }
|
||||||
|
|
||||||
|
iframe { border: 0; }
|
||||||
|
|
||||||
|
audio { width: 100%; }
|
||||||
|
|
||||||
|
img,
|
||||||
|
video,
|
||||||
|
iframe {
|
||||||
|
@extend .img-fluid;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > * {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
28
app/assets/stylesheets/dark.scss
Normal file
28
app/assets/stylesheets/dark.scss
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
$black: black;
|
||||||
|
$white: white;
|
||||||
|
$cyan: #13fefe;
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--foreground: #{$white};
|
||||||
|
--background: #{$black};
|
||||||
|
--color: #{$cyan};
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
background-color: $white;
|
||||||
|
color: $black;
|
||||||
|
border: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: $black;
|
||||||
|
background-color: $cyan;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background-color: $cyan;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
box-shadow: 0 0 0 0.2rem $cyan;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
.editor {
|
.old.editor {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
*, *::before, *::after { box-sizing: inherit; }
|
*, *::before, *::after { box-sizing: inherit; }
|
||||||
|
|
||||||
|
|
22
app/assets/stylesheets/new_editor.scss
Normal file
22
app/assets/stylesheets/new_editor.scss
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
.new-editor {
|
||||||
|
.editor {
|
||||||
|
.menubar {
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
|
label.btn {
|
||||||
|
margin-bottom: 0.3rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-group {
|
||||||
|
.btn {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ProseMirror,
|
||||||
|
& > ol li {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,8 +18,9 @@ module ActiveStorage
|
||||||
# para que puedan propagarse correctamente a través de todo el
|
# para que puedan propagarse correctamente a través de todo el
|
||||||
# stack.
|
# stack.
|
||||||
def blob_args
|
def blob_args
|
||||||
params.require(:blob).permit(:filename, :byte_size, :checksum, :content_type, metadata: {}).to_h.symbolize_keys.tap do |ba|
|
params.require(:blob).permit(:filename, :byte_size, :checksum, :content_type,
|
||||||
ba[:filename] = ba[:filename].unicode_normalize
|
metadata: {}).to_h.symbolize_keys.tap do |ba|
|
||||||
|
ba[:filename] = ba[:filename].unicode_normalize.sub(/\A_+/, '')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,16 +6,45 @@ module ActiveStorage
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
included do
|
included do
|
||||||
|
alias_method :original_show, :show
|
||||||
|
|
||||||
|
# Permitir incrustar archivos subidos (especialmente PDFs) desde
|
||||||
|
# otros sitios.
|
||||||
|
def show
|
||||||
|
original_show.tap do |s|
|
||||||
|
response.headers.delete 'X-Frame-Options'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
rescue_from ActiveRecord::RecordNotFound, with: :page_not_found
|
||||||
|
|
||||||
# Asociar el archivo subido al sitio correspondiente. Cada sitio
|
# Asociar el archivo subido al sitio correspondiente. Cada sitio
|
||||||
# tiene su propio servicio de subida de archivos.
|
# tiene su propio servicio de subida de archivos.
|
||||||
def update
|
def update
|
||||||
if (token = decode_verified_token)
|
if (token = decode_verified_token)
|
||||||
if acceptable_content?(token)
|
if acceptable_content?(token)
|
||||||
named_disk_service(token[:service_name]).upload token[:key], request.body, checksum: token[:checksum]
|
blob = ActiveStorage::Blob.find_by_key! token[:key]
|
||||||
|
|
||||||
blob = ActiveStorage::Blob.find_by_key token[:key]
|
|
||||||
site = Site.find_by_name token[:service_name]
|
site = Site.find_by_name token[:service_name]
|
||||||
|
|
||||||
|
if remote_file?(token)
|
||||||
|
begin
|
||||||
|
url = request.body.read
|
||||||
|
body = Down.download(url, max_size: 111.megabytes)
|
||||||
|
checksum = Digest::MD5.file(body.path).base64digest
|
||||||
|
blob.metadata[:url] = url
|
||||||
|
blob.update_columns checksum: checksum, byte_size: body.size, metadata: blob.metadata
|
||||||
|
rescue StandardError => e
|
||||||
|
ExceptionNotifier.notify_exception(e, data: { key: token[:key], url: url, site: site.name })
|
||||||
|
|
||||||
|
head :content_too_large
|
||||||
|
end
|
||||||
|
else
|
||||||
|
body = request.body
|
||||||
|
checksum = token[:checksum]
|
||||||
|
end
|
||||||
|
|
||||||
|
named_disk_service(token[:service_name]).upload token[:key], body, checksum: checksum
|
||||||
|
|
||||||
site.static_files.attach(blob)
|
site.static_files.attach(blob)
|
||||||
else
|
else
|
||||||
head :unprocessable_entity
|
head :unprocessable_entity
|
||||||
|
@ -26,6 +55,17 @@ module ActiveStorage
|
||||||
rescue ActiveStorage::IntegrityError
|
rescue ActiveStorage::IntegrityError
|
||||||
head :unprocessable_entity
|
head :unprocessable_entity
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
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
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -23,7 +23,39 @@ module Api
|
||||||
# XXX: Por alguna razón Airbrake envía los datos con Content-Type:
|
# XXX: Por alguna razón Airbrake envía los datos con Content-Type:
|
||||||
# text/plain.
|
# text/plain.
|
||||||
def airbrake_params
|
def airbrake_params
|
||||||
@airbrake_params ||= params.merge!(FastJsonparser.parse(request.raw_post) || {}).permit!
|
@airbrake_params ||=
|
||||||
|
params.merge!(FastJsonparser.parse(request.raw_post) || {})
|
||||||
|
.permit(
|
||||||
|
{
|
||||||
|
errors: [
|
||||||
|
:type,
|
||||||
|
:message,
|
||||||
|
{ backtrace: %i[file line column function] }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
context: [
|
||||||
|
:url,
|
||||||
|
:language,
|
||||||
|
:severity,
|
||||||
|
:userAgent,
|
||||||
|
:windowError,
|
||||||
|
:rootDirectory,
|
||||||
|
{
|
||||||
|
history: [
|
||||||
|
:date,
|
||||||
|
:type,
|
||||||
|
:severity,
|
||||||
|
:target,
|
||||||
|
:method,
|
||||||
|
:duration,
|
||||||
|
:statusCode,
|
||||||
|
{ arguments: [] }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def site
|
def site
|
||||||
|
|
|
@ -9,7 +9,7 @@ module Api
|
||||||
|
|
||||||
# Lista de nombres de dominios a emitir certificados
|
# Lista de nombres de dominios a emitir certificados
|
||||||
def index
|
def index
|
||||||
render json: sites_names + alternative_names + api_names + www_names
|
render json: alternative_names + api_names + www_names
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
@ -18,17 +18,16 @@ module Api
|
||||||
name.end_with?('.') ? name[0..-2] : "#{name}.#{Site.domain}"
|
name.end_with?('.') ? name[0..-2] : "#{name}.#{Site.domain}"
|
||||||
end
|
end
|
||||||
|
|
||||||
# Nombres de los sitios
|
def subdomain?(name)
|
||||||
def sites_names
|
name.end_with? ".#{Site.domain}"
|
||||||
Site.all.order(:name).pluck(:name).map do |name|
|
|
||||||
canonicalize name
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Dominios alternativos
|
# Dominios alternativos
|
||||||
def alternative_names
|
def alternative_names
|
||||||
(DeployAlternativeDomain.all.map(&:hostname) + DeployLocalizedDomain.all.map(&:hostname)).map do |name|
|
(DeployAlternativeDomain.all.map(&:hostname) + DeployLocalizedDomain.all.map(&:hostname)).map do |name|
|
||||||
canonicalize name
|
canonicalize name
|
||||||
|
end.reject do |name|
|
||||||
|
subdomain? name
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -41,6 +40,8 @@ module Api
|
||||||
.or(Site.where(colaboracion_anonima: true))
|
.or(Site.where(colaboracion_anonima: true))
|
||||||
.select("'api.' || name as name").map(&:name).map do |name|
|
.select("'api.' || name as name").map(&:name).map do |name|
|
||||||
canonicalize name
|
canonicalize name
|
||||||
|
end.reject do |name|
|
||||||
|
subdomain? name
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -68,7 +68,7 @@ module Api
|
||||||
|
|
||||||
# respuesta de error a plataformas
|
# respuesta de error a plataformas
|
||||||
def platforms_answer(exception)
|
def platforms_answer(exception)
|
||||||
ExceptionNotifier.notify_exception(exception, data: { headers: request.headers.to_h }
|
ExceptionNotifier.notify_exception(exception, data: { headers: request.headers.to_h })
|
||||||
|
|
||||||
head :forbidden
|
head :forbidden
|
||||||
end
|
end
|
||||||
|
|
|
@ -110,27 +110,6 @@ class SitesController < ApplicationController
|
||||||
redirect_to sites_path
|
redirect_to sites_path
|
||||||
end
|
end
|
||||||
|
|
||||||
# Obtiene y streamea archivos estáticos desde el repositorio mismo,
|
|
||||||
# pero sólo los públicos (es decir los archivos subidos desde Sutty).
|
|
||||||
def static_file
|
|
||||||
authorize site
|
|
||||||
|
|
||||||
file = params.require(:file) + '.' + params.require(:format)
|
|
||||||
|
|
||||||
raise ActionController::RoutingError.new(nil, nil) unless file.start_with? 'public/'
|
|
||||||
|
|
||||||
path = site.relative_path file
|
|
||||||
|
|
||||||
raise ActionController::RoutingError.new(nil, nil) unless File.exist? path
|
|
||||||
|
|
||||||
# TODO: Hacer esto usa recursos, pero menos que generar el sitio
|
|
||||||
# cada vez. Para poder usar X-Accel tendríamos que montar los
|
|
||||||
# repositorios en el servidor web, cosa que no queremos, o hacer
|
|
||||||
# links simbólicos desde todos los public, o usar un servidor web
|
|
||||||
# local que soporte sendfile mejor que Rails (nghttpd?)
|
|
||||||
send_file path
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def site
|
def site
|
||||||
|
|
|
@ -6,3 +6,4 @@ import './prosemirror'
|
||||||
import './timezone'
|
import './timezone'
|
||||||
import './turbolinks-anchors'
|
import './turbolinks-anchors'
|
||||||
import './validation'
|
import './validation'
|
||||||
|
import './new_editor'
|
||||||
|
|
14
app/javascript/etc/new_editor.js
Normal file
14
app/javascript/etc/new_editor.js
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import SuttyEditor from "@suttyweb/editor";
|
||||||
|
|
||||||
|
import "@suttyweb/editor/dist/style.css";
|
||||||
|
|
||||||
|
document.addEventListener("turbolinks:load", () => {
|
||||||
|
document.querySelectorAll(".new-editor").forEach((editorContainer) => {
|
||||||
|
new SuttyEditor({
|
||||||
|
target: editorContainer,
|
||||||
|
props: {
|
||||||
|
textareaEl: editorContainer.querySelector("textarea"),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
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
|
|
@ -56,6 +56,10 @@ class DeployJob < ApplicationJob
|
||||||
rescue URI::Error
|
rescue URI::Error
|
||||||
nil
|
nil
|
||||||
end.compact
|
end.compact
|
||||||
|
|
||||||
|
if d == @site.deployment_list.last && !status
|
||||||
|
raise DeployException, 'Falló la compilación'
|
||||||
|
end
|
||||||
rescue StandardError => e
|
rescue StandardError => e
|
||||||
status = false
|
status = false
|
||||||
seconds ||= 0
|
seconds ||= 0
|
||||||
|
|
|
@ -30,7 +30,7 @@ class GitlabNotifierJob < ApplicationJob
|
||||||
count: 1,
|
count: 1,
|
||||||
issue: @issue['iid'],
|
issue: @issue['iid'],
|
||||||
user_agents: [user_agent].compact,
|
user_agents: [user_agent].compact,
|
||||||
params: [request&.filtered_parameters].compact,
|
params: request&.filtered_parameters&.as_json,
|
||||||
urls: [url].compact
|
urls: [url].compact
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
@ -192,7 +192,7 @@ class GitlabNotifierJob < ApplicationJob
|
||||||
```
|
```
|
||||||
#{request.request_method} #{url}
|
#{request.request_method} #{url}
|
||||||
|
|
||||||
#{pp request.filtered_parameters}
|
#{pp request.filtered_parameters.as_json}
|
||||||
```
|
```
|
||||||
|
|
||||||
REQUEST
|
REQUEST
|
||||||
|
|
|
@ -11,7 +11,7 @@ module ActionDispatch
|
||||||
# Devolver el nombre de archivo con caracteres unicode
|
# Devolver el nombre de archivo con caracteres unicode
|
||||||
# normalizados
|
# normalizados
|
||||||
def original_filename
|
def original_filename
|
||||||
@original_filename.unicode_normalize
|
@original_filename.unicode_normalize.sub(/\A_+/, '')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -96,7 +96,7 @@ module ActiveStorage
|
||||||
end
|
end
|
||||||
|
|
||||||
def blob_for(key)
|
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
|
end
|
||||||
end
|
end
|
||||||
|
|
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
|
|
@ -1,7 +1,6 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require 'distributed_press/v1/client/site'
|
require 'distributed_press/v1/client/site'
|
||||||
require 'njalla/v1'
|
|
||||||
|
|
||||||
# Soportar Distributed Press APIv1
|
# Soportar Distributed Press APIv1
|
||||||
#
|
#
|
||||||
|
@ -15,8 +14,8 @@ require 'njalla/v1'
|
||||||
class DeployDistributedPress < Deploy
|
class DeployDistributedPress < Deploy
|
||||||
store :values, accessors: %i[hostname remote_site_id remote_info], coder: JSON
|
store :values, accessors: %i[hostname remote_site_id remote_info], coder: JSON
|
||||||
|
|
||||||
before_create :create_remote_site!, :create_njalla_records!
|
before_create :create_remote_site!
|
||||||
before_destroy :delete_remote_site!, :delete_njalla_records!
|
before_destroy :delete_remote_site!
|
||||||
|
|
||||||
DEPENDENCIES = %i[deploy_local]
|
DEPENDENCIES = %i[deploy_local]
|
||||||
|
|
||||||
|
@ -31,17 +30,12 @@ class DeployDistributedPress < Deploy
|
||||||
time_start
|
time_start
|
||||||
|
|
||||||
create_remote_site! if remote_site_id.blank?
|
create_remote_site! if remote_site_id.blank?
|
||||||
create_njalla_records!
|
|
||||||
save
|
save
|
||||||
|
|
||||||
if remote_site_id.blank?
|
if remote_site_id.blank?
|
||||||
raise DeployJob::DeployException, 'El sitio no se creó en Distributed Press'
|
raise DeployJob::DeployException, 'El sitio no se creó en Distributed Press'
|
||||||
end
|
end
|
||||||
|
|
||||||
if create_njalla_records? && remote_info[:njalla].blank?
|
|
||||||
raise DeployJob::DeployException, 'No se pudieron crear los registros necesarios en Njalla'
|
|
||||||
end
|
|
||||||
|
|
||||||
site_client.tap do |c|
|
site_client.tap do |c|
|
||||||
stdout = Thread.new(publisher.logger_out) do |io|
|
stdout = Thread.new(publisher.logger_out) do |io|
|
||||||
until io.eof?
|
until io.eof?
|
||||||
|
@ -145,29 +139,6 @@ class DeployDistributedPress < Deploy
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
|
|
||||||
# Crea los registros en Njalla
|
|
||||||
#
|
|
||||||
# XXX: Esto depende de nuestro DNS actual, cuando lo migremos hay
|
|
||||||
# que eliminarlo.
|
|
||||||
#
|
|
||||||
# @return [nil]
|
|
||||||
def create_njalla_records!
|
|
||||||
return unless create_njalla_records?
|
|
||||||
|
|
||||||
self.remote_info ||= {}
|
|
||||||
self.remote_info[:njalla] ||= {}
|
|
||||||
self.remote_info[:njalla][:a] ||= njalla.add_record(name: site.name, type: 'CNAME', content: "#{Site.domain}.").to_h
|
|
||||||
self.remote_info[:njalla][:cname] ||= njalla.add_record(name: "www.#{site.name}", type: 'CNAME', content: "#{Site.domain}.").to_h
|
|
||||||
self.remote_info[:njalla][:ns] ||= njalla.add_record(name: "_dnslink.#{site.name}", type: 'NS', content: "#{publisher.hostname}.").to_h
|
|
||||||
|
|
||||||
nil
|
|
||||||
rescue HTTParty::Error => e
|
|
||||||
ExceptionNotifier.notify_exception(e, data: { site: site.name })
|
|
||||||
self.remote_info.delete :njalla
|
|
||||||
ensure
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
|
|
||||||
# Registra lo que sucedió
|
# Registra lo que sucedió
|
||||||
#
|
#
|
||||||
# @param status [Bool]
|
# @param status [Bool]
|
||||||
|
@ -185,31 +156,4 @@ class DeployDistributedPress < Deploy
|
||||||
ExceptionNotifier.notify_exception(e, data: { site: site.name })
|
ExceptionNotifier.notify_exception(e, data: { site: site.name })
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
|
|
||||||
def delete_njalla_records!
|
|
||||||
return unless create_njalla_records?
|
|
||||||
|
|
||||||
%w[a ns cname].each do |type|
|
|
||||||
next if (id = remote_info.dig('njalla', type, 'id')).blank?
|
|
||||||
|
|
||||||
njalla.remove_record(id: id.to_i)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Actualizar registros en Njalla
|
|
||||||
#
|
|
||||||
# @return [Njalla::V1::Domain]
|
|
||||||
def njalla
|
|
||||||
@njalla ||=
|
|
||||||
begin
|
|
||||||
client = Njalla::V1::Client.new(token: Rails.application.credentials.njalla)
|
|
||||||
|
|
||||||
Njalla::V1::Domain.new(domain: Site.domain, client: client)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Detecta si tenemos que crear registros en Njalla
|
|
||||||
def create_njalla_records?
|
|
||||||
!site.name.end_with?('.')
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -7,6 +7,20 @@ class DeployLocal < Deploy
|
||||||
|
|
||||||
before_destroy :remove_destination!
|
before_destroy :remove_destination!
|
||||||
|
|
||||||
|
def bundle(output: false)
|
||||||
|
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 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
|
# Realizamos la construcción del sitio usando Jekyll y un entorno
|
||||||
# limpio para no pasarle secretos
|
# limpio para no pasarle secretos
|
||||||
#
|
#
|
||||||
|
@ -55,7 +69,7 @@ class DeployLocal < Deploy
|
||||||
#
|
#
|
||||||
# @return [nil]
|
# @return [nil]
|
||||||
def cleanup!
|
def cleanup!
|
||||||
FileUtils.rm_rf(gems_dir)
|
FileUtils.rm_rf(site.bundle_path)
|
||||||
FileUtils.rm_rf(yarn_cache_dir)
|
FileUtils.rm_rf(yarn_cache_dir)
|
||||||
FileUtils.rm_rf(File.join(site.path, 'node_modules'))
|
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, '.sass-cache'))
|
||||||
|
@ -106,9 +120,11 @@ class DeployLocal < Deploy
|
||||||
File.exist? pnpm_lock
|
File.exist? pnpm_lock
|
||||||
end
|
end
|
||||||
|
|
||||||
def git_lfs(output: false)
|
def pnpm(output: false)
|
||||||
run %(git lfs fetch), output: output
|
return true unless pnpm_lock?
|
||||||
run %(git lfs checkout), output: output
|
|
||||||
|
run %(pnpm config set store-dir "#{pnpm_cache_dir}"), output: output
|
||||||
|
run 'pnpm install --production', output: output
|
||||||
end
|
end
|
||||||
|
|
||||||
def gem(output: false)
|
def gem(output: false)
|
||||||
|
@ -122,22 +138,6 @@ class DeployLocal < Deploy
|
||||||
run 'yarn install --production', output: output
|
run 'yarn install --production', output: output
|
||||||
end
|
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 config set --local clean 'true'), output: output
|
|
||||||
run %(bundle config set --local deployment 'true'), output: output
|
|
||||||
run %(bundle config set --local path '#{gems_dir}'), output: output
|
|
||||||
run %(bundle config set --local without 'test development'), output: output
|
|
||||||
run %(bundle config set --local cache_all 'false'), output: output
|
|
||||||
run %(bundle install), output: output
|
|
||||||
end
|
|
||||||
|
|
||||||
def jekyll_build(output: false)
|
def jekyll_build(output: false)
|
||||||
with_tempfile(site.private_key_pem) do |file|
|
with_tempfile(site.private_key_pem) do |file|
|
||||||
flags = extra_flags(private_key: file)
|
flags = extra_flags(private_key: file)
|
||||||
|
|
|
@ -24,7 +24,11 @@ class MetadataContent < MetadataTemplate
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_s
|
def to_s
|
||||||
sanitizer.sanitize value, tags: [], attributes: []
|
Nokogiri::HTML5.fragment(value).tap do |html|
|
||||||
|
html.css('[src^="public/"]').each do |element|
|
||||||
|
element['src'] = convert_internal_path_to_src element['src']
|
||||||
|
end
|
||||||
|
end.to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
@ -42,21 +46,22 @@ class MetadataContent < MetadataTemplate
|
||||||
# TODO: En lugar de comprobar el Content Type acá, restringir los
|
# TODO: En lugar de comprobar el Content Type acá, restringir los
|
||||||
# tipos de archivo a aceptar en ActiveStorage.
|
# tipos de archivo a aceptar en ActiveStorage.
|
||||||
def sanitize(html_string)
|
def sanitize(html_string)
|
||||||
html = Nokogiri::HTML.fragment(super html_string)
|
html = Nokogiri::HTML5.fragment(super html_string)
|
||||||
elements = 'img,audio,video,iframe'
|
elements = 'img,audio,video,iframe'
|
||||||
|
|
||||||
# Eliminar elementos sin src y comprobar su origen
|
# Eliminar elementos sin src y comprobar su origen
|
||||||
html.css(elements).each do |element|
|
html.css(elements).each do |element|
|
||||||
unless element['src']
|
|
||||||
element.remove
|
|
||||||
next
|
|
||||||
end
|
|
||||||
|
|
||||||
begin
|
begin
|
||||||
|
raise URI::Error unless element['src'].present?
|
||||||
|
|
||||||
uri = URI element['src']
|
uri = URI element['src']
|
||||||
|
|
||||||
# No permitimos recursos externos
|
# No permitimos recursos externos
|
||||||
element.remove unless uri.scheme == 'https' && uri.hostname.end_with?(Site.domain)
|
raise URI::Error unless Rails.application.config.hosts.include?(uri.hostname)
|
||||||
|
|
||||||
|
element['src'] = convert_src_to_internal_path uri
|
||||||
|
|
||||||
|
raise URI::Error if element['src'].blank?
|
||||||
rescue URI::Error
|
rescue URI::Error
|
||||||
element.remove
|
element.remove
|
||||||
end
|
end
|
||||||
|
@ -73,16 +78,74 @@ class MetadataContent < MetadataTemplate
|
||||||
end
|
end
|
||||||
|
|
||||||
# Elimina los estilos salvo los que asigne el editor
|
# Elimina los estilos salvo los que asigne el editor
|
||||||
html.css('*').each do |element|
|
html.css('[style]').each do |element|
|
||||||
next if elements_with_style.include? element.name.downcase
|
if (style = sanitize_style(element['style'])).present?
|
||||||
|
element['style'] = style
|
||||||
|
else
|
||||||
element.remove_attribute('style')
|
element.remove_attribute('style')
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
html.to_s.html_safe
|
html.to_s.html_safe
|
||||||
end
|
end
|
||||||
|
|
||||||
def elements_with_style
|
# Limpia estilos en base a una lista de permitidos
|
||||||
@elements_with_style ||= %w[div mark].freeze
|
#
|
||||||
|
# @param style [String]
|
||||||
|
# @return [String]
|
||||||
|
def sanitize_style(style)
|
||||||
|
style.split(';').reduce({}) do |style_hash, style_string|
|
||||||
|
key, value = style_string.split(':', 2)
|
||||||
|
|
||||||
|
style_hash[key] ||= value
|
||||||
|
style_hash
|
||||||
|
end.slice(*allowed_styles).map do |style_pair|
|
||||||
|
style_pair.join(':')
|
||||||
|
end.join(';')
|
||||||
|
end
|
||||||
|
|
||||||
|
# Estilos permitidos
|
||||||
|
#
|
||||||
|
# @return [Array<String>]
|
||||||
|
def allowed_styles
|
||||||
|
@allowed_styles ||= %w[text-align color background-color]
|
||||||
|
end
|
||||||
|
|
||||||
|
# Convierte una ubicación local al sitio en una URL de ActiveStorage
|
||||||
|
#
|
||||||
|
# XXX: Por qué son tan díficiles de encontrar las rutas de AS
|
||||||
|
#
|
||||||
|
# @param path [String]
|
||||||
|
# @return [String]
|
||||||
|
def convert_internal_path_to_src(path)
|
||||||
|
key = path.split('/').second
|
||||||
|
blob = ActiveStorage::Blob.find_by(service_name: site.name, key: key)
|
||||||
|
|
||||||
|
return unless blob
|
||||||
|
|
||||||
|
"/rails/active_storage/blobs/#{blob.signed_id}/#{blob.filename}"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Convierte una URI en una ruta interna del sitio actual
|
||||||
|
#
|
||||||
|
# XXX: No verifica si el archivo existe o no. Se supone que existe
|
||||||
|
# porque ya fue subido antes.
|
||||||
|
#
|
||||||
|
# @param uri [URI]
|
||||||
|
# @return [String,nil]
|
||||||
|
def convert_src_to_internal_path(uri)
|
||||||
|
signed_id = uri.path.split('/').fifth
|
||||||
|
blob = ActiveStorage::Blob.find_signed(signed_id)
|
||||||
|
|
||||||
|
return unless blob
|
||||||
|
return unless blob.service_name == site.name
|
||||||
|
|
||||||
|
blob_path = Pathname.new(blob.service.path_for(blob.key)).realpath
|
||||||
|
site_path = Pathname.new(site.path).realpath
|
||||||
|
|
||||||
|
blob_path.relative_path_from(site_path).to_s
|
||||||
|
rescue ActiveSupport::MessageVerifier::InvalidSignature => e
|
||||||
|
ExceptionNotifier.notify_exception(e, data: { site: site.name })
|
||||||
|
nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
4
app/models/metadata_new_content.rb
Normal file
4
app/models/metadata_new_content.rb
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# Testear el nuevo editor
|
||||||
|
class MetadataNewContent < MetadataContent; end
|
4
app/models/metadata_new_html.rb
Normal file
4
app/models/metadata_new_html.rb
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# Campos en HTML con el nuevo editor
|
||||||
|
class MetadataNewHtml < MetadataHtml; end
|
|
@ -6,7 +6,7 @@ class MetadataPath < MetadataTemplate
|
||||||
#
|
#
|
||||||
# @return [String]
|
# @return [String]
|
||||||
def default_value
|
def default_value
|
||||||
File.join(site.path, "_#{lang}", "#{date}-#{slug}#{ext}")
|
File.join(site.path, "_#{lang}", "#{limited_name}#{ext}")
|
||||||
end
|
end
|
||||||
|
|
||||||
# La ruta del archivo según Jekyll
|
# La ruta del archivo según Jekyll
|
||||||
|
@ -46,4 +46,12 @@ class MetadataPath < MetadataTemplate
|
||||||
def date
|
def date
|
||||||
post.date.value.strftime('%F')
|
post.date.value.strftime('%F')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Limita el nombre de archivo a 255 bytes, de forma que siempre
|
||||||
|
# podemos guardarlo
|
||||||
|
#
|
||||||
|
# @return [String]
|
||||||
|
def limited_name
|
||||||
|
"#{date}-#{slug}".mb_chars.limit(255 - ext.length)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -210,12 +210,12 @@ MetadataTemplate = Struct.new(:site, :document, :name, :label, :type,
|
||||||
|
|
||||||
def allowed_attributes
|
def allowed_attributes
|
||||||
@allowed_attributes ||= %w[style href src alt controls data-align data-multimedia data-multimedia-inner id
|
@allowed_attributes ||= %w[style href src alt controls data-align data-multimedia data-multimedia-inner id
|
||||||
name start].freeze
|
name rel target referrerpolicy class colspan rowspan role data-turbo start type reversed].freeze
|
||||||
end
|
end
|
||||||
|
|
||||||
def allowed_tags
|
def allowed_tags
|
||||||
@allowed_tags ||= %w[strong em del u mark p h1 h2 h3 h4 h5 h6 ul ol li img iframe audio video div figure blockquote
|
@allowed_tags ||= %w[strong em del u mark p h1 h2 h3 h4 h5 h6 ul ol li img iframe audio video div figure blockquote
|
||||||
figcaption a sub sup small].freeze
|
figcaption a sub sup small table thead tbody tfoot tr th td br code].freeze
|
||||||
end
|
end
|
||||||
|
|
||||||
# Decifra el valor
|
# Decifra el valor
|
||||||
|
|
|
@ -132,8 +132,10 @@ class Post
|
||||||
src = element.attributes['src']
|
src = element.attributes['src']
|
||||||
|
|
||||||
next unless src&.value&.start_with? 'public/'
|
next unless src&.value&.start_with? 'public/'
|
||||||
|
file = MetadataFile.new(site: site, post: self, document: document, layout: layout)
|
||||||
|
file.value['path'] = src.value
|
||||||
|
|
||||||
src.value = Rails.application.routes.url_helpers.site_static_file_url(site, file: src.value)
|
src.value = Rails.application.routes.url_helpers.url_for(file.static_file)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Notificar a les usuaries que están viendo una previsualización
|
# Notificar a les usuaries que están viendo una previsualización
|
||||||
|
@ -299,7 +301,7 @@ class Post
|
||||||
yaml['layout'] = layout.name.to_s
|
yaml['layout'] = layout.name.to_s
|
||||||
yaml['uuid'] = uuid.value
|
yaml['uuid'] = uuid.value
|
||||||
# Y que no se procese liquid
|
# Y que no se procese liquid
|
||||||
yaml['liquid'] = false
|
yaml['render_with_liquid'] = false
|
||||||
yaml['usuaries'] = usuaries.map(&:id).uniq
|
yaml['usuaries'] = usuaries.map(&:id).uniq
|
||||||
yaml['created_at'] = created_at.value
|
yaml['created_at'] = created_at.value
|
||||||
yaml['last_modified_at'] = modified_at
|
yaml['last_modified_at'] = modified_at
|
||||||
|
|
|
@ -159,19 +159,19 @@ class Site < ApplicationRecord
|
||||||
|
|
||||||
# Traer la ruta del sitio
|
# Traer la ruta del sitio
|
||||||
def path
|
def path
|
||||||
File.join(Site.site_path, name)
|
::File.join(Site.site_path, name)
|
||||||
end
|
end
|
||||||
|
|
||||||
# La ruta anterior
|
# La ruta anterior
|
||||||
def path_was
|
def path_was
|
||||||
File.join(Site.site_path, name_was)
|
::File.join(Site.site_path, name_was)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Limpiar la ruta y unirla con el separador de directorios del
|
# Limpiar la ruta y unirla con el separador de directorios del
|
||||||
# sistema operativo. Como si algún día fuera a cambiar o
|
# sistema operativo. Como si algún día fuera a cambiar o
|
||||||
# soportáramos Windows :P
|
# soportáramos Windows :P
|
||||||
def relative_path(suspicious_path)
|
def relative_path(suspicious_path)
|
||||||
File.join(path, *suspicious_path.gsub('..', '/').gsub('./', '').squeeze('/').split('/'))
|
::File.join(path, *suspicious_path.gsub('..', '/').gsub('./', '').squeeze('/').split('/'))
|
||||||
end
|
end
|
||||||
|
|
||||||
# Obtiene la lista de traducciones actuales
|
# Obtiene la lista de traducciones actuales
|
||||||
|
@ -209,11 +209,9 @@ class Site < ApplicationRecord
|
||||||
# Trae los datos del directorio _data dentro del sitio
|
# Trae los datos del directorio _data dentro del sitio
|
||||||
def data
|
def data
|
||||||
unless jekyll.data.present?
|
unless jekyll.data.present?
|
||||||
run_in_path do
|
|
||||||
jekyll.reader.read_data
|
jekyll.reader.read_data
|
||||||
jekyll.data['layouts'] ||= {}
|
jekyll.data['layouts'] ||= {}
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
jekyll.data
|
jekyll.data
|
||||||
end
|
end
|
||||||
|
@ -263,10 +261,8 @@ class Site < ApplicationRecord
|
||||||
#
|
#
|
||||||
# @return [Hash]
|
# @return [Hash]
|
||||||
def theme_layouts
|
def theme_layouts
|
||||||
run_in_path do
|
|
||||||
jekyll.reader.read_layouts
|
jekyll.reader.read_layouts
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
# Poner en la cola de compilación
|
# Poner en la cola de compilación
|
||||||
def enqueue!
|
def enqueue!
|
||||||
|
@ -290,7 +286,7 @@ class Site < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def jekyll?
|
def jekyll?
|
||||||
File.directory? path
|
::File.directory? path
|
||||||
end
|
end
|
||||||
|
|
||||||
def jekyll
|
def jekyll
|
||||||
|
@ -298,9 +294,7 @@ class Site < ApplicationRecord
|
||||||
begin
|
begin
|
||||||
install_gems
|
install_gems
|
||||||
|
|
||||||
Jekyll::Site.new(configuration).tap do |site|
|
Jekyll::Site.new(configuration)
|
||||||
site.reader = JekyllData::Reader.new(site) if site.theme
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -310,7 +304,7 @@ class Site < ApplicationRecord
|
||||||
# documentos de Jekyll hacia Sutty para que podamos leer los datos que
|
# documentos de Jekyll hacia Sutty para que podamos leer los datos que
|
||||||
# necesitamos.
|
# necesitamos.
|
||||||
def load_jekyll
|
def load_jekyll
|
||||||
return unless name.present? && File.directory?(path)
|
return unless name.present? && ::File.directory?(path)
|
||||||
|
|
||||||
reload_jekyll!
|
reload_jekyll!
|
||||||
end
|
end
|
||||||
|
@ -338,7 +332,7 @@ class Site < ApplicationRecord
|
||||||
# metadatos de Document
|
# metadatos de Document
|
||||||
@configuration =
|
@configuration =
|
||||||
::Jekyll.configuration('source' => path,
|
::Jekyll.configuration('source' => path,
|
||||||
'destination' => File.join(path, '_site'),
|
'destination' => ::File.join(path, '_site'),
|
||||||
'safe' => true, 'watch' => false,
|
'safe' => true, 'watch' => false,
|
||||||
'quiet' => true, 'excerpt_separator' => '')
|
'quiet' => true, 'excerpt_separator' => '')
|
||||||
|
|
||||||
|
@ -363,7 +357,7 @@ class Site < ApplicationRecord
|
||||||
|
|
||||||
# El directorio donde se almacenan los sitios
|
# El directorio donde se almacenan los sitios
|
||||||
def self.site_path
|
def self.site_path
|
||||||
@site_path ||= File.realpath(ENV.fetch('SITE_PATH', Rails.root.join('_sites')))
|
@site_path ||= ::File.realpath(ENV.fetch('SITE_PATH', Rails.root.join('_sites')))
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.default
|
def self.default
|
||||||
|
@ -387,6 +381,15 @@ class Site < ApplicationRecord
|
||||||
@docs = nil
|
@docs = nil
|
||||||
end
|
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
|
private
|
||||||
|
|
||||||
# Asegurarse que el sitio tenga una llave privada
|
# Asegurarse que el sitio tenga una llave privada
|
||||||
|
@ -399,7 +402,7 @@ class Site < ApplicationRecord
|
||||||
def clone_skel!
|
def clone_skel!
|
||||||
return if jekyll?
|
return if jekyll?
|
||||||
|
|
||||||
Rugged::Repository.clone_at ENV['SKEL_SUTTY'], path
|
Rugged::Repository.clone_at(ENV['SKEL_SUTTY'], path, checkout_branch: design.gem)
|
||||||
|
|
||||||
# Necesita un bloque
|
# Necesita un bloque
|
||||||
repository.rugged.remotes.rename('origin', 'upstream') {}
|
repository.rugged.remotes.rename('origin', 'upstream') {}
|
||||||
|
@ -426,8 +429,8 @@ class Site < ApplicationRecord
|
||||||
config.theme = design.gem unless design.no_theme?
|
config.theme = design.gem unless design.no_theme?
|
||||||
config.description = description
|
config.description = description
|
||||||
config.title = title
|
config.title = title
|
||||||
config.url = url(slash: false)
|
config.url ||= url(slash: false)
|
||||||
config.hostname = hostname
|
config.hostname ||= hostname
|
||||||
config.locales = locales.map(&:to_s)
|
config.locales = locales.map(&:to_s)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -496,26 +499,52 @@ class Site < ApplicationRecord
|
||||||
def install_gems
|
def install_gems
|
||||||
return unless persisted?
|
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?
|
if !gems_installed? || gemfile_updated? || gemfile_lock_updated?
|
||||||
deploys.find_by_type('DeployLocal').send(:bundle)
|
deploy_local.bundle
|
||||||
touch
|
touch
|
||||||
|
FileUtils.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
|
||||||
end
|
end
|
||||||
|
|
||||||
# Detecta si el repositorio de gemas existe
|
# Detecta si el repositorio de gemas existe
|
||||||
def gem_dir?
|
def gems_installed?
|
||||||
Rails.root.join('_storage', 'gems', name).directory?
|
gem_path.directory? && !gem_path.empty?
|
||||||
end
|
end
|
||||||
|
|
||||||
# Detecta si el Gemfile fue modificado
|
# Detecta si el Gemfile fue modificado
|
||||||
def gemfile_updated?
|
def gemfile_updated?
|
||||||
updated_at < File.mtime(File.join(path, 'Gemfile'))
|
updated_at < ::File.mtime(gemfile_path)
|
||||||
end
|
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?
|
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
|
||||||
end
|
end
|
||||||
|
|
|
@ -21,7 +21,7 @@ class Site
|
||||||
# Leer el archivo de configuración y setear los atributos en el
|
# Leer el archivo de configuración y setear los atributos en el
|
||||||
# objeto actual, creando los metodos de ostruct
|
# objeto actual, creando los metodos de ostruct
|
||||||
def read
|
def read
|
||||||
data = YAML.safe_load(File.read(path))
|
data = YAML.safe_load(File.read(path), permitted_classes: [Time])
|
||||||
@hash = data.hash
|
@hash = data.hash
|
||||||
|
|
||||||
data.each do |key, value|
|
data.each do |key, value|
|
||||||
|
|
|
@ -235,5 +235,10 @@ class Site
|
||||||
|
|
||||||
r&.success?
|
r&.success?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def lfs_cleanup
|
||||||
|
git_sh("git", "lfs", "prune")
|
||||||
|
git_sh("git", "lfs", "dedup")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -16,7 +16,7 @@ class Site
|
||||||
#
|
#
|
||||||
# @return [nil]
|
# @return [nil]
|
||||||
def generate_private_key_pem!
|
def generate_private_key_pem!
|
||||||
self.private_key_pem ||= DistributedPress::V1::Social::Client.new(public_key_url: nil, key_size: 2048).private_key.export
|
self.private_key_pem ||= ::DistributedPress::V1::Social::Client.new(public_key_url: nil, key_size: 2048).private_key.export
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
66
app/policies/indexed_post_policy.rb
Normal file
66
app/policies/indexed_post_policy.rb
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# Política de acceso a artículos
|
||||||
|
class IndexedPostPolicy
|
||||||
|
attr_reader :indexed_post, :usuarie, :site
|
||||||
|
|
||||||
|
def initialize(usuarie, indexed_post)
|
||||||
|
@usuarie = usuarie
|
||||||
|
@indexed_post = indexed_post
|
||||||
|
@site = indexed_post.site
|
||||||
|
end
|
||||||
|
|
||||||
|
def index?
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
# Les invitades solo pueden ver sus propios posts
|
||||||
|
def show?
|
||||||
|
site.usuarie?(usuarie) || site.indexed_posts.by_usuarie(usuarie.id).find_by_post_id(indexed_post.post_id).present?
|
||||||
|
end
|
||||||
|
|
||||||
|
def preview?
|
||||||
|
show?
|
||||||
|
end
|
||||||
|
|
||||||
|
def new?
|
||||||
|
create?
|
||||||
|
end
|
||||||
|
|
||||||
|
def create?
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
def edit?
|
||||||
|
update?
|
||||||
|
end
|
||||||
|
|
||||||
|
# Les invitades solo pueden modificar sus propios artículos
|
||||||
|
def update?
|
||||||
|
show?
|
||||||
|
end
|
||||||
|
|
||||||
|
# Solo las usuarias pueden eliminar artículos. Les invitades pueden
|
||||||
|
# borrar sus propios artículos
|
||||||
|
def destroy?
|
||||||
|
update?
|
||||||
|
end
|
||||||
|
|
||||||
|
# Las usuarias pueden ver todos los posts
|
||||||
|
#
|
||||||
|
# Les invitades solo pueden ver sus propios posts
|
||||||
|
class Scope
|
||||||
|
attr_reader :usuarie, :scope
|
||||||
|
|
||||||
|
def initialize(usuarie, scope)
|
||||||
|
@usuarie = usuarie
|
||||||
|
@scope = scope
|
||||||
|
end
|
||||||
|
|
||||||
|
def resolve
|
||||||
|
return scope if scope&.first&.site&.usuarie? usuarie
|
||||||
|
|
||||||
|
scope.by_usuarie(usuarie.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -23,12 +23,15 @@ class CleanupService
|
||||||
#
|
#
|
||||||
# @return [nil]
|
# @return [nil]
|
||||||
def cleanup_older_sites!
|
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
|
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.deploys.find_each(&:cleanup!)
|
||||||
|
|
||||||
site.repository.gc
|
site.repository.gc
|
||||||
|
site.repository.lfs_cleanup
|
||||||
site.touch
|
site.touch
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -37,10 +40,13 @@ class CleanupService
|
||||||
#
|
#
|
||||||
# @return [nil]
|
# @return [nil]
|
||||||
def cleanup_newer_sites!
|
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
|
next unless File.directory? site.path
|
||||||
|
|
||||||
|
Rails.logger.info "Limpiando repositorio git de #{site.name}"
|
||||||
|
|
||||||
site.repository.gc
|
site.repository.gc
|
||||||
|
site.repository.lfs_cleanup
|
||||||
site.touch
|
site.touch
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -25,4 +25,4 @@
|
||||||
class: 'form-control'
|
class: 'form-control'
|
||||||
|
|
||||||
.form-group
|
.form-group
|
||||||
= f.submit t('.submit'), class: 'btn btn-lg btn-block'
|
= f.submit t('.submit'), class: 'btn btn-secondary btn-lg btn-block'
|
||||||
|
|
|
@ -30,5 +30,5 @@
|
||||||
placeholder: t('activerecord.attributes.usuarie.email')
|
placeholder: t('activerecord.attributes.usuarie.email')
|
||||||
.actions
|
.actions
|
||||||
= f.submit t('.resend_confirmation_instructions'),
|
= f.submit t('.resend_confirmation_instructions'),
|
||||||
class: 'btn btn-lg btn-block'
|
class: 'btn btn-secondary btn-lg btn-block'
|
||||||
= render 'devise/shared/links'
|
= render 'devise/shared/links'
|
||||||
|
|
|
@ -32,4 +32,4 @@
|
||||||
placeholder: t('activerecord.attributes.usuarie.password')
|
placeholder: t('activerecord.attributes.usuarie.password')
|
||||||
.actions
|
.actions
|
||||||
= f.submit t('devise.invitations.edit.submit_button'),
|
= f.submit t('devise.invitations.edit.submit_button'),
|
||||||
class: 'btn btn-lg btn-block'
|
class: 'btn btn-secondary btn-lg btn-block'
|
||||||
|
|
|
@ -16,4 +16,4 @@
|
||||||
= f.text_field field, class: 'form-control'
|
= f.text_field field, class: 'form-control'
|
||||||
.actions
|
.actions
|
||||||
= f.submit t('devise.invitations.new.submit_button'),
|
= f.submit t('devise.invitations.new.submit_button'),
|
||||||
class: 'btn btn-lg btn-block'
|
class: 'btn btn-secondary btn-lg btn-block'
|
||||||
|
|
|
@ -39,6 +39,6 @@
|
||||||
|
|
||||||
.actions
|
.actions
|
||||||
= f.submit t('.change_my_password'),
|
= f.submit t('.change_my_password'),
|
||||||
class: 'btn btn-lg btn-block'
|
class: 'btn btn-secondary btn-lg btn-block'
|
||||||
|
|
||||||
= render 'devise/shared/links'
|
= render 'devise/shared/links'
|
||||||
|
|
|
@ -20,5 +20,5 @@
|
||||||
placeholder: t('activerecord.attributes.usuarie.email')
|
placeholder: t('activerecord.attributes.usuarie.email')
|
||||||
.actions
|
.actions
|
||||||
= f.submit t('.send_me_reset_password_instructions'),
|
= f.submit t('.send_me_reset_password_instructions'),
|
||||||
class: 'btn btn-lg btn-block'
|
class: 'btn btn-secondary btn-lg btn-block'
|
||||||
= render 'devise/shared/links'
|
= render 'devise/shared/links'
|
||||||
|
|
|
@ -55,7 +55,7 @@
|
||||||
= t('.we_need_your_current_password_to_confirm_your_changes')
|
= t('.we_need_your_current_password_to_confirm_your_changes')
|
||||||
.actions
|
.actions
|
||||||
= f.submit t('.update'),
|
= f.submit t('.update'),
|
||||||
class: 'btn btn-lg btn-block'
|
class: 'btn btn-secondary btn-lg btn-block'
|
||||||
%hr/
|
%hr/
|
||||||
|
|
||||||
.sr-only
|
.sr-only
|
||||||
|
@ -63,4 +63,4 @@
|
||||||
= button_to t('.cancel_my_account'),
|
= button_to t('.cancel_my_account'),
|
||||||
registration_path(resource_name),
|
registration_path(resource_name),
|
||||||
data: { confirm: t('.are_you_sure') },
|
data: { confirm: t('.are_you_sure') },
|
||||||
method: :delete, class: 'btn btn-block'
|
method: :delete, class: 'btn btn-secondary btn-block'
|
||||||
|
|
|
@ -56,6 +56,6 @@
|
||||||
|
|
||||||
.actions
|
.actions
|
||||||
= f.submit t('.sign_up'),
|
= f.submit t('.sign_up'),
|
||||||
class: 'btn btn-lg btn-block'
|
class: 'btn btn-secondary btn-lg btn-block'
|
||||||
|
|
||||||
= render 'devise/shared/links'
|
= render 'devise/shared/links'
|
||||||
|
|
|
@ -35,5 +35,5 @@
|
||||||
remember_for: distance_of_time_in_words(Usuarie.remember_for))
|
remember_for: distance_of_time_in_words(Usuarie.remember_for))
|
||||||
.actions
|
.actions
|
||||||
= f.submit t('.sign_in'),
|
= f.submit t('.sign_in'),
|
||||||
class: 'btn btn-lg btn-block'
|
class: 'btn btn-secondary btn-lg btn-block'
|
||||||
= render 'devise/shared/links'
|
= render 'devise/shared/links'
|
||||||
|
|
|
@ -4,12 +4,12 @@
|
||||||
|
|
||||||
- if controller_name != 'sessions'
|
- if controller_name != 'sessions'
|
||||||
= link_to t('.sign_in'), new_session_path(resource_name, params: locale),
|
= link_to t('.sign_in'), new_session_path(resource_name, params: locale),
|
||||||
class: 'btn btn-lg btn-block btn-success'
|
class: 'btn btn-lg btn-block btn-secondary'
|
||||||
%br/
|
%br/
|
||||||
|
|
||||||
- if devise_mapping.registerable? && controller_name != 'registrations'
|
- if devise_mapping.registerable? && controller_name != 'registrations'
|
||||||
= link_to t('.sign_up'), new_registration_path(resource_name, params: locale),
|
= link_to t('.sign_up'), new_registration_path(resource_name, params: locale),
|
||||||
class: 'btn btn-lg btn-block btn-success'
|
class: 'btn btn-lg btn-block btn-secondary'
|
||||||
%br/
|
%br/
|
||||||
|
|
||||||
- if devise_mapping.recoverable?
|
- if devise_mapping.recoverable?
|
||||||
|
|
|
@ -20,5 +20,5 @@
|
||||||
placeholder: t('activerecord.attributes.usuarie.email')
|
placeholder: t('activerecord.attributes.usuarie.email')
|
||||||
.actions
|
.actions
|
||||||
= f.submit t('.resend_unlock_instructions'),
|
= f.submit t('.resend_unlock_instructions'),
|
||||||
class: 'btn btn-lg btn-block'
|
class: 'btn btn-secondary btn-lg btn-block'
|
||||||
= render 'devise/shared/links'
|
= render 'devise/shared/links'
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
= select_tag 'to',
|
= select_tag 'to',
|
||||||
options_for_select(@options, @lang_to),
|
options_for_select(@options, @lang_to),
|
||||||
class: 'form-control'
|
class: 'form-control'
|
||||||
= submit_tag t('i18n.translate'), class: 'btn', name: nil
|
= submit_tag t('i18n.translate'), class: 'btn btn-secondary', name: nil
|
||||||
- else
|
- else
|
||||||
= t('i18n.translating.from')
|
= t('i18n.translating.from')
|
||||||
= select_tag 'from',
|
= select_tag 'from',
|
||||||
|
@ -21,7 +21,7 @@
|
||||||
= select_tag 'to',
|
= select_tag 'to',
|
||||||
options_for_select(@options, @lang_to),
|
options_for_select(@options, @lang_to),
|
||||||
class: 'form-control'
|
class: 'form-control'
|
||||||
= submit_tag t('i18n.change'), class: 'btn', name: nil
|
= submit_tag t('i18n.change'), class: 'btn btn-secondary', name: nil
|
||||||
|
|
||||||
= render 'layouts/help', help: t('help.i18n.index')
|
= render 'layouts/help', help: t('help.i18n.index')
|
||||||
|
|
||||||
|
@ -33,16 +33,16 @@
|
||||||
= hidden_field 'i18n', 'lang_to', value: @lang_to
|
= hidden_field 'i18n', 'lang_to', value: @lang_to
|
||||||
.form-group
|
.form-group
|
||||||
.dropdown.inline
|
.dropdown.inline
|
||||||
%button.btn.dropdown-toggle{type: 'button',
|
%button.btn.btn-secondary.dropdown-toggle{type: 'button',
|
||||||
data: { toggle: 'dropdown' },
|
data: { toggle: 'dropdown' },
|
||||||
aria: { haspopup: 'true', expanded: 'false' }}
|
aria: { haspopup: 'true', expanded: 'false' }}
|
||||||
= t('i18n.jump')
|
= t('i18n.jump')
|
||||||
.dropdown-menu{aria: { labelledby: t('i18n.jump') }}
|
.dropdown-menu{aria: { labelledby: t('i18n.jump') }}
|
||||||
- @site.data.dig(@lang_from).each_pair do |section, content|
|
- @site.data.dig(@lang_from).each_pair do |section, content|
|
||||||
%a.dropdown-item{href: "##{section}"}= t("help.i18n.#{section}")
|
%a.dropdown-item{href: "##{section}"}= t("help.i18n.#{section}")
|
||||||
= submit_tag t('i18n.save'), class: 'btn'
|
= submit_tag t('i18n.save'), class: 'btn btn-secondary'
|
||||||
|
|
||||||
= render 'i18n/recursive', data: @site.data.dig(@lang_from), superkeys: []
|
= render 'i18n/recursive', data: @site.data.dig(@lang_from), superkeys: []
|
||||||
|
|
||||||
.form-group
|
.form-group
|
||||||
= submit_tag t('i18n.save'), class: 'btn'
|
= submit_tag t('i18n.save'), class: 'btn btn-secondary'
|
||||||
|
|
|
@ -17,15 +17,15 @@
|
||||||
- if @site&.tienda?
|
- if @site&.tienda?
|
||||||
%li.nav-item
|
%li.nav-item
|
||||||
= link_to t('.tienda'), @site.tienda_url,
|
= link_to t('.tienda'), @site.tienda_url,
|
||||||
role: 'button', class: 'btn'
|
role: 'button', class: 'btn btn-secondary'
|
||||||
|
|
||||||
%li.nav-item
|
%li.nav-item
|
||||||
= link_to t('.contact_us'), t('.contact_us_href'),
|
= link_to t('.contact_us'), t('.contact_us_href'),
|
||||||
class: 'btn', rel: 'me', target: '_blank'
|
class: 'btn btn-secondary', rel: 'me', target: '_blank'
|
||||||
|
|
||||||
%li.nav-item
|
%li.nav-item
|
||||||
= link_to t('.logout'), main_app.destroy_usuarie_session_path,
|
= link_to t('.logout'), main_app.destroy_usuarie_session_path,
|
||||||
method: :delete, role: 'button', class: 'btn'
|
method: :delete, role: 'button', class: 'btn btn-secondary'
|
||||||
- else
|
- else
|
||||||
- params.permit!
|
- params.permit!
|
||||||
- I18n.available_locales.each do |locale|
|
- I18n.available_locales.each do |locale|
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
= link_to text, link, class: 'btn',
|
= link_to text, link, class: 'btn btn-secondary',
|
||||||
data: { toggle: 'tooltip' }, 'aria-role': 'button', title: tooltip
|
data: { toggle: 'tooltip' }, 'aria-role': 'button', title: tooltip
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
%script{ type: 'text/javascript', src: '/env.js' }
|
%script{ type: 'text/javascript', src: '/env.js' }
|
||||||
= csrf_meta_tags
|
= csrf_meta_tags
|
||||||
= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload'
|
= 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'
|
= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload'
|
||||||
= stylesheet_pack_tag 'application', 'data-turbolinks-track': 'reload'
|
= stylesheet_pack_tag 'application', 'data-turbolinks-track': 'reload'
|
||||||
= favicon_link_tag 'sutty_cuadrada.png', rel: 'apple-touch-icon', type: 'image/png'
|
= favicon_link_tag 'sutty_cuadrada.png', rel: 'apple-touch-icon', type: 'image/png'
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
- invalid_help = site.config.fetch('invalid_help', t('.invalid_help'))
|
- invalid_help = site.config.fetch('invalid_help', t('.invalid_help'))
|
||||||
- sending_help = site.config.fetch('sending_help', t('.sending_help'))
|
- sending_help = site.config.fetch('sending_help', t('.sending_help'))
|
||||||
.form-group
|
.form-group
|
||||||
= submit_tag t('.save'), class: 'btn submit-post'
|
= submit_tag t('.save'), class: 'btn btn-secondary submit-post'
|
||||||
= render 'bootstrap/alert', class: 'invalid-help d-none' do
|
= render 'bootstrap/alert', class: 'invalid-help d-none' do
|
||||||
= invalid_help
|
= invalid_help
|
||||||
= render 'bootstrap/alert', class: 'sending-help d-none' do
|
= render 'bootstrap/alert', class: 'sending-help d-none' do
|
||||||
|
|
3
app/views/posts/attribute_ro/_new_content.haml
Normal file
3
app/views/posts/attribute_ro/_new_content.haml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
%tr{ id: attribute }
|
||||||
|
%th= post_label_t(attribute, post: post)
|
||||||
|
%td{ lang: locale, dir: dir }= metadata.value
|
3
app/views/posts/attribute_ro/_new_html.haml
Normal file
3
app/views/posts/attribute_ro/_new_html.haml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
%tr{ id: attribute }
|
||||||
|
%th= post_label_t(attribute, post: post)
|
||||||
|
%td{ lang: locale, dir: dir }= metadata.value.html_safe
|
|
@ -3,7 +3,7 @@
|
||||||
= render 'posts/attribute_feedback',
|
= render 'posts/attribute_feedback',
|
||||||
post: post, attribute: attribute, metadata: metadata
|
post: post, attribute: attribute, metadata: metadata
|
||||||
|
|
||||||
.editor{ id: attribute, data: { editor: '' } }
|
.old.editor{ id: attribute, data: { editor: '' } }
|
||||||
-# Esto es para luego decirle al navegador que se olvide estas cosas.
|
-# Esto es para luego decirle al navegador que se olvide estas cosas.
|
||||||
= hidden_field_tag 'storage_keys[]', "#{request.original_url}##{attribute}", data: { target: 'storage-key' }
|
= hidden_field_tag 'storage_keys[]', "#{request.original_url}##{attribute}", data: { target: 'storage-key' }
|
||||||
= render 'bootstrap/alert' do
|
= render 'bootstrap/alert' do
|
||||||
|
@ -20,82 +20,82 @@
|
||||||
TODO: Eliminar todo el espacio en blanco para minificar HTML
|
TODO: Eliminar todo el espacio en blanco para minificar HTML
|
||||||
.editor-toolbar{ style: 'z-index: 1' }
|
.editor-toolbar{ style: 'z-index: 1' }
|
||||||
.editor-primary-toolbar.scrollbar-black
|
.editor-primary-toolbar.scrollbar-black
|
||||||
%button.btn{ type: 'button', title: t('editor.multimedia'), data: { editor_button: 'multimedia' } }>
|
%button.btn.btn-secondary{ type: 'button', title: t('editor.multimedia'), data: { editor_button: 'multimedia' } }>
|
||||||
%i.fa.fa-fw.fa-upload>
|
%i.fa.fa-fw.fa-upload>
|
||||||
%span.sr-only>= t('editor.multimedia')
|
%span.sr-only>= t('editor.multimedia')
|
||||||
%button.btn{ type: 'button', title: t('editor.bold'), data: { editor_button: 'mark-bold' } }>
|
%button.btn.btn-secondary{ type: 'button', title: t('editor.bold'), data: { editor_button: 'mark-bold' } }>
|
||||||
%i.fa.fa-fw.fa-bold>
|
%i.fa.fa-fw.fa-bold>
|
||||||
%span.sr-only>= t('editor.bold')
|
%span.sr-only>= t('editor.bold')
|
||||||
%button.btn{ type: 'button', title: t('editor.italic'), data: { editor_button: 'mark-italic' } }>
|
%button.btn.btn-secondary{ type: 'button', title: t('editor.italic'), data: { editor_button: 'mark-italic' } }>
|
||||||
%i.fa.fa-fw.fa-italic>
|
%i.fa.fa-fw.fa-italic>
|
||||||
%span.sr-only>= t('editor.italic')
|
%span.sr-only>= t('editor.italic')
|
||||||
%button.btn{ type: 'button', title: t('editor.mark'), data: { editor_button: 'mark-mark' } }>
|
%button.btn.btn-secondary{ type: 'button', title: t('editor.mark'), data: { editor_button: 'mark-mark' } }>
|
||||||
%i.fa.fa-fw.fa-tint>
|
%i.fa.fa-fw.fa-tint>
|
||||||
%span.sr-only>= t('editor.mark')
|
%span.sr-only>= t('editor.mark')
|
||||||
%button.btn{ type: 'button', title: t('editor.link'), data: { editor_button: 'mark-link' } }>
|
%button.btn.btn-secondary{ type: 'button', title: t('editor.link'), data: { editor_button: 'mark-link' } }>
|
||||||
%i.fa.fa-fw.fa-link>
|
%i.fa.fa-fw.fa-link>
|
||||||
%span.sr-only>= t('editor.link')
|
%span.sr-only>= t('editor.link')
|
||||||
%button.btn{ type: 'button', title: t('editor.deleted'), data: { editor_button: 'mark-deleted' } }>
|
%button.btn.btn-secondary{ type: 'button', title: t('editor.deleted'), data: { editor_button: 'mark-deleted' } }>
|
||||||
%i.fa.fa-fw.fa-strikethrough>
|
%i.fa.fa-fw.fa-strikethrough>
|
||||||
%span.sr-only>= t('editor.deleted')
|
%span.sr-only>= t('editor.deleted')
|
||||||
%button.btn{ type: 'button', title: t('editor.underline'), data: { editor_button: 'mark-underline' } }>
|
%button.btn.btn-secondary{ type: 'button', title: t('editor.underline'), data: { editor_button: 'mark-underline' } }>
|
||||||
%i.fa.fa-fw.fa-underline>
|
%i.fa.fa-fw.fa-underline>
|
||||||
%span.sr-only>= t('editor.underline')
|
%span.sr-only>= t('editor.underline')
|
||||||
%button.btn{ type: 'button', title: t('editor.super'), data: { editor_button: 'mark-super' } }>
|
%button.btn.btn-secondary{ type: 'button', title: t('editor.super'), data: { editor_button: 'mark-super' } }>
|
||||||
%i.fa.fa-fw.fa-superscript>
|
%i.fa.fa-fw.fa-superscript>
|
||||||
%span.sr-only>= t('editor.super')
|
%span.sr-only>= t('editor.super')
|
||||||
%button.btn{ type: 'button', title: t('editor.sub'), data: { editor_button: 'mark-sub' } }>
|
%button.btn.btn-secondary{ type: 'button', title: t('editor.sub'), data: { editor_button: 'mark-sub' } }>
|
||||||
%i.fa.fa-fw.fa-subscript>
|
%i.fa.fa-fw.fa-subscript>
|
||||||
%span.sr-only>= t('editor.sub')
|
%span.sr-only>= t('editor.sub')
|
||||||
%button.btn{ type: 'button', title: t('editor.small'), data: { editor_button: 'mark-small' } }>
|
%button.btn.btn-secondary{ type: 'button', title: t('editor.small'), data: { editor_button: 'mark-small' } }>
|
||||||
%i.fa.fa-fw.fa-subscript>
|
%i.fa.fa-fw.fa-subscript>
|
||||||
%span.sr-only>= t('editor.small')
|
%span.sr-only>= t('editor.small')
|
||||||
%button.btn.mr-0{ type: 'button', title: t('editor.h1'), data: { editor_button: 'block-h1' } }>
|
%button.btn.btn-secondary.mr-0{ type: 'button', title: t('editor.h1'), data: { editor_button: 'block-h1' } }>
|
||||||
%i.fa.fa-fw.fa-heading>
|
%i.fa.fa-fw.fa-heading>
|
||||||
1
|
1
|
||||||
%span.sr-only>= t('editor.h1')
|
%span.sr-only>= t('editor.h1')
|
||||||
%details.d-inline>
|
%details.d-inline>
|
||||||
%summary.d-inline>
|
%summary.d-inline>
|
||||||
%span.btn.ml-0{ role: 'button', title: t('editor.more') }>
|
%span.btn.btn-secondary.ml-0{ role: 'button', title: t('editor.more') }>
|
||||||
%i.fa.fa-caret-right>
|
%i.fa.fa-caret-right>
|
||||||
%span.sr-only= t('editor.more')
|
%span.sr-only= t('editor.more')
|
||||||
.d-inline>
|
.d-inline>
|
||||||
%button.btn{ type: 'button', title: t('editor.h2'), data: { editor_button: 'block-h2' } }>
|
%button.btn.btn-secondary{ type: 'button', title: t('editor.h2'), data: { editor_button: 'block-h2' } }>
|
||||||
%i.fa.fa-fw.fa-heading>
|
%i.fa.fa-fw.fa-heading>
|
||||||
2
|
2
|
||||||
%span.sr-only>= t('editor.h2')
|
%span.sr-only>= t('editor.h2')
|
||||||
%button.btn{ type: 'button', title: t('editor.h3'), data: { editor_button: 'block-h3' } }>
|
%button.btn.btn-secondary{ type: 'button', title: t('editor.h3'), data: { editor_button: 'block-h3' } }>
|
||||||
%i.fa.fa-fw.fa-heading>
|
%i.fa.fa-fw.fa-heading>
|
||||||
3
|
3
|
||||||
%span.sr-only>= t('editor.h3')
|
%span.sr-only>= t('editor.h3')
|
||||||
%button.btn{ type: 'button', title: t('editor.h4'), data: { editor_button: 'block-h4' } }>
|
%button.btn.btn-secondary{ type: 'button', title: t('editor.h4'), data: { editor_button: 'block-h4' } }>
|
||||||
%i.fa.fa-fw.fa-heading>
|
%i.fa.fa-fw.fa-heading>
|
||||||
4
|
4
|
||||||
%span.sr-only>= t('editor.h4')
|
%span.sr-only>= t('editor.h4')
|
||||||
%button.btn{ type: 'button', title: t('editor.h5'), data: { editor_button: 'block-h5' } }>
|
%button.btn.btn-secondary{ type: 'button', title: t('editor.h5'), data: { editor_button: 'block-h5' } }>
|
||||||
%i.fa.fa-fw.fa-heading>
|
%i.fa.fa-fw.fa-heading>
|
||||||
5
|
5
|
||||||
%span.sr-only>= t('editor.h5')
|
%span.sr-only>= t('editor.h5')
|
||||||
%button.btn{ type: 'button', title: t('editor.h6'), data: { editor_button: 'block-h6' } }>
|
%button.btn.btn-secondary{ type: 'button', title: t('editor.h6'), data: { editor_button: 'block-h6' } }>
|
||||||
%i.fa.fa-fw.fa-heading>
|
%i.fa.fa-fw.fa-heading>
|
||||||
6
|
6
|
||||||
%span.sr-only>= t('editor.h6')
|
%span.sr-only>= t('editor.h6')
|
||||||
%button.btn{ type: 'button', title: t('editor.ul'), data: { editor_button: 'block-unordered_list' } }>
|
%button.btn.btn-secondary{ type: 'button', title: t('editor.ul'), data: { editor_button: 'block-unordered_list' } }>
|
||||||
%i.fa.fa-fw.fa-list-ul>
|
%i.fa.fa-fw.fa-list-ul>
|
||||||
%span.sr-only>= t('editor.ul')
|
%span.sr-only>= t('editor.ul')
|
||||||
%button.btn{ type: 'button', title: t('editor.ol'), data: { editor_button: 'block-ordered_list' } }>
|
%button.btn.btn-secondary{ type: 'button', title: t('editor.ol'), data: { editor_button: 'block-ordered_list' } }>
|
||||||
%i.fa.fa-fw.fa-list-ol>
|
%i.fa.fa-fw.fa-list-ol>
|
||||||
%span.sr-only>= t('editor.ol')
|
%span.sr-only>= t('editor.ol')
|
||||||
%button.btn{ type: 'button', title: t('editor.left'), data: { editor_button: 'parentBlock-left' } }>
|
%button.btn.btn-secondary{ type: 'button', title: t('editor.left'), data: { editor_button: 'parentBlock-left' } }>
|
||||||
%i.fa.fa-fw.fa-align-left>
|
%i.fa.fa-fw.fa-align-left>
|
||||||
%span.sr-only>= t('editor.left')
|
%span.sr-only>= t('editor.left')
|
||||||
%button.btn{ type: 'button', title: t('editor.center'), data: { editor_button: 'parentBlock-center' } }>
|
%button.btn.btn-secondary{ type: 'button', title: t('editor.center'), data: { editor_button: 'parentBlock-center' } }>
|
||||||
%i.fa.fa-fw.fa-align-center>
|
%i.fa.fa-fw.fa-align-center>
|
||||||
%span.sr-only>= t('editor.center')
|
%span.sr-only>= t('editor.center')
|
||||||
%button.btn{ type: 'button', title: t('editor.right'), data: { editor_button: 'parentBlock-right' } }>
|
%button.btn.btn-secondary{ type: 'button', title: t('editor.right'), data: { editor_button: 'parentBlock-right' } }>
|
||||||
%i.fa.fa-fw.fa-align-right>
|
%i.fa.fa-fw.fa-align-right>
|
||||||
%span.sr-only>= t('editor.right')
|
%span.sr-only>= t('editor.right')
|
||||||
%button.btn{ type: 'button', title: t('editor.blockquote'), data: { editor_button: 'block-blockquote' } }>
|
%button.btn.btn-secondary{ type: 'button', title: t('editor.blockquote'), data: { editor_button: 'block-blockquote' } }>
|
||||||
%i.fa.fa-fw.fa-quote-left>
|
%i.fa.fa-fw.fa-quote-left>
|
||||||
%span.sr-only>= t('editor.blockquote')
|
%span.sr-only>= t('editor.blockquote')
|
||||||
|
|
||||||
|
@ -116,8 +116,8 @@
|
||||||
%label{ for: 'multimedia-alt' }= t('editor.description')
|
%label{ for: 'multimedia-alt' }= t('editor.description')
|
||||||
%input.form-control{ type: 'text', id: 'multimedia-alt', name: 'multimedia-alt' }/
|
%input.form-control{ type: 'text', id: 'multimedia-alt', name: 'multimedia-alt' }/
|
||||||
.form-group
|
.form-group
|
||||||
%button.btn{ type: 'button', id: 'multimedia-file-upload', name: 'multimedia-file-upload' }= t('editor.multimedia-upload')
|
%button.btn.btn-secondary{ type: 'button', id: 'multimedia-file-upload', name: 'multimedia-file-upload' }= t('editor.multimedia-upload')
|
||||||
%button.btn{ type: 'button', id: 'multimedia-remove', name: 'multimedia-remove' }= t('editor.multimedia-remove')
|
%button.btn.btn-secondary{ type: 'button', id: 'multimedia-remove', name: 'multimedia-remove' }= t('editor.multimedia-remove')
|
||||||
|
|
||||||
.form-group{ data: { editor_auxiliary: 'link' } }
|
.form-group{ data: { editor_auxiliary: 'link' } }
|
||||||
%label{ for: 'link-url' }= t('editor.url')
|
%label{ for: 'link-url' }= t('editor.url')
|
||||||
|
@ -127,4 +127,4 @@
|
||||||
%p= t('editor.word')
|
%p= t('editor.word')
|
||||||
|
|
||||||
.editor-content.form-control.h-auto.mt-1{ contenteditable: 'true' }
|
.editor-content.form-control.h-auto.mt-1{ contenteditable: 'true' }
|
||||||
= metadata.value.html_safe
|
= metadata.to_s.html_safe
|
||||||
|
|
9
app/views/posts/attributes/_new_content.haml
Normal file
9
app/views/posts/attributes/_new_content.haml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
.form-group
|
||||||
|
= label_tag "#{base}_#{attribute}", post_label_t(attribute, post: post)
|
||||||
|
= render 'posts/attribute_feedback',
|
||||||
|
post: post, attribute: attribute, metadata: metadata
|
||||||
|
|
||||||
|
.new-editor.content{ id: attribute }
|
||||||
|
= text_area_tag "#{base}[#{attribute}]", metadata.to_s.html_safe,
|
||||||
|
dir: dir, lang: locale,
|
||||||
|
**field_options(attribute, metadata), class: 'd-none'
|
6
app/views/posts/attributes/_new_html.haml
Normal file
6
app/views/posts/attributes/_new_html.haml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
-# Editor de contenido
|
||||||
|
= render 'posts/attributes/new_content',
|
||||||
|
base: 'post', post: post, attribute: attribute,
|
||||||
|
metadata: metadata, site: site,
|
||||||
|
dir: dir, locale: locale,
|
||||||
|
autofocus: (post.attributes.first == attribute)
|
|
@ -1,3 +1,6 @@
|
||||||
.row.justify-content-center
|
.row.justify-content-center
|
||||||
.col-md-8
|
.col-md-8
|
||||||
|
= render 'layouts/details', summary: "Post" do
|
||||||
= render 'posts/form', site: @site, post: @post
|
= render 'posts/form', site: @site, post: @post
|
||||||
|
= render 'layouts/details', summary: t('.moderation_queue') do
|
||||||
|
= render 'posts/moderation_queue', site: @site, post: @post, moderation_queue: @moderation_queue
|
||||||
|
|
|
@ -15,13 +15,13 @@
|
||||||
= render 'schemas/row', site: @site, schema: schema, filter: @filter_params
|
= render 'schemas/row', site: @site, schema: schema, filter: @filter_params
|
||||||
|
|
||||||
- if policy(@site_stat).index?
|
- if policy(@site_stat).index?
|
||||||
= link_to t('stats.index.title'), site_stats_path(@site), class: 'btn'
|
= link_to t('stats.index.title'), site_stats_path(@site), class: 'btn btn-secondary'
|
||||||
|
|
||||||
- if policy(@site).edit?
|
- if policy(@site).edit?
|
||||||
= link_to t('sites.edit.btn', site: @site.title), edit_site_path(@site), class: 'btn'
|
= link_to t('sites.edit.btn', site: @site.title), edit_site_path(@site), class: 'btn btn-secondary'
|
||||||
|
|
||||||
- if policy(@site).private?
|
- if policy(@site).private?
|
||||||
= link_to t('sites.private'), '../private/' + @site.name, class: 'btn', target: '_blank', rel: 'noopener'
|
= link_to t('sites.private'), '../private/' + @site.name, class: 'btn btn-secondary', target: '_blank', rel: 'noopener'
|
||||||
|
|
||||||
- if policy(SiteUsuarie.new(@site, current_usuarie)).index?
|
- if policy(SiteUsuarie.new(@site, current_usuarie)).index?
|
||||||
= render 'layouts/btn_with_tooltip',
|
= render 'layouts/btn_with_tooltip',
|
||||||
|
@ -33,9 +33,9 @@
|
||||||
- if @site.design.credits
|
- if @site.design.credits
|
||||||
= render 'bootstrap/alert' do
|
= render 'bootstrap/alert' do
|
||||||
= sanitize_markdown @site.design.credits
|
= sanitize_markdown @site.design.credits
|
||||||
= link_to t('sites.donations.text'), t('sites.donations.url'), class: 'btn'
|
= link_to t('sites.donations.text'), t('sites.donations.url'), class: 'btn btn-secondary'
|
||||||
- if @site.design.designer_url
|
- if @site.design.designer_url
|
||||||
= link_to t('sites.designer_url'), @site.design.designer_url, class: 'btn'
|
= link_to t('sites.designer_url'), @site.design.designer_url, class: 'btn btn-secondary'
|
||||||
|
|
||||||
%section.col
|
%section.col
|
||||||
.d-flex.justify-content-between.align-items-center.pl-2-plus.pr-2-plus.mb-2
|
.d-flex.justify-content-between.align-items-center.pl-2-plus.pr-2-plus.mb-2
|
||||||
|
@ -75,19 +75,19 @@
|
||||||
%th.border-0{ colspan: '4' }
|
%th.border-0{ colspan: '4' }
|
||||||
.d-flex.flex-row.justify-content-between
|
.d-flex.flex-row.justify-content-between
|
||||||
%div
|
%div
|
||||||
= submit_tag t('posts.reorder.submit'), class: 'btn'
|
= submit_tag t('posts.reorder.submit'), class: 'btn btn-secondary'
|
||||||
%button.btn{ data: { action: 'reorder#unselect' } }
|
%button.btn.btn-secondary{ data: { action: 'reorder#unselect' } }
|
||||||
= t('posts.reorder.unselect')
|
= t('posts.reorder.unselect')
|
||||||
%span.badge{ data: { target: 'reorder.counter' } } 0
|
%span.badge{ data: { target: 'reorder.counter' } } 0
|
||||||
%button.btn{ data: { action: 'reorder#up' } }= t('posts.reorder.up')
|
%button.btn.btn-secondary{ data: { action: 'reorder#up' } }= t('posts.reorder.up')
|
||||||
%button.btn{ data: { action: 'reorder#down' } }= t('posts.reorder.down')
|
%button.btn.btn-secondary{ data: { action: 'reorder#down' } }= t('posts.reorder.down')
|
||||||
%button.btn{ data: { action: 'reorder#top' } }= t('posts.reorder.top')
|
%button.btn.btn-secondary{ data: { action: 'reorder#top' } }= t('posts.reorder.top')
|
||||||
%button.btn{ data: { action: 'reorder#bottom' } }= t('posts.reorder.bottom')
|
%button.btn.btn-secondary{ data: { action: 'reorder#bottom' } }= t('posts.reorder.bottom')
|
||||||
|
|
||||||
- if @site.pagination
|
- if @site.pagination
|
||||||
%div
|
%div
|
||||||
= link_to_prev_page @posts, t('posts.prev'), class: 'btn'
|
= link_to_prev_page @posts, t('posts.prev'), class: 'btn btn-secondary'
|
||||||
= link_to_next_page @posts, t('posts.next'), class: 'btn'
|
= link_to_next_page @posts, t('posts.next'), class: 'btn btn-secondary'
|
||||||
%tbody
|
%tbody
|
||||||
- dir = @site.data.dig(params[:locale], 'dir')
|
- dir = @site.data.dig(params[:locale], 'dir')
|
||||||
- size = @posts.size
|
- size = @posts.size
|
||||||
|
@ -126,9 +126,9 @@
|
||||||
= post.order
|
= post.order
|
||||||
%td.text-nowrap
|
%td.text-nowrap
|
||||||
- if @usuarie || policy(post).edit?
|
- if @usuarie || policy(post).edit?
|
||||||
= link_to t('posts.edit'), edit_site_post_path(@site, post.path), class: 'btn btn-block'
|
= link_to t('posts.edit'), edit_site_post_path(@site, post.path), class: 'btn btn-secondary btn-block'
|
||||||
- if @usuarie || policy(post).destroy?
|
- if @usuarie || policy(post).destroy?
|
||||||
= link_to t('posts.destroy'), site_post_path(@site, post.path), class: 'btn btn-block', method: :delete, data: { confirm: t('posts.confirm_destroy') }
|
= link_to t('posts.destroy'), site_post_path(@site, post.path), class: 'btn btn-secondary btn-block', method: :delete, data: { confirm: t('posts.confirm_destroy') }
|
||||||
|
|
||||||
#footnotes{ hidden: true }
|
#footnotes{ hidden: true }
|
||||||
- @filter_params.each do |param, value|
|
- @filter_params.each do |param, value|
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
%article.content.table-responsive-md
|
%article.content.table-responsive-md
|
||||||
= link_to t('posts.edit'),
|
= link_to t('posts.edit'),
|
||||||
edit_site_post_path(@site, @post.id),
|
edit_site_post_path(@site, @post.id),
|
||||||
class: 'btn btn-block'
|
class: 'btn btn-secondary btn-block'
|
||||||
|
|
||||||
%table.table.table-condensed
|
%table.table.table-condensed
|
||||||
%thead
|
%thead
|
||||||
|
@ -30,5 +30,5 @@
|
||||||
- next if metadata.front_matter?
|
- next if metadata.front_matter?
|
||||||
|
|
||||||
- cache [metadata, I18n.locale] do
|
- cache [metadata, I18n.locale] do
|
||||||
%section.editor{ id: attr, dir: dir }
|
%section.content.pb-3{ id: attr, dir: dir }
|
||||||
= @post.public_send(attr).value.html_safe
|
= @post.public_send(attr).to_s.html_safe
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
method: :post,
|
method: :post,
|
||||||
class: 'form-inline inline' do
|
class: 'form-inline inline' do
|
||||||
= submit_tag site.enqueued? ? t('sites.enqueued') : t('sites.enqueue'),
|
= submit_tag site.enqueued? ? t('sites.enqueued') : t('sites.enqueue'),
|
||||||
class: "btn no-border-radius #{local_assigns[:class]}",
|
class: "btn btn-secondary #{local_assigns[:class]}",
|
||||||
title: site.enqueued? ? t('help.sites.enqueued') : t('help.sites.enqueue'),
|
title: site.enqueued? ? t('help.sites.enqueued') : t('help.sites.enqueue'),
|
||||||
data: { disable_with: t('sites.enqueued') },
|
data: { disable_with: t('sites.enqueued') },
|
||||||
disabled: site.enqueued?
|
disabled: site.enqueued?
|
||||||
|
|
|
@ -72,10 +72,10 @@
|
||||||
.btn-group{ role: 'group', 'aria-label': t('.design.actions') }
|
.btn-group{ role: 'group', 'aria-label': t('.design.actions') }
|
||||||
- if design.url
|
- if design.url
|
||||||
= link_to t('.design.url'), design.url,
|
= link_to t('.design.url'), design.url,
|
||||||
target: '_blank', class: 'btn'
|
target: '_blank', class: 'btn btn-secondary'
|
||||||
- if design.license
|
- if design.license
|
||||||
= link_to t('.design.license'), design.license,
|
= link_to t('.design.license'), design.license,
|
||||||
target: '_blank', class: 'btn'
|
target: '_blank', class: 'btn btn-secondary'
|
||||||
%hr/
|
%hr/
|
||||||
|
|
||||||
.form-group.licenses#license_id
|
.form-group.licenses#license_id
|
||||||
|
@ -99,7 +99,7 @@
|
||||||
tags: %w[p a strong em ul ol li h1 h2 h3 h4 h5 h6]
|
tags: %w[p a strong em ul ol li h1 h2 h3 h4 h5 h6]
|
||||||
|
|
||||||
- unless licencia.custom?
|
- unless licencia.custom?
|
||||||
= link_to t('.licencia.url'), licencia.url, target: '_blank', class: 'btn', rel: 'noopener'
|
= link_to t('.licencia.url'), licencia.url, target: '_blank', class: 'btn btn-secondary', rel: 'noopener'
|
||||||
|
|
||||||
%hr/
|
%hr/
|
||||||
|
|
||||||
|
@ -163,4 +163,4 @@
|
||||||
deploy: deploy, site: site
|
deploy: deploy, site: site
|
||||||
|
|
||||||
.form-group
|
.form-group
|
||||||
= f.submit submit, class: 'btn btn-lg btn-block'
|
= f.submit submit, class: 'btn btn-secondary btn-lg btn-block'
|
||||||
|
|
|
@ -27,4 +27,4 @@
|
||||||
.row.justify-content-center
|
.row.justify-content-center
|
||||||
.col-md-8
|
.col-md-8
|
||||||
= link_to t('.merge.request'), site_pull_path(@site),
|
= link_to t('.merge.request'), site_pull_path(@site),
|
||||||
method: 'post', class: 'btn btn-lg'
|
method: 'post', class: 'btn btn-secondary btn-lg'
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
%p.lead= t('.help')
|
%p.lead= t('.help')
|
||||||
- if policy(Site).new?
|
- if policy(Site).new?
|
||||||
= link_to t('sites.new.title'), new_site_path,
|
= link_to t('sites.new.title'), new_site_path,
|
||||||
class: 'btn'
|
class: 'btn btn-secondary'
|
||||||
|
|
||||||
%section.col
|
%section.col
|
||||||
- if @sites.empty?
|
- if @sites.empty?
|
||||||
|
@ -29,18 +29,18 @@
|
||||||
= site.title
|
= site.title
|
||||||
%p.lead= site.description
|
%p.lead= site.description
|
||||||
%br
|
%br
|
||||||
= link_to t('.visit'), site.url, class: 'btn'
|
= link_to t('.visit'), site.url, class: 'btn btn-secondary'
|
||||||
- if rol.temporal
|
- if rol.temporal
|
||||||
= button_to t('sites.invitations.accept'),
|
= button_to t('sites.invitations.accept'),
|
||||||
site_usuaries_accept_invitation_path(site),
|
site_usuaries_accept_invitation_path(site),
|
||||||
method: :patch,
|
method: :patch,
|
||||||
title: t('help.sites.invitations.accept'),
|
title: t('help.sites.invitations.accept'),
|
||||||
class: 'btn'
|
class: 'btn btn-secondary'
|
||||||
= button_to t('sites.invitations.reject'),
|
= button_to t('sites.invitations.reject'),
|
||||||
site_usuaries_reject_invitation_path(site),
|
site_usuaries_reject_invitation_path(site),
|
||||||
method: :patch,
|
method: :patch,
|
||||||
title: t('help.sites.invitations.reject'),
|
title: t('help.sites.invitations.reject'),
|
||||||
class: 'btn'
|
class: 'btn btn-secondary'
|
||||||
- else
|
- else
|
||||||
- if policy(site).show?
|
- if policy(site).show?
|
||||||
= render 'layouts/btn_with_tooltip',
|
= render 'layouts/btn_with_tooltip',
|
||||||
|
@ -54,10 +54,4 @@
|
||||||
text: t('usuaries.index.title'),
|
text: t('usuaries.index.title'),
|
||||||
type: 'info',
|
type: 'info',
|
||||||
link: site_usuaries_path(site)
|
link: site_usuaries_path(site)
|
||||||
- if policy(site).pull? && site.repository.needs_pull?
|
|
||||||
= render 'layouts/btn_with_tooltip',
|
|
||||||
tooltip: t('help.sites.pull'),
|
|
||||||
text: t('.pull'),
|
|
||||||
type: 'info',
|
|
||||||
link: site_pull_path(site)
|
|
||||||
= render 'sites/build', site: site
|
= render 'sites/build', site: site
|
||||||
|
|
|
@ -11,11 +11,11 @@
|
||||||
|
|
||||||
%form.mb-5.form-inline{ method: 'get' }
|
%form.mb-5.form-inline{ method: 'get' }
|
||||||
- Stat::INTERVALS.each do |interval|
|
- Stat::INTERVALS.each do |interval|
|
||||||
= link_to t(".#{interval}"), site_stats_path(interval: interval, urls: params[:urls], period_start: params[:period_start].to_date.try(:"beginning_of_#{interval}").to_date, period_end: params[:period_end]), class: "mb-0 btn #{'btn-primary active' if @interval == interval}"
|
= link_to t(".#{interval}"), site_stats_path(interval: interval, urls: params[:urls], period_start: params[:period_start].to_date.try(:"beginning_of_#{interval}").to_date, period_end: params[:period_end]), class: "mb-0 btn #{@interval == interval ? 'btn-primary active' : 'btn-secondary' }"
|
||||||
|
|
||||||
%input.form-control{ type: 'date', name: :period_start, value: params[:period_start] }
|
%input.form-control{ type: 'date', name: :period_start, value: params[:period_start] }
|
||||||
%input.form-control{ type: 'date', name: :period_end, value: params[:period_end] }
|
%input.form-control{ type: 'date', name: :period_end, value: params[:period_end] }
|
||||||
%button.btn.mb-0{ type: 'submit' }= t('.filter')
|
%button.btn.btn-secondary.mb-0{ type: 'submit' }= t('.filter')
|
||||||
|
|
||||||
.mb-5
|
.mb-5
|
||||||
%h2= t('.host.title', count: @hostnames.size)
|
%h2= t('.host.title', count: @hostnames.size)
|
||||||
|
@ -34,7 +34,7 @@
|
||||||
%textarea#urls.form-control{ name: 'urls', autocomplete: 'on', required: true, rows: @normalized_urls.size + 1, aria_describedby: 'help-urls' }= @normalized_urls.join("\n")
|
%textarea#urls.form-control{ name: 'urls', autocomplete: 'on', required: true, rows: @normalized_urls.size + 1, aria_describedby: 'help-urls' }= @normalized_urls.join("\n")
|
||||||
%small#help-urls.feedback.form-text.text-muted= t('.urls.help')
|
%small#help-urls.feedback.form-text.text-muted= t('.urls.help')
|
||||||
.form-group
|
.form-group
|
||||||
%button.btn{ type: 'submit' }= t('.urls.submit')
|
%button.btn.btn-secondary{ type: 'submit' }= t('.urls.submit')
|
||||||
- if @normalized_urls.present?
|
- if @normalized_urls.present?
|
||||||
= line_chart site_stats_uris_path(urls: @normalized_urls, **@chart_params), **@chart_options
|
= line_chart site_stats_uris_path(urls: @normalized_urls, **@chart_params), **@chart_options
|
||||||
|
|
||||||
|
|
|
@ -9,13 +9,13 @@
|
||||||
- if @policy.invite?
|
- if @policy.invite?
|
||||||
= link_to t('.invite'),
|
= link_to t('.invite'),
|
||||||
site_usuaries_invite_path(@site, invite_as: u.to_s),
|
site_usuaries_invite_path(@site, invite_as: u.to_s),
|
||||||
class: 'btn',
|
class: 'btn btn-secondary',
|
||||||
data: { toggle: 'tooltip' },
|
data: { toggle: 'tooltip' },
|
||||||
title: t('.help.invite', invite_as: u.to_s)
|
title: t('.help.invite', invite_as: u.to_s)
|
||||||
- if policy(Collaboration.new(@site)).collaborate?
|
- if policy(Collaboration.new(@site)).collaborate?
|
||||||
= link_to t('.public_invite'),
|
= link_to t('.public_invite'),
|
||||||
site_collaborate_path(@site),
|
site_collaborate_path(@site),
|
||||||
class: 'btn',
|
class: 'btn btn-secondary',
|
||||||
data: { toggle: 'tooltip' },
|
data: { toggle: 'tooltip' },
|
||||||
title: t('.help.public_invite')
|
title: t('.help.public_invite')
|
||||||
%p.lead= t(".help.#{u}")
|
%p.lead= t(".help.#{u}")
|
||||||
|
@ -38,7 +38,7 @@
|
||||||
- if @policy.demote? && @site.usuarie?(cuenta)
|
- if @policy.demote? && @site.usuarie?(cuenta)
|
||||||
= link_to t('.demote.text'),
|
= link_to t('.demote.text'),
|
||||||
site_usuarie_demote_path(@site, cuenta),
|
site_usuarie_demote_path(@site, cuenta),
|
||||||
class: 'btn',
|
class: 'btn btn-secondary',
|
||||||
data: { toggle: 'tooltip',
|
data: { toggle: 'tooltip',
|
||||||
confirm: t('.demote.confirm') },
|
confirm: t('.demote.confirm') },
|
||||||
title: t('.help.demote'),
|
title: t('.help.demote'),
|
||||||
|
@ -46,7 +46,7 @@
|
||||||
- if @policy.promote? && @site.invitade?(cuenta)
|
- if @policy.promote? && @site.invitade?(cuenta)
|
||||||
= link_to t('.promote.text'),
|
= link_to t('.promote.text'),
|
||||||
site_usuarie_promote_path(@site, cuenta),
|
site_usuarie_promote_path(@site, cuenta),
|
||||||
class: 'btn',
|
class: 'btn btn-secondary',
|
||||||
data: { toggle: 'tooltip',
|
data: { toggle: 'tooltip',
|
||||||
confirm: t('.promote.confirm') },
|
confirm: t('.promote.confirm') },
|
||||||
title: t('.help.promote'),
|
title: t('.help.promote'),
|
||||||
|
@ -54,7 +54,7 @@
|
||||||
- if @policy.destroy?
|
- if @policy.destroy?
|
||||||
= link_to t('.destroy.text'),
|
= link_to t('.destroy.text'),
|
||||||
site_usuarie_path(@site, cuenta),
|
site_usuarie_path(@site, cuenta),
|
||||||
class: 'btn',
|
class: 'btn btn-secondary',
|
||||||
data: { toggle: 'tooltip',
|
data: { toggle: 'tooltip',
|
||||||
confirm: t('.destroy.confirm') },
|
confirm: t('.destroy.confirm') },
|
||||||
title: t('.help.destroy'),
|
title: t('.help.destroy'),
|
||||||
|
|
|
@ -13,4 +13,4 @@
|
||||||
invite_as: invite_as)
|
invite_as: invite_as)
|
||||||
= f.text_area :invitaciones, class: 'form-control'
|
= f.text_area :invitaciones, class: 'form-control'
|
||||||
.form-group
|
.form-group
|
||||||
= f.submit t('.submit'), class: 'btn'
|
= f.submit t('.submit'), class: 'btn btn-secondary'
|
||||||
|
|
8
bin/modified_files
Executable file
8
bin/modified_files
Executable file
|
@ -0,0 +1,8 @@
|
||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
test -n "${CI_MERGE_REQUEST_DIFF_BASE_SHA}"
|
||||||
|
|
||||||
|
git diff --name-status ${CI_MERGE_REQUEST_DIFF_BASE_SHA} \
|
||||||
|
| grep -v "^D" \
|
||||||
|
| cut -f 2
|
4
bin/with_extension
Executable file
4
bin/with_extension
Executable file
|
@ -0,0 +1,4 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
grep "\.${1}$"
|
||||||
|
exit 0
|
|
@ -21,6 +21,17 @@ require 'rails/test_unit/railtie'
|
||||||
# you've limited to :test, :development, or :production.
|
# you've limited to :test, :development, or :production.
|
||||||
Bundler.require(*Rails.groups)
|
Bundler.require(*Rails.groups)
|
||||||
|
|
||||||
|
if %w[development test].include? ENV['RAILS_ENV']
|
||||||
|
# https://github.com/bkeepers/dotenv/pull/453
|
||||||
|
Dotenv::Railtie.class_eval do
|
||||||
|
def overload
|
||||||
|
Dotenv.overload(*dotenv_files.reverse)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Dotenv::Railtie.overload
|
||||||
|
end
|
||||||
|
|
||||||
module Sutty
|
module Sutty
|
||||||
# Sutty!
|
# Sutty!
|
||||||
class Application < Rails::Application
|
class Application < Rails::Application
|
||||||
|
@ -45,6 +56,8 @@ module Sutty
|
||||||
config.active_storage.queues.purge = :default
|
config.active_storage.queues.purge = :default
|
||||||
config.active_job.queue_adapter = :que
|
config.active_job.queue_adapter = :que
|
||||||
|
|
||||||
|
config.active_record.schema_format = :sql
|
||||||
|
|
||||||
config.to_prepare do
|
config.to_prepare do
|
||||||
# Load application's model / class decorators
|
# Load application's model / class decorators
|
||||||
Dir.glob(File.join(File.dirname(__FILE__), '..', 'app', '**', '*_decorator.rb')).sort.each do |c|
|
Dir.glob(File.join(File.dirname(__FILE__), '..', 'app', '**', '*_decorator.rb')).sort.each do |c|
|
||||||
|
|
252
config/brakeman.ignore
Normal file
252
config/brakeman.ignore
Normal file
|
@ -0,0 +1,252 @@
|
||||||
|
{
|
||||||
|
"ignored_warnings": [
|
||||||
|
{
|
||||||
|
"warning_type": "Redirect",
|
||||||
|
"warning_code": 18,
|
||||||
|
"fingerprint": "0ae5c3990d49dfbfd4fd61874451f7a576d5056aca913068adf58c314625f810",
|
||||||
|
"check_name": "Redirect",
|
||||||
|
"message": "Possible unprotected redirect",
|
||||||
|
"file": "app/controllers/api/v1/posts_controller.rb",
|
||||||
|
"line": 20,
|
||||||
|
"link": "https://brakemanscanner.org/docs/warning_types/redirect/",
|
||||||
|
"code": "redirect_to((params[:redirect_to] or origin.to_s))",
|
||||||
|
"render_path": null,
|
||||||
|
"location": {
|
||||||
|
"type": "method",
|
||||||
|
"class": "Api::V1::PostsController",
|
||||||
|
"method": "create"
|
||||||
|
},
|
||||||
|
"user_input": "params[:redirect_to]",
|
||||||
|
"confidence": "High",
|
||||||
|
"cwe_id": [
|
||||||
|
601
|
||||||
|
],
|
||||||
|
"note": "https://0xacab.org/sutty/sutty/-/issues/14957"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"warning_type": "Denial of Service",
|
||||||
|
"warning_code": 76,
|
||||||
|
"fingerprint": "1947d1a2ae6e4bf718d0cc563e660efca96897165e9a8dd18186c1d7abe6ddf6",
|
||||||
|
"check_name": "RegexDoS",
|
||||||
|
"message": "Model attribute used in regular expression",
|
||||||
|
"file": "app/controllers/api/v1/base_controller.rb",
|
||||||
|
"line": 20,
|
||||||
|
"link": "https://brakemanscanner.org/docs/warning_types/denial_of_service/",
|
||||||
|
"code": "/\\.#{Site.domain}\\z/",
|
||||||
|
"render_path": null,
|
||||||
|
"location": {
|
||||||
|
"type": "method",
|
||||||
|
"class": "Api::V1::BaseController",
|
||||||
|
"method": "site_id"
|
||||||
|
},
|
||||||
|
"user_input": "Site.domain",
|
||||||
|
"confidence": "Medium",
|
||||||
|
"cwe_id": [
|
||||||
|
20,
|
||||||
|
185
|
||||||
|
],
|
||||||
|
"note": "No es un atributo, es una variable de entorno"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"warning_type": "Cross-Site Scripting",
|
||||||
|
"warning_code": 4,
|
||||||
|
"fingerprint": "28d98d08a15c4b3ad94a2cfa20a12573de12d99f1a30b3ca51074ee1f1886592",
|
||||||
|
"check_name": "LinkToHref",
|
||||||
|
"message": "Potentially unsafe model attribute in `link_to` href",
|
||||||
|
"file": "app/views/layouts/_breadcrumb.haml",
|
||||||
|
"line": 19,
|
||||||
|
"link": "https://brakemanscanner.org/docs/warning_types/link_to_href",
|
||||||
|
"code": "link_to(t(\".tienda\"), Site.find(params[:site_id]).tienda_url, :role => \"button\", :class => \"btn\")",
|
||||||
|
"render_path": [
|
||||||
|
{
|
||||||
|
"type": "controller",
|
||||||
|
"class": "Api::V1::NoticesController",
|
||||||
|
"method": "site",
|
||||||
|
"line": 31,
|
||||||
|
"file": "app/controllers/api/v1/notices_controller.rb",
|
||||||
|
"rendered": {
|
||||||
|
"name": "layouts/application",
|
||||||
|
"file": "app/views/layouts/application.html.haml"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "template",
|
||||||
|
"name": "layouts/application",
|
||||||
|
"line": 25,
|
||||||
|
"file": "app/views/layouts/application.html.haml",
|
||||||
|
"rendered": {
|
||||||
|
"name": "layouts/_breadcrumb",
|
||||||
|
"file": "app/views/layouts/_breadcrumb.haml"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"location": {
|
||||||
|
"type": "template",
|
||||||
|
"template": "layouts/_breadcrumb"
|
||||||
|
},
|
||||||
|
"user_input": "Site.find(params[:site_id]).tienda_url",
|
||||||
|
"confidence": "Weak",
|
||||||
|
"cwe_id": [
|
||||||
|
79
|
||||||
|
],
|
||||||
|
"note": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"warning_type": "Redirect",
|
||||||
|
"warning_code": 18,
|
||||||
|
"fingerprint": "5034e51aaa1bac06d15fdde5956edffbfd65f94f5620a409526bbea896dc7b5f",
|
||||||
|
"check_name": "Redirect",
|
||||||
|
"message": "Possible unprotected redirect",
|
||||||
|
"file": "app/controllers/api/v1/contact_controller.rb",
|
||||||
|
"line": 26,
|
||||||
|
"link": "https://brakemanscanner.org/docs/warning_types/redirect/",
|
||||||
|
"code": "redirect_to((params[:redirect] or origin.to_s))",
|
||||||
|
"render_path": null,
|
||||||
|
"location": {
|
||||||
|
"type": "method",
|
||||||
|
"class": "Api::V1::ContactController",
|
||||||
|
"method": "receive"
|
||||||
|
},
|
||||||
|
"user_input": "params[:redirect]",
|
||||||
|
"confidence": "High",
|
||||||
|
"cwe_id": [
|
||||||
|
601
|
||||||
|
],
|
||||||
|
"note": "https://0xacab.org/sutty/sutty/-/issues/14957"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"warning_type": "Mass Assignment",
|
||||||
|
"warning_code": 70,
|
||||||
|
"fingerprint": "50582f39f8dfa900d3f2b5b9908b1592f8b8bd9e2d0b9d1cc05d77e5ede2d94e",
|
||||||
|
"check_name": "MassAssignment",
|
||||||
|
"message": "Specify exact keys allowed for mass assignment instead of using `permit!` which allows any keys",
|
||||||
|
"file": "app/views/layouts/_link_rel_alternate.haml",
|
||||||
|
"line": 2,
|
||||||
|
"link": "https://brakemanscanner.org/docs/warning_types/mass_assignment/",
|
||||||
|
"code": "params.permit!",
|
||||||
|
"render_path": [
|
||||||
|
{
|
||||||
|
"type": "controller",
|
||||||
|
"class": "Api::V1::BaseController",
|
||||||
|
"method": "site_id",
|
||||||
|
"line": 20,
|
||||||
|
"file": "app/controllers/api/v1/base_controller.rb",
|
||||||
|
"rendered": {
|
||||||
|
"name": "layouts/application",
|
||||||
|
"file": "app/views/layouts/application.html.haml"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "template",
|
||||||
|
"name": "layouts/application",
|
||||||
|
"line": 21,
|
||||||
|
"file": "app/views/layouts/application.html.haml",
|
||||||
|
"rendered": {
|
||||||
|
"name": "layouts/_link_rel_alternate",
|
||||||
|
"file": "app/views/layouts/_link_rel_alternate.haml"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"location": {
|
||||||
|
"type": "template",
|
||||||
|
"template": "layouts/_link_rel_alternate"
|
||||||
|
},
|
||||||
|
"user_input": null,
|
||||||
|
"confidence": "Medium",
|
||||||
|
"cwe_id": [
|
||||||
|
915
|
||||||
|
],
|
||||||
|
"note": "https://0xacab.org/sutty/sutty/-/issues/14958"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"warning_type": "Mass Assignment",
|
||||||
|
"warning_code": 70,
|
||||||
|
"fingerprint": "b8e0aa898288bebb614ccc1340d169caa196d315c6ac2e4744081cc892c2ae97",
|
||||||
|
"check_name": "MassAssignment",
|
||||||
|
"message": "Specify exact keys allowed for mass assignment instead of using `permit!` which allows any keys",
|
||||||
|
"file": "app/views/layouts/_breadcrumb.haml",
|
||||||
|
"line": 30,
|
||||||
|
"link": "https://brakemanscanner.org/docs/warning_types/mass_assignment/",
|
||||||
|
"code": "params.permit!",
|
||||||
|
"render_path": [
|
||||||
|
{
|
||||||
|
"type": "controller",
|
||||||
|
"class": "Api::V1::BaseController",
|
||||||
|
"method": "site_id",
|
||||||
|
"line": 20,
|
||||||
|
"file": "app/controllers/api/v1/base_controller.rb",
|
||||||
|
"rendered": {
|
||||||
|
"name": "layouts/application",
|
||||||
|
"file": "app/views/layouts/application.html.haml"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "template",
|
||||||
|
"name": "layouts/application",
|
||||||
|
"line": 25,
|
||||||
|
"file": "app/views/layouts/application.html.haml",
|
||||||
|
"rendered": {
|
||||||
|
"name": "layouts/_breadcrumb",
|
||||||
|
"file": "app/views/layouts/_breadcrumb.haml"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"location": {
|
||||||
|
"type": "template",
|
||||||
|
"template": "layouts/_breadcrumb"
|
||||||
|
},
|
||||||
|
"user_input": null,
|
||||||
|
"confidence": "Medium",
|
||||||
|
"cwe_id": [
|
||||||
|
915
|
||||||
|
],
|
||||||
|
"note": "https://0xacab.org/sutty/sutty/-/issues/14958"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"warning_type": "Cross-Site Scripting",
|
||||||
|
"warning_code": 4,
|
||||||
|
"fingerprint": "c051421c7cf4c2706b8e27bfd2f3b0661ec6a6df873da322a6b634b59e80351b",
|
||||||
|
"check_name": "LinkToHref",
|
||||||
|
"message": "Potentially unsafe model attribute in `link_to` href",
|
||||||
|
"file": "app/views/sites/_form.haml",
|
||||||
|
"line": 74,
|
||||||
|
"link": "https://brakemanscanner.org/docs/warning_types/link_to_href",
|
||||||
|
"code": "link_to(t(\".design.url\"), (Unresolved Model).new.url, :target => \"_blank\", :class => \"btn\")",
|
||||||
|
"render_path": [
|
||||||
|
{
|
||||||
|
"type": "controller",
|
||||||
|
"class": "SitesController",
|
||||||
|
"method": "new",
|
||||||
|
"line": 31,
|
||||||
|
"file": "app/controllers/sites_controller.rb",
|
||||||
|
"rendered": {
|
||||||
|
"name": "sites/new",
|
||||||
|
"file": "app/views/sites/new.haml"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "template",
|
||||||
|
"name": "sites/new",
|
||||||
|
"line": 6,
|
||||||
|
"file": "app/views/sites/new.haml",
|
||||||
|
"rendered": {
|
||||||
|
"name": "sites/_form",
|
||||||
|
"file": "app/views/sites/_form.haml"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"location": {
|
||||||
|
"type": "template",
|
||||||
|
"template": "sites/_form"
|
||||||
|
},
|
||||||
|
"user_input": "(Unresolved Model).new.url",
|
||||||
|
"confidence": "Weak",
|
||||||
|
"cwe_id": [
|
||||||
|
79
|
||||||
|
],
|
||||||
|
"note": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"updated": "2024-01-11 18:12:14 -0300",
|
||||||
|
"brakeman_version": "5.4.1"
|
||||||
|
}
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
String.include CoreExtensions::String::StripTags
|
String.include CoreExtensions::String::StripTags
|
||||||
Jekyll::Document.include CoreExtensions::Jekyll::Document::Path
|
Jekyll::Document.include CoreExtensions::Jekyll::Document::Path
|
||||||
|
Jekyll::DataReader.include Jekyll::Readers::DataReaderDecorator
|
||||||
|
|
||||||
# Definir tags de Liquid que provienen de complementos para que siempre
|
# Definir tags de Liquid que provienen de complementos para que siempre
|
||||||
# devuelvan contenido vacío.
|
# devuelvan contenido vacío.
|
||||||
|
@ -37,6 +38,19 @@ end
|
||||||
#
|
#
|
||||||
# TODO: Aplicar monkey patches en otro lado...
|
# TODO: Aplicar monkey patches en otro lado...
|
||||||
module Jekyll
|
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
|
Site.class_eval do
|
||||||
def configure_theme
|
def configure_theme
|
||||||
self.theme = nil
|
self.theme = nil
|
||||||
|
@ -57,9 +71,27 @@ module Jekyll
|
||||||
# No necesitamos los archivos estáticos
|
# No necesitamos los archivos estáticos
|
||||||
def retrieve_static_files(_, _); end
|
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
|
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
|
end
|
||||||
|
|
||||||
# Lee los layouts
|
# Lee los layouts
|
||||||
|
@ -175,14 +207,3 @@ module PgSearch
|
||||||
end
|
end
|
||||||
end
|
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
|
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
Rails.application.configure do
|
Rails.application.configure do
|
||||||
next unless ENV['RAILS_ENV'] == 'development'
|
next if Rails.env.test?
|
||||||
|
|
||||||
domain = ENV.fetch('SUTTY', 'sutty.nl')
|
domain = ENV.fetch('SUTTY', 'sutty.nl')
|
||||||
|
|
||||||
config.hosts << domain
|
|
||||||
config.hosts << "panel.#{domain}"
|
config.hosts << "panel.#{domain}"
|
||||||
config.hosts << "api.#{domain}"
|
config.hosts << "api.#{domain}"
|
||||||
|
config.hosts << /\Aapi\./
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
en:
|
en:
|
||||||
|
dark: Dark
|
||||||
dir: ltr
|
dir: ltr
|
||||||
en: English
|
en: English
|
||||||
es: Castellano
|
es: Castellano
|
||||||
|
@ -325,6 +326,10 @@ en:
|
||||||
[Pixelfed](https://pixelfed.social/site/about), and
|
[Pixelfed](https://pixelfed.social/site/about), and
|
||||||
[others](https://fediverse.party/)) can follow your site,
|
[others](https://fediverse.party/)) can follow your site,
|
||||||
receive news and interact with them.
|
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:
|
stats:
|
||||||
index:
|
index:
|
||||||
title: Statistics
|
title: Statistics
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
es:
|
es:
|
||||||
|
dark: Oscuro
|
||||||
es: Castellano
|
es: Castellano
|
||||||
en: English
|
en: English
|
||||||
es-AR: Castellano Rioplatense
|
es-AR: Castellano Rioplatense
|
||||||
|
@ -330,6 +331,10 @@ es:
|
||||||
[Pixelfed](https://pixelfed.social/site/about) y
|
[Pixelfed](https://pixelfed.social/site/about) y
|
||||||
[otros](https://fediverse.party/)) pueden seguir a tu sitio,
|
[otros](https://fediverse.party/)) pueden seguir a tu sitio,
|
||||||
recibir novedades e interactuar con ellas.
|
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:
|
stats:
|
||||||
index:
|
index:
|
||||||
title: Estadísticas
|
title: Estadísticas
|
||||||
|
|
|
@ -28,9 +28,6 @@ Rails.application.routes.draw do
|
||||||
# alias en nginx sin tener que usar expresiones regulares para
|
# alias en nginx sin tener que usar expresiones regulares para
|
||||||
# detectar el nombre del sitio.
|
# detectar el nombre del sitio.
|
||||||
get '/sites/private/:site_id(*file)', to: 'private#show', constraints: { site_id: %r{[^/]+} }
|
get '/sites/private/:site_id(*file)', to: 'private#show', constraints: { site_id: %r{[^/]+} }
|
||||||
# Obtener archivos estáticos desde el directorio público
|
|
||||||
get '/sites/:site_id/static_file/(*file)', to: 'sites#static_file', as: 'site_static_file',
|
|
||||||
constraints: { site_id: %r{[^/]+} }
|
|
||||||
get '/env.js', to: 'env#index'
|
get '/env.js', to: 'env#index'
|
||||||
|
|
||||||
match '/api/v3/projects/:site_id/notices' => 'api/v1/notices#create', via: %i[post]
|
match '/api/v3/projects/:site_id/notices' => 'api/v1/notices#create', via: %i[post]
|
||||||
|
|
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
|
|
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
|
namespace :cleanup do
|
||||||
desc 'Cleanup sites'
|
desc 'Cleanup sites'
|
||||||
task everything: :environment do
|
task everything: :environment do |_, args|
|
||||||
before = ENV.fetch('BEFORE', '30').to_i.days.ago
|
before = ENV.fetch('BEFORE', '30').to_i.days.ago
|
||||||
service = CleanupService.new(before: before)
|
|
||||||
|
|
||||||
service.cleanup_everything!
|
CleanupJob.perform_later(before)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -19,6 +19,10 @@ check program stats
|
||||||
every "0 1 * * *"
|
every "0 1 * * *"
|
||||||
if status != 0 then alert
|
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
|
check process que with pidfile /srv/tmp/que.pid
|
||||||
start program = "/usr/bin/foreman run -f /srv/Procfile -d /srv que"
|
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'"
|
stop program = "/bin/sh -c 'cat /srv/tmp/que.pid | xargs -r kill'"
|
||||||
|
|
|
@ -12,7 +12,8 @@
|
||||||
"@rails/actiontext": "^6.0.0",
|
"@rails/actiontext": "^6.0.0",
|
||||||
"@rails/activestorage": "^6.1.3-1",
|
"@rails/activestorage": "^6.1.3-1",
|
||||||
"@rails/ujs": "^6.1.3-1",
|
"@rails/ujs": "^6.1.3-1",
|
||||||
"@rails/webpacker": "5.2.1",
|
"@rails/webpacker": "5.4.4",
|
||||||
|
"@suttyweb/editor": "^0.1.25",
|
||||||
"babel-loader": "^8.2.2",
|
"babel-loader": "^8.2.2",
|
||||||
"chart.js": "^3.5.1",
|
"chart.js": "^3.5.1",
|
||||||
"chartkick": "^4.0.5",
|
"chartkick": "^4.0.5",
|
||||||
|
|
BIN
public/assets/.sprockets-manifest-c6294bb290dcb7473076f4de99ce9c00.json
(Stored with Git LFS)
BIN
public/assets/.sprockets-manifest-c6294bb290dcb7473076f4de99ce9c00.json
(Stored with Git LFS)
Binary file not shown.
BIN
public/assets/activestorage-a32231d4c74e0bd4b322c1466c29120152be4ea1f4b896d9bfa546b49c39c52f.js
(Stored with Git LFS)
Normal file
BIN
public/assets/activestorage-a32231d4c74e0bd4b322c1466c29120152be4ea1f4b896d9bfa546b49c39c52f.js
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
public/assets/activestorage-a32231d4c74e0bd4b322c1466c29120152be4ea1f4b896d9bfa546b49c39c52f.js.gz
(Stored with Git LFS)
Normal file
BIN
public/assets/activestorage-a32231d4c74e0bd4b322c1466c29120152be4ea1f4b896d9bfa546b49c39c52f.js.gz
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
public/assets/application-fe50a06d71e148768669fb4e35c788fc1ac8ba9d94d55ed4292a198004c8dcc5.css
(Stored with Git LFS)
Normal file
BIN
public/assets/application-fe50a06d71e148768669fb4e35c788fc1ac8ba9d94d55ed4292a198004c8dcc5.css
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
public/assets/application-fe50a06d71e148768669fb4e35c788fc1ac8ba9d94d55ed4292a198004c8dcc5.css.gz
(Stored with Git LFS)
Normal file
BIN
public/assets/application-fe50a06d71e148768669fb4e35c788fc1ac8ba9d94d55ed4292a198004c8dcc5.css.gz
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
public/assets/blazer/application-de70452dc5fa2016142132ea36573395d45b4e5c5cf772ef1558462a46996967.css
(Stored with Git LFS)
Normal file
BIN
public/assets/blazer/application-de70452dc5fa2016142132ea36573395d45b4e5c5cf772ef1558462a46996967.css
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
public/assets/blazer/application-de70452dc5fa2016142132ea36573395d45b4e5c5cf772ef1558462a46996967.css.gz
(Stored with Git LFS)
Normal file
BIN
public/assets/blazer/application-de70452dc5fa2016142132ea36573395d45b4e5c5cf772ef1558462a46996967.css.gz
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
public/assets/blazer/application-ec191b6689a395c0fede5f96bcc30a93a832cfbb325d6c50f1c31fffe8f7352b.js
(Stored with Git LFS)
Normal file
BIN
public/assets/blazer/application-ec191b6689a395c0fede5f96bcc30a93a832cfbb325d6c50f1c31fffe8f7352b.js
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
public/assets/blazer/application-ec191b6689a395c0fede5f96bcc30a93a832cfbb325d6c50f1c31fffe8f7352b.js.gz
(Stored with Git LFS)
Normal file
BIN
public/assets/blazer/application-ec191b6689a395c0fede5f96bcc30a93a832cfbb325d6c50f1c31fffe8f7352b.js.gz
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
public/assets/dark-e2d0356b7ccd0cf1af4e1bfd28d32c69caf3f11f464c5b5cef404487f864ae73.css
(Stored with Git LFS)
Normal file
BIN
public/assets/dark-e2d0356b7ccd0cf1af4e1bfd28d32c69caf3f11f464c5b5cef404487f864ae73.css
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
public/assets/dark-e2d0356b7ccd0cf1af4e1bfd28d32c69caf3f11f464c5b5cef404487f864ae73.css.gz
(Stored with Git LFS)
Normal file
BIN
public/assets/dark-e2d0356b7ccd0cf1af4e1bfd28d32c69caf3f11f464c5b5cef404487f864ae73.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