Scope jobs and controllers by account

This commit is contained in:
Stanko K.R.
2025-11-14 12:24:13 +01:00
committed by Mike Dalessio
parent b4f3ee5d86
commit e0693de7c3
24 changed files with 95 additions and 137 deletions
+1 -1
View File
@@ -2,7 +2,7 @@ class Cards::TaggingsController < ApplicationController
include CardScoped
def new
@tags = Tag.all.alphabetically
@tags = Current.account.tags.all.alphabetically
fresh_when etag: [ @tags, @card.tags ]
end
+1 -1
View File
@@ -7,6 +7,6 @@ module ColumnScoped
private
def set_column
@column = Column.find(params[:column_id])
@column = Current.account.columns.find(params[:column_id])
end
end
+4 -14
View File
@@ -1,5 +1,4 @@
class JoinCodesController < ApplicationController
disallow_account_scope
allow_unauthenticated_access
before_action :set_join_code
@@ -30,22 +29,13 @@ class JoinCodesController < ApplicationController
private
def set_join_code
# TODO:PLANB: this find should be scoped by account
# 2025-11-12 [Stanko]: I think that we don't have to scope this by account as the code
# is globally unique and indexed. Except if we need to scope it
# for some other reason?
@join_code ||= Account::JoinCode.active.find_by(code: code)
@join_code ||= Account::JoinCode.active.find_by(
code: params.expect(:code),
account: Current.account
)
end
def ensure_join_code_is_valid
head :not_found unless @join_code&.active?
end
def tenant
params.expect(:tenant)
end
def code
params.expect(:code)
end
end
+1 -1
View File
@@ -2,7 +2,7 @@ class My::MenusController < ApplicationController
def show
@filters = Current.user.filters.all
@boards = Current.user.boards.ordered_by_recently_accessed
@tags = Tag.all.alphabetically
@tags = Current.account.tags.all.alphabetically
@users = Current.account.users.active.alphabetically
fresh_when etag: [ @filters, @boards, @tags, @users ]
+1 -1
View File
@@ -1,6 +1,6 @@
class Prompts::TagsController < ApplicationController
def index
@tags = Tag.all.alphabetically
@tags = Current.account.tags.all.alphabetically
if stale? etag: @tags
render layout: false
+6 -1
View File
@@ -3,6 +3,11 @@ class QrCodesController < ApplicationController
def show
expires_in 1.year, public: true
render svg: RQRCode::QRCode.new(QrCodeLink.from_signed(params[:id]).url).as_svg(viewbox: true, fill: :white, color: :black)
qr_code_svg = RQRCode::QRCode
.new(QrCodeLink.from_signed(params[:id]).url)
.as_svg(viewbox: true, fill: :white, color: :black)
render svg: qr_code_svg
end
end
+2 -2
View File
@@ -19,11 +19,11 @@ class Users::AvatarsController < ApplicationController
private
def set_user
@user = User.find(params[:user_id])
@user = Current.account.users.find(params[:user_id])
end
def ensure_permission_to_administer_user
head :forbidden unless Current.user.admin? || Current.user == @user
head :forbidden unless Current.user.can_change?(@user)
end
def render_avatar_or_initials
@@ -1,5 +1,4 @@
class Users::EmailAddresses::ConfirmationsController < ApplicationController
disallow_account_scope
allow_unauthenticated_access
before_action :set_user
@@ -9,7 +8,7 @@ class Users::EmailAddresses::ConfirmationsController < ApplicationController
end
def create
user = User.change_email_address_using_token(token)
user = @user.change_email_address_using_token(token)
terminate_session if Current.session
start_new_session_for user.identity
@@ -19,7 +18,7 @@ class Users::EmailAddresses::ConfirmationsController < ApplicationController
private
def set_user
@user = User.find(params[:user_id])
@user = Current.account.users.active.find(params[:user_id])
end
def token
@@ -1,8 +1,4 @@
class Users::EmailAddressesController < ApplicationController
disallow_account_scope
layout "public"
before_action :set_user
rate_limit to: 5, within: 1.hour, only: :create
@@ -2,7 +2,7 @@ class Webhooks::ActivationsController < ApplicationController
before_action :ensure_admin
def create
webhook = Webhook.find(params[:webhook_id])
webhook = Current.account.webhooks.find(params[:webhook_id])
webhook.activate
redirect_to webhook
@@ -1,5 +0,0 @@
class Card::AutoPostponeAllDueJob < ApplicationJob
def perform
Card.auto_postpone_all_due
end
end
@@ -1,5 +0,0 @@
class Webhook::CleanupDeliveriesJob < ApplicationJob
def perform
Webhook::Delivery.cleanup
end
end
+4 -1
View File
@@ -4,7 +4,10 @@ class Account < ApplicationRecord
has_one :join_code
has_many :users, dependent: :destroy
has_many :boards, dependent: :destroy
has_many :cards, through: :boards
has_many :cards, dependent: :destroy
has_many :webhooks, dependent: :destroy
has_many :tags, dependent: :destroy
has_many :columns, dependent: :destroy
has_many_attached :uploads
+15 -15
View File
@@ -1,6 +1,21 @@
class QrCodeLink
attr_reader :url
class << self
def from_signed(signed)
new verifier.verify(signed, purpose: :qr_code)
end
def verifier
ActiveSupport::MessageVerifier.new(secret, url_safe: true)
end
private
def secret
Rails.application.key_generator.generate_key("qr_codes")
end
end
def initialize(url)
@url = url
end
@@ -8,19 +23,4 @@ class QrCodeLink
def signed
self.class.verifier.generate(@url, purpose: :qr_code)
end
def self.from_signed(signed)
new verifier.verify(signed, purpose: :qr_code)
end
private
class << self
def verifier
ActiveSupport::MessageVerifier.new(secret, url_safe: true)
end
def secret
Rails.application.key_generator.generate_key("qr_codes")
end
end
end
+4 -7
View File
@@ -4,21 +4,18 @@ module User::EmailAddressChangeable
extend ActiveSupport::Concern
class_methods do
def change_email_address_using_token(token)
parsed_token = SignedGlobalID.parse(token, for: EMAIL_CHANGE_TOKEN_PURPOSE)
user = parsed_token&.find
if parsed_token.nil?
raise ArgumentError, "The token is invalid"
elsif user.nil?
raise ArgumentError, "The user no longer exists"
elsif user.identity.email_address != parsed_token.params.fetch("old_email_address")
elsif parsed_token.find != self
raise ArgumentError, "The token was generated for a different user"
elsif identity.email_address != parsed_token.params.fetch("old_email_address")
raise ArgumentError, "The token was generated for a different email address"
else
new_email_address = parsed_token.params.fetch("new_email_address")
user.change_email_address(new_email_address)
end
change_email_address(new_email_address)
end
end
+1 -1
View File
@@ -17,7 +17,7 @@
<p class="txt-medium margin-none">Share the link below to invite people to this account</p>
</header>
<% url = join_url(code: @join_code.code, tenant: Current.account.external_account_id, script_name: nil) %>
<% url = join_url(code: @join_code.code, script_name: Current.account.slug) %>
<div class="flex align-center gap-half">
<input type="text" class="input flex-item-grow" value="<%= url %>" readonly>
@@ -1,6 +1,6 @@
<p class="subtitle">Confirm your email address change</p>
<%= link_to "Yes use use this email address", user_email_address_confirmation_url(user_id: @user.id, email_address_token: @token), class: "btn" %>
<%= link_to "Yes use use this email address", user_email_address_confirmation_url(script_name: @user.account.slug, user_id: @user.id, email_address_token: @token), class: "btn" %>
<p class="margin-block-start-double">If you didnt request this change, you can ignore this email. Your email address WILL NOT be changed unless you hit the button.</p>
+1 -1
View File
@@ -40,7 +40,7 @@
<div class="flex align-center gap">
<div class="flex align-center gap input input--actor">
<%= form.email_field :email_address, class: "input full-width", autocomplete: "username", placeholder: "Email address", required: true, readonly: true, value: @user.identity.email_address %>
<%= link_to "Change email", new_user_email_address_url(script_name: nil, user_id: Current.user.id), class: "btn btn--plain txt-link txt-small txt-nowrap" %>
<%= link_to "Change email", new_user_email_address_path(user_id: Current.user.id), class: "btn btn--plain txt-link txt-small txt-nowrap" %>
</div>
</div>
<button type="submit" id="log_in" class="btn btn--reversed center" data-form-target="submit">
+2 -1
View File
@@ -7,6 +7,7 @@ production: &production
# Application cleanup
auto_postpone_all_due:
class: Card::AutoPostponeAllDueJob
command: "Card.auto_postpone_all_due"
schedule: every hour at minute 50
delete_unused_tags:
class: DeleteUnusedTagsJob
@@ -20,7 +21,7 @@ production: &production
command: "SolidQueue::RecurringExecution.clear_in_batches"
schedule: every hour at minute 52
cleanup_webhook_deliveries:
class: Webhook::CleanupDeliveriesJob
command: "Webhook::Delivery.cleanup"
schedule: every 4 hours at minute 51
cleanup_magic_links:
command: "MagicLink.cleanup"
+5 -2
View File
@@ -134,8 +134,11 @@ Rails.application.routes.draw do
resources :qr_codes
get "join/:tenant/:code", to: "join_codes#new", as: :join
post "join/:tenant/:code", to: "join_codes#create"
# FIXME: Remove this before release
get "join/:tenant/:code", to: redirect { |params, request| "/#{params[:tenant]}/join/#{params[:code]}" }
get "join/:code", to: "join_codes#new", as: :join
post "join/:code", to: "join_codes#create"
namespace :users do
resources :joins
+8 -20
View File
@@ -2,23 +2,19 @@ require "test_helper"
class JoinCodesControllerTest < ActionDispatch::IntegrationTest
setup do
@tenant = accounts("37s").external_account_id
@account = accounts("37s")
@join_code = account_join_codes(:"37s")
end
test "new" do
untenanted do
get join_path(tenant: @tenant, code: @join_code.code)
end
get join_path(code: @join_code.code, script_name: @account.slug)
assert_response :success
assert_in_body "37signals"
end
test "new with an invalid code" do
untenanted do
get join_path(tenant: @tenant, code: "INVALID-CODE")
end
get join_path(code: "INVALID-CODE", script_name: @account.slug)
assert_response :not_found
end
@@ -26,39 +22,31 @@ class JoinCodesControllerTest < ActionDispatch::IntegrationTest
test "new with an inactive code" do
@join_code.update!(usage_count: @join_code.usage_limit)
untenanted do
get join_path(tenant: @tenant, code: @join_code.code)
end
get join_path(code: @join_code.code, script_name: @account.slug)
assert_response :not_found
end
test "create" do
untenanted do
assert_difference -> { Identity.count }, 1 do
assert_difference -> { User.count }, 1 do
post join_path(tenant: @tenant, code: @join_code.code), params: { email_address: "new_user@example.com" }
post join_path(code: @join_code.code, script_name: @account.slug), params: { email_address: "new_user@example.com" }
end
end
assert_redirected_to session_magic_link_path
account = accounts("37s")
assert_equal new_users_join_url(script_name: account.slug), session[:return_to_after_authenticating]
end
assert_equal new_users_join_url(script_name: @account.slug), session[:return_to_after_authenticating]
end
test "create for existing identity" do
identity = identities(:jz)
untenanted do
assert_no_difference -> { Identity.count } do
assert_no_difference -> { User.count } do
post join_path(tenant: @tenant, code: @join_code.code), params: { email_address: identity.email_address }
post join_path(code: @join_code.code, script_name: @account.slug), params: { email_address: identity.email_address }
end
end
account = accounts("37s")
assert_redirected_to landing_url(script_name: account.slug)
end
assert_redirected_to landing_url(script_name: @account.slug)
end
end
@@ -8,28 +8,22 @@ class Users::EmailAddresses::ConfirmationsControllerTest < ActionDispatch::Integ
end
test "show" do
untenanted do
get user_email_address_confirmation_path(user_id: @user.id, email_address_token: @token)
get user_email_address_confirmation_path(user_id: @user.id, email_address_token: @token, script_name: @user.account.slug)
assert_response :success
end
end
test "create" do
old_email = @user.identity.email_address
untenanted do
post user_email_address_confirmation_path(user_id: @user.id, email_address_token: @token)
post user_email_address_confirmation_path(user_id: @user.id, email_address_token: @token, script_name: @user.account.slug)
assert_equal @new_email, @user.reload.identity.email_address
assert_redirected_to edit_user_url(script_name: @user.account.slug, id: @user)
end
end
test "create with invalid token" do
untenanted do
assert_raises(ArgumentError) do
post user_email_address_confirmation_path(user_id: @user.id, email_address_token: "invalid")
end
post user_email_address_confirmation_path(user_id: @user.id, email_address_token: "invalid", script_name: @user.account.slug)
end
end
end
@@ -9,40 +9,32 @@ class Users::EmailAddressesControllerTest < ActionDispatch::IntegrationTest
end
test "new" do
untenanted do
get new_user_email_address_path(@user)
get new_user_email_address_path(@user, script_name: @user.account.slug)
assert_response :success
end
end
test "create" do
untenanted do
assert_emails 1 do
post user_email_addresses_path(@user), params: { email_address: "newemail@example.com" }
post user_email_addresses_path(@user, script_name: @user.account.slug), params: { email_address: "newemail@example.com" }
end
assert_response :success
end
end
test "create with existing email in same account" do
existing_user = users(:kevin)
existing_email = existing_user.identity.email_address
untenanted do
post user_email_addresses_path(@user), params: { email_address: existing_email }
post user_email_addresses_path(@user, script_name: @user.account.slug), params: { email_address: existing_email }
assert_redirected_to new_user_email_address_path(@user)
assert_equal "You already have a user in this account with that email address", flash[:alert]
end
end
test "create for other user" do
other_user = users(:kevin)
untenanted do
assert_no_emails do
post user_email_addresses_path(other_user), params: { email_address: "newemail@example.com" }
post user_email_addresses_path(other_user, script_name: @user.account.slug), params: { email_address: "newemail@example.com" }
end
assert_response :not_found
end
end
end
@@ -35,21 +35,21 @@ class User::EmailAddressChangeableTest < ActiveSupport::TestCase
test "change_email_address_using_token" do
token = @user.send(:generate_email_address_change_token, to: @new_email)
User.change_email_address_using_token(token)
@user.change_email_address_using_token(token)
assert_equal @new_email, @user.reload.identity.email_address
end
test "change_email_address_using_token with invalid token" do
assert_raises(ArgumentError, match: /invalid/) do
User.change_email_address_using_token("invalid_token")
@user.change_email_address_using_token("invalid_token")
end
token = @user.send(:generate_email_address_change_token, to: @new_email)
@identity.update!(email_address: "different@example.com")
assert_raises(ArgumentError, match: /different email address/) do
User.change_email_address_using_token(token)
@user.change_email_address_using_token(token)
end
end
end