From 26494ff1bae1c8232d5ae3c1f3882256640769cf Mon Sep 17 00:00:00 2001 From: Monica Cellio Date: Fri, 24 Jan 2025 13:47:51 -0500 Subject: [PATCH 01/55] checkpoint: created new table, user profile page shows values if present, edit form is a layout mess and doesn't accept edits yet, attempt at URL validation not yet hooked up --- app/controllers/users_controller.rb | 15 +++++++++++++-- app/models/user.rb | 7 +++++++ app/models/user_website.rb | 5 +++++ app/views/users/edit_profile.html.erb | 12 ++++++++++++ app/views/users/show.html.erb | 14 ++++++++++++++ db/migrate/20250123141400_create_user_websites.rb | 11 +++++++++++ db/schema.rb | 12 +++++++++++- 7 files changed, 73 insertions(+), 3 deletions(-) create mode 100644 app/models/user_website.rb create mode 100644 db/migrate/20250123141400_create_user_websites.rb diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index bcd8aa87e..9fdbdbabd 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -368,9 +368,20 @@ def validate_profile_website(profile_params) flash[:danger] = 'Invalid profile website link.' end + def ensure_protocol(uri) + if URI.parse(uri).instance_of?(URI::Generic) + # URI::Generic indicates the user didn't include a protocol, so we'll add one now so that it can be + # parsed correctly in the view later on. + return "https://#{uri}" + else + return uri + end + rescue URI::InvalidURIError + return null + end + def update_profile - profile_params = params.require(:user).permit(:username, :profile_markdown, :website, :twitter, :discord) - profile_params[:twitter] = profile_params[:twitter].delete('@') + profile_params = params.require(:user).permit(:username, :profile_markdown, :website, :discord) if profile_params[:website].present? validate_profile_website(profile_params) diff --git a/app/models/user.rb b/app/models/user.rb index 46a27df1a..888937428 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,3 +1,4 @@ +# coding: utf-8 # Represents a user. Most of the User's logic is controlled by Devise and its overrides. A user, as far as the # application code (i.e. excluding Devise) is concerned, has many questions, answers, and votes. class User < ApplicationRecord @@ -27,6 +28,8 @@ class User < ApplicationRecord has_many :comment_threads_locked, class_name: 'CommentThread', foreign_key: :locked_by_id, dependent: :nullify has_many :category_filter_defaults, dependent: :destroy has_many :filters, dependent: :destroy + has_many :user_websites, dependent: :destroy + accepts_nested_attributes_for :user_websites belongs_to :deleted_by, required: false, class_name: 'User' validates :username, presence: true, length: { minimum: 3, maximum: 50 } @@ -130,6 +133,10 @@ def website_domain website.nil? ? website : URI.parse(website).hostname end + def websites_for + user_websites.order(position) + end + def is_moderator is_global_moderator || community_user&.is_moderator || is_admin || community_user&.privilege?('mod') || false end diff --git a/app/models/user_website.rb b/app/models/user_website.rb new file mode 100644 index 000000000..94bcc1afb --- /dev/null +++ b/app/models/user_website.rb @@ -0,0 +1,5 @@ +class UserWebsite < ApplicationRecord + + belongs_to :user + +end diff --git a/app/views/users/edit_profile.html.erb b/app/views/users/edit_profile.html.erb index 82f2a03bb..fc93eb67a 100644 --- a/app/views/users/edit_profile.html.erb +++ b/app/views/users/edit_profile.html.erb @@ -44,6 +44,18 @@

Note: Links are not shown publicly until you have earned the Participate Everywhere ability.

+
+

External links (your web site, blog, social media, GitHub, etc)

+
+ <%= form_for current_user do |uw_form| %> + <%= uw_form.fields_for :user_websites do |w| %> + Label: <%= w.text_field :label %> + URL: <%= w.text_field :url %> + <% end %> + <% end %> +
+
+
<%= f.label :website, class: "form-element" %> diff --git a/app/views/users/show.html.erb b/app/views/users/show.html.erb index bb10e0ec4..279206165 100644 --- a/app/views/users/show.html.erb +++ b/app/views/users/show.html.erb @@ -57,6 +57,20 @@ <% end %>
+ <% if @user.user_websites.size > 0 %> +
+

External links

+ + <% @user.user_websites.first(3).each do |w| %> + + + + + <% end %> +
<%= w.label %><%= link_to w.url, w.url, rel: 'nofollow' %>
+
+ <% end %> +
<% if user_signed_in? %> <%= link_to new_subscription_path(type: 'user', qualifier: @user.id, return_to: request.path), class: "button is-outlined is-small" do %> diff --git a/db/migrate/20250123141400_create_user_websites.rb b/db/migrate/20250123141400_create_user_websites.rb new file mode 100644 index 000000000..ec77b2d17 --- /dev/null +++ b/db/migrate/20250123141400_create_user_websites.rb @@ -0,0 +1,11 @@ +class CreateUserWebsites < ActiveRecord::Migration[7.0] + def change + create_table :user_websites do |t| + t.column :label, :string, limit:80 + t.string :url + t.integer :position + end + add_reference :user_websites, :user, null: false, foreign_key: true + add_index(:user_websites, [:user_id, :url], unique: true) + end +end diff --git a/db/schema.rb b/db/schema.rb index d71e83045..90e2ab534 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2024_10_20_193053) do +ActiveRecord::Schema[7.0].define(version: 2025_01_23_141400) do create_table "abilities", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| t.bigint "community_id" t.string "name" @@ -684,6 +684,15 @@ t.index ["community_user_id"], name: "index_user_abilities_on_community_user_id" end + create_table "user_websites", charset: "utf8mb4", collation: "utf8mb4_unicode_ci", force: :cascade do |t| + t.string "label", limit: 80 + t.string "url" + t.integer "position" + t.bigint "user_id", null: false + t.index ["user_id", "url"], name: "index_user_websites_on_user_id_and_url", unique: true + t.index ["user_id"], name: "index_user_websites_on_user_id" + end + create_table "users", charset: "utf8mb4", collation: "utf8mb4_unicode_ci", force: :cascade do |t| t.string "email" t.string "encrypted_password" @@ -826,6 +835,7 @@ add_foreign_key "thread_followers", "posts" add_foreign_key "user_abilities", "abilities" add_foreign_key "user_abilities", "community_users" + add_foreign_key "user_websites", "users" add_foreign_key "users", "users", column: "deleted_by_id" add_foreign_key "votes", "communities" add_foreign_key "warning_templates", "communities" From d95d0e05389099a85b437e64dd2790387f1be263 Mon Sep 17 00:00:00 2001 From: Monica Cellio Date: Fri, 24 Jan 2025 15:28:40 -0500 Subject: [PATCH 02/55] less-bad layout --- app/views/users/edit_profile.html.erb | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/app/views/users/edit_profile.html.erb b/app/views/users/edit_profile.html.erb index fc93eb67a..503f419f7 100644 --- a/app/views/users/edit_profile.html.erb +++ b/app/views/users/edit_profile.html.erb @@ -49,8 +49,16 @@
<%= form_for current_user do |uw_form| %> <%= uw_form.fields_for :user_websites do |w| %> - Label: <%= w.text_field :label %> - URL: <%= w.text_field :url %> +
+
+
Label:
+
<%= w.text_field :label, class: 'form-element' %>
+
+
+
URL:
+
<%= w.text_field :url, class: 'form-element' %>
+
+
<% end %> <% end %>
From b2b2593cac7da0032e504f624d82f71c7bd6dbbe Mon Sep 17 00:00:00 2001 From: Monica Cellio Date: Sat, 25 Jan 2025 20:48:32 -0500 Subject: [PATCH 03/55] spacing tweak --- app/views/users/edit_profile.html.erb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/users/edit_profile.html.erb b/app/views/users/edit_profile.html.erb index 503f419f7..3361948b2 100644 --- a/app/views/users/edit_profile.html.erb +++ b/app/views/users/edit_profile.html.erb @@ -55,8 +55,8 @@
<%= w.text_field :label, class: 'form-element' %>
-
URL:
-
<%= w.text_field :url, class: 'form-element' %>
+
URL:
+
<%= w.text_field :url, class: 'form-element' %>
<% end %> From 6ef2850b88410501d4121972814f1351ce09c300 Mon Sep 17 00:00:00 2001 From: Monica Cellio Date: Sat, 25 Jan 2025 20:48:55 -0500 Subject: [PATCH 04/55] attempting to validate URLs (doesn't work) --- app/controllers/users_controller.rb | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 9fdbdbabd..8cf359134 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -357,7 +357,7 @@ def edit_profile def validate_profile_website(profile_params) uri = profile_params[:website] - + if URI.parse(uri).instance_of?(URI::Generic) # URI::Generic indicates the user didn't include a protocol, so we'll add one now so that it can be # parsed correctly in the view later on. @@ -368,6 +368,13 @@ def validate_profile_website(profile_params) flash[:danger] = 'Invalid profile website link.' end + def validate_profile_websites(profile_params) + sites = profile_params[:user_websites] + for ws in sites do + ensure_protocol(ws.url) || flash[:danger] = 'Invalid external link' + ws.url + end + end + def ensure_protocol(uri) if URI.parse(uri).instance_of?(URI::Generic) # URI::Generic indicates the user didn't include a protocol, so we'll add one now so that it can be @@ -381,12 +388,12 @@ def ensure_protocol(uri) end def update_profile - profile_params = params.require(:user).permit(:username, :profile_markdown, :website, :discord) + profile_params = params.require(:user).permit(:username, :profile_markdown, :website, :discord, :user_websites) - if profile_params[:website].present? - validate_profile_website(profile_params) + if profile_params[:user_websites].present? + validate_profile_websites(profile_params) end - + @user = current_user if params[:user][:avatar].present? From 0c3f7106ea5c22ce1e51be861c1466f0a32960ca Mon Sep 17 00:00:00 2001 From: Monica Cellio Date: Sat, 25 Jan 2025 22:00:33 -0500 Subject: [PATCH 05/55] remove redundant form (edits can now be submitted but they don't update) --- app/views/users/edit_profile.html.erb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/views/users/edit_profile.html.erb b/app/views/users/edit_profile.html.erb index 3361948b2..cea75ba1f 100644 --- a/app/views/users/edit_profile.html.erb +++ b/app/views/users/edit_profile.html.erb @@ -47,8 +47,7 @@

External links (your web site, blog, social media, GitHub, etc)

- <%= form_for current_user do |uw_form| %> - <%= uw_form.fields_for :user_websites do |w| %> + <%= f.fields_for :user_websites do |w| %>
Label:
@@ -60,7 +59,6 @@
<% end %> - <% end %>
From afe68b265794ca54e79ddf6121bfec00cb0ac689 Mon Sep 17 00:00:00 2001 From: Monica Cellio Date: Sun, 26 Jan 2025 13:51:36 -0500 Subject: [PATCH 06/55] put error-handling in correct place --- app/controllers/users_controller.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 8cf359134..2f1e723bf 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -370,9 +370,7 @@ def validate_profile_website(profile_params) def validate_profile_websites(profile_params) sites = profile_params[:user_websites] - for ws in sites do - ensure_protocol(ws.url) || flash[:danger] = 'Invalid external link' + ws.url - end + ws.url.all? { |u| ensure_protocol(u) } end def ensure_protocol(uri) @@ -391,7 +389,7 @@ def update_profile profile_params = params.require(:user).permit(:username, :profile_markdown, :website, :discord, :user_websites) if profile_params[:user_websites].present? - validate_profile_websites(profile_params) + validate_profile_websites(profile_params) || flash[:danger] = "Invalid external link" end @user = current_user From 6ddbe008f1e4ca4f40cd34d94fc28453551a7622 Mon Sep 17 00:00:00 2001 From: Monica Cellio Date: Sun, 26 Jan 2025 14:01:26 -0500 Subject: [PATCH 07/55] make sure we assign response? --- app/controllers/users_controller.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 2f1e723bf..bc0a60e3a 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -370,7 +370,7 @@ def validate_profile_website(profile_params) def validate_profile_websites(profile_params) sites = profile_params[:user_websites] - ws.url.all? { |u| ensure_protocol(u) } + ws.url.all? { |u| u = ensure_protocol(u) } end def ensure_protocol(uri) @@ -382,14 +382,14 @@ def ensure_protocol(uri) return uri end rescue URI::InvalidURIError - return null + return nil end def update_profile profile_params = params.require(:user).permit(:username, :profile_markdown, :website, :discord, :user_websites) if profile_params[:user_websites].present? - validate_profile_websites(profile_params) || flash[:danger] = "Invalid external link" + profile_params[:user_websistes] = validate_profile_websites(profile_params) || flash[:danger] = "Invalid external link" end @user = current_user From fb0fd7fbe97e5adbb441aecfa667f29115d7df93 Mon Sep 17 00:00:00 2001 From: Monica Cellio Date: Sun, 26 Jan 2025 14:04:27 -0500 Subject: [PATCH 08/55] comment --- app/controllers/users_controller.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index bc0a60e3a..b6df08bdb 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -355,6 +355,7 @@ def edit_profile render layout: 'without_sidebar' end + # to be deleted (when I'm done debugging with it) def validate_profile_website(profile_params) uri = profile_params[:website] From 119fac3e2fc325eed4fea2c49f584bf3a4b9de8f Mon Sep 17 00:00:00 2001 From: Monica Cellio Date: Sun, 26 Jan 2025 14:19:56 -0500 Subject: [PATCH 09/55] minor cleanup (these were probably bugs but not the only ones) --- app/controllers/users_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index b6df08bdb..c026d94df 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -371,7 +371,7 @@ def validate_profile_website(profile_params) def validate_profile_websites(profile_params) sites = profile_params[:user_websites] - ws.url.all? { |u| u = ensure_protocol(u) } + sites.all? { |u| u = ensure_protocol(u.url) } end def ensure_protocol(uri) From 17a3bde48fdf7ab436b49f8afe8ea8e24912bfe5 Mon Sep 17 00:00:00 2001 From: Monica Cellio Date: Sun, 26 Jan 2025 15:56:02 -0500 Subject: [PATCH 10/55] review feedback --- app/controllers/users_controller.rb | 14 ++++++++++++-- app/models/user_website.rb | 2 -- app/views/users/show.html.erb | 18 +++++++++--------- 3 files changed, 21 insertions(+), 13 deletions(-) diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index c026d94df..94f9999aa 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -371,7 +371,17 @@ def validate_profile_website(profile_params) def validate_profile_websites(profile_params) sites = profile_params[:user_websites] - sites.all? { |u| u = ensure_protocol(u.url) } + # Create a hash mapping the submitted URL to the validated URL. + # If any of the validated are `nil`, this indicates there was an error + # and we can use this to give feedback. + websites = sites.map { |u| [u, ensure_protocol(u.url)] }.to_h + + # Check for `nil` values and produce an array of erroneous submitted values. + errors = websites.select { |k, v| v.nil? }.keys + flash[:danger] = "Invalid external link: #{errors.join(', ')}" + + # Return just the valid websites for saving + websites.compact end def ensure_protocol(uri) @@ -390,7 +400,7 @@ def update_profile profile_params = params.require(:user).permit(:username, :profile_markdown, :website, :discord, :user_websites) if profile_params[:user_websites].present? - profile_params[:user_websistes] = validate_profile_websites(profile_params) || flash[:danger] = "Invalid external link" + profile_params[:user_websistes] = validate_profile_websites(profile_params) end @user = current_user diff --git a/app/models/user_website.rb b/app/models/user_website.rb index 94bcc1afb..3f7b56e28 100644 --- a/app/models/user_website.rb +++ b/app/models/user_website.rb @@ -1,5 +1,3 @@ class UserWebsite < ApplicationRecord - belongs_to :user - end diff --git a/app/views/users/show.html.erb b/app/views/users/show.html.erb index 279206165..7fa82bd9b 100644 --- a/app/views/users/show.html.erb +++ b/app/views/users/show.html.erb @@ -59,15 +59,15 @@ <% if @user.user_websites.size > 0 %>
-

External links

- - <% @user.user_websites.first(3).each do |w| %> - - - - - <% end %> -
<%= w.label %><%= link_to w.url, w.url, rel: 'nofollow' %>
+

External links

+ + <% @user.user_websites.each do |w| %> + + + + + <% end %> +
<%= w.label %><%= link_to w.url, w.url, rel: 'nofollow' %>
<% end %> From 65e3b71cdf7d324bb1c0aef888b8632ebb219664 Mon Sep 17 00:00:00 2001 From: Monica Cellio Date: Sun, 26 Jan 2025 16:02:35 -0500 Subject: [PATCH 11/55] added some comments --- app/controllers/users_controller.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 94f9999aa..d762fe94f 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -369,7 +369,8 @@ def validate_profile_website(profile_params) flash[:danger] = 'Invalid profile website link.' end - def validate_profile_websites(profile_params) + # Return the user websites that pass validation (only). + def validated_profile_websites(profile_params) sites = profile_params[:user_websites] # Create a hash mapping the submitted URL to the validated URL. # If any of the validated are `nil`, this indicates there was an error @@ -384,6 +385,7 @@ def validate_profile_websites(profile_params) websites.compact end + # Ensure that a "naked" URL like example.com gets a protocol. def ensure_protocol(uri) if URI.parse(uri).instance_of?(URI::Generic) # URI::Generic indicates the user didn't include a protocol, so we'll add one now so that it can be @@ -399,8 +401,9 @@ def ensure_protocol(uri) def update_profile profile_params = params.require(:user).permit(:username, :profile_markdown, :website, :discord, :user_websites) + # Ensure that all user-supplied URLs are valid (strip ones that aren't). if profile_params[:user_websites].present? - profile_params[:user_websistes] = validate_profile_websites(profile_params) + profile_params[:user_websistes] = validated_profile_websites(profile_params) end @user = current_user From c7f5dd5d6427f6da920e9586c90e5bf7ec565be1 Mon Sep 17 00:00:00 2001 From: Oleg Valter Date: Mon, 27 Jan 2025 02:02:33 +0300 Subject: [PATCH 12/55] fixed up rubocop errors --- app/controllers/users_controller.rb | 16 ++++++++-------- app/models/user.rb | 1 - 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index d762fe94f..91d94aef7 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -358,7 +358,7 @@ def edit_profile # to be deleted (when I'm done debugging with it) def validate_profile_website(profile_params) uri = profile_params[:website] - + if URI.parse(uri).instance_of?(URI::Generic) # URI::Generic indicates the user didn't include a protocol, so we'll add one now so that it can be # parsed correctly in the view later on. @@ -375,10 +375,10 @@ def validated_profile_websites(profile_params) # Create a hash mapping the submitted URL to the validated URL. # If any of the validated are `nil`, this indicates there was an error # and we can use this to give feedback. - websites = sites.map { |u| [u, ensure_protocol(u.url)] }.to_h + websites = sites.to_h { |u| [u, ensure_protocol(u.url)] } # Check for `nil` values and produce an array of erroneous submitted values. - errors = websites.select { |k, v| v.nil? }.keys + errors = websites.select { |_, v| v.nil? }.keys flash[:danger] = "Invalid external link: #{errors.join(', ')}" # Return just the valid websites for saving @@ -390,13 +390,13 @@ def ensure_protocol(uri) if URI.parse(uri).instance_of?(URI::Generic) # URI::Generic indicates the user didn't include a protocol, so we'll add one now so that it can be # parsed correctly in the view later on. - return "https://#{uri}" + "https://#{uri}" else - return uri + uri end rescue URI::InvalidURIError - return nil - end + nil + end def update_profile profile_params = params.require(:user).permit(:username, :profile_markdown, :website, :discord, :user_websites) @@ -405,7 +405,7 @@ def update_profile if profile_params[:user_websites].present? profile_params[:user_websistes] = validated_profile_websites(profile_params) end - + @user = current_user if params[:user][:avatar].present? diff --git a/app/models/user.rb b/app/models/user.rb index 888937428..eba209973 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,4 +1,3 @@ -# coding: utf-8 # Represents a user. Most of the User's logic is controlled by Devise and its overrides. A user, as far as the # application code (i.e. excluding Devise) is concerned, has many questions, answers, and votes. class User < ApplicationRecord From c37592e5737d86f00028eefa30ffa855a64de359 Mon Sep 17 00:00:00 2001 From: Monica Cellio Date: Tue, 28 Jan 2025 15:08:23 -0500 Subject: [PATCH 13/55] installed maintenance task gem (see https://github.com/Shopify/maintenance_tasks) and added task to set up user_websites table for existing users --- Gemfile | 2 + .../initialize_user_websites_task.rb | 41 +++++++++++++++++++ config/routes.rb | 1 + ...aintenance_tasks_runs.maintenance_tasks.rb | 24 +++++++++++ ...ange_cursor_to_string.maintenance_tasks.rb | 19 +++++++++ ...ve_index_on_task_name.maintenance_tasks.rb | 16 ++++++++ ...aintenance_tasks_runs.maintenance_tasks.rb | 8 ++++ ...aintenance_tasks_runs.maintenance_tasks.rb | 14 +++++++ ...ck_columns_to_bigints.maintenance_tasks.rb | 18 ++++++++ ...me_and_status_to_runs.maintenance_tasks.rb | 20 +++++++++ ..._add_metadata_to_runs.maintenance_tasks.rb | 8 ++++ db/schema.rb | 23 ++++++++++- 12 files changed, 193 insertions(+), 1 deletion(-) create mode 100644 app/tasks/maintenance/initialize_user_websites_task.rb create mode 100644 db/migrate/20250128030354_create_maintenance_tasks_runs.maintenance_tasks.rb create mode 100644 db/migrate/20250128030355_change_cursor_to_string.maintenance_tasks.rb create mode 100644 db/migrate/20250128030356_remove_index_on_task_name.maintenance_tasks.rb create mode 100644 db/migrate/20250128030357_add_arguments_to_maintenance_tasks_runs.maintenance_tasks.rb create mode 100644 db/migrate/20250128030358_add_lock_version_to_maintenance_tasks_runs.maintenance_tasks.rb create mode 100644 db/migrate/20250128030359_change_runs_tick_columns_to_bigints.maintenance_tasks.rb create mode 100644 db/migrate/20250128030360_add_index_on_task_name_and_status_to_runs.maintenance_tasks.rb create mode 100644 db/migrate/20250128030361_add_metadata_to_runs.maintenance_tasks.rb diff --git a/Gemfile b/Gemfile index c20323811..e97fd1b83 100644 --- a/Gemfile +++ b/Gemfile @@ -100,3 +100,5 @@ group :development do gem 'spring', '~> 4.0' gem 'web-console', '~> 4.2' end + +gem "maintenance_tasks", "~> 2.10" diff --git a/app/tasks/maintenance/initialize_user_websites_task.rb b/app/tasks/maintenance/initialize_user_websites_task.rb new file mode 100644 index 000000000..56bd9b0df --- /dev/null +++ b/app/tasks/maintenance/initialize_user_websites_task.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +module Maintenance + class InitializeUserWebsitesTask < MaintenanceTasks::Task + def collection + # Collection to be iterated over + # Must be Active Record Relation or Array + User.all + end + + def process(user) + # The work to be done in a single iteration of the task. + # This should be idempotent, as the same element may be processed more + # than once if the task is interrupted and resumed. + unless user.user_websites.where(user_id: user.id, position: 1).size > 0 + if user.website + UserWebsite.create!(user_id: user.id, position: 1, label: "website", url: user.website) + else + # Need label for uniqueness constraint; won't show in UI without URL + UserWebsite.create!(user_id: user.id, position: 1, label: "1") + end + end + unless user.user_websites.where(user_id: user.id, position: 2).size > 0 + if user.twitter + UserWebsite.create!(user_id: user.id, position: 2, label: "Twitter", url: "https://twitter.com/" + user.twitter) + else + UserWebsite.create!(user_id: user.id, position: 2, label: "2") + end + end + # This check *should* be superfluous, but just in case... + unless user.user_websites.where(user_id: user.id, position: 3).size > 0 + UserWebsite.create!(user_id: user.id, position: 3, label: "3") + end + end + + def count + # Optionally, define the number of rows that will be iterated over + # This is used to track the task's progress + end + end +end diff --git a/config/routes.rb b/config/routes.rb index 8b8b6b37b..bf646a447 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,4 +1,5 @@ Rails.application.routes.draw do + mount MaintenanceTasks::Engine, at: "/maintenance_tasks" # Add normal sign in/sign up, confirmations, registrations, unlocking and password editing routes only if no SSO or mixed sign in. devise_for :users, only: %i[sessions registrations confirmations unlock passwords], controllers: { sessions: 'users/sessions', registrations: 'users/registrations' }, diff --git a/db/migrate/20250128030354_create_maintenance_tasks_runs.maintenance_tasks.rb b/db/migrate/20250128030354_create_maintenance_tasks_runs.maintenance_tasks.rb new file mode 100644 index 000000000..c9b215d68 --- /dev/null +++ b/db/migrate/20250128030354_create_maintenance_tasks_runs.maintenance_tasks.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +# This migration comes from maintenance_tasks (originally 20201211151756) +class CreateMaintenanceTasksRuns < ActiveRecord::Migration[6.0] + def change + create_table(:maintenance_tasks_runs) do |t| + t.string(:task_name, null: false) + t.datetime(:started_at) + t.datetime(:ended_at) + t.float(:time_running, default: 0.0, null: false) + t.integer(:tick_count, default: 0, null: false) + t.integer(:tick_total) + t.string(:job_id) + t.bigint(:cursor) + t.string(:status, default: :enqueued, null: false) + t.string(:error_class) + t.string(:error_message) + t.text(:backtrace) + t.timestamps + t.index(:task_name) + t.index([:task_name, :created_at], order: { created_at: :desc }) + end + end +end diff --git a/db/migrate/20250128030355_change_cursor_to_string.maintenance_tasks.rb b/db/migrate/20250128030355_change_cursor_to_string.maintenance_tasks.rb new file mode 100644 index 000000000..4e414cad3 --- /dev/null +++ b/db/migrate/20250128030355_change_cursor_to_string.maintenance_tasks.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +# This migration comes from maintenance_tasks (originally 20210219212931) +class ChangeCursorToString < ActiveRecord::Migration[6.0] + # This migration will clear all existing data in the cursor column with MySQL. + # Ensure no Tasks are paused when this migration is deployed, or they will be resumed from the start. + # Running tasks are able to gracefully handle this change, even if interrupted. + def up + change_table(:maintenance_tasks_runs) do |t| + t.change(:cursor, :string) + end + end + + def down + change_table(:maintenance_tasks_runs) do |t| + t.change(:cursor, :bigint) + end + end +end diff --git a/db/migrate/20250128030356_remove_index_on_task_name.maintenance_tasks.rb b/db/migrate/20250128030356_remove_index_on_task_name.maintenance_tasks.rb new file mode 100644 index 000000000..1b5cadf8d --- /dev/null +++ b/db/migrate/20250128030356_remove_index_on_task_name.maintenance_tasks.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +# This migration comes from maintenance_tasks (originally 20210225152418) +class RemoveIndexOnTaskName < ActiveRecord::Migration[6.0] + def up + change_table(:maintenance_tasks_runs) do |t| + t.remove_index(:task_name) + end + end + + def down + change_table(:maintenance_tasks_runs) do |t| + t.index(:task_name) + end + end +end diff --git a/db/migrate/20250128030357_add_arguments_to_maintenance_tasks_runs.maintenance_tasks.rb b/db/migrate/20250128030357_add_arguments_to_maintenance_tasks_runs.maintenance_tasks.rb new file mode 100644 index 000000000..8478268eb --- /dev/null +++ b/db/migrate/20250128030357_add_arguments_to_maintenance_tasks_runs.maintenance_tasks.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +# This migration comes from maintenance_tasks (originally 20210517131953) +class AddArgumentsToMaintenanceTasksRuns < ActiveRecord::Migration[6.0] + def change + add_column(:maintenance_tasks_runs, :arguments, :text) + end +end diff --git a/db/migrate/20250128030358_add_lock_version_to_maintenance_tasks_runs.maintenance_tasks.rb b/db/migrate/20250128030358_add_lock_version_to_maintenance_tasks_runs.maintenance_tasks.rb new file mode 100644 index 000000000..b302565af --- /dev/null +++ b/db/migrate/20250128030358_add_lock_version_to_maintenance_tasks_runs.maintenance_tasks.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +# This migration comes from maintenance_tasks (originally 20211210152329) +class AddLockVersionToMaintenanceTasksRuns < ActiveRecord::Migration[6.0] + def change + add_column( + :maintenance_tasks_runs, + :lock_version, + :integer, + default: 0, + null: false, + ) + end +end diff --git a/db/migrate/20250128030359_change_runs_tick_columns_to_bigints.maintenance_tasks.rb b/db/migrate/20250128030359_change_runs_tick_columns_to_bigints.maintenance_tasks.rb new file mode 100644 index 000000000..064b07705 --- /dev/null +++ b/db/migrate/20250128030359_change_runs_tick_columns_to_bigints.maintenance_tasks.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +# This migration comes from maintenance_tasks (originally 20220706101937) +class ChangeRunsTickColumnsToBigints < ActiveRecord::Migration[6.0] + def up + change_table(:maintenance_tasks_runs, bulk: true) do |t| + t.change(:tick_count, :bigint) + t.change(:tick_total, :bigint) + end + end + + def down + change_table(:maintenance_tasks_runs, bulk: true) do |t| + t.change(:tick_count, :integer) + t.change(:tick_total, :integer) + end + end +end diff --git a/db/migrate/20250128030360_add_index_on_task_name_and_status_to_runs.maintenance_tasks.rb b/db/migrate/20250128030360_add_index_on_task_name_and_status_to_runs.maintenance_tasks.rb new file mode 100644 index 000000000..74d77e6be --- /dev/null +++ b/db/migrate/20250128030360_add_index_on_task_name_and_status_to_runs.maintenance_tasks.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +# This migration comes from maintenance_tasks (originally 20220713131925) +class AddIndexOnTaskNameAndStatusToRuns < ActiveRecord::Migration[6.0] + def change + remove_index( + :maintenance_tasks_runs, + column: [:task_name, :created_at], + order: { created_at: :desc }, + name: :index_maintenance_tasks_runs_on_task_name_and_created_at, + ) + + add_index( + :maintenance_tasks_runs, + [:task_name, :status, :created_at], + name: :index_maintenance_tasks_runs, + order: { created_at: :desc }, + ) + end +end diff --git a/db/migrate/20250128030361_add_metadata_to_runs.maintenance_tasks.rb b/db/migrate/20250128030361_add_metadata_to_runs.maintenance_tasks.rb new file mode 100644 index 000000000..054ef0e2a --- /dev/null +++ b/db/migrate/20250128030361_add_metadata_to_runs.maintenance_tasks.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +# This migration comes from maintenance_tasks (originally 20230622035229) +class AddMetadataToRuns < ActiveRecord::Migration[6.0] + def change + add_column(:maintenance_tasks_runs, :metadata, :text) + end +end diff --git a/db/schema.rb b/db/schema.rb index 90e2ab534..97e097dba 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2025_01_23_141400) do +ActiveRecord::Schema[7.0].define(version: 2025_01_28_030361) do create_table "abilities", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| t.bigint "community_id" t.string "name" @@ -309,6 +309,27 @@ t.index ["name"], name: "index_licenses_on_name" end + create_table "maintenance_tasks_runs", charset: "utf8mb4", collation: "utf8mb4_unicode_ci", force: :cascade do |t| + t.string "task_name", null: false + t.datetime "started_at", precision: nil + t.datetime "ended_at", precision: nil + t.float "time_running", default: 0.0, null: false + t.bigint "tick_count", default: 0, null: false + t.bigint "tick_total" + t.string "job_id" + t.string "cursor" + t.string "status", default: "enqueued", null: false + t.string "error_class" + t.string "error_message" + t.text "backtrace" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.text "arguments" + t.integer "lock_version", default: 0, null: false + t.text "metadata" + t.index ["task_name", "status", "created_at"], name: "index_maintenance_tasks_runs", order: { created_at: :desc } + end + create_table "micro_auth_apps", charset: "utf8mb4", collation: "utf8mb4_unicode_ci", force: :cascade do |t| t.string "name" t.string "app_id" From 32a742ab7651769e44330baecb94af2c547cd4f5 Mon Sep 17 00:00:00 2001 From: Monica Cellio Date: Tue, 28 Jan 2025 15:28:39 -0500 Subject: [PATCH 14/55] filter out not-real user_websites --- app/views/users/show.html.erb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/users/show.html.erb b/app/views/users/show.html.erb index 7fa82bd9b..6efdbd7c5 100644 --- a/app/views/users/show.html.erb +++ b/app/views/users/show.html.erb @@ -57,11 +57,11 @@ <% end %> - <% if @user.user_websites.size > 0 %> + <% if @user.user_websites.where.not(url: [nil, ""]).size > 0 %>

External links

- <% @user.user_websites.each do |w| %> + <% @user.user_websites.where.not(url: [nil, ""]).each do |w| %> From 2a9d3e0259d20b6e4c220bd4c0a8227f47a8670e Mon Sep 17 00:00:00 2001 From: Monica Cellio Date: Tue, 28 Jan 2025 15:49:35 -0500 Subject: [PATCH 15/55] rubocop --- Gemfile | 2 +- .../initialize_user_websites_task.rb | 17 +++++++++-------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/Gemfile b/Gemfile index e97fd1b83..02374a9e5 100644 --- a/Gemfile +++ b/Gemfile @@ -101,4 +101,4 @@ group :development do gem 'web-console', '~> 4.2' end -gem "maintenance_tasks", "~> 2.10" +gem 'maintenance_tasks', '~> 2.10' diff --git a/app/tasks/maintenance/initialize_user_websites_task.rb b/app/tasks/maintenance/initialize_user_websites_task.rb index 56bd9b0df..09bc77bdd 100644 --- a/app/tasks/maintenance/initialize_user_websites_task.rb +++ b/app/tasks/maintenance/initialize_user_websites_task.rb @@ -12,24 +12,25 @@ def process(user) # The work to be done in a single iteration of the task. # This should be idempotent, as the same element may be processed more # than once if the task is interrupted and resumed. - unless user.user_websites.where(user_id: user.id, position: 1).size > 0 + unless user.user_websites.where(user_id: user.id, position: 1).size.positive? if user.website - UserWebsite.create!(user_id: user.id, position: 1, label: "website", url: user.website) + UserWebsite.create!(user_id: user.id, position: 1, label: 'website', url: user.website) else # Need label for uniqueness constraint; won't show in UI without URL - UserWebsite.create!(user_id: user.id, position: 1, label: "1") + UserWebsite.create!(user_id: user.id, position: 1, label: '1') end end - unless user.user_websites.where(user_id: user.id, position: 2).size > 0 + unless user.user_websites.where(user_id: user.id, position: 2).size.positive? if user.twitter - UserWebsite.create!(user_id: user.id, position: 2, label: "Twitter", url: "https://twitter.com/" + user.twitter) + UserWebsite.create!(user_id: user.id, position: 2, label: 'Twitter', + url: "https://twitter.com/#{user.twitter}") else - UserWebsite.create!(user_id: user.id, position: 2, label: "2") + UserWebsite.create!(user_id: user.id, position: 2, label: '2') end end # This check *should* be superfluous, but just in case... - unless user.user_websites.where(user_id: user.id, position: 3).size > 0 - UserWebsite.create!(user_id: user.id, position: 3, label: "3") + unless user.user_websites.where(user_id: user.id, position: 3).size.positive? + UserWebsite.create!(user_id: user.id, position: 3, label: '3') end end From 37f8548120713a8d721df75c475d7ed64e659ba9 Mon Sep 17 00:00:00 2001 From: Monica Cellio Date: Tue, 28 Jan 2025 22:39:40 -0500 Subject: [PATCH 16/55] need to access the user_websites attrs explicitly --- app/controllers/users_controller.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 91d94aef7..4045e73df 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -371,7 +371,7 @@ def validate_profile_website(profile_params) # Return the user websites that pass validation (only). def validated_profile_websites(profile_params) - sites = profile_params[:user_websites] + sites = profile_params[user_websites_attributes: [:id, :label, :url]] # Create a hash mapping the submitted URL to the validated URL. # If any of the validated are `nil`, this indicates there was an error # and we can use this to give feedback. @@ -399,10 +399,10 @@ def ensure_protocol(uri) end def update_profile - profile_params = params.require(:user).permit(:username, :profile_markdown, :website, :discord, :user_websites) + profile_params = params.require(:user).permit(:username, :profile_markdown, :website, :discord, user_websites_attributes: [:id, :label, :url]) # Ensure that all user-supplied URLs are valid (strip ones that aren't). - if profile_params[:user_websites].present? + if profile_params[user_websites: [:id, :label, :url]].present? profile_params[:user_websistes] = validated_profile_websites(profile_params) end From 11a5c89cc7ffc57265c05fd462394c83c61125f4 Mon Sep 17 00:00:00 2001 From: Monica Cellio Date: Tue, 28 Jan 2025 22:46:45 -0500 Subject: [PATCH 17/55] need both in the permit list --- app/controllers/users_controller.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 4045e73df..ac0e69326 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -371,7 +371,7 @@ def validate_profile_website(profile_params) # Return the user websites that pass validation (only). def validated_profile_websites(profile_params) - sites = profile_params[user_websites_attributes: [:id, :label, :url]] + sites = profile_params[:user_websites] # Create a hash mapping the submitted URL to the validated URL. # If any of the validated are `nil`, this indicates there was an error # and we can use this to give feedback. @@ -399,7 +399,7 @@ def ensure_protocol(uri) end def update_profile - profile_params = params.require(:user).permit(:username, :profile_markdown, :website, :discord, user_websites_attributes: [:id, :label, :url]) + profile_params = params.require(:user).permit(:username, :profile_markdown, :website, :discord, :user_websites, user_websites_attributes: [:id, :label, :url]) # Ensure that all user-supplied URLs are valid (strip ones that aren't). if profile_params[user_websites: [:id, :label, :url]].present? From 12ccc610509df71c05b2110ac0ce9615847920ad Mon Sep 17 00:00:00 2001 From: Oleg Valter Date: Wed, 29 Jan 2025 06:51:07 +0300 Subject: [PATCH 18/55] fixed :user_websites_attributes accessors --- Gemfile.lock | 12 ++++++++++++ app/controllers/users_controller.rb | 12 ++++++++---- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 52d4d8c7f..d5b3e3146 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -131,6 +131,7 @@ GEM crass (1.0.6) css_parser (1.16.0) addressable + csv (3.3.2) date (3.3.4) devise (4.8.1) bcrypt (~> 3.0) @@ -164,6 +165,8 @@ GEM actionview (>= 5.0.0) activesupport (>= 5.0.0) jmespath (1.6.1) + job-iteration (1.8.0) + activejob (>= 5.2) jquery-rails (4.5.0) rails-dom-testing (>= 1, < 3) railties (>= 4.2.0) @@ -189,6 +192,14 @@ GEM net-imap net-pop net-smtp + maintenance_tasks (2.10.1) + actionpack (>= 6.1) + activejob (>= 6.1) + activerecord (>= 6.1) + csv + job-iteration (>= 1.3.6) + railties (>= 6.1) + zeitwerk (>= 2.6.2) marcel (1.0.4) matrix (0.4.2) memory_profiler (1.0.0) @@ -415,6 +426,7 @@ DEPENDENCIES jquery-rails (~> 4.5.0) letter_opener_web (~> 2.0) listen (~> 3.7) + maintenance_tasks (~> 2.10) memory_profiler (~> 1.0) minitest (~> 5.16.0) minitest-ci (~> 3.4.0) diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index ac0e69326..b217889f6 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -371,7 +371,7 @@ def validate_profile_website(profile_params) # Return the user websites that pass validation (only). def validated_profile_websites(profile_params) - sites = profile_params[:user_websites] + sites = profile_params[:user_websites_attributes] # Create a hash mapping the submitted URL to the validated URL. # If any of the validated are `nil`, this indicates there was an error # and we can use this to give feedback. @@ -399,11 +399,15 @@ def ensure_protocol(uri) end def update_profile - profile_params = params.require(:user).permit(:username, :profile_markdown, :website, :discord, :user_websites, user_websites_attributes: [:id, :label, :url]) + profile_params = params.require(:user).permit(:username, + :profile_markdown, + :website, + :discord, + user_websites_attributes: [:id, :label, :url]) # Ensure that all user-supplied URLs are valid (strip ones that aren't). - if profile_params[user_websites: [:id, :label, :url]].present? - profile_params[:user_websistes] = validated_profile_websites(profile_params) + if profile_params[:user_websites_attributes].present? + profile_params[:user_websites_attributes] = validated_profile_websites(profile_params) end @user = current_user From 8c71e6f1257fc0b1c64253f8ec27babbad5e81d8 Mon Sep 17 00:00:00 2001 From: Oleg Valter Date: Wed, 29 Jan 2025 07:42:34 +0300 Subject: [PATCH 19/55] validated_profile_websites should correctly transform values --- app/controllers/users_controller.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index b217889f6..1199b80e3 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -372,10 +372,11 @@ def validate_profile_website(profile_params) # Return the user websites that pass validation (only). def validated_profile_websites(profile_params) sites = profile_params[:user_websites_attributes] + # Create a hash mapping the submitted URL to the validated URL. # If any of the validated are `nil`, this indicates there was an error # and we can use this to give feedback. - websites = sites.to_h { |u| [u, ensure_protocol(u.url)] } + websites = sites.transform_values { |w| w.merge({ url: ensure_protocol(w[:url]) }) } # Check for `nil` values and produce an array of erroneous submitted values. errors = websites.select { |_, v| v.nil? }.keys From de877bfc451eb195c2ff4bca6c1e85c5ad58b438 Mon Sep 17 00:00:00 2001 From: Oleg Valter Date: Wed, 29 Jan 2025 07:55:20 +0300 Subject: [PATCH 20/55] removed no longer needed validate_profile_website --- app/controllers/users_controller.rb | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 1199b80e3..fe3e30190 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -355,20 +355,6 @@ def edit_profile render layout: 'without_sidebar' end - # to be deleted (when I'm done debugging with it) - def validate_profile_website(profile_params) - uri = profile_params[:website] - - if URI.parse(uri).instance_of?(URI::Generic) - # URI::Generic indicates the user didn't include a protocol, so we'll add one now so that it can be - # parsed correctly in the view later on. - profile_params[:website] = "https://#{uri}" - end - rescue URI::InvalidURIError - profile_params.delete(:website) - flash[:danger] = 'Invalid profile website link.' - end - # Return the user websites that pass validation (only). def validated_profile_websites(profile_params) sites = profile_params[:user_websites_attributes] From a89c7301b68473b515938e33785013819a98315d Mon Sep 17 00:00:00 2001 From: Oleg Valter Date: Wed, 29 Jan 2025 07:56:48 +0300 Subject: [PATCH 21/55] temporarily fixed :twitter not being adding to permitted params --- app/controllers/users_controller.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index fe3e30190..fa4862e32 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -390,6 +390,7 @@ def update_profile :profile_markdown, :website, :discord, + :twitter, user_websites_attributes: [:id, :label, :url]) # Ensure that all user-supplied URLs are valid (strip ones that aren't). From d8d7104d6c6b294b7f7cdcd4d157e0ed2ba040f5 Mon Sep 17 00:00:00 2001 From: Oleg Valter Date: Wed, 29 Jan 2025 07:58:01 +0300 Subject: [PATCH 22/55] removed unnecessary comments --- app/controllers/users_controller.rb | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index fa4862e32..d4c7fed01 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -359,24 +359,19 @@ def edit_profile def validated_profile_websites(profile_params) sites = profile_params[:user_websites_attributes] - # Create a hash mapping the submitted URL to the validated URL. - # If any of the validated are `nil`, this indicates there was an error - # and we can use this to give feedback. websites = sites.transform_values { |w| w.merge({ url: ensure_protocol(w[:url]) }) } # Check for `nil` values and produce an array of erroneous submitted values. errors = websites.select { |_, v| v.nil? }.keys flash[:danger] = "Invalid external link: #{errors.join(', ')}" - # Return just the valid websites for saving websites.compact end # Ensure that a "naked" URL like example.com gets a protocol. def ensure_protocol(uri) + # URI::Generic indicates the user didn't include a protocol if URI.parse(uri).instance_of?(URI::Generic) - # URI::Generic indicates the user didn't include a protocol, so we'll add one now so that it can be - # parsed correctly in the view later on. "https://#{uri}" else uri From c37f01609e3ba0298dc80b0a9a10ed2977339ace Mon Sep 17 00:00:00 2001 From: Oleg Valter Date: Wed, 29 Jan 2025 08:03:15 +0300 Subject: [PATCH 23/55] 'invalid external link' should only be flashed if there actually are errors --- app/controllers/users_controller.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index d4c7fed01..cddb9a63e 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -362,8 +362,11 @@ def validated_profile_websites(profile_params) websites = sites.transform_values { |w| w.merge({ url: ensure_protocol(w[:url]) }) } # Check for `nil` values and produce an array of erroneous submitted values. - errors = websites.select { |_, v| v.nil? }.keys - flash[:danger] = "Invalid external link: #{errors.join(', ')}" + errors = websites.select { |_, w| w[:url].nil? }.keys + + unless errors.empty? + flash[:danger] = "Invalid external link: #{errors.join(', ')}" + end websites.compact end From acaa348855eba1c090ae9bca8e00c1accc236207 Mon Sep 17 00:00:00 2001 From: Oleg Valter Date: Wed, 29 Jan 2025 09:09:19 +0300 Subject: [PATCH 24/55] added User#same? method --- app/models/user.rb | 6 ++++++ app/views/users/show.html.erb | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/models/user.rb b/app/models/user.rb index eba209973..95d508d25 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -63,6 +63,12 @@ def trust_level community_user.trust_level end + # Checks whether this user is the same as a given user + # @param [User] user user to compare with + def same?(user) + id == user.id + end + # This class makes heavy use of predicate names, and their use is prevalent throughout the codebase # because of the importance of these methods. # rubocop:disable Naming/PredicateName diff --git a/app/views/users/show.html.erb b/app/views/users/show.html.erb index 6efdbd7c5..1442c04c6 100644 --- a/app/views/users/show.html.erb +++ b/app/views/users/show.html.erb @@ -93,7 +93,7 @@ <% end %> - <% if current_user&.id == @user.id %> + <% if current_user&.same?(@user) %> <%= link_to qr_login_code_path, class: 'button is-outlined is-small' do %> Mobile Sign In <% end %> From a38f0e10f87c03f9da6ae12c0a8ffd948a041671 Mon Sep 17 00:00:00 2001 From: Oleg Valter Date: Wed, 29 Jan 2025 09:39:17 +0300 Subject: [PATCH 25/55] validated_profile_websites should only return those params that have non-nil urls --- app/controllers/users_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index cddb9a63e..195bb7793 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -368,7 +368,7 @@ def validated_profile_websites(profile_params) flash[:danger] = "Invalid external link: #{errors.join(', ')}" end - websites.compact + websites.reject { |_, w| w[:url].nil? } end # Ensure that a "naked" URL like example.com gets a protocol. From 27979fb35e4f16e61a9cbff97ac85d0e25887ecb Mon Sep 17 00:00:00 2001 From: Oleg Valter Date: Wed, 29 Jan 2025 09:46:34 +0300 Subject: [PATCH 26/55] fixed danger notice when some of the user website links are invalid --- app/controllers/users_controller.rb | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 195bb7793..4d8f6cf0a 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -361,11 +361,14 @@ def validated_profile_websites(profile_params) websites = sites.transform_values { |w| w.merge({ url: ensure_protocol(w[:url]) }) } - # Check for `nil` values and produce an array of erroneous submitted values. - errors = websites.select { |_, w| w[:url].nil? }.keys + invalid_keys = websites.select { |_, w| w[:url].nil? }.keys + + errors = invalid_keys.map do |key| + "\"#{sites[key][:url]}\"" + end unless errors.empty? - flash[:danger] = "Invalid external link: #{errors.join(', ')}" + flash[:danger] = "Some of the external link URLs are invalid: #{errors.join(', ')}" end websites.reject { |_, w| w[:url].nil? } From e80b93dca6618cc8184e6049d9f1f0bb24461e4d Mon Sep 17 00:00:00 2001 From: Oleg Valter Date: Wed, 29 Jan 2025 09:53:51 +0300 Subject: [PATCH 27/55] temporary fix for users_controller tests until twitter/discord is removed --- test/controllers/users_controller_test.rb | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/test/controllers/users_controller_test.rb b/test/controllers/users_controller_test.rb index 17a18c257..8d957f100 100644 --- a/test/controllers/users_controller_test.rb +++ b/test/controllers/users_controller_test.rb @@ -115,8 +115,13 @@ class UsersControllerTest < ActionController::TestCase test 'should update profile text' do sign_in users(:standard_user) - patch :update_profile, params: { user: { profile_markdown: 'ABCDEF GHIJKL', website: 'https://example.com/user', - twitter: '@standard_user' } } + patch :update_profile, params: { + user: { + profile_markdown: 'ABCDEF GHIJKL', + website: 'https://example.com/user', + twitter: 'standard_user' + } + } assert_response 302 assert_not_nil flash[:success] assert_not_nil assigns(:user) From d35dbd411518b7a9a2e2f28ea9bb0be3c40f139b Mon Sep 17 00:00:00 2001 From: Oleg Valter Date: Wed, 29 Jan 2025 10:03:55 +0300 Subject: [PATCH 28/55] switched to maintenance_tasks v2.1.1 (can't test Ruby 2.7.8 on higher versions) --- Gemfile | 2 +- Gemfile.lock | 19 ++++++++----------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/Gemfile b/Gemfile index 02374a9e5..b09b258fd 100644 --- a/Gemfile +++ b/Gemfile @@ -101,4 +101,4 @@ group :development do gem 'web-console', '~> 4.2' end -gem 'maintenance_tasks', '~> 2.10' +gem 'maintenance_tasks', '~> 2.1.1' diff --git a/Gemfile.lock b/Gemfile.lock index d5b3e3146..47bccbcc9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -131,7 +131,6 @@ GEM crass (1.0.6) css_parser (1.16.0) addressable - csv (3.3.2) date (3.3.4) devise (4.8.1) bcrypt (~> 3.0) @@ -165,7 +164,7 @@ GEM actionview (>= 5.0.0) activesupport (>= 5.0.0) jmespath (1.6.1) - job-iteration (1.8.0) + job-iteration (1.3.6) activejob (>= 5.2) jquery-rails (4.5.0) rails-dom-testing (>= 1, < 3) @@ -192,14 +191,12 @@ GEM net-imap net-pop net-smtp - maintenance_tasks (2.10.1) - actionpack (>= 6.1) - activejob (>= 6.1) - activerecord (>= 6.1) - csv - job-iteration (>= 1.3.6) - railties (>= 6.1) - zeitwerk (>= 2.6.2) + maintenance_tasks (2.1.1) + actionpack (>= 6.0) + activejob (>= 6.0) + activerecord (>= 6.0) + job-iteration (~> 1.3.6) + railties (>= 6.0) marcel (1.0.4) matrix (0.4.2) memory_profiler (1.0.0) @@ -426,7 +423,7 @@ DEPENDENCIES jquery-rails (~> 4.5.0) letter_opener_web (~> 2.0) listen (~> 3.7) - maintenance_tasks (~> 2.10) + maintenance_tasks (~> 2.1.1) memory_profiler (~> 1.0) minitest (~> 5.16.0) minitest-ci (~> 3.4.0) From 1a3d4fa73de4a09397bff3b280115bd3839b4d7e Mon Sep 17 00:00:00 2001 From: Monica Cellio Date: Wed, 29 Jan 2025 10:36:01 -0500 Subject: [PATCH 29/55] default order so edit page will work right --- app/models/user_website.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/models/user_website.rb b/app/models/user_website.rb index 3f7b56e28..ccc27d9ea 100644 --- a/app/models/user_website.rb +++ b/app/models/user_website.rb @@ -1,3 +1,4 @@ class UserWebsite < ApplicationRecord belongs_to :user + default_scope { order(:position) } end From 2223e5dbe264e13fa00ff0eded4e1c8cfb0a2f38 Mon Sep 17 00:00:00 2001 From: Monica Cellio Date: Wed, 29 Jan 2025 13:50:49 -0500 Subject: [PATCH 30/55] simplication: allow anything, only linkify http links, and apply nerf rules to whole section --- app/controllers/users_controller.rb | 11 +++++++---- app/models/user.rb | 9 +++++++-- app/views/users/edit_profile.html.erb | 10 ++++------ app/views/users/show.html.erb | 16 ++++++++++++---- 4 files changed, 30 insertions(+), 16 deletions(-) diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 4d8f6cf0a..12e9793ed 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -359,7 +359,10 @@ def edit_profile def validated_profile_websites(profile_params) sites = profile_params[:user_websites_attributes] - websites = sites.transform_values { |w| w.merge({ url: ensure_protocol(w[:url]) }) } + websites = sites.select { |_, v| v[:url].present? && v[:url] != ''} + .transform_values { |w| w.merge({ url: ensure_protocol(w[:url]) }) } + +# websites = sites.transform_values { |w| w.merge({ url: ensure_protocol(w[:url]) }) } invalid_keys = websites.select { |_, w| w[:url].nil? }.keys @@ -395,9 +398,9 @@ def update_profile user_websites_attributes: [:id, :label, :url]) # Ensure that all user-supplied URLs are valid (strip ones that aren't). - if profile_params[:user_websites_attributes].present? - profile_params[:user_websites_attributes] = validated_profile_websites(profile_params) - end +# if profile_params[:user_websites_attributes].present? +# profile_params[:user_websites_attributes] = validated_profile_websites(profile_params) +# end @user = current_user diff --git a/app/models/user.rb b/app/models/user.rb index 95d508d25..84021c1d9 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,3 +1,4 @@ +# coding: utf-8 # Represents a user. Most of the User's logic is controlled by Devise and its overrides. A user, as far as the # application code (i.e. excluding Devise) is concerned, has many questions, answers, and votes. class User < ApplicationRecord @@ -138,8 +139,12 @@ def website_domain website.nil? ? website : URI.parse(website).hostname end - def websites_for - user_websites.order(position) + def all_websites_for + user_websites.order(position: :asc) + end + + def valid_websites_for + user_websites.where.not(url: [nil, ""]).order(position: :asc) end def is_moderator diff --git a/app/views/users/edit_profile.html.erb b/app/views/users/edit_profile.html.erb index 41d1ba83c..f454c1421 100644 --- a/app/views/users/edit_profile.html.erb +++ b/app/views/users/edit_profile.html.erb @@ -47,17 +47,15 @@
-

External links (your web site, blog, social media, GitHub, etc)

+

Extra fields -- your web site, GitHub profile, social-media usernames, whatever you want. Only values that begin with "http" are rendered as links.

<%= f.fields_for :user_websites do |w| %>
-
-
Label:
-
<%= w.text_field :label, class: 'form-element' %>
+
+
<%= w.text_field :label, class: 'form-element', autocomplete: 'off', placeholder: 'label' %>
-
URL:
-
<%= w.text_field :url, class: 'form-element' %>
+
<%= w.text_field :url, class: 'form-element', autocomplete: 'off', placeholder: 'https://...' %>
<% end %> diff --git a/app/views/users/show.html.erb b/app/views/users/show.html.erb index 1442c04c6..6cc9673c4 100644 --- a/app/views/users/show.html.erb +++ b/app/views/users/show.html.erb @@ -57,19 +57,27 @@ <% end %>
- <% if @user.user_websites.where.not(url: [nil, ""]).size > 0 %> + <% unless !user_signed_in? && !@user.community_user.privilege?('unrestricted') %> + <% if @user.valid_websites_for.size.positive? %>
-

External links

+

Extra fields

<%= w.label %> <%= link_to w.url, w.url, rel: 'nofollow' %>
- <% @user.user_websites.where.not(url: [nil, ""]).each do |w| %> + <% @user.valid_websites_for.each do |w| %> - + <% end %>
<%= w.label %><%= link_to w.url, w.url, rel: 'nofollow' %> + <% if w.url[0,4] == 'http' %> + <%= link_to w.url, w.url, rel: 'nofollow' %> + <% else %> + <%= w.url %> + <% end %> +
<% end %> + <% end %>
<% if user_signed_in? %> From 92cd4589944644b78b3fddc825c12e589c44bc66 Mon Sep 17 00:00:00 2001 From: Monica Cellio Date: Wed, 29 Jan 2025 21:35:22 -0500 Subject: [PATCH 31/55] catch blank values and convert to nil for DB constraint, and now we no longer need to seed labels --- app/controllers/users_controller.rb | 20 ++++++++++++++----- .../initialize_user_websites_task.rb | 11 +++++----- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 12e9793ed..75440a51f 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -358,7 +358,7 @@ def edit_profile # Return the user websites that pass validation (only). def validated_profile_websites(profile_params) sites = profile_params[:user_websites_attributes] - + websites = sites.select { |_, v| v[:url].present? && v[:url] != ''} .transform_values { |w| w.merge({ url: ensure_protocol(w[:url]) }) } @@ -377,6 +377,17 @@ def validated_profile_websites(profile_params) websites.reject { |_, w| w[:url].nil? } end + def cleaned_profile_websites(profile_params) + sites = profile_params[:user_websites_attributes] + + sites.transform_values do |w| + w.merge({ + label: w[:label].present? ? w[:label] : nil, + url: w[:url].present? ? w[:url] : nil + }) + end + end + # Ensure that a "naked" URL like example.com gets a protocol. def ensure_protocol(uri) # URI::Generic indicates the user didn't include a protocol @@ -397,10 +408,9 @@ def update_profile :twitter, user_websites_attributes: [:id, :label, :url]) - # Ensure that all user-supplied URLs are valid (strip ones that aren't). -# if profile_params[:user_websites_attributes].present? -# profile_params[:user_websites_attributes] = validated_profile_websites(profile_params) -# end + if profile_params[:user_websites_attributes].present? + profile_params[:user_websites_attributes] = cleaned_profile_websites(profile_params) + end @user = current_user diff --git a/app/tasks/maintenance/initialize_user_websites_task.rb b/app/tasks/maintenance/initialize_user_websites_task.rb index 09bc77bdd..0a0b578ef 100644 --- a/app/tasks/maintenance/initialize_user_websites_task.rb +++ b/app/tasks/maintenance/initialize_user_websites_task.rb @@ -13,24 +13,23 @@ def process(user) # This should be idempotent, as the same element may be processed more # than once if the task is interrupted and resumed. unless user.user_websites.where(user_id: user.id, position: 1).size.positive? - if user.website + if user.website.present? UserWebsite.create!(user_id: user.id, position: 1, label: 'website', url: user.website) else - # Need label for uniqueness constraint; won't show in UI without URL - UserWebsite.create!(user_id: user.id, position: 1, label: '1') + UserWebsite.create!(user_id: user.id, position: 1) end end unless user.user_websites.where(user_id: user.id, position: 2).size.positive? - if user.twitter + if user.twitter.present? UserWebsite.create!(user_id: user.id, position: 2, label: 'Twitter', url: "https://twitter.com/#{user.twitter}") else - UserWebsite.create!(user_id: user.id, position: 2, label: '2') + UserWebsite.create!(user_id: user.id, position: 2) end end # This check *should* be superfluous, but just in case... unless user.user_websites.where(user_id: user.id, position: 3).size.positive? - UserWebsite.create!(user_id: user.id, position: 3, label: '3') + UserWebsite.create!(user_id: user.id, position: 3) end end From 981b92e746a2653aecc2e34e1b65f8f6b92994d6 Mon Sep 17 00:00:00 2001 From: Oleg Valter Date: Thu, 30 Jan 2025 05:37:21 +0300 Subject: [PATCH 32/55] removed validated_profile_websites as we don't use it anymore --- app/controllers/users_controller.rb | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 75440a51f..89eb83644 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -355,28 +355,6 @@ def edit_profile render layout: 'without_sidebar' end - # Return the user websites that pass validation (only). - def validated_profile_websites(profile_params) - sites = profile_params[:user_websites_attributes] - - websites = sites.select { |_, v| v[:url].present? && v[:url] != ''} - .transform_values { |w| w.merge({ url: ensure_protocol(w[:url]) }) } - -# websites = sites.transform_values { |w| w.merge({ url: ensure_protocol(w[:url]) }) } - - invalid_keys = websites.select { |_, w| w[:url].nil? }.keys - - errors = invalid_keys.map do |key| - "\"#{sites[key][:url]}\"" - end - - unless errors.empty? - flash[:danger] = "Some of the external link URLs are invalid: #{errors.join(', ')}" - end - - websites.reject { |_, w| w[:url].nil? } - end - def cleaned_profile_websites(profile_params) sites = profile_params[:user_websites_attributes] From 74926e1b058c212870cec6a50d5aeab871e9b398 Mon Sep 17 00:00:00 2001 From: Oleg Valter Date: Thu, 30 Jan 2025 05:39:27 +0300 Subject: [PATCH 33/55] rubocop fix for user model --- app/models/user.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/models/user.rb b/app/models/user.rb index 84021c1d9..c7ed17cbf 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,4 +1,3 @@ -# coding: utf-8 # Represents a user. Most of the User's logic is controlled by Devise and its overrides. A user, as far as the # application code (i.e. excluding Devise) is concerned, has many questions, answers, and votes. class User < ApplicationRecord @@ -144,7 +143,7 @@ def all_websites_for end def valid_websites_for - user_websites.where.not(url: [nil, ""]).order(position: :asc) + user_websites.where.not(url: [nil, '']).order(position: :asc) end def is_moderator From 0169871b2c8d4cddd85c3e8f51e040ba3c2ec94c Mon Sep 17 00:00:00 2001 From: Monica Cellio Date: Wed, 29 Jan 2025 21:52:42 -0500 Subject: [PATCH 34/55] remove unused fields from edit form --- app/views/users/edit_profile.html.erb | 25 ++++++------------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/app/views/users/edit_profile.html.erb b/app/views/users/edit_profile.html.erb index f454c1421..d9a85bff8 100644 --- a/app/views/users/edit_profile.html.erb +++ b/app/views/users/edit_profile.html.erb @@ -62,26 +62,13 @@
-
-
- <%= f.label :website, class: "form-element" %> - A link to anywhere on the internet for your stuff. - <%= f.text_field :website, class: 'form-element', autocomplete: 'off', placeholder: 'https://...' %> -
- -
- <%= f.label :twitter, class: "form-element" %> - Your Twitter username, if you've got one you want to share. - <%= f.text_field :twitter, class: 'form-element', autocomplete: 'off', placeholder: '@username' %> -
- -
- <%= f.label :discord, class: 'form-element' %> - Your Discord user tag, username or username#1234. - <%= f.text_field :discord, class: 'form-element', autocomplete: 'off', placeholder: 'username#1234' %> -
+
+ <%= f.label :discord, class: 'form-element' %> + Your Discord user tag, username or username#1234. + <%= f.text_field :discord, class: 'form-element', autocomplete: 'off', placeholder: 'username#1234' %>
- + + <%= f.submit 'Save', class: 'button is-filled' %> <% end %> From 5c8b48dfe7315df561fb39b6fff2c97e62d6be06 Mon Sep 17 00:00:00 2001 From: Monica Cellio Date: Wed, 29 Jan 2025 21:53:41 -0500 Subject: [PATCH 35/55] removed unused method --- app/models/user.rb | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/app/models/user.rb b/app/models/user.rb index c7ed17cbf..fb959190f 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,3 +1,4 @@ +# coding: utf-8 # Represents a user. Most of the User's logic is controlled by Devise and its overrides. A user, as far as the # application code (i.e. excluding Devise) is concerned, has many questions, answers, and votes. class User < ApplicationRecord @@ -138,10 +139,6 @@ def website_domain website.nil? ? website : URI.parse(website).hostname end - def all_websites_for - user_websites.order(position: :asc) - end - def valid_websites_for user_websites.where.not(url: [nil, '']).order(position: :asc) end From 32428a8c8789c5635a359e252f057374159dd9cd Mon Sep 17 00:00:00 2001 From: Monica Cellio Date: Wed, 29 Jan 2025 22:00:04 -0500 Subject: [PATCH 36/55] rubocop (someday I will figure out why my editor is adding those utf-8 lines and kill it...) --- app/models/user.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/app/models/user.rb b/app/models/user.rb index fb959190f..84036cd4f 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,4 +1,3 @@ -# coding: utf-8 # Represents a user. Most of the User's logic is controlled by Devise and its overrides. A user, as far as the # application code (i.e. excluding Devise) is concerned, has many questions, answers, and votes. class User < ApplicationRecord From 4a3696206c4e608aac494f3e2996f89f32e558c2 Mon Sep 17 00:00:00 2001 From: Monica Cellio Date: Wed, 29 Jan 2025 22:06:29 -0500 Subject: [PATCH 37/55] removed website and twitter sections, moved discord below extra fields --- app/views/users/show.html.erb | 30 ++++++++---------------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/app/views/users/show.html.erb b/app/views/users/show.html.erb index 6cc9673c4..9fa3c2c91 100644 --- a/app/views/users/show.html.erb +++ b/app/views/users/show.html.erb @@ -24,28 +24,6 @@
-

- <% if @user.website.present? %> - <% unless !user_signed_in? && !@user.community_user.privilege?('unrestricted') %> - - - <%= link_to @user.website_domain, @user.website, rel: 'nofollow', - 'aria-label': "Visit website of #{rtl_safe_username(@user)} at #{@user.website_domain}" %> - - <% end %> - <% end %> - <% if @user.twitter.present? %> - - <%= link_to @user.twitter, "https://twitter.com/#{@user.twitter}", - 'aria-label': "Visit twitter account of #{rtl_safe_username(@user)}" %> - - <% end %> - <% if @user.discord.present? %> - - <%= @user.discord %> - - <% end %> -

<% effective_profile = raw(sanitize(@user.profile&.strip || '', scrubber: scrubber)) %> <% if effective_profile.blank? %> @@ -79,6 +57,14 @@ <% end %> <% end %> +

+ <% if @user.discord.present? %> + + <%= @user.discord %> + + <% end %> +

+
<% if user_signed_in? %> <%= link_to new_subscription_path(type: 'user', qualifier: @user.id, return_to: request.path), class: "button is-outlined is-small" do %> From 73dc8b10fdd20575069d22c6b44ce9bbc64de110 Mon Sep 17 00:00:00 2001 From: Oleg Valter Date: Thu, 30 Jan 2025 08:51:12 +0300 Subject: [PATCH 38/55] removed no longer needed ensure_protocol --- app/controllers/users_controller.rb | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 89eb83644..34e7e7e29 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -366,18 +366,6 @@ def cleaned_profile_websites(profile_params) end end - # Ensure that a "naked" URL like example.com gets a protocol. - def ensure_protocol(uri) - # URI::Generic indicates the user didn't include a protocol - if URI.parse(uri).instance_of?(URI::Generic) - "https://#{uri}" - else - uri - end - rescue URI::InvalidURIError - nil - end - def update_profile profile_params = params.require(:user).permit(:username, :profile_markdown, From e10334d36d99789fb968c7759c09aa56eaa0fbe3 Mon Sep 17 00:00:00 2001 From: Monica Cellio Date: Thu, 30 Jan 2025 13:41:54 -0500 Subject: [PATCH 39/55] add CSS classes for mobile --- app/views/users/edit_profile.html.erb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/views/users/edit_profile.html.erb b/app/views/users/edit_profile.html.erb index d9a85bff8..faee47e20 100644 --- a/app/views/users/edit_profile.html.erb +++ b/app/views/users/edit_profile.html.erb @@ -50,11 +50,11 @@

Extra fields -- your web site, GitHub profile, social-media usernames, whatever you want. Only values that begin with "http" are rendered as links.

<%= f.fields_for :user_websites do |w| %> -
-
+
+
<%= w.text_field :label, class: 'form-element', autocomplete: 'off', placeholder: 'label' %>
-
+
<%= w.text_field :url, class: 'form-element', autocomplete: 'off', placeholder: 'https://...' %>
From 134e0e02045c57dd53bfbfd537b69eaf6116696b Mon Sep 17 00:00:00 2001 From: Monica Cellio Date: Thu, 30 Jan 2025 21:01:46 -0500 Subject: [PATCH 40/55] on login, ensure that the right number of rows in user_websites exists --- app/controllers/users/sessions_controller.rb | 3 +++ app/models/user.rb | 9 +++++++++ app/models/user_website.rb | 2 ++ 3 files changed, 14 insertions(+) diff --git a/app/controllers/users/sessions_controller.rb b/app/controllers/users/sessions_controller.rb index 9719fd745..44202c76a 100644 --- a/app/controllers/users/sessions_controller.rb +++ b/app/controllers/users/sessions_controller.rb @@ -92,6 +92,9 @@ def post_sign_in(user) return false end + # Make sure the rows in user_websites get created + user.ensure_websites + true end diff --git a/app/models/user.rb b/app/models/user.rb index 84036cd4f..e544f247d 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -142,6 +142,15 @@ def valid_websites_for user_websites.where.not(url: [nil, '']).order(position: :asc) end + def ensure_websites + pos = user_websites.size + while pos < UserWebsite::MaxRows + pos += 1 + UserWebsite.create(user_id: id, position: pos) + end + end + + def is_moderator is_global_moderator || community_user&.is_moderator || is_admin || community_user&.privilege?('mod') || false end diff --git a/app/models/user_website.rb b/app/models/user_website.rb index ccc27d9ea..28d135eb0 100644 --- a/app/models/user_website.rb +++ b/app/models/user_website.rb @@ -1,4 +1,6 @@ class UserWebsite < ApplicationRecord belongs_to :user default_scope { order(:position) } + + MaxRows = 3 end From d6b4e6c5cd81285987e8bcc62c573ee1e55187fe Mon Sep 17 00:00:00 2001 From: Monica Cellio Date: Thu, 30 Jan 2025 21:08:23 -0500 Subject: [PATCH 41/55] really, rubocop? screaming constants? SHOUTING IS RUDE :-P --- app/models/user.rb | 3 +-- app/models/user_website.rb | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/models/user.rb b/app/models/user.rb index e544f247d..9e65e2fe6 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -144,13 +144,12 @@ def valid_websites_for def ensure_websites pos = user_websites.size - while pos < UserWebsite::MaxRows + while pos < UserWebsite::MAX_ROWS pos += 1 UserWebsite.create(user_id: id, position: pos) end end - def is_moderator is_global_moderator || community_user&.is_moderator || is_admin || community_user&.privilege?('mod') || false end diff --git a/app/models/user_website.rb b/app/models/user_website.rb index 28d135eb0..6b50d909f 100644 --- a/app/models/user_website.rb +++ b/app/models/user_website.rb @@ -2,5 +2,5 @@ class UserWebsite < ApplicationRecord belongs_to :user default_scope { order(:position) } - MaxRows = 3 + MAX_ROWS = 3 end From ac5ad731ec462dbeca3eb9286725c8f36c8d0e86 Mon Sep 17 00:00:00 2001 From: Monica Cellio Date: Fri, 31 Jan 2025 10:41:41 -0500 Subject: [PATCH 42/55] oh, *that's* how you attach it to user creation! (moved ensure call) --- app/controllers/users/sessions_controller.rb | 3 --- app/models/user.rb | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/app/controllers/users/sessions_controller.rb b/app/controllers/users/sessions_controller.rb index 44202c76a..9719fd745 100644 --- a/app/controllers/users/sessions_controller.rb +++ b/app/controllers/users/sessions_controller.rb @@ -92,9 +92,6 @@ def post_sign_in(user) return false end - # Make sure the rows in user_websites get created - user.ensure_websites - true end diff --git a/app/models/user.rb b/app/models/user.rb index 9e65e2fe6..e20d7292b 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -45,7 +45,7 @@ class User < ApplicationRecord scope :active, -> { where(deleted: false) } scope :deleted, -> { where(deleted: true) } - after_create :send_welcome_tour_message + after_create :send_welcome_tour_message, :ensure_websites def self.list_includes includes(:posts, :avatar_attachment) From 20e38103c1b7235671370ed086b3185c19bbd556 Mon Sep 17 00:00:00 2001 From: Oleg Valter Date: Sat, 1 Feb 2025 20:26:01 +0300 Subject: [PATCH 43/55] renamed User#same? method to User#same_as? --- app/models/user.rb | 2 +- app/views/users/show.html.erb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/user.rb b/app/models/user.rb index e20d7292b..d585a9818 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -65,7 +65,7 @@ def trust_level # Checks whether this user is the same as a given user # @param [User] user user to compare with - def same?(user) + def same_as?(user) id == user.id end diff --git a/app/views/users/show.html.erb b/app/views/users/show.html.erb index 9fa3c2c91..cc055bdb4 100644 --- a/app/views/users/show.html.erb +++ b/app/views/users/show.html.erb @@ -87,7 +87,7 @@
<% end %> - <% if current_user&.same?(@user) %> + <% if current_user&.same_as?(@user) %> <%= link_to qr_login_code_path, class: 'button is-outlined is-small' do %> Mobile Sign In <% end %> From 3a663c01cff349961656d8e0491861f7da16bc07 Mon Sep 17 00:00:00 2001 From: Oleg Valter Date: Sat, 1 Feb 2025 20:30:25 +0300 Subject: [PATCH 44/55] fixed up newly introduced tab stops --- app/views/users/edit_profile.html.erb | 10 +++--- app/views/users/show.html.erb | 48 +++++++++++++-------------- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/app/views/users/edit_profile.html.erb b/app/views/users/edit_profile.html.erb index faee47e20..654a2ef29 100644 --- a/app/views/users/edit_profile.html.erb +++ b/app/views/users/edit_profile.html.erb @@ -51,12 +51,12 @@
<%= f.fields_for :user_websites do |w| %>
-
-
<%= w.text_field :label, class: 'form-element', autocomplete: 'off', placeholder: 'label' %>
-
-
+
+
<%= w.text_field :label, class: 'form-element', autocomplete: 'off', placeholder: 'label' %>
+
+
<%= w.text_field :url, class: 'form-element', autocomplete: 'off', placeholder: 'https://...' %>
-
+
<% end %>
diff --git a/app/views/users/show.html.erb b/app/views/users/show.html.erb index cc055bdb4..e6ff79907 100644 --- a/app/views/users/show.html.erb +++ b/app/views/users/show.html.erb @@ -28,33 +28,33 @@ <% effective_profile = raw(sanitize(@user.profile&.strip || '', scrubber: scrubber)) %> <% if effective_profile.blank? %>

A quiet enigma. We don't know anything about <%= rtl_safe_username(@user) %> yet.

- <% elsif !user_signed_in? && !@user.community_user.privilege?('unrestricted') %> - <%= sanitize(effective_profile, attributes: %w()) %> - <% else %> + <% elsif !user_signed_in? && !@user.community_user.privilege?('unrestricted') %> + <%= sanitize(effective_profile, attributes: %w()) %> + <% else %> <%= effective_profile %> <% end %>
<% unless !user_signed_in? && !@user.community_user.privilege?('unrestricted') %> - <% if @user.valid_websites_for.size.positive? %> -
-

Extra fields

- - <% @user.valid_websites_for.each do |w| %> - - - - - <% end %> -
<%= w.label %> - <% if w.url[0,4] == 'http' %> - <%= link_to w.url, w.url, rel: 'nofollow' %> - <% else %> - <%= w.url %> - <% end %> -
-
- <% end %> + <% if @user.valid_websites_for.size.positive? %> +
+

Extra fields

+ + <% @user.valid_websites_for.each do |w| %> + + + + + <% end %> +
<%= w.label %> + <% if w.url[0,4] == 'http' %> + <%= link_to w.url, w.url, rel: 'nofollow' %> + <% else %> + <%= w.url %> + <% end %> +
+
+ <% end %> <% end %>

@@ -164,7 +164,7 @@ <% if current_user&.id == @user.id || current_user&.is_moderator %> - +
User since <%= @user.created_at %>User since <%= @user.created_at %>
<% end %> @@ -174,7 +174,7 @@

<% @abilities.each do |a| %> - <% if @user.privilege?(a.internal_id) %> + <% if @user.privilege?(a.internal_id) %>
From afacd506222bda83551a80c0a6880d9fa1b54ae4 Mon Sep 17 00:00:00 2001 From: Oleg Valter Date: Sat, 1 Feb 2025 20:37:38 +0300 Subject: [PATCH 45/55] 'present? or nil' is exactly what 'presence' does --- app/controllers/users_controller.rb | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 34e7e7e29..6a579b55e 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -359,10 +359,7 @@ def cleaned_profile_websites(profile_params) sites = profile_params[:user_websites_attributes] sites.transform_values do |w| - w.merge({ - label: w[:label].present? ? w[:label] : nil, - url: w[:url].present? ? w[:url] : nil - }) + w.merge({ label: w[:label].presence, url: w[:url].presence }) end end From ca1d60a441b9f5cde539d3576475e92c52474ecd Mon Sep 17 00:00:00 2001 From: Oleg Valter Date: Sat, 1 Feb 2025 21:12:25 +0300 Subject: [PATCH 46/55] test for fixing the pipeline --- .circleci/config.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 3df3f5de9..c03e4c997 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -83,6 +83,8 @@ jobs: - run: name: Install packages command: | + wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | sudo tee /etc/apt/trusted.gpg.d/google.asc >/dev/null + sudo apt-get update sudo apt-get --allow-releaseinfo-change -qq update sudo apt-get -y install git libmariadb-dev libmagickwand-dev - checkout From c28a25394084fc63b48ca6091ee8a5def846ef7e Mon Sep 17 00:00:00 2001 From: Oleg Valter Date: Sat, 1 Feb 2025 21:19:22 +0300 Subject: [PATCH 47/55] Revert "test for fixing the pipeline" This reverts commit ca1d60a441b9f5cde539d3576475e92c52474ecd. --- .circleci/config.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index c03e4c997..3df3f5de9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -83,8 +83,6 @@ jobs: - run: name: Install packages command: | - wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | sudo tee /etc/apt/trusted.gpg.d/google.asc >/dev/null - sudo apt-get update sudo apt-get --allow-releaseinfo-change -qq update sudo apt-get -y install git libmariadb-dev libmagickwand-dev - checkout From 23c1a021a0c79aecf3d00cfc57dd08a363064d87 Mon Sep 17 00:00:00 2001 From: Oleg Valter Date: Sun, 2 Feb 2025 00:10:15 +0300 Subject: [PATCH 48/55] removed unnecessary comments from user websites maintenance tasks & empty 'count' --- app/tasks/maintenance/initialize_user_websites_task.rb | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/app/tasks/maintenance/initialize_user_websites_task.rb b/app/tasks/maintenance/initialize_user_websites_task.rb index 0a0b578ef..6afc112a5 100644 --- a/app/tasks/maintenance/initialize_user_websites_task.rb +++ b/app/tasks/maintenance/initialize_user_websites_task.rb @@ -3,15 +3,10 @@ module Maintenance class InitializeUserWebsitesTask < MaintenanceTasks::Task def collection - # Collection to be iterated over - # Must be Active Record Relation or Array User.all end def process(user) - # The work to be done in a single iteration of the task. - # This should be idempotent, as the same element may be processed more - # than once if the task is interrupted and resumed. unless user.user_websites.where(user_id: user.id, position: 1).size.positive? if user.website.present? UserWebsite.create!(user_id: user.id, position: 1, label: 'website', url: user.website) @@ -32,10 +27,5 @@ def process(user) UserWebsite.create!(user_id: user.id, position: 3) end end - - def count - # Optionally, define the number of rows that will be iterated over - # This is used to track the task's progress - end end end From 704feaaed74837047e26b3c3cb668827cb494355 Mon Sep 17 00:00:00 2001 From: Oleg Valter Date: Sun, 2 Feb 2025 00:18:45 +0300 Subject: [PATCH 49/55] user_id is implicit when accessing user.user_websites --- app/tasks/maintenance/initialize_user_websites_task.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/tasks/maintenance/initialize_user_websites_task.rb b/app/tasks/maintenance/initialize_user_websites_task.rb index 6afc112a5..6d84ac982 100644 --- a/app/tasks/maintenance/initialize_user_websites_task.rb +++ b/app/tasks/maintenance/initialize_user_websites_task.rb @@ -7,14 +7,15 @@ def collection end def process(user) - unless user.user_websites.where(user_id: user.id, position: 1).size.positive? + unless user.user_websites.where(position: 1).size.positive? if user.website.present? UserWebsite.create!(user_id: user.id, position: 1, label: 'website', url: user.website) else UserWebsite.create!(user_id: user.id, position: 1) end end - unless user.user_websites.where(user_id: user.id, position: 2).size.positive? + + unless user.user_websites.where(position: 2).size.positive? if user.twitter.present? UserWebsite.create!(user_id: user.id, position: 2, label: 'Twitter', url: "https://twitter.com/#{user.twitter}") @@ -22,8 +23,9 @@ def process(user) UserWebsite.create!(user_id: user.id, position: 2) end end + # This check *should* be superfluous, but just in case... - unless user.user_websites.where(user_id: user.id, position: 3).size.positive? + unless user.user_websites.where(position: 3).size.positive? UserWebsite.create!(user_id: user.id, position: 3) end end From a152b4a21250743933a3eabe6379467b83221b18 Mon Sep 17 00:00:00 2001 From: Monica Cellio Date: Sat, 1 Feb 2025 22:37:53 -0500 Subject: [PATCH 50/55] update user controller test to remove website/twitter, add discord --- test/controllers/users_controller_test.rb | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/test/controllers/users_controller_test.rb b/test/controllers/users_controller_test.rb index 8d957f100..345e79b5c 100644 --- a/test/controllers/users_controller_test.rb +++ b/test/controllers/users_controller_test.rb @@ -113,13 +113,12 @@ class UsersControllerTest < ActionController::TestCase assert_response 200 end - test 'should update profile text' do + test 'should update profile text and discord name' do sign_in users(:standard_user) patch :update_profile, params: { user: { profile_markdown: 'ABCDEF GHIJKL', - website: 'https://example.com/user', - twitter: 'standard_user' + discord: 'example_user#1234' } } assert_response 302 @@ -127,7 +126,7 @@ class UsersControllerTest < ActionController::TestCase assert_not_nil assigns(:user) assert_equal users(:standard_user).id, assigns(:user).id assert_not_nil assigns(:user).profile - assert_equal 'standard_user', assigns(:user).twitter + assert_equal 'example_user#1234', assigns(:user).discord end test 'should get full posts list for a user' do From d76638006bb4156cf290032c0a80a4710df3db13 Mon Sep 17 00:00:00 2001 From: Oleg Valter Date: Sun, 2 Feb 2025 23:22:31 +0300 Subject: [PATCH 51/55] where().size.positive? can be shortened to exists?() --- app/tasks/maintenance/initialize_user_websites_task.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/tasks/maintenance/initialize_user_websites_task.rb b/app/tasks/maintenance/initialize_user_websites_task.rb index 6d84ac982..80a91b948 100644 --- a/app/tasks/maintenance/initialize_user_websites_task.rb +++ b/app/tasks/maintenance/initialize_user_websites_task.rb @@ -7,7 +7,7 @@ def collection end def process(user) - unless user.user_websites.where(position: 1).size.positive? + unless user.user_websites.exists?(position: 1) if user.website.present? UserWebsite.create!(user_id: user.id, position: 1, label: 'website', url: user.website) else @@ -15,7 +15,7 @@ def process(user) end end - unless user.user_websites.where(position: 2).size.positive? + unless user.user_websites.exists?(position: 2) if user.twitter.present? UserWebsite.create!(user_id: user.id, position: 2, label: 'Twitter', url: "https://twitter.com/#{user.twitter}") @@ -25,7 +25,7 @@ def process(user) end # This check *should* be superfluous, but just in case... - unless user.user_websites.where(position: 3).size.positive? + unless user.user_websites.exists?(position: 3) UserWebsite.create!(user_id: user.id, position: 3) end end From 2d52f7e2baf6e4bdf71f9e324a8e9bcd46c1afb4 Mon Sep 17 00:00:00 2001 From: Oleg Valter Date: Mon, 3 Feb 2025 00:50:32 +0300 Subject: [PATCH 52/55] UsersController#update_profile should allow partial updates --- app/controllers/users_controller.rb | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 6a579b55e..1b4aec6f0 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -388,8 +388,14 @@ def update_profile end end - profile_rendered = helpers.post_markdown(:user, :profile_markdown) - if @user.update(profile_params.merge(profile: profile_rendered)) + if params[:user][:profile_markdown].present? + profile_rendered = helpers.post_markdown(:user, :profile_markdown) + profile_params = profile_params.merge(profile: profile_rendered) + end + + status = @user.update(profile_params) + + if status flash[:success] = 'Your profile details were updated.' redirect_to user_path(current_user) else From 720ee8b282e65dd46da9727d695244f8cdfe8e4f Mon Sep 17 00:00:00 2001 From: Oleg Valter Date: Mon, 3 Feb 2025 02:41:03 +0300 Subject: [PATCH 53/55] split the overloaded update_profile test --- test/controllers/users_controller_test.rb | 25 +++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/test/controllers/users_controller_test.rb b/test/controllers/users_controller_test.rb index 345e79b5c..7ac6c7567 100644 --- a/test/controllers/users_controller_test.rb +++ b/test/controllers/users_controller_test.rb @@ -113,19 +113,28 @@ class UsersControllerTest < ActionController::TestCase assert_response 200 end - test 'should update profile text and discord name' do + test 'should redirect & show success notice on profile update' do sign_in users(:standard_user) - patch :update_profile, params: { - user: { - profile_markdown: 'ABCDEF GHIJKL', - discord: 'example_user#1234' - } - } + patch :update_profile, params: { user: { username: 'std' } } assert_response 302 assert_not_nil flash[:success] assert_not_nil assigns(:user) assert_equal users(:standard_user).id, assigns(:user).id - assert_not_nil assigns(:user).profile + end + + test 'should update profile text' do + sign_in users(:standard_user) + patch :update_profile, params: { + user: { profile_markdown: 'ABCDEF GHIJKL' } + } + assert_equal assigns(:user).profile.strip, '

ABCDEF GHIJKL

' + end + + test 'should update user discord link' do + sign_in users(:standard_user) + patch :update_profile, params: { + user: { discord: 'example_user#1234' } + } assert_equal 'example_user#1234', assigns(:user).discord end From 4b72f57c8fddd9dd67efd1168446813e23233b12 Mon Sep 17 00:00:00 2001 From: Monica Cellio Date: Mon, 3 Feb 2025 23:02:24 -0500 Subject: [PATCH 54/55] add editor test for user_websites --- test/controllers/users_controller_test.rb | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/controllers/users_controller_test.rb b/test/controllers/users_controller_test.rb index 7ac6c7567..1363bcdd1 100644 --- a/test/controllers/users_controller_test.rb +++ b/test/controllers/users_controller_test.rb @@ -130,6 +130,19 @@ class UsersControllerTest < ActionController::TestCase assert_equal assigns(:user).profile.strip, '

ABCDEF GHIJKL

' end + test 'should update websites' do + sign_in users(:standard_user) + patch :update_profile, params: { + user: { user_websites_attributes: { + '0': { label: 'web', url: 'example.com' } + } + } + } + assert_not_nil assigns(:user).user_websites + assert_equal 'web', assigns(:user).user_websites.first.label + assert_equal 'example.com', assigns(:user).user_websites.first.url + end + test 'should update user discord link' do sign_in users(:standard_user) patch :update_profile, params: { From 80db9e962ec0bafa63dfba1978cd155dc5ffc41a Mon Sep 17 00:00:00 2001 From: Monica Cellio Date: Mon, 3 Feb 2025 23:07:16 -0500 Subject: [PATCH 55/55] rubocop --- test/controllers/users_controller_test.rb | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/test/controllers/users_controller_test.rb b/test/controllers/users_controller_test.rb index 1363bcdd1..0aa5d0863 100644 --- a/test/controllers/users_controller_test.rb +++ b/test/controllers/users_controller_test.rb @@ -133,16 +133,15 @@ class UsersControllerTest < ActionController::TestCase test 'should update websites' do sign_in users(:standard_user) patch :update_profile, params: { - user: { user_websites_attributes: { - '0': { label: 'web', url: 'example.com' } - } - } - } + user: { user_websites_attributes: { + '0': { label: 'web', url: 'example.com' } + } } + } assert_not_nil assigns(:user).user_websites assert_equal 'web', assigns(:user).user_websites.first.label assert_equal 'example.com', assigns(:user).user_websites.first.url end - + test 'should update user discord link' do sign_in users(:standard_user) patch :update_profile, params: {