5
0
Fork 0
mirror of https://0xacab.org/sutty/sutty synced 2024-11-22 12:26:21 +00:00
panel/app/services/post_service.rb
2024-06-04 12:49:37 -03:00

276 lines
7.7 KiB
Ruby

# frozen_string_literal: true
# Este servicio se encarga de crear artículos y guardarlos en git,
# asignándoselos a une usuarie
PostService = Struct.new(:site, :usuarie, :post, :params, keyword_init: true) do
# Crea un artículo nuevo y modificar las asociaciones
#
# @return Post
def create
self.post = Post.build(site: site, locale: locale, layout: layout)
post.usuaries << usuarie
params[:post][:draft] = true if site.invitade? usuarie
params.require(:post).permit(:slug).tap do |p|
post.slug.value = p[:slug] if p[:slug].present?
end
if post.update(post_params)
added_paths << post.path.value
# Recorrer todas las asociaciones y agregarse donde corresponda
update_associations(post)
commit(action: :created, add: added_paths)
end
# Devolver el post aunque no se haya salvado para poder rescatar los
# errores
post
end
# Crear un post anónimo, con opciones más limitadas. No usamos post.
#
# @todo Permitir asociaciones?
def create_anonymous
# XXX: Confiamos en el parámetro de idioma porque estamos
# verificándolos en Site#posts
self.post = Post.build(site: site, locale: locale, layout: layouts)
# Los artículos anónimos siempre son borradores
params[:draft] = true
commit(action: :created, add: [post.path.absolute]) if post.update(anon_post_params)
post
end
# Al actualizar, modificamos un post pre-existente, todas las
# relaciones anteriores y las relaciones actuales.
def update
post.usuaries << usuarie
params[:post][:draft] = true if site.invitade? usuarie
if post.update(post_params)
# Eliminar ("mover") el archivo si cambió de ubicación.
rm = []
rm << post.path.value_was if post.path.changed?
added_paths << post.path.value
# Recorrer todas las asociaciones y agregarse donde corresponda
update_associations(post)
commit(action: :updated, add: added_paths, rm: rm)
end
# Devolver el post aunque no se haya salvado para poder rescatar los
# errores
post
end
# @todo Eliminar relaciones
def destroy
post.destroy!
commit(action: :destroyed, rm: [post.path.absolute]) if post.destroyed?
post
end
# Reordena todos los posts que soporten orden de acuerdo a un hash de
# uuids y nuevas posiciones. La posición actual la da la posición en
# el array.
#
# { uuid => 2, uuid => 1, uuid => 0 }
def reorder
reorder = params.require(:post).permit(reorder: {})&.dig(:reorder)&.transform_values(&:to_i)
posts = site.indexed_posts.where(locale: locale, post_id: reorder.keys).map(&:post)
files = posts.map do |post|
next unless post.attribute? :order
order = reorder[post.uuid.value]
next if post.order.value == order
post.order.value = order
post.path.absolute
end.compact
return if files.empty?
# TODO: Implementar transacciones!
posts.map do |post|
post.save(validate: false)
end
commit(action: :reorder, add: files)
end
private
def commit(action:, add: [], rm: [])
site.repository.commit(add: add,
rm: rm,
usuarie: usuarie,
message: I18n.t("post_service.#{action}",
title: post&.title&.value))
GitPushJob.perform_later(site)
end
# Solo permitir cambiar estos atributos de cada articulo
def post_params
params.require(:post).permit(post.params)
end
# Eliminar metadatos internos
def anon_post_params
params.permit(post.params).delete_if do |k, _|
%w[date slug order uuid].include? k.to_s
end
end
# @return [Symbol]
def locale
params.dig(:post, :lang)&.to_sym || I18n.locale
end
# @return [Layout]
def layout
site.layouts[
(params.dig(:post, :layout) || params[:layout]).to_sym
]
end
# Si les usuaries modifican o crean una licencia, considerarla
# personalizada en el panel.
def update_site_license!
return unless site.usuarie?(usuarie) && post.layout.name == :license && !site.licencia.custom?
site.update licencia: Licencia.find_by_icons('custom')
end
# @return [Set<String>]
def associated_posts_to_save
@associated_posts_to_save ||= Set.new
end
# @return [Set<String>]
def added_paths
@added_paths ||= Set.new
end
# Recolectar campos asociados que no estén vacíos
#
# @param [Post]
# @return [Array<Symbol>]
def association_attributes(post)
post.attributes.select do |attribute|
post[attribute].try(:inverse?)
end
end
# @param :post_ids [Array<String>]
# @return [Association]
def associated_posts(post_ids)
site.indexed_posts.where(post_id: post_ids).map(&:post)
end
# Modificar las asociaciones en cascada, manteniendo reciprocidad
# y guardando los archivos correspondientes.
#
# HABTM, Locales: si se rompe de un lado se elimina en el otro y lo
# mismo si se agrega.
#
# HasMany: la relación es de uno a muchos. Al quitar uno, se elimina
# la relación inversa. Al agregar uno, se elimina su relación
# anterior en el tercer Post y se actualiza con la nueva.
#
# BelongsTo: la inversa de HasMany. Al cambiarla, se quita de la
# relación anterior y se agrega en la nueva.
#
# @param :post [Post]
# @return [nil]
def update_associations(post)
association_attributes(post).each do |attribute|
metadata = post[attribute]
next unless metadata.changed?
inverse_attribute = post[attribute].inverse
value_was = metadata.value_was.dup
value = metadata.value.dup
case metadata.type
when 'has_and_belongs_to_many', 'locales'
associated_posts(value_was - value).each do |remove_post|
remove_relation_from(remove_post[inverse_attribute], post.uuid.value)
end
associated_posts(value - value_was).each do |add_post|
add_relation_to(add_post[inverse_attribute], post.uuid.value)
end
when 'has_many'
associated_posts(value_was - value).each do |remove_post|
remove_relation_from(remove_post[inverse_attribute], '')
end
associated_posts(value - value_was).each do |add_post|
associated_posts(add_post[inverse_attribute].value_was).each do |remove_post|
remove_relation_from(remove_post[attribute], add_post.uuid.value)
end
add_relation_to(add_post[inverse_attribute], post.uuid.value)
end
when 'belongs_to', 'has_one'
if value_was.present?
associated_posts(value_was).each do |remove_post|
remove_relation_from(remove_post[inverse_attribute], post.uuid.value)
end
end
associated_posts(value).each do |add_post|
add_relation_to(add_post[inverse_attribute], post.uuid.value)
end
end
end
associated_posts_to_save.each do |associated_post|
next unless associated_post.save(validate: false)
added_paths << associated_post.path.value
end
nil
end
# @todo por qué no podemos usar nil para deshabilitar un valor?
# @param :metadata [MetadataTemplate]
# @param :value [String]
# @return [nil]
def remove_relation_from(metadata, value)
case metadata.value
when Array then metadata.value.delete(value)
when String then metadata.value = ''
end
associated_posts_to_save << metadata.post
nil
end
# @todo El validador ya debería eliminar valores duplicados
# @param :metadata [MetadataTemplate]
# @param :value [String]
# @return [nil]
def add_relation_to(metadata, value)
case metadata.value
when Array
metadata.value << value
metadata.value.uniq!
when String then metadata.value = value
end
associated_posts_to_save << metadata.post
nil
end
end