Merge branch 'main' into mobile-columns-pt-iii

* main:
  Only hide the blank slate when there are no cards present
  Support local installations where the app is loaded over HTTP
  Revert "Make sure new card drafts are refreshed when reused"
  Make sure new card drafts are refreshed when reused
  Add a "*" to the queues handled by the solid queue worker pool
  Remove unnecessary recurring execution cleanup task
  Refactor: Replace pluck(:id) with ids method
This commit is contained in:
Andy Smith
2026-01-06 10:29:20 -06:00
9 changed files with 45 additions and 115 deletions
+2 -2
View File
@@ -24,12 +24,12 @@
border: 2px dashed var(--color-ink-light);
border-radius: 1ch;
color: var(--color-ink-dark);
margin-block-start: 5dvh;
margin-block-start: 2dvh;
padding: 1ch 2ch;
rotate: -3deg;
font-weight: 500;
.cards:not(.cards--grid) & {
.cards:has(.card) & {
display: none;
}
}
+1 -1
View File
@@ -30,7 +30,7 @@ class BoardsController < ApplicationController
end
def edit
selected_user_ids = @board.users.pluck :id
selected_user_ids = @board.users.ids
@selected_users, @unselected_users = \
@board.account.users.active.alphabetically.includes(:identity).partition { |user| selected_user_ids.include? user.id }
end
@@ -2,31 +2,19 @@ module RequestForgeryProtection
extend ActiveSupport::Concern
included do
after_action :append_sec_fetch_site_to_vary_header
protect_from_forgery using: :header_only, with: :exception
end
private
def append_sec_fetch_site_to_vary_header
vary_header = response.headers["Vary"].to_s.split(",").map(&:strip).reject(&:blank?)
response.headers["Vary"] = (vary_header + [ "Sec-Fetch-Site" ]).join(",")
def verified_via_header_only?
super || allowed_api_request? || allowed_insecure_context_request?
end
def verified_request?
request.get? || request.head? || !protect_against_forgery? ||
(valid_request_origin? && safe_fetch_site?)
def allowed_api_request?
sec_fetch_site_value.nil? && request.format.json?
end
SAFE_FETCH_SITES = %w[ same-origin same-site ]
def safe_fetch_site?
SAFE_FETCH_SITES.include?(sec_fetch_site_value) || (sec_fetch_site_value.nil? && api_request?)
end
def api_request?
request.format.json?
end
def sec_fetch_site_value
request.headers["Sec-Fetch-Site"].to_s.downcase.presence
def allowed_insecure_context_request?
sec_fetch_site_value.nil? && !request.ssl? && !Rails.configuration.force_ssl
end
end
+3 -4
View File
@@ -21,7 +21,7 @@ module Board::Storage
end
def card_ids
@card_ids ||= cards.pluck(:id)
@card_ids ||= cards.ids
end
def card_image_bytes
@@ -36,7 +36,7 @@ module Board::Storage
def comment_embed_bytes
card_ids.each_slice(BATCH_SIZE).sum do |batch|
sum_embed_bytes_for "Comment", Comment.where(card_id: batch).pluck(:id)
sum_embed_bytes_for "Comment", Comment.where(card_id: batch).ids
end
end
@@ -46,8 +46,7 @@ module Board::Storage
def sum_embed_bytes_for(record_type, record_ids)
rich_text_ids = ActionText::RichText \
.where(record_type: record_type, record_id: record_ids)
.pluck(:id)
.where(record_type: record_type, record_id: record_ids).ids
sum_blob_bytes_in_batches \
ActiveStorage::Attachment.where(record_type: "ActionText::RichText", name: "embeds"),
+1 -1
View File
@@ -13,6 +13,6 @@ module User::Accessor
private
def grant_access_to_boards
Access.insert_all account.boards.all_access.pluck(:id).collect { |board_id| { id: ActiveRecord::Type::Uuid.generate, board_id: board_id, user_id: id, account_id: account.id } }
Access.insert_all account.boards.all_access.ids.collect { |board_id| { id: ActiveRecord::Type::Uuid.generate, board_id: board_id, user_id: id, account_id: account.id } }
end
end
+1 -1
View File
@@ -3,7 +3,7 @@ default: &default
- polling_interval: 1
batch_size: 500
workers:
- queues: [ "default", "solid_queue_recurring", "backend", "webhooks" ]
- queues: [ "default", "solid_queue_recurring", "backend", "webhooks", "*" ]
threads: 3
processes: <%= Integer(ENV.fetch("JOB_CONCURRENCY") { Concurrent.physical_processor_count }) %>
polling_interval: 0.1
-6
View File
@@ -18,9 +18,6 @@ production: &production
clear_solid_queue_finished_jobs:
command: "SolidQueue::Job.clear_finished_in_batches(sleep_between_batches: 0.3)"
schedule: every hour at minute 12
clear_solid_queue_recurring_executions:
command: "SolidQueue::RecurringExecution.clear_in_batches"
schedule: every hour at minute 52
cleanup_webhook_deliveries:
command: "Webhook::Delivery.cleanup"
schedule: every 4 hours at minute 51
@@ -43,9 +40,6 @@ beta:
clear_solid_queue_finished_jobs:
command: "SolidQueue::Job.clear_finished_in_batches(sleep_between_batches: 0.3)"
schedule: every hour at minute 12
clear_solid_queue_recurring_executions:
command: "SolidQueue::RecurringExecution.clear_in_batches"
schedule: every hour at minute 52
staging: *production
development: *production
+1
View File
@@ -132,6 +132,7 @@ ActiveRecord::Schema[8.2].define(version: 1) do
t.index ["key"], name: "index_solid_queue_semaphores_on_key", unique: true
end
# If these FKs are removed, make sure to periodically run `RecurringExecution.clear_in_batches`
add_foreign_key "solid_queue_blocked_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade
add_foreign_key "solid_queue_claimed_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade
add_foreign_key "solid_queue_failed_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade
@@ -6,83 +6,16 @@ class RequestForgeryProtectionTest < ActionDispatch::IntegrationTest
@original_allow_forgery_protection = ActionController::Base.allow_forgery_protection
ActionController::Base.allow_forgery_protection = true
@original_force_ssl = Rails.configuration.force_ssl
end
teardown do
ActionController::Base.allow_forgery_protection = @original_allow_forgery_protection
Rails.configuration.force_ssl = @original_force_ssl
end
test "fails if Sec-Fetch-Site is cross-site" do
assert_no_difference -> { Board.count } do
post boards_path,
params: { board: { name: "Test Board" } },
headers: { "Sec-Fetch-Site" => "cross-site" }
end
assert_response :unprocessable_entity
end
test "succeeds with same-origin Sec-Fetch-Site" do
assert_difference -> { Board.count }, +1 do
post boards_path,
params: { board: { name: "Test Board" } },
headers: { "Sec-Fetch-Site" => "same-origin" }
end
assert_response :redirect
end
test "succeeds with same-site Sec-Fetch-Site" do
assert_difference -> { Board.count }, +1 do
post boards_path,
params: { board: { name: "Test Board" } },
headers: { "Sec-Fetch-Site" => "same-site" }
end
assert_response :redirect
end
test "fails with none Sec-Fetch-Site" do
assert_no_difference -> { Board.count } do
post boards_path,
params: { board: { name: "Test Board" } },
headers: { "Sec-Fetch-Site" => "none" }
end
assert_response :unprocessable_entity
end
test "fails when Sec-Fetch-Site header is missing" do
assert_no_difference -> { Board.count } do
post boards_path, params: { board: { name: "Test Board" } }
end
assert_response :unprocessable_entity
end
test "GET requests succeed regardless of Sec-Fetch-Site header" do
get board_path(boards(:writebook)), headers: { "Sec-Fetch-Site" => "cross-site" }
assert_response :success
end
test "appends Sec-Fetch-Site to Vary header on GET requests" do
get board_path(boards(:writebook))
assert_response :success
assert_includes response.headers["Vary"], "Sec-Fetch-Site"
end
test "appends Sec-Fetch-Site to Vary header on POST requests" do
post boards_path,
params: { board: { name: "Test Board" } },
headers: { "Sec-Fetch-Site" => "same-origin" }
assert_response :redirect
assert_includes response.headers["Vary"], "Sec-Fetch-Site"
end
test "JSON request succeeds with missing Sec-Fetch-Site" do
test "JSON request succeeds with missing Sec-Fetch-Site header" do
assert_difference -> { Board.count }, +1 do
post boards_path,
params: { board: { name: "Test Board" } },
@@ -92,22 +25,37 @@ class RequestForgeryProtectionTest < ActionDispatch::IntegrationTest
assert_response :created
end
test "JSON request fails with cross-site Sec-Fetch-Site" do
test "HTTP request succeeds with missing Sec-Fetch-Site header when force_ssl is disabled" do
Rails.configuration.force_ssl = false
assert_difference -> { Board.count }, +1 do
post boards_path,
params: { board: { name: "Test Board" } }
end
assert_response :redirect
end
test "HTTP request fails with missing Sec-Fetch-Site header when force_ssl is enabled" do
Rails.configuration.force_ssl = true
assert_no_difference -> { Board.count } do
post boards_path,
params: { board: { name: "Test Board" } },
headers: { "Sec-Fetch-Site" => "cross-site" },
as: :json
params: { board: { name: "Test Board" } }
end
assert_response :unprocessable_entity
end
private
def csrf_token
@csrf_token ||= begin
get new_board_path
response.body[/name="authenticity_token" value="([^"]+)"/, 1]
end
test "HTTPS request fails with missing Sec-Fetch-Site header" do
Rails.configuration.force_ssl = false
assert_no_difference -> { Board.count } do
post boards_path,
params: { board: { name: "Test Board" } },
headers: { "X-Forwarded-Proto" => "https" }
end
assert_response :unprocessable_entity
end
end