• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

benwbrum / fromthepage / 18010022425

25 Sep 2025 02:01PM UTC coverage: 64.658% (+0.04%) from 64.619%
18010022425

push

github

web-flow
4734 - Article rename race condition fix (#4735)

* 4734 - Article rename race condition fix

* 4734 - Consider all names 1.hour.ago

* rubocop

---------

Co-authored-by: Ben W. Brumfield <benwbrum@gmail.com>

1812 of 3327 branches covered (54.46%)

Branch coverage included in aggregate %.

22 of 22 new or added lines in 6 files covered. (100.0%)

66 existing lines in 1 file now uncovered.

8007 of 11859 relevant lines covered (67.52%)

105.83 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

54.95
/app/controllers/admin_controller.rb
1
class AdminController < ApplicationController
1✔
2
  include ErrorHelper
1✔
3

4
  before_action :authorized?
1✔
5

6
  PAGES_PER_SCREEN = 20
1✔
7

8
  def authorized?
1✔
9
    unless user_signed_in? && current_user.admin
53✔
10
      redirect_to main_app.dashboard_path
3✔
11
    end
12
  end
13

14
  def index
1✔
15
    @users = User.all
17✔
16
    @owners = User.where(owner: true)
17✔
17

18
    transcription_deeds = Deed.where(deed_type: DeedType.transcriptions_or_corrections_no_edits)
17✔
19
    contributor_deeds = Deed.where(deed_type: DeedType.contributor_types)
17✔
20

21
    # Count stats for dashboard
22
    @pages_per_hour         = transcription_deeds.where('created_at between ? and ?', Time.now - 1.hour, Time.now).count
17✔
23
    @contributions_per_hour = contributor_deeds.where('created_at between ? and ?', Time.now - 1.hour, Time.now).count
17✔
24
    @collections_count      = Collection.all.count
17✔
25
    @articles_count         = Article.all.count
17✔
26
    @works_count            = Work.all.count
17✔
27
    @ia_works_count         = IaWork.all.count
17✔
28
    @pages_count            = Page.all.count
17✔
29
    @transcribed_count      = Page.where.not(status: :new).count
17✔
30
    @notes_count            = Note.all.count
17✔
31
    @users_count            = User.all.count
17✔
32
    @owners_count           = User.where(owner: true).count
17✔
33

34
    @transcription_counts = {}
17✔
35
    @contribution_counts = {}
17✔
36
    @activity_project_counts = {}
17✔
37
    @unique_contributor_counts = {}
17✔
38
    @hours_spent_counts = {}
17✔
39
    @week_intervals=[ 1, 2, 4, 12, 26, 52, 104, 156, 208 ]
17✔
40
    @week_intervals.each do |weeks_ago|
17✔
41
      start_date = Date.yesterday - weeks_ago.weeks
153✔
42
      end_date = start_date + 1.week
153✔
43
      @transcription_counts[weeks_ago] = transcription_deeds.where('created_at between ? and ?', start_date, end_date).count
153✔
44
      @contribution_counts[weeks_ago] = contributor_deeds.where('created_at between ? and ?', start_date, end_date).count
153✔
45
      @activity_project_counts[weeks_ago] = contributor_deeds.where('created_at between ? and ?', start_date, end_date).distinct.count(:collection_id)
153✔
46
      @unique_contributor_counts[weeks_ago] = contributor_deeds.where('created_at between ? and ?', start_date, end_date).distinct.count(:user_id)
153✔
47
      minutes_spent = AhoyActivitySummary.where('date between ? and ?', start_date, end_date).sum(:minutes)
153✔
48
      @hours_spent_counts[weeks_ago] = minutes_spent.to_f / 60
153✔
49
    end
50

51
    @version = ActiveRecord::Migrator.current_version
17✔
52

53

54
=begin
55
    sql_online =
56
      'SELECT count(DISTINCT user_id) count '+
57
      'FROM interactions '+
58
      'WHERE created_on > date_sub(UTC_TIMESTAMP(), INTERVAL 20 MINUTE) '+
59
      'AND user_id IS NOT NULL'
60

61
    @users_count = Interaction.connection.select_value(sql_online)
62
=end
63
  end
64

65
  def user_list
1✔
66
    if params[:search]
10✔
67
      @users = User.search(params[:search]).order(created_at: :desc).paginate page: params[:page], per_page: PAGES_PER_SCREEN
3✔
68
    else
7✔
69
      @users = User.order(created_at: :desc).paginate page: params[:page], per_page: PAGES_PER_SCREEN
7✔
70
    end
71
  end
72

73
  def edit_user
1✔
74
  end
75

76
  def user_visits
1✔
UNCOV
77
    @visits = @user.visits.order(started_at: :desc).paginate page: params[:page], per_page: PAGES_PER_SCREEN
×
78
  end
79

80
  def visit_actions
1✔
UNCOV
81
    @visit = Visit.find(params[:visit_id])
×
UNCOV
82
    @actions = @visit.ahoy_events.order(time: :asc).paginate page: params[:page], per_page: 500
×
83
  end
84

85
  def visit_deeds
1✔
UNCOV
86
    @visit = Visit.find(params[:visit_id])
×
87
  end
88

89
  def update_user
1✔
90
    owner = @user.owner
1✔
91
    if @user.update(user_params)
1✔
92
      if owner == false && @user.owner == true
1!
93
        if SMTP_ENABLED
1!
94
          begin
1✔
95
            text = PageBlock.find_by(view: 'new_owner').html
1✔
96
            UserMailer.new_owner(@user, text).deliver!
1✔
97
          rescue StandardError => e
UNCOV
98
            log_smtp_error(e, current_user)
×
99
          end
100
        end
101
      end
102

103
      flash[:notice] = t('.user_profile_updated')
1✔
104
      if owner
1!
UNCOV
105
        ajax_redirect_to action: 'owner_list'
×
106
      else
1✔
107
        ajax_redirect_to action: 'user_list'
1✔
108
      end
109

110
    else
×
UNCOV
111
      render action: 'edit_user'
×
112
    end
113
  end
114

115
  def delete_user
1✔
116
    @user.soft_delete
1✔
117
    # @user.destroy
118
    flash[:notice] = t('.user_profile_deleted')
1✔
119
    redirect_to action: 'user_list'
1✔
120
  end
121

122
  def expunge_confirmation
1✔
123
  end
124

125
  def expunge_user
1✔
126
    @user.expunge
×
UNCOV
127
    flash[:notice] = t('.user_expunged', user: @user.display_name)
×
128
    if params[:flag_id]
×
UNCOV
129
      ajax_redirect_to action: 'revert_flag', flag_id: params[:flag_id]
×
130
    else
×
UNCOV
131
      ajax_redirect_to action: 'user_list'  # what if we came from the flag list?  TODO
×
132
    end
133
  end
134

135

136
  def flag_list
1✔
137
    @flags = Flag.where(status: Flag::Status::UNCONFIRMED).order(content_at: :desc).paginate page: params[:page], per_page: PAGES_PER_SCREEN
2✔
138
  end
139

140
  def revert_flag
1✔
141
    # find the flag
UNCOV
142
    flag = Flag.find(params[:flag_id])
×
143
    # revert the content
UNCOV
144
    flag.revert_content!
×
145
    # redirect to flag list at the appropriate page
UNCOV
146
    redirect_to action: 'flag_list', page: params[:page]
×
147
  end
148

149
  def ok_flag
1✔
150
    # find the flag
UNCOV
151
    flag = Flag.find(params[:flag_id])
×
152
    # revert the content
UNCOV
153
    flag.mark_ok!
×
154
    # redirect to flag list at the appropriate page
UNCOV
155
    redirect_to action: 'flag_list', page: params[:page]
×
156
  end
157

158
  def ok_user
1✔
UNCOV
159
    flag = Flag.find(params[:flag_id])
×
UNCOV
160
    flag.ok_user
×
UNCOV
161
    redirect_to action: 'flag_list', page: params[:page]
×
162
  end
163

164
  def tail_logfile
1✔
165
    @lines = params[:lines].to_i
1✔
166
    if @lines == 0
1!
167
      @lines=5000
1✔
168
    end
169
    development_logfile = "#{Rails.root}/log/development.log"
1✔
170
    production_logfile = "#{Rails.root}/log/production.log"
1✔
171
    @dev_tail = `tail -#{@lines} #{development_logfile}`
1✔
172
    @prod_tail = `tail -#{@lines} #{production_logfile}`
1✔
173
  end
174

175
  def autoflag
1✔
176
    flash[:notice] = t('.flag_message')
×
177

UNCOV
178
    cmd = 'rake fromthepage:flag_abuse &'
×
179
    logger.info(cmd)
×
UNCOV
180
    system(cmd)
×
181

UNCOV
182
    redirect_to action: 'flag_list', page: params[:page]
×
183
  end
184

185
  def uploads
1✔
186
    @document_uploads = DocumentUpload.order('id DESC').paginate page: params[:page], per_page: PAGES_PER_SCREEN
1✔
187
  end
188

189
  def delete_upload
1✔
190
    @document_upload = DocumentUpload.find(params[:id])
1✔
191
    @document_upload.destroy
1✔
192
    flash[:notice] = t('.uploaded_document_deleted')
1✔
193
    redirect_to action: 'uploads'
1✔
194
  end
195

196
  def process_upload
1✔
197
    @document_upload = DocumentUpload.find(params[:id])
×
UNCOV
198
    @document_upload.submit_process
×
UNCOV
199
    flash[:notice] = t('.uploaded_document_queued')
×
UNCOV
200
    redirect_to action: 'uploads'
×
201
  end
202

203
  def view_processing_log
1✔
UNCOV
204
    @document_upload = DocumentUpload.find(params[:id])
×
UNCOV
205
    render content_type: 'text/plain', plain: `cat #{@document_upload.log_file}`, layout: false
×
206
  end
207

208
  def collection_list
1✔
UNCOV
209
    @collections = Collection.order(:title)
×
210
  end
211

212
  def work_list
1✔
UNCOV
213
    @collections = Collection.order(:title).paginate(page: params[:page], per_page: 10)
×
UNCOV
214
    @works = Work.order(:title)
×
215
  end
216

217
  def article_list
1✔
UNCOV
218
    @collections = Collection.all
×
219
  end
220

221
  def page_list
1✔
UNCOV
222
    @pages = Page.order(:title).paginate(page: params[:page], per_page: PAGES_PER_SCREEN)
×
223
  end
224

225
  def settings
1✔
226
    @email_text = PageBlock.find_by(view: 'new_owner').html
3✔
227
    @flag_denylist = PageBlock.find_by(view: 'flag_denylist').html
3✔
228
    @email_denylist = (PageBlock.where(view: 'email_denylist').first ? PageBlock.where(view: 'email_denylist').first.html : '')
3✔
229
  end
230

231
  def update
1✔
232
    # need the original email text to update
233
    block = PageBlock.find_by(view: 'new_owner')
1✔
234
    if params[:admin][:welcome_text] != block.html
1!
235
      block.html = params[:admin][:welcome_text]
1✔
236
      block.save!
1✔
237
    end
238

239
    block = PageBlock.find_by(view: 'flag_denylist')
1✔
240
    if params[:admin][:flag_denylist] != block.html
1!
241
      block.html = params[:admin][:flag_denylist]
1✔
242
      block.save!
1✔
243
    end
244

245
    block = PageBlock.where(view: 'email_denylist').first || PageBlock.new(view: 'email_denylist')
1✔
246
    if params[:admin][:email_denylist] != block.html
1!
247
      block.html = params[:admin][:email_denylist]
1✔
248
      block.save!
1✔
249
    end
250

251

252
    flash[:notice] = t('.admin_settings_updated')
1✔
253

254
    redirect_to action: 'settings'
1✔
255
  end
256

257
  def owner_list
1✔
258
    @collections = Collection.all
7✔
259
    # @owners = User.where(owner: true).order(paid_date: :desc).paginate(:page => params[:page], :per_page => PAGES_PER_SCREEN)
260
    if params[:search]
7!
UNCOV
261
      @owners = User.search(params[:search]).where(owner: true).order(paid_date: :desc).paginate(page: params[:page], per_page: PAGES_PER_SCREEN)
✔
262
    elsif params[:sort]
7✔
263
      sort = params[:sort]
3✔
264
      dir = params[:dir].upcase
3✔
265
      @owners = User.where(owner: true).order("#{sort} #{dir}").paginate(page: params[:page], per_page: PAGES_PER_SCREEN)
3✔
266
    else
4✔
267
      @owners = User.where(owner: true).order(created_at: :desc).paginate(page: params[:page], per_page: PAGES_PER_SCREEN)
4✔
268
    end
269
  end
270

271
  def downgrade
1✔
272
    u = User.find(params[:user_id])
1✔
273
    u.downgrade
1✔
274
    redirect_back fallback_location: { action: 'user_list' }, notice: t('.user_downgraded_successfully')
1✔
275
  end
276

277
  def moderation
1✔
UNCOV
278
    @collections = Collection.where(messageboards_enabled: true)
×
279
  end
280

281
  def searches
1✔
UNCOV
282
    searches = case params[:filter]
×
283
    when 'nonowner'
×
UNCOV
284
                 SearchAttempt.where(owner: false)
×
285
    when 'findaproject'
×
UNCOV
286
                 SearchAttempt.where(search_type: 'findaproject')
×
287
    when 'collectionwork'
×
UNCOV
288
                 SearchAttempt.where.not(search_type: 'findaproject')
×
289
    when 'collection'
×
UNCOV
290
                 SearchAttempt.where(search_type: 'collection')
×
291
    when 'collection-title'
×
UNCOV
292
                 SearchAttempt.where(search_type: 'collection-title')
×
293
    when 'work'
×
UNCOV
294
                 SearchAttempt.where(search_type: 'work')
×
295
    else
×
296
                 SearchAttempt.all
×
297
    end
298

UNCOV
299
    @searches = searches.order('id DESC').paginate(page: params[:page], per_page: PAGES_PER_SCREEN)
×
300

301
    this_week = SearchAttempt.where('created_at > ?', 1.week.ago)
×
302

303
    unless this_week.empty?
×
304
      by_visit = this_week.joins(:visit).group('visits.id')
×
UNCOV
305
      total_days = 7.0
×
306
      total_searches = this_week.count.nonzero? || 1
×
307
      total_visits = by_visit.size.nonzero? || 1
×
308

309
      @find_a_project_searches_per_day = (this_week.where(search_type: 'findaproject').count / total_days).round(2)
×
UNCOV
310
      @collection_work_searches_per_day = (this_week.where.not(search_type: 'findaproject').count / total_days).round(2)
×
311

UNCOV
312
      @find_a_project_average_hits = (this_week.where(search_type: 'findaproject').average(:hits) || 0).to_f
×
313
                                                                                                       .round(2)
314
      @collection_work_average_hits = (this_week.where.not(search_type: 'findaproject').average(:hits) || 0).to_f
×
315
                                                                                                            .round(2)
316

317
      @clickthrough_rate = ((this_week.where('clicks > 0').count.to_f / total_searches) * 100).round(1)
×
318
      @clickthrough_rate_visit = ((by_visit.sum(:clicks).values.count(&:positive?).to_f / total_visits) * 100).round(1)
×
319

UNCOV
320
      @contribution_rate = ((this_week.where('contributions > 0').count.to_f / total_searches) * 100).round(1)
×
UNCOV
321
      @contribution_rate_visit = ((by_visit.sum(:contributions).values.count(&:positive?).to_f / total_visits) * 100)
×
322
                                 .round(1)
323
    end
324

325
    start_d = params[:start_date]
×
326
    end_d = params[:end_date]
×
327
    max_date = 1.day.ago.end_of_day
×
UNCOV
328
    @start_date = start_d&.to_datetime&.beginning_of_day || 1.week.ago.beginning_of_day
×
UNCOV
329
    @end_date = end_d&.to_datetime&.end_of_day || max_date
×
UNCOV
330
    @end_date = max_date if max_date < @end_date
×
331
  end
332

333
  def delete_tag
1✔
334
    tag = Tag.find params[:tag_id]
×
UNCOV
335
    tag.destroy
×
336

UNCOV
337
    redirect_to action: 'tag_list'
×
338
  end
339

340
  def show_tag
1✔
341
    @tag = Tag.find params[:tag_id]
×
342
    @collections = @tag.collections.order(:title)
×
343
    @possible_duplicates = []
×
UNCOV
344
    clean_text = @tag.ai_text.gsub(/^\W*/, '').gsub(/\W*$/, '')
×
UNCOV
345
    Tag.where("regexp_replace(upper(ai_text), '[^A-Z0-9]', '') like regexp_replace(upper('%#{clean_text}%'), '[^A-Z0-9]', '')").each do |t|
×
UNCOV
346
      @possible_duplicates << t unless t == @tag
×
347
    end
348
  end
349

350
  def edit_tag
1✔
UNCOV
351
    @tag = Tag.find params[:tag_id]
×
352
  end
353

354
  def update_tag
1✔
UNCOV
355
    @tag = Tag.find params[:tag_id]
×
UNCOV
356
    @tag.update(tag_params)
×
UNCOV
357
    redirect_to action: 'tag_list'
×
358
  end
359

360
  def merge_tag
1✔
361
    target_tag = Tag.find params[:target_tag_id]
×
362
    source_tag = Tag.find params[:source_tag_id]
×
363

364
    target_tag.collections << source_tag.collections
×
UNCOV
365
    target_tag.save
×
366

UNCOV
367
    source_tag.destroy!
×
368

UNCOV
369
    flash[:notice] = t('.tags_merged', target_tag: target_tag.ai_text, source_tag: source_tag.ai_text)
×
370

UNCOV
371
    redirect_to admin_tags_show_path(target_tag.id)
×
372
  end
373

374
  def tag_list
1✔
UNCOV
375
    @tag_to_count_map = Tag.joins(:collections_tags).group(:id).count
×
UNCOV
376
    @tags = Tag.all.order(:canonical, :ai_text)
×
377
  end
378

379
  private
1✔
380
  def tag_params
1✔
UNCOV
381
    params.require(:tag).permit(:ai_text, :canonical, :tag_type)
×
382
  end
383

384

385
  def user_params
1✔
386
    params.require(:user).permit(:real_name, :login, :email, :account_type, :start_date, :paid_date, :user, :owner)
1✔
387
  end
388
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc