mirror of
https://0xacab.org/sutty/sutty
synced 2024-11-26 09:16:22 +00:00
Merge branch 'rails' of 0xacab.org:sutty/sutty into issue-7537
This commit is contained in:
commit
26965ea120
24 changed files with 351 additions and 50 deletions
77
app/controllers/api/v1/webhooks_controller.rb
Normal file
77
app/controllers/api/v1/webhooks_controller.rb
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Api
|
||||||
|
module V1
|
||||||
|
# Recibe webhooks y lanza un PullJob
|
||||||
|
class WebhooksController < BaseController
|
||||||
|
# responde con forbidden si falla la validación del token
|
||||||
|
rescue_from ActiveRecord::RecordNotFound, with: :platforms_answer
|
||||||
|
|
||||||
|
# Trae los cambios a partir de un post de Webhooks:
|
||||||
|
# (Gitlab, Github, Gitea, etc)
|
||||||
|
#
|
||||||
|
# @return [nil]
|
||||||
|
def pull
|
||||||
|
message = I18n.with_locale(site.default_locale) do
|
||||||
|
I18n.t('webhooks.pull.message')
|
||||||
|
end
|
||||||
|
|
||||||
|
GitPullJob.perform_later(site, usuarie, message)
|
||||||
|
head :ok
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# encuentra el sitio a partir de la url
|
||||||
|
def site
|
||||||
|
@site ||= Site.find_by_name!(params[:site_id])
|
||||||
|
end
|
||||||
|
|
||||||
|
# valida el token que envía la plataforma del webhook
|
||||||
|
#
|
||||||
|
# @return [String]
|
||||||
|
def token
|
||||||
|
@token ||=
|
||||||
|
begin
|
||||||
|
# Gitlab
|
||||||
|
if request.headers['X-Gitlab-Token'].present?
|
||||||
|
request.headers['X-Gitlab-Token']
|
||||||
|
# Github
|
||||||
|
elsif request.headers['X-Hub-Signature-256'].present?
|
||||||
|
token_from_signature(request.headers['X-Hub-Signature-256'], 'sha256=')
|
||||||
|
# Gitea
|
||||||
|
elsif request.headers['X-Gitea-Signature'].present?
|
||||||
|
token_from_signature(request.headers['X-Gitea-Signature'])
|
||||||
|
else
|
||||||
|
raise ActiveRecord::RecordNotFound, 'proveedor no soportado'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# valida token a partir de firma de webhook
|
||||||
|
#
|
||||||
|
# @return [String, Boolean]
|
||||||
|
def token_from_signature(signature, prepend = '')
|
||||||
|
payload = request.body.read
|
||||||
|
site.roles.where(temporal: false, rol: 'usuarie').pluck(:token).find do |token|
|
||||||
|
new_signature = prepend + OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), token, payload)
|
||||||
|
ActiveSupport::SecurityUtils.secure_compare(new_signature, signature.to_s)
|
||||||
|
end.tap do |t|
|
||||||
|
raise ActiveRecord::RecordNotFound, 'token no encontrado' if t.nil?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# encuentra le usuarie
|
||||||
|
def usuarie
|
||||||
|
@usuarie ||= site.roles.find_by!(temporal: false, rol: 'usuarie', token: token).usuarie
|
||||||
|
end
|
||||||
|
|
||||||
|
# respuesta de error a plataformas
|
||||||
|
def platforms_answer(exception)
|
||||||
|
ExceptionNotifier.notify_exception(exception, data: { headers: request.headers.to_h }
|
||||||
|
|
||||||
|
head :forbidden
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -59,7 +59,11 @@ class ApplicationController < ActionController::Base
|
||||||
#
|
#
|
||||||
# @return [String,Symbol]
|
# @return [String,Symbol]
|
||||||
def current_locale
|
def current_locale
|
||||||
session[:locale] = params[:change_locale_to] if params[:change_locale_to].present?
|
locale = params[:change_locale_to]
|
||||||
|
|
||||||
|
if locale.present? && I18n.locale_available?(locale)
|
||||||
|
session[:locale] = params[:change_locale_to]
|
||||||
|
end
|
||||||
|
|
||||||
session[:locale] || current_usuarie&.lang || I18n.locale
|
session[:locale] || current_usuarie&.lang || I18n.locale
|
||||||
end
|
end
|
||||||
|
|
|
@ -24,6 +24,7 @@ class PostsController < ApplicationController
|
||||||
|
|
||||||
# Todos los artículos de este sitio para el idioma actual
|
# Todos los artículos de este sitio para el idioma actual
|
||||||
@posts = site.indexed_posts.where(locale: locale)
|
@posts = site.indexed_posts.where(locale: locale)
|
||||||
|
@posts = @posts.page(filter_params.delete(:page)) if site.pagination
|
||||||
# De este tipo
|
# De este tipo
|
||||||
@posts = @posts.where(layout: filter_params[:layout]) if filter_params[:layout]
|
@posts = @posts.where(layout: filter_params[:layout]) if filter_params[:layout]
|
||||||
# Que estén dentro de la categoría
|
# Que estén dentro de la categoría
|
||||||
|
@ -156,7 +157,7 @@ class PostsController < ApplicationController
|
||||||
#
|
#
|
||||||
# @return [Hash]
|
# @return [Hash]
|
||||||
def filter_params
|
def filter_params
|
||||||
@filter_params ||= params.permit(:q, :category, :layout).to_hash.select do |_, v|
|
@filter_params ||= params.permit(:q, :category, :layout, :page).to_hash.select do |_, v|
|
||||||
v.present?
|
v.present?
|
||||||
end.transform_keys(&:to_sym)
|
end.transform_keys(&:to_sym)
|
||||||
end
|
end
|
||||||
|
|
|
@ -57,6 +57,7 @@ class SitesController < ApplicationController
|
||||||
usuarie: current_usuarie)
|
usuarie: current_usuarie)
|
||||||
|
|
||||||
if service.update.valid?
|
if service.update.valid?
|
||||||
|
flash[:notice] = I18n.t('sites.update.post')
|
||||||
redirect_to site_posts_path(site, locale: site.default_locale)
|
redirect_to site_posts_path(site, locale: site.default_locale)
|
||||||
else
|
else
|
||||||
render 'edit'
|
render 'edit'
|
||||||
|
|
13
app/jobs/git_pull_job.rb
Normal file
13
app/jobs/git_pull_job.rb
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# Permite traer los cambios desde webhooks
|
||||||
|
|
||||||
|
class GitPullJob < ApplicationJob
|
||||||
|
# @param :site [Site]
|
||||||
|
# @param :usuarie [Usuarie]
|
||||||
|
# @param :message [String]
|
||||||
|
# @return [nil]
|
||||||
|
def perform(site, usuarie, message)
|
||||||
|
site.repository.merge(usuarie, message) if site.repository.fetch&.positive?
|
||||||
|
end
|
||||||
|
end
|
|
@ -52,7 +52,12 @@ class DeployDistributedPress < Deploy
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
status = c.publish(publishing_site, deploy_local.destination)
|
begin
|
||||||
|
status = c.publish(publishing_site, deploy_local.destination)
|
||||||
|
rescue DistributedPress::V1::Error => e
|
||||||
|
ExceptionNotifier.notify_exception(e, data: { site: site.name })
|
||||||
|
status = false
|
||||||
|
end
|
||||||
|
|
||||||
if status
|
if status
|
||||||
self.remote_info[:distributed_press] = c.show(publishing_site).to_h
|
self.remote_info[:distributed_press] = c.show(publishing_site).to_h
|
||||||
|
|
|
@ -130,7 +130,12 @@ class DeployLocal < Deploy
|
||||||
end
|
end
|
||||||
|
|
||||||
def bundle(output: false)
|
def bundle(output: false)
|
||||||
run %(bundle install --deployment --no-cache --path="#{gems_dir}" --clean --without test development), output: output
|
run %(bundle config set --local clean 'true'), output: output
|
||||||
|
run %(bundle config set --local deployment 'true'), output: output
|
||||||
|
run %(bundle config set --local path '#{gems_dir}'), output: output
|
||||||
|
run %(bundle config set --local without 'test development'), output: output
|
||||||
|
run %(bundle config set --local cache_all 'false'), output: output
|
||||||
|
run %(bundle install), output: output
|
||||||
end
|
end
|
||||||
|
|
||||||
def jekyll_build(output: false)
|
def jekyll_build(output: false)
|
||||||
|
|
|
@ -8,9 +8,9 @@ class MetadataRelatedPosts < MetadataArray
|
||||||
#
|
#
|
||||||
# @return [Hash]
|
# @return [Hash]
|
||||||
def values
|
def values
|
||||||
@values ||= posts.pluck(:title, :layout, :post_id).to_h do |row|
|
@values ||= posts.pluck(:title, :created_at, :layout, :post_id).to_h do |row|
|
||||||
row.tap do |value|
|
row.tap do |value|
|
||||||
value[0] = "#{value[0]} (#{site.layouts[value.delete_at(1)].humanized_name})"
|
value[0] = "#{value[0]} #{value.delete_at(1).strftime('%F')} (#{site.layouts[value.delete_at(1)].humanized_name})"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -210,7 +210,7 @@ MetadataTemplate = Struct.new(:site, :document, :name, :label, :type,
|
||||||
|
|
||||||
def allowed_attributes
|
def allowed_attributes
|
||||||
@allowed_attributes ||= %w[style href src alt controls data-align data-multimedia data-multimedia-inner id
|
@allowed_attributes ||= %w[style href src alt controls data-align data-multimedia data-multimedia-inner id
|
||||||
name].freeze
|
name start].freeze
|
||||||
end
|
end
|
||||||
|
|
||||||
def allowed_tags
|
def allowed_tags
|
||||||
|
|
|
@ -14,6 +14,8 @@ class Rol < ApplicationRecord
|
||||||
|
|
||||||
validates_inclusion_of :rol, in: ROLES
|
validates_inclusion_of :rol, in: ROLES
|
||||||
|
|
||||||
|
before_save :add_token_if_missing!
|
||||||
|
|
||||||
def invitade?
|
def invitade?
|
||||||
rol == INVITADE
|
rol == INVITADE
|
||||||
end
|
end
|
||||||
|
@ -25,4 +27,11 @@ class Rol < ApplicationRecord
|
||||||
def self.role?(rol)
|
def self.role?(rol)
|
||||||
ROLES.include? rol
|
ROLES.include? rol
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Asegurarse que tenga un token
|
||||||
|
def add_token_if_missing!
|
||||||
|
self.token ||= SecureRandom.hex(64)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,17 +1,19 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
# Indexa todos los artículos de un sitio
|
|
||||||
#
|
|
||||||
# TODO: Hacer opcional
|
|
||||||
class Site
|
class Site
|
||||||
|
# Indexa todos los artículos de un sitio
|
||||||
|
#
|
||||||
|
# TODO: Hacer opcional
|
||||||
module Index
|
module Index
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
included do
|
included do
|
||||||
# TODO: Debería ser un Job?
|
|
||||||
after_create :index_posts!
|
|
||||||
has_many :indexed_posts, dependent: :destroy
|
has_many :indexed_posts, dependent: :destroy
|
||||||
|
|
||||||
|
MODIFIED_STATUSES = %i[added modified].freeze
|
||||||
|
DELETED_STATUSES = %i[deleted].freeze
|
||||||
|
LOCALE_FROM_PATH = /\A_/.freeze
|
||||||
|
|
||||||
def index_posts!
|
def index_posts!
|
||||||
Site.transaction do
|
Site.transaction do
|
||||||
jekyll.read
|
jekyll.read
|
||||||
|
@ -24,6 +26,105 @@ class Site
|
||||||
update(last_indexed_commit: repository.head_commit.oid)
|
update(last_indexed_commit: repository.head_commit.oid)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Encuentra los artículos modificados entre dos commits y los
|
||||||
|
# reindexa.
|
||||||
|
def reindex_changes!
|
||||||
|
return unless reindexable?
|
||||||
|
|
||||||
|
Site.transaction do
|
||||||
|
remove_deleted_posts!
|
||||||
|
reindex_modified_posts!
|
||||||
|
|
||||||
|
update(last_indexed_commit: repository.head_commit.oid)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# No hacer nada si el repositorio no cambió o no hubo cambios
|
||||||
|
# necesarios
|
||||||
|
def reindexable?
|
||||||
|
return false if last_indexed_commit.blank?
|
||||||
|
return false if last_indexed_commit == repository.head_commit.oid
|
||||||
|
|
||||||
|
!indexable_posts.empty?
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Trae el último commit indexado desde el repositorio
|
||||||
|
#
|
||||||
|
# @return [Rugged::Commit]
|
||||||
|
def indexed_commit
|
||||||
|
@indexed_commit ||= repository.rugged.lookup(last_indexed_commit)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Calcula la diferencia entre el último commit indexado y el
|
||||||
|
# actual
|
||||||
|
#
|
||||||
|
# XXX: Esto no tiene en cuenta modificaciones en la historia como
|
||||||
|
# cambio de ramas, reverts y etc, solo asume que se mueve hacia
|
||||||
|
# adelante en la misma rama o las dos ramas están relacionadas.
|
||||||
|
#
|
||||||
|
# @return [Rugged::Diff]
|
||||||
|
def diff_with_head
|
||||||
|
@diff_with_head ||= indexed_commit.diff(repository.head_commit)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Obtiene todos los archivos a reindexar
|
||||||
|
#
|
||||||
|
# @return [Array<Rugged::Delta>]
|
||||||
|
def indexable_posts
|
||||||
|
@indexable_posts ||=
|
||||||
|
diff_with_head.each_delta.select do |delta|
|
||||||
|
locales.any? do |locale|
|
||||||
|
delta.old_file[:path].start_with? "_#{locale}/"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Elimina los artículos eliminados o que cambiaron de ubicación
|
||||||
|
# del índice
|
||||||
|
def remove_deleted_posts!
|
||||||
|
indexable_posts.select do |delta|
|
||||||
|
DELETED_STATUSES.include? delta.status
|
||||||
|
end.each do |delta|
|
||||||
|
locale, path = locale_and_path_from(delta.old_file[:path])
|
||||||
|
|
||||||
|
indexed_posts.destroy_by(locale: locale, path: path).tap do |destroyed_posts|
|
||||||
|
next unless destroyed_posts.empty?
|
||||||
|
|
||||||
|
Rails.logger.info I18n.t('indexed_posts.deleted', site: name, path: path, records: destroyed_posts.count)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Reindexa artículos que cambiaron de ubicación, se agregaron
|
||||||
|
# o fueron modificados
|
||||||
|
def reindex_modified_posts!
|
||||||
|
indexable_posts.select do |delta|
|
||||||
|
MODIFIED_STATUSES.include? delta.status
|
||||||
|
end.each do |delta|
|
||||||
|
locale, path = locale_and_path_from(delta.new_file[:path])
|
||||||
|
|
||||||
|
posts(lang: locale).find(path).index!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Obtiene el idioma y la ruta del post a partir de la ubicación en
|
||||||
|
# el disco.
|
||||||
|
#
|
||||||
|
# Las rutas vienen en ASCII-9BIT desde Rugged, pero en realidad
|
||||||
|
# son UTF-8
|
||||||
|
#
|
||||||
|
# @return [Array<String>]
|
||||||
|
def locale_and_path_from(path)
|
||||||
|
locale, path = path.force_encoding('utf-8').split(File::SEPARATOR, 2)
|
||||||
|
|
||||||
|
[
|
||||||
|
locale.sub(LOCALE_FROM_PATH, ''),
|
||||||
|
File.basename(path, '.*')
|
||||||
|
]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -54,7 +54,7 @@ class Site
|
||||||
# Incorpora los cambios en el repositorio actual
|
# Incorpora los cambios en el repositorio actual
|
||||||
#
|
#
|
||||||
# @return [Rugged::Commit]
|
# @return [Rugged::Commit]
|
||||||
def merge(usuarie)
|
def merge(usuarie, message = I18n.t('sites.fetch.merge.message'))
|
||||||
merge = rugged.merge_commits(head_commit, remote_head_commit)
|
merge = rugged.merge_commits(head_commit, remote_head_commit)
|
||||||
|
|
||||||
# No hacemos nada si hay conflictos, pero notificarnos
|
# No hacemos nada si hay conflictos, pero notificarnos
|
||||||
|
@ -69,12 +69,16 @@ class Site
|
||||||
.create(rugged, update_ref: 'HEAD',
|
.create(rugged, update_ref: 'HEAD',
|
||||||
parents: [head_commit, remote_head_commit],
|
parents: [head_commit, remote_head_commit],
|
||||||
tree: merge.write_tree(rugged),
|
tree: merge.write_tree(rugged),
|
||||||
message: I18n.t('sites.fetch.merge.message'),
|
message: message,
|
||||||
author: author(usuarie), committer: committer)
|
author: author(usuarie), committer: committer)
|
||||||
|
|
||||||
# Forzamos el checkout para mover el HEAD al último commit y
|
# Forzamos el checkout para mover el HEAD al último commit y
|
||||||
# escribir los cambios
|
# escribir los cambios
|
||||||
rugged.checkout 'HEAD', strategy: :force
|
rugged.checkout 'HEAD', strategy: :force
|
||||||
|
|
||||||
|
git_sh("git", "lfs", "fetch", "origin", default_branch)
|
||||||
|
# reemplaza los pointers por los archivos correspondientes
|
||||||
|
git_sh("git", "lfs", "checkout")
|
||||||
commit
|
commit
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ class Usuarie < ApplicationRecord
|
||||||
|
|
||||||
validates_uniqueness_of :email
|
validates_uniqueness_of :email
|
||||||
validates_with EmailAddress::ActiveRecordValidator, field: :email
|
validates_with EmailAddress::ActiveRecordValidator, field: :email
|
||||||
|
validate :locale_available!
|
||||||
|
|
||||||
before_create :lang_from_locale!
|
before_create :lang_from_locale!
|
||||||
before_update :remove_confirmation_invitation_inconsistencies!
|
before_update :remove_confirmation_invitation_inconsistencies!
|
||||||
|
@ -78,4 +79,15 @@ class Usuarie < ApplicationRecord
|
||||||
self.invitation_accepted_at ||= Time.now.utc
|
self.invitation_accepted_at ||= Time.now.utc
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Muestra un error si el idioma no está disponible al cambiar el
|
||||||
|
# idioma de la cuenta.
|
||||||
|
#
|
||||||
|
# @return [nil]
|
||||||
|
def locale_available!
|
||||||
|
return if I18n.locale_available? self.lang
|
||||||
|
|
||||||
|
errors.add(:lang, I18n.t('activerecord.errors.models.usuarie.attributes.lang.not_available'))
|
||||||
|
nil
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -33,6 +33,7 @@ SiteService = Struct.new(:site, :usuarie, :params, keyword_init: true) do
|
||||||
add_licencias &&
|
add_licencias &&
|
||||||
add_code_of_conduct &&
|
add_code_of_conduct &&
|
||||||
add_privacy_policy &&
|
add_privacy_policy &&
|
||||||
|
site.index_posts! &&
|
||||||
deploy
|
deploy
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
- flash.each do |type, message|
|
- flash.each do |type, message|
|
||||||
- unless type == 'js'
|
- unless type == 'js'
|
||||||
= render 'bootstrap/alert' do
|
= render 'bootstrap/alert' do
|
||||||
= message
|
= sanitize_markdown message, tags: %w[a strong em]
|
||||||
|
|
|
@ -84,7 +84,10 @@
|
||||||
%button.btn{ data: { action: 'reorder#top' } }= t('posts.reorder.top')
|
%button.btn{ data: { action: 'reorder#top' } }= t('posts.reorder.top')
|
||||||
%button.btn{ data: { action: 'reorder#bottom' } }= t('posts.reorder.bottom')
|
%button.btn{ data: { action: 'reorder#bottom' } }= t('posts.reorder.bottom')
|
||||||
|
|
||||||
%div
|
- if @site.pagination
|
||||||
|
%div
|
||||||
|
= link_to_prev_page @posts, t('posts.prev'), class: 'btn'
|
||||||
|
= link_to_next_page @posts, t('posts.next'), class: 'btn'
|
||||||
%tbody
|
%tbody
|
||||||
- dir = @site.data.dig(params[:locale], 'dir')
|
- dir = @site.data.dig(params[:locale], 'dir')
|
||||||
- size = @posts.size
|
- size = @posts.size
|
||||||
|
|
|
@ -46,36 +46,37 @@
|
||||||
.invalid-feedback= site.errors.messages[:description].join(', ')
|
.invalid-feedback= site.errors.messages[:description].join(', ')
|
||||||
%hr/
|
%hr/
|
||||||
|
|
||||||
.form-group#design_id
|
- unless site.persisted?
|
||||||
%h2= t('.design.title')
|
.form-group#design_id
|
||||||
%p.lead= t('.help.design')
|
%h2= t('.design.title')
|
||||||
- if invalid? site, :design_id
|
%p.lead= t('.help.design')
|
||||||
= render 'bootstrap/alert' do
|
- if invalid? site, :design_id
|
||||||
= t('activerecord.errors.models.site.attributes.design_id.layout_incompatible.help',
|
= render 'bootstrap/alert' do
|
||||||
layouts: site.incompatible_layouts.to_sentence)
|
= t('activerecord.errors.models.site.attributes.design_id.layout_incompatible.help',
|
||||||
.row.row-cols-1.row-cols-md-2.designs
|
layouts: site.incompatible_layouts.to_sentence)
|
||||||
-# Demasiado complejo para un f.collection_radio_buttons
|
.row.row-cols-1.row-cols-md-2.designs
|
||||||
- Design.all.order(priority: :desc).each do |design|
|
-# Demasiado complejo para un f.collection_radio_buttons
|
||||||
.design.col.d-flex.flex-column
|
- Design.all.order(priority: :desc).each do |design|
|
||||||
.custom-control.custom-radio
|
.design.col.d-flex.flex-column
|
||||||
= f.radio_button :design_id, design.id,
|
.custom-control.custom-radio
|
||||||
checked: design.id == site.design_id,
|
= f.radio_button :design_id, design.id,
|
||||||
disabled: design.disabled,
|
checked: design.id == site.design_id,
|
||||||
required: true, class: 'custom-control-input'
|
disabled: design.disabled,
|
||||||
= f.label "design_id_#{design.id}", design.name,
|
required: true, class: 'custom-control-input'
|
||||||
class: 'custom-control-label'
|
= f.label "design_id_#{design.id}", design.name,
|
||||||
.flex-fill
|
class: 'custom-control-label'
|
||||||
= sanitize_markdown design.description,
|
.flex-fill
|
||||||
tags: %w[p a strong em]
|
= sanitize_markdown design.description,
|
||||||
|
tags: %w[p a strong em]
|
||||||
|
|
||||||
.btn-group{ role: 'group', 'aria-label': t('.design.actions') }
|
.btn-group{ role: 'group', 'aria-label': t('.design.actions') }
|
||||||
- if design.url
|
- if design.url
|
||||||
= link_to t('.design.url'), design.url,
|
= link_to t('.design.url'), design.url,
|
||||||
target: '_blank', class: 'btn'
|
target: '_blank', class: 'btn'
|
||||||
- if design.license
|
- if design.license
|
||||||
= link_to t('.design.license'), design.license,
|
= link_to t('.design.license'), design.license,
|
||||||
target: '_blank', class: 'btn'
|
target: '_blank', class: 'btn'
|
||||||
%hr/
|
%hr/
|
||||||
|
|
||||||
.form-group.licenses#license_id
|
.form-group.licenses#license_id
|
||||||
%h2= t('.licencia.title')
|
%h2= t('.licencia.title')
|
||||||
|
|
|
@ -142,7 +142,7 @@ Rails.application.configure do
|
||||||
}
|
}
|
||||||
config.action_mailer.default_options = { from: ENV.fetch('DEFAULT_FROM', "noreply@sutty.nl") }
|
config.action_mailer.default_options = { from: ENV.fetch('DEFAULT_FROM', "noreply@sutty.nl") }
|
||||||
|
|
||||||
config.middleware.use ExceptionNotification::Rack, gitlab: {}, ignore_exceptions: (['DeployJob::DeployAlreadyRunningException'] + ExceptionNotifier.ignored_exceptions)
|
config.middleware.use ExceptionNotification::Rack, gitlab: {}, ignore_exceptions: ['DeployJob::DeployAlreadyRunningException']
|
||||||
|
|
||||||
Rails.application.routes.default_url_options[:host] = "panel.#{ENV.fetch('SUTTY', 'sutty.nl')}"
|
Rails.application.routes.default_url_options[:host] = "panel.#{ENV.fetch('SUTTY', 'sutty.nl')}"
|
||||||
Rails.application.routes.default_url_options[:protocol] = 'https'
|
Rails.application.routes.default_url_options[:protocol] = 'https'
|
||||||
|
|
|
@ -171,6 +171,7 @@ en:
|
||||||
usuarie: User
|
usuarie: User
|
||||||
licencia: License
|
licencia: License
|
||||||
design: Design
|
design: Design
|
||||||
|
indexed_post: Indexed post
|
||||||
attributes:
|
attributes:
|
||||||
usuarie:
|
usuarie:
|
||||||
email: 'E-mail address'
|
email: 'E-mail address'
|
||||||
|
@ -199,6 +200,10 @@ en:
|
||||||
layout_incompatible:
|
layout_incompatible:
|
||||||
error: "Design can't be changed because there are posts with incompatible layouts"
|
error: "Design can't be changed because there are posts with incompatible layouts"
|
||||||
help: "Your site has posts with layouts only compatible with the current design. If you change it, the site won't work as you expect. If you're trying out designs, you can delete posts in the following incompatible layouts:: %{layouts}."
|
help: "Your site has posts with layouts only compatible with the current design. If you change it, the site won't work as you expect. If you're trying out designs, you can delete posts in the following incompatible layouts:: %{layouts}."
|
||||||
|
usuarie:
|
||||||
|
attributes:
|
||||||
|
lang:
|
||||||
|
not_available: "This language is not yet available, would you help us by translating Sutty into it?"
|
||||||
errors:
|
errors:
|
||||||
argument_error: 'Argument `%{argument}` must be an instance of %{class}'
|
argument_error: 'Argument `%{argument}` must be an instance of %{class}'
|
||||||
unknown_locale: 'Unknown %{locale} locale'
|
unknown_locale: 'Unknown %{locale} locale'
|
||||||
|
@ -420,6 +425,8 @@ en:
|
||||||
title: 'Edit %{site}'
|
title: 'Edit %{site}'
|
||||||
submit: 'Save changes'
|
submit: 'Save changes'
|
||||||
btn: 'Configuration'
|
btn: 'Configuration'
|
||||||
|
update:
|
||||||
|
post: "Your changes have been saved. **If you enabled a publication method, don't forget to publish changes.**"
|
||||||
form:
|
form:
|
||||||
errors:
|
errors:
|
||||||
title: There were errors and we couldn't save your changes :(
|
title: There were errors and we couldn't save your changes :(
|
||||||
|
@ -428,7 +435,7 @@ en:
|
||||||
name: "This will be the host name for your site, ie. **example**.sutty.nl. Choose an expression up to 63 characters. It can contain only lowercase letters, numbers and dashes, **and no spaces**. It can't start or end with a dash, or be entirely composed of numbers."
|
name: "This will be the host name for your site, ie. **example**.sutty.nl. Choose an expression up to 63 characters. It can contain only lowercase letters, numbers and dashes, **and no spaces**. It can't start or end with a dash, or be entirely composed of numbers."
|
||||||
title: 'The title can be anything you want'
|
title: 'The title can be anything you want'
|
||||||
description: 'You site description that appears in search engines. Between 50 and 160 characters.'
|
description: 'You site description that appears in search engines. Between 50 and 160 characters.'
|
||||||
design: 'Select the design for your site. You can change it later. We add more designs from time to time!'
|
design: 'Select the design for your site. We add more designs from time to time!'
|
||||||
licencia: 'Everything we publish has automatic copyright. This
|
licencia: 'Everything we publish has automatic copyright. This
|
||||||
means nobody can use our works without explicit permission. By
|
means nobody can use our works without explicit permission. By
|
||||||
using licenses, we stablish conditions by which we want to share
|
using licenses, we stablish conditions by which we want to share
|
||||||
|
@ -479,6 +486,9 @@ en:
|
||||||
success: 'Site upgrade has been completed. Your next build will run this upgrade :)'
|
success: 'Site upgrade has been completed. Your next build will run this upgrade :)'
|
||||||
error: "There was an error when trying to upgrade your site. This could be due to conflicts that couldn't be solved automatically. A report of the issue has already been sent to our admins. Sorry for the inconvenience! :("
|
error: "There was an error when trying to upgrade your site. This could be due to conflicts that couldn't be solved automatically. A report of the issue has already been sent to our admins. Sorry for the inconvenience! :("
|
||||||
message: 'Skeleton upgrade'
|
message: 'Skeleton upgrade'
|
||||||
|
webhooks:
|
||||||
|
pull:
|
||||||
|
message: 'Webhooks pull'
|
||||||
footer:
|
footer:
|
||||||
powered_by: 'is developed by'
|
powered_by: 'is developed by'
|
||||||
i18n:
|
i18n:
|
||||||
|
@ -700,7 +710,7 @@ en:
|
||||||
new: 'Create'
|
new: 'Create'
|
||||||
edit: 'Configure'
|
edit: 'Configure'
|
||||||
posts:
|
posts:
|
||||||
new: 'New %{layout}'
|
new: 'Add %{layout}'
|
||||||
edit: 'Editing'
|
edit: 'Editing'
|
||||||
usuaries:
|
usuaries:
|
||||||
index: 'Users'
|
index: 'Users'
|
||||||
|
@ -724,3 +734,5 @@ en:
|
||||||
build_stats:
|
build_stats:
|
||||||
index:
|
index:
|
||||||
title: "Publications"
|
title: "Publications"
|
||||||
|
indexed_posts:
|
||||||
|
deleted: "Deleted indexed post %{path} from %{site} (records: %{records})"
|
||||||
|
|
|
@ -171,6 +171,7 @@ es:
|
||||||
usuarie: Usuarie
|
usuarie: Usuarie
|
||||||
licencia: Licencia
|
licencia: Licencia
|
||||||
design: Diseño
|
design: Diseño
|
||||||
|
indexed_post: Artículo indexado
|
||||||
attributes:
|
attributes:
|
||||||
usuarie:
|
usuarie:
|
||||||
email: 'Correo electrónico'
|
email: 'Correo electrónico'
|
||||||
|
@ -199,6 +200,10 @@ es:
|
||||||
layout_incompatible:
|
layout_incompatible:
|
||||||
error: 'No se puede cambiar la plantilla porque hay artículos con formatos incompatibles'
|
error: 'No se puede cambiar la plantilla porque hay artículos con formatos incompatibles'
|
||||||
help: 'En tu sitio hay artículos que solo son compatibles con el diseño actual, si cambias la plantilla el sitio no funcionará como esperas. Si estás probando plantillas, puedes eliminar los artículos en los formatos incompatibles: %{layouts}.'
|
help: 'En tu sitio hay artículos que solo son compatibles con el diseño actual, si cambias la plantilla el sitio no funcionará como esperas. Si estás probando plantillas, puedes eliminar los artículos en los formatos incompatibles: %{layouts}.'
|
||||||
|
usuarie:
|
||||||
|
attributes:
|
||||||
|
lang:
|
||||||
|
not_available: "Este idioma todavía no está disponible, ¿nos ayudas a agregarlo y mantenerlo?"
|
||||||
errors:
|
errors:
|
||||||
argument_error: 'El argumento `%{argument}` debe ser una instancia de %{class}'
|
argument_error: 'El argumento `%{argument}` debe ser una instancia de %{class}'
|
||||||
unknown_locale: 'El idioma %{locale} es desconocido'
|
unknown_locale: 'El idioma %{locale} es desconocido'
|
||||||
|
@ -426,6 +431,8 @@ es:
|
||||||
title: 'Editar %{site}'
|
title: 'Editar %{site}'
|
||||||
submit: 'Guardar cambios'
|
submit: 'Guardar cambios'
|
||||||
btn: 'Configuración'
|
btn: 'Configuración'
|
||||||
|
update:
|
||||||
|
post: "Tus cambios han sido guardados. **Si agregaste un método de publicación, no te olvides de publicar cambios.**"
|
||||||
form:
|
form:
|
||||||
errors:
|
errors:
|
||||||
title: Hubo errores y no pudimos guardar tus cambios :(
|
title: Hubo errores y no pudimos guardar tus cambios :(
|
||||||
|
@ -434,7 +441,7 @@ es:
|
||||||
name: 'El nombre de tu sitio que formará parte de la dirección (**ejemplo**.sutty.nl). Solo puede contener hasta 63 letras minúsculas, números y guiones, pero **sin espacios**. No puede empezar ni terminar con guión, ni estar compuesto enteramente por números.'
|
name: 'El nombre de tu sitio que formará parte de la dirección (**ejemplo**.sutty.nl). Solo puede contener hasta 63 letras minúsculas, números y guiones, pero **sin espacios**. No puede empezar ni terminar con guión, ni estar compuesto enteramente por números.'
|
||||||
title: 'El título de tu sitio puede ser lo que quieras.'
|
title: 'El título de tu sitio puede ser lo que quieras.'
|
||||||
description: 'La descripción del sitio, que saldrá en buscadores. Entre 50 y 160 caracteres.'
|
description: 'La descripción del sitio, que saldrá en buscadores. Entre 50 y 160 caracteres.'
|
||||||
design: 'Elegí el diseño que va a tener tu sitio aquí. Podés cambiarlo luego. De tanto en tanto vamos sumando diseños nuevos.'
|
design: 'Elegí el diseño que va a tener tu sitio aquí. De tanto en tanto vamos sumando diseños nuevos.'
|
||||||
licencia: 'Todo lo que publicamos posee automáticamente derechos
|
licencia: 'Todo lo que publicamos posee automáticamente derechos
|
||||||
de autore. Esto significa que nadie puede hacer uso de nuestras
|
de autore. Esto significa que nadie puede hacer uso de nuestras
|
||||||
obras sin permiso explícito. Con las licencias establecemos
|
obras sin permiso explícito. Con las licencias establecemos
|
||||||
|
@ -487,6 +494,9 @@ es:
|
||||||
success: 'Ya se incorporaron los cambios en el sitio, se aplicarán en la próxima compilación que hagas :)'
|
success: 'Ya se incorporaron los cambios en el sitio, se aplicarán en la próxima compilación que hagas :)'
|
||||||
error: 'Hubo un error al incorporar los cambios en el sitio. Esto puede deberse a conflictos entre cambios que no se pueden resolver automáticamente. Hemos enviado un reporte del problema a les administradores de Sutty para que estén al tanto de la situación. ¡Lo sentimos! :('
|
error: 'Hubo un error al incorporar los cambios en el sitio. Esto puede deberse a conflictos entre cambios que no se pueden resolver automáticamente. Hemos enviado un reporte del problema a les administradores de Sutty para que estén al tanto de la situación. ¡Lo sentimos! :('
|
||||||
message: 'Actualización del esqueleto'
|
message: 'Actualización del esqueleto'
|
||||||
|
webhooks:
|
||||||
|
pull:
|
||||||
|
message: 'Traer los cambios a partir de un evento remoto'
|
||||||
footer:
|
footer:
|
||||||
powered_by: 'es desarrollada por'
|
powered_by: 'es desarrollada por'
|
||||||
i18n:
|
i18n:
|
||||||
|
@ -708,7 +718,7 @@ es:
|
||||||
new: 'Crear'
|
new: 'Crear'
|
||||||
edit: 'Configurar'
|
edit: 'Configurar'
|
||||||
posts:
|
posts:
|
||||||
new: 'Nuevo %{layout}'
|
new: 'Agregar %{layout}'
|
||||||
edit: 'Editando'
|
edit: 'Editando'
|
||||||
usuaries:
|
usuaries:
|
||||||
index: 'Usuaries'
|
index: 'Usuaries'
|
||||||
|
@ -732,3 +742,5 @@ es:
|
||||||
build_stats:
|
build_stats:
|
||||||
index:
|
index:
|
||||||
title: "Publicaciones"
|
title: "Publicaciones"
|
||||||
|
indexed_posts:
|
||||||
|
deleted: "Eliminado artículo %{path} de %{site} (filas: %{records})"
|
||||||
|
|
|
@ -17,6 +17,8 @@ Rails.application.routes.draw do
|
||||||
|
|
||||||
get :'contact/cookie', to: 'invitades#contact_cookie'
|
get :'contact/cookie', to: 'invitades#contact_cookie'
|
||||||
post :'contact/:form', to: 'contact#receive', as: :contact
|
post :'contact/:form', to: 'contact#receive', as: :contact
|
||||||
|
|
||||||
|
post :'webhooks/pull', to: 'webhooks#pull'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
8
db/migrate/20230519143500_add_pagination_to_site.rb
Normal file
8
db/migrate/20230519143500_add_pagination_to_site.rb
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# Agrega la opción de paginación a los sitios
|
||||||
|
class AddPaginationToSite < ActiveRecord::Migration[6.1]
|
||||||
|
def change
|
||||||
|
add_column :sites, :pagination, :boolean, default: false
|
||||||
|
end
|
||||||
|
end
|
12
db/migrate/20230731195050_add_token_to_roles.rb
Normal file
12
db/migrate/20230731195050_add_token_to_roles.rb
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
class AddTokenToRoles < ActiveRecord::Migration[6.1]
|
||||||
|
def up
|
||||||
|
add_column :roles, :token, :string
|
||||||
|
Rol.find_each do |m|
|
||||||
|
m.update_column( :token, SecureRandom.hex(64) )
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
remove_column :roles, :token
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,18 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# Almacenar el último commit indexado
|
||||||
|
class AddLastIndexedCommitToSites < ActiveRecord::Migration[6.1]
|
||||||
|
def up
|
||||||
|
add_column :sites, :last_indexed_commit, :string, null: true
|
||||||
|
|
||||||
|
Site.find_each do |site|
|
||||||
|
site.update_columns(last_indexed_commit: site.repository.head_commit.oid)
|
||||||
|
rescue Rugged::Error, Rugged::OSError => e
|
||||||
|
puts "Falló #{site.name}, ignorando: #{e.message}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
remove_column :sites, :last_indexed_commit
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in a new issue