Skip to content

Commit 04c3643

Browse files
authored
Merge pull request #1780 from codidact/art/1772/user-deletions
Fix user deletions
2 parents 2ced927 + 82e9caf commit 04c3643

6 files changed

Lines changed: 84 additions & 66 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,4 @@ node_modules
7575

7676
# YARD data
7777
.yardoc
78+
doc/

app/controllers/users_controller.rb

Lines changed: 0 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -310,39 +310,6 @@ def mod_privileges
310310
@abilities = Ability.all
311311
end
312312

313-
def destroy
314-
if @user.votes.count > 100
315-
render json: { status: 'failed', message: 'Users with more than 100 votes cannot be destroyed.' },
316-
status: :unprocessable_entity
317-
return
318-
end
319-
320-
if @user.at_least_moderator?
321-
render json: { status: 'failed', message: 'Admins and moderators cannot be destroyed.' },
322-
status: :unprocessable_entity
323-
return
324-
end
325-
326-
before = @user.attributes_print
327-
@user.block('user destroyed')
328-
329-
if @user.destroy
330-
Post.unscoped.by(@user).update_all(user_id: SiteSetting['SoftDeleteTransferUser'],
331-
deleted: true, deleted_at: DateTime.now,
332-
deleted_by_id: SiteSetting['SoftDeleteTransferUser'])
333-
Comment.unscoped.by(@user).update_all(user_id: SiteSetting['SoftDeleteTransferUser'],
334-
deleted: true)
335-
Flag.unscoped.by(@user).update_all(user_id: SiteSetting['SoftDeleteTransferUser'])
336-
SuggestedEdit.unscoped.by(@user).update_all(user_id: SiteSetting['SoftDeleteTransferUser'])
337-
AuditLog.moderator_audit(event_type: 'user_destroy', user: current_user, comment: "<<User #{before}>>")
338-
render json: { status: 'success' }
339-
else
340-
render json: { status: 'failed',
341-
message: 'Failed to destroy user; ask a dev.' },
342-
status: :internal_server_error
343-
end
344-
end
345-
346313
def soft_delete
347314
if @user.at_least_moderator?
348315
render json: { status: 'failed', message: 'Admins and moderators cannot be deleted.' },

app/models/application_record.rb

Lines changed: 83 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@ def match_search(term, **cols)
2020
end
2121

2222
def attributes_print(join: ', ')
23-
attributes.map { |k, v| "#{k}: #{v.inspect}" }.join(join)
23+
attributes.map do |k, v|
24+
val = v.inspect.length > 100 ? "#{v.inspect[0, 100]}..." : v.inspect
25+
"#{k}: #{val}"
26+
end.join(join)
2427
end
2528

2629
def self.sanitize_for_search(term, **cols)
@@ -55,16 +58,13 @@ def self.with_lax_group_rules
5558
end
5659
end
5760

61+
# rubocop:disable Metrics/MethodLength
5862
def self.useful_err_msg
5963
[
6064
'The inverted database guide has found an insurmountable problem. Please poke it with a ' \
6165
'paperclip before anyone finds out.',
62-
'The modular cable meter has found a problem. You need to kick your IT technician in the ' \
63-
'shins immediately.',
6466
'The integral output port has found a problem. Please take it back to the shop and take ' \
6567
'the rest of the day off.',
66-
'The integral expansion converter has encountered a terminal error. You must take legal ' \
67-
'advice urgently.',
6868
'Congratulations. You have reached the end of the internet.',
6969
'The Spanish Inquisition raised an unexpected error. Cannot continue without comfy-chair-interrogation.',
7070
'The server halted in an after-you loop.',
@@ -82,16 +82,91 @@ def self.useful_err_msg
8282
'recover using Hawking radiation.',
8383
'Operations are on pause while we attempt to recapture the codidactyl. Please hold.',
8484
'The data center is on fire. Please hold while we activate fire suppression systems.',
85-
'The reciprocal controller flag is set incorrectly. Please stand on your head and rickroll yourself to fix this.'
85+
'The reciprocal controller flag is set incorrectly. Please stand on your head and rickroll yourself to fix this.',
86+
'The quantum cache has become uncertain. Please observe it again after making a cup of tea.',
87+
'The recursive handshake timed out while waiting for itself to finish. Try waving at yourself in a mirror ' \
88+
'until it responds.',
89+
'The left-handed API key doesn\'t fit this right-handed endpoint. Please rotate it 180 degrees while humming ' \
90+
'the API theme song.',
91+
'Your session was garbage-collected for leaving crumbs in the cookie jar. Kindly sweep the cookies into a ' \
92+
'jar-shaped folder.',
93+
'Your request fell through a race condition and finished second. Present it with a silver medal and try again.',
94+
'The DNS insists today it stands for \'Did Not Start\'. Please send it motivational cat posters.',
95+
'The OAuth token is shy. Whisper your scopes softly and offer it a comfort blanket.',
96+
'The distributed consensus reached a polite disagreement. Break the tie with a round of rock-paper-scissors.',
97+
'The feature flag tripped over a rug and toggled itself off. Please help it up and brush off the dust.',
98+
'The uptime counter overflowed into nap time. Wake it gently with a teaspoon of coffee.',
99+
'The cron job saw its shadow and scheduled six more weeks of maintenance. Offer it a flashlight and try again.',
100+
'The heisenbug vanished when we opened the logs. Please look away and try again.',
101+
'The Kubernetes pod drifted out to sea. Dispatch a small boat with sandwiches.',
102+
'The retry policy is composing a strongly worded letter to the server. Offer a thesaurus for better results.',
103+
'The message queue joined a queue. Estimated wait time: variable. Send it a good book.',
104+
'The microservice macroed itself and forgot how to be small. Remind it of its humble beginnings.',
105+
'The database index is on a gap year. Send postcards to encourage its return.',
106+
'The deploy canary learned to sing and flew away. Try luring it back with birdseed.',
107+
'Single sign-on stepped out to find itself. Back soon. Leave a trail of breadcrumbs.',
108+
'The entropy pool is empty. Kindly imagine wiggling your mouse.',
109+
'The RAID array eloped with a NAS. Send congratulations and request a postcard.',
110+
'The config file contains more opinions than agreement. Appoint a moderator.',
111+
'The null pointer was so moved it pointed somewhere. Politely direct it back.',
112+
'The stack is overflowing with feelings. Fetching tissues. Offer emotional support.',
113+
'The exception handler forgot its mitts. Knit it a pair.',
114+
'The SSL certificate expired in dog years. Bake it a birthday cake.',
115+
'The WebSocket is sulking in silent mode. Send a heartfelt apology.',
116+
'The API gateway misplaced the keys to the database. Check under the welcome mat.',
117+
'The firmware updated its relationship status to \'complicated\'. Send couples counselling pamphlets.',
118+
'The load balancer is weighing its options. Provide a set of calibrated scales.',
119+
'The build pipeline tripped over a merge conflict and needs a plaster. Apply gentle pressure.',
120+
'The logger switched to interpretive dance. Learn the choreography to decode the message.',
121+
'The search index refuses to be found. Host a welcome‑back party.',
122+
'The container misplaced its container. Check the lost‑and‑found container.',
123+
'The recursive acronym needs a breather before it expands again. Offer a paper bag.',
124+
'The data lake froze over. Please try ice skating.',
125+
'The fiber was delayed by a lorry attempting a three-point turn. Offer to navigate.',
126+
'The server popped out for a bacon butty. Back shortly. Fry an extra one for its return.',
127+
'The permissions matrix rotated 90 degrees and became modern art. Hang it in a gallery.',
128+
'Your cookie consent banner ate the cookies. Supply a healthy snack alternative.',
129+
'The scheduler double-booked Tuesday with next Tuesday. Buy it a new calendar.',
130+
'The GPU is busy appreciating gradients in a sunset. Offer it a camera.',
131+
'The CLI took everything literally and nothing personally. Send it idioms to process.',
132+
'The time server is early, the database is late, and reality is pending. Send them a group chat invite.',
133+
'Your session collided with a parallel universe and yielded a merge conflict. Try diplomatic talks.',
134+
'The health check caught a cold. Sending soup packets. Add a warm blanket.',
135+
'The sandbox discovered a cat and refuses to collapse the wavefunction. Provide a cardboard box.',
136+
'The debugger hit a breakpoint and started journaling. Offer coloured pens.',
137+
'The profiler is timing itself. Results may vary. Suggest it use a stopwatch.',
138+
'The error reporter experienced an error and needs a quiet moment. Light a scented candle.',
139+
'The audit log is practicing mindfulness and noticed your request drift by. Join it for meditation.',
140+
'The dependency tree is climbing itself. Mind the branches. Offer a ladder.',
141+
'The semaphore got stage fright and won’t signal. Provide an encouraging audience.',
142+
'The background job went foreground and now refuses to blend in. Offer camouflage.',
143+
'The clipboard copied the vibe but not the content. Try a mood reset.',
144+
'The keyboard buffer is full of unsent compliments. Press send on kindness.',
145+
'The ops runbook recommends tea, biscuits, and a gentle retry. Add a scone.',
146+
'The metrics exporter is measuring twice and cutting never. Provide scissors.',
147+
'The NTP sync is waiting for time to catch up with itself. Send it a postcard from the future.',
148+
'The transaction forgot its commit vows and wandered off. Hold a recommitment ceremony.',
149+
'The scheduler moved your slot to the next available epoch. Estimated delay: 73 years.',
150+
'The test suite passed locally and moved abroad. Mail it a travel adaptor.',
151+
'The feature toggle is indecisive and would like a coin flip. Supply a two‑headed coin.',
152+
'The namespace is arguing about space. Offer a tape measure.',
153+
'The host machine asked politely for a host gift. Bring flowers.',
154+
'The compliance engine needs a hug from a certified hugger. Book a professional cuddle.',
155+
'The API versioning strategy is writing its memoir. Offer an editor.'
86156
]
87157
end
158+
# rubocop:enable Metrics/MethodLength
88159
end
89160

90161
module UserSortable
91162
# Sort a collection according to a user selection, by mapping user selectable values to column names.
92163
# SQL injection safe.
93-
# @param term_opts Hash of search term options: { term: params[:sort], default: :created_at }
94-
# @param field_mappings Hash of user-selectable values to column names: { age: :created_at, rep: :threshold }
164+
# @param term_opts Hash of search term options
165+
# @param field_mappings Hash of user-selectable values to column names: +{ age: :created_at, rep: :threshold }+
166+
# @option term_opts :term [String] A user-provided search term to apply - usually from +params+, e.g. +params[:sort]+.
167+
# Should be one of the keys in +field_mappings+.
168+
# @option term_opts :default [Symbol] A column name to apply as the default sort ordering.
169+
# @return [ActiveRecord::Relation] A relation of the current type, with the sort ordering applied.
95170
def user_sort(term_opts, **field_mappings)
96171
default = term_opts[:default] || :created_at
97172
requested = term_opts[:term]

app/views/users/mod.html.erb

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,6 @@
2525
<p><strong>Take care!</strong> Actions in this section may not be reversible, and you will not be asked to confirm
2626
after initiating an action.</p>
2727
<div class="delete-actions">
28-
<%= link_to 'Destroy user', destroy_user_path(@user.id), remote: true,
29-
method: :delete, class: 'js-destroy-user button is-danger is-filled', role: 'button' %>
3028
<%= link_to 'Delete community profile', soft_delete_user_path(@user.id, type: 'profile'), remote: true,
3129
method: :delete, class: 'js-soft-delete button is-danger is-filled', role: 'button' %>
3230
<% if current_user.is_global_moderator || current_user.is_global_admin %>

config/routes.rb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,6 @@
9999
get 'flags', to: 'flags#queue', as: :flag_queue
100100
get 'flags/handled', to: 'flags#handled', as: :handled_flags
101101
get 'flags/escalated', to: 'flags#escalated_queue', as: :escalated_flags
102-
delete 'users/destroy/:id', to: 'users#destroy', as: :destroy_user
103102
get 'users/votes/:id', to: 'moderator#user_vote_summary', as: :mod_vote_summary
104103
post 'flags/:id/resolve', to: 'flags#resolve', as: :resolve_flag
105104
post 'flags/:id/escalate', to: 'flags#escalate', as: :escalate_flag

test/controllers/users_controller_test.rb

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -69,28 +69,6 @@ class UsersControllerTest < ActionController::TestCase
6969
assert_response(:not_found)
7070
end
7171

72-
test 'should destroy user' do
73-
sign_in users(:global_admin)
74-
delete :destroy, params: { id: users(:standard_user).id }
75-
assert_not_nil assigns(:user)
76-
assert_equal 'success', JSON.parse(response.body)['status']
77-
assert_response(:success)
78-
end
79-
80-
test 'should require authentication to destroy user' do
81-
sign_out :user
82-
delete :destroy, params: { id: users(:standard_user).id }
83-
assert_nil assigns(:user)
84-
assert_response(:not_found)
85-
end
86-
87-
test 'should require moderator status to destroy user' do
88-
sign_in users(:standard_user)
89-
delete :destroy, params: { id: users(:standard_user).id }
90-
assert_nil assigns(:user)
91-
assert_response(:not_found)
92-
end
93-
9472
test 'should soft-delete user' do
9573
sign_in users(:global_admin)
9674
delete :soft_delete, params: { id: users(:standard_user).id, type: 'user' }

0 commit comments

Comments
 (0)