0
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2026-08-09 21:45:25 +08:00
discourse/app/services/sidebar_section_updater.rb
Isaac Janzen 7f18e37eae
FIX: Enforce per-section sidebar link cap on partial updates (#42309)
## Summary

- enforce the resulting active-link count while holding the sidebar
section row lock
- scope the validation to sidebar updates so Community reset and
unrelated model saves do not load or retain stale link associations
- allow existing over-limit sections to make non-growing and
deletion-based repairs
- use a translatable ActiveRecord error key

This replaces #42277 after its revert in #42287.

## Source

- Patch Triage: https://patch.discourse.org/patch-triage/1583
2026-08-04 09:17:17 -05:00

66 lines
1.9 KiB
Ruby
Vendored

# frozen_string_literal: true
class SidebarSectionUpdater
def self.update!(sidebar_section:, user:, section_params:, links_params:)
new(sidebar_section:, user:, section_params:, links_params:).update!
end
def initialize(sidebar_section:, user:, section_params:, links_params:)
@sidebar_section = sidebar_section
@user = user
@section_params = section_params
@links_params = (links_params || []).map { |link| link.to_h.with_indifferent_access }
end
def update!
@sidebar_section.with_lock do
@sidebar_section.assign_attributes(
@section_params.merge(sidebar_urls_attributes: @links_params),
)
@sidebar_section.save!(context: :sidebar_section_update)
@sidebar_section.sidebar_section_links.update_all(user_id: @sidebar_section.user_id)
update_link_order
end
publish_public_update if @sidebar_section.public?
@sidebar_section
end
private
def update_link_order
order =
@sidebar_section
.sidebar_urls
.sort_by do |url|
@links_params.index { |link| link[:name] == url.name && link[:value] == url.value } || -1
end
.each_with_index
.map { |url, index| [url.id, index] }
.to_h
set_order(order)
end
def set_order(order)
position_generator =
(0..@sidebar_section.sidebar_section_links.count * 2).excluding(
@sidebar_section.sidebar_section_links.map(&:position),
).each
links =
@sidebar_section
.sidebar_section_links
.sort_by { |link| order[link.linkable_id] }
.map { |link| link.attributes.merge(position: position_generator.next) }
@sidebar_section.sidebar_section_links.upsert_all(links, update_only: [:position])
end
def publish_public_update
StaffActionLogger.new(@user).log_update_public_sidebar_section(@sidebar_section)
MessageBus.publish("/refresh-sidebar-sections", nil)
Site.clear_anon_cache!
end
end