mirror of
https://0xacab.org/sutty/sutty
synced 2024-11-16 17:36:22 +00:00
Merge branch 'rails' of 0xacab.org:sutty/sutty into issue-13498
This commit is contained in:
commit
959da50dac
11 changed files with 224 additions and 74 deletions
1
Gemfile
1
Gemfile
|
@ -40,7 +40,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'
|
||||||
|
|
|
@ -366,9 +366,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)
|
||||||
|
@ -636,7 +633,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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -5,9 +5,12 @@
|
||||||
class GitPullJob < ApplicationJob
|
class GitPullJob < ApplicationJob
|
||||||
# @param :site [Site]
|
# @param :site [Site]
|
||||||
# @param :usuarie [Usuarie]
|
# @param :usuarie [Usuarie]
|
||||||
# @param :message [String]
|
|
||||||
# @return [nil]
|
# @return [nil]
|
||||||
def perform(site, usuarie, message)
|
def perform(site, usuarie)
|
||||||
site.repository.merge(usuarie, message) if site.repository.fetch&.positive?
|
return unless site.repository.origin
|
||||||
|
return unless site.repository.fetch.positive?
|
||||||
|
|
||||||
|
site.repository.merge(usuarie)
|
||||||
|
site.reindex_changes!
|
||||||
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
|
||||||
|
|
|
@ -36,6 +36,15 @@ class IndexedPost < ApplicationRecord
|
||||||
|
|
||||||
belongs_to :site
|
belongs_to :site
|
||||||
|
|
||||||
|
# Encuentra el post original
|
||||||
|
#
|
||||||
|
# @return [nil,Post]
|
||||||
|
def post
|
||||||
|
return if post_id.blank?
|
||||||
|
|
||||||
|
@post ||= site.posts(lang: locale).find(post_id, uuid: true)
|
||||||
|
end
|
||||||
|
|
||||||
# Convertir locale a direccionario de PG
|
# Convertir locale a direccionario de PG
|
||||||
#
|
#
|
||||||
# @param [String,Symbol]
|
# @param [String,Symbol]
|
||||||
|
|
|
@ -1,19 +1,124 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Site
|
||||||
# Indexa todos los artículos de un sitio
|
# Indexa todos los artículos de un sitio
|
||||||
#
|
#
|
||||||
# TODO: Hacer opcional
|
# TODO: Hacer opcional
|
||||||
class Site
|
|
||||||
module Index
|
module Index
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
included do
|
included do
|
||||||
has_many :indexed_posts, dependent: :destroy
|
has_many :indexed_posts, dependent: :destroy
|
||||||
|
|
||||||
|
MODIFIED_STATUSES = %i[added modified].freeze
|
||||||
|
DELETED_STATUSES = %i[deleted].freeze
|
||||||
|
LOCALE_FROM_PATH = /\A_/.freeze
|
||||||
|
|
||||||
def index_posts!
|
def index_posts!
|
||||||
Site.transaction do
|
Site.transaction do
|
||||||
docs.each(&:index!)
|
docs.each(&:index!)
|
||||||
end
|
|
||||||
|
update(last_indexed_commit: repository.head_commit.oid)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Encuentra los artículos modificados entre dos commits y los
|
||||||
|
# reindexa.
|
||||||
|
def reindex_changes!
|
||||||
|
return unless reindexable?
|
||||||
|
|
||||||
|
Site.transaction do
|
||||||
|
remove_deleted_posts!
|
||||||
|
reindex_modified_posts!
|
||||||
|
|
||||||
|
update(last_indexed_commit: repository.head_commit.oid)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# No hacer nada si el repositorio no cambió o no hubo cambios
|
||||||
|
# necesarios
|
||||||
|
def reindexable?
|
||||||
|
return false if last_indexed_commit.blank?
|
||||||
|
return false if last_indexed_commit == repository.head_commit.oid
|
||||||
|
|
||||||
|
!indexable_posts.empty?
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Trae el último commit indexado desde el repositorio
|
||||||
|
#
|
||||||
|
# @return [Rugged::Commit]
|
||||||
|
def indexed_commit
|
||||||
|
@indexed_commit ||= repository.rugged.lookup(last_indexed_commit)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Calcula la diferencia entre el último commit indexado y el
|
||||||
|
# actual
|
||||||
|
#
|
||||||
|
# XXX: Esto no tiene en cuenta modificaciones en la historia como
|
||||||
|
# cambio de ramas, reverts y etc, solo asume que se mueve hacia
|
||||||
|
# adelante en la misma rama o las dos ramas están relacionadas.
|
||||||
|
#
|
||||||
|
# @return [Rugged::Diff]
|
||||||
|
def diff_with_head
|
||||||
|
@diff_with_head ||= indexed_commit.diff(repository.head_commit)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Obtiene todos los archivos a reindexar
|
||||||
|
#
|
||||||
|
# @return [Array<Rugged::Delta>]
|
||||||
|
def indexable_posts
|
||||||
|
@indexable_posts ||=
|
||||||
|
diff_with_head.each_delta.select do |delta|
|
||||||
|
locales.any? do |locale|
|
||||||
|
delta.old_file[:path].start_with? "_#{locale}/"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Elimina los artículos eliminados o que cambiaron de ubicación
|
||||||
|
# del índice
|
||||||
|
def remove_deleted_posts!
|
||||||
|
indexable_posts.select do |delta|
|
||||||
|
DELETED_STATUSES.include? delta.status
|
||||||
|
end.each do |delta|
|
||||||
|
locale, path = locale_and_path_from(delta.old_file[:path])
|
||||||
|
|
||||||
|
indexed_posts.destroy_by(locale: locale, path: path).tap do |destroyed_posts|
|
||||||
|
next unless destroyed_posts.empty?
|
||||||
|
|
||||||
|
Rails.logger.info I18n.t('indexed_posts.deleted', site: name, path: path, records: destroyed_posts.count)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Reindexa artículos que cambiaron de ubicación, se agregaron
|
||||||
|
# o fueron modificados
|
||||||
|
def reindex_modified_posts!
|
||||||
|
indexable_posts.select do |delta|
|
||||||
|
MODIFIED_STATUSES.include? delta.status
|
||||||
|
end.each do |delta|
|
||||||
|
locale, path = locale_and_path_from(delta.new_file[:path])
|
||||||
|
|
||||||
|
posts(lang: locale).find(path).index!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Obtiene el idioma y la ruta del post a partir de la ubicación en
|
||||||
|
# el disco.
|
||||||
|
#
|
||||||
|
# Las rutas vienen en ASCII-9BIT desde Rugged, pero en realidad
|
||||||
|
# son UTF-8
|
||||||
|
#
|
||||||
|
# @return [Array<String>]
|
||||||
|
def locale_and_path_from(path)
|
||||||
|
locale, path = path.force_encoding('utf-8').split(File::SEPARATOR, 2)
|
||||||
|
|
||||||
|
[
|
||||||
|
locale.sub(LOCALE_FROM_PATH, ''),
|
||||||
|
File.basename(path, '.*')
|
||||||
|
]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
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
|
|
@ -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'
|
||||||
|
@ -733,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'
|
||||||
|
@ -741,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})"
|
||||||
|
|
|
@ -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