Skip to content

Commit 9afd0f4

Browse files
committed
Admin interface with manual email addition
This adds a feature where admins can manually associate emails with users, without email confirmation - useful for helping people who wants to add old email addresses they are no longer able to access.
1 parent de7a40b commit 9afd0f4

25 files changed

Lines changed: 793 additions & 4 deletions

app/assets/stylesheets/application.postcss.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,4 @@
2222
@import "components/stats.css";
2323
@import "components/reports.css";
2424
@import "components/help.css";
25-
@import "components/admin-merge.css";
25+
@import "components/admin.css";

app/assets/stylesheets/components/admin-merge.css renamed to app/assets/stylesheets/components/admin.css

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
/* Admin Styles */
2+
3+
/* Admin Users Table */
4+
.settings-page .email-table-wrap {
5+
overflow-x: auto;
6+
}
7+
18
/* Admin Topic Merge Styles */
29

310
.admin-merge-container {

app/controllers/admin/base_controller.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
11
# frozen_string_literal: true
22

33
class Admin::BaseController < ApplicationController
4+
layout 'admin'
5+
46
before_action :require_admin
57

8+
helper_method :active_admin_section
9+
10+
def active_admin_section
11+
:dashboard
12+
end
13+
614
private
715

816
def require_admin
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# frozen_string_literal: true
2+
3+
class Admin::DashboardController < Admin::BaseController
4+
def active_admin_section
5+
:dashboard
6+
end
7+
8+
def show
9+
end
10+
end
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# frozen_string_literal: true
2+
3+
class Admin::EmailChangesController < Admin::BaseController
4+
def active_admin_section
5+
:email_changes
6+
end
7+
8+
def index
9+
@email_changes = AdminEmailChange.includes(performed_by: { person: :default_alias },
10+
target_user: { person: :default_alias })
11+
.order(created_at: :desc)
12+
.limit(100)
13+
end
14+
end

app/controllers/admin/imap_sync_states_controller.rb

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
# frozen_string_literal: true
22

3-
class Admin::ImapSyncStatesController < ApplicationController
3+
class Admin::ImapSyncStatesController < Admin::BaseController
4+
def active_admin_section
5+
:imap_sync
6+
end
7+
48
def index
59
@states = ImapSyncState.order(:mailbox_label)
610
respond_to do |format|

app/controllers/admin/topic_merges_controller.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
class Admin::TopicMergesController < Admin::BaseController
44
before_action :set_source_topic, only: [ :new, :preview, :create ]
5+
6+
def active_admin_section
7+
:topic_merges
8+
end
59
before_action :set_target_topic, only: [ :preview, :create ]
610

711
def index
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# frozen_string_literal: true
2+
3+
class Admin::UsersController < Admin::BaseController
4+
before_action :set_user, only: [:toggle_admin, :new_email, :confirm_email, :add_email]
5+
6+
def active_admin_section
7+
:users
8+
end
9+
10+
def index
11+
@users = User.active
12+
.includes(person: [:default_alias, :aliases])
13+
.order(created_at: :desc)
14+
.limit(params.fetch(:limit, 50).to_i)
15+
.offset(params.fetch(:offset, 0).to_i)
16+
end
17+
18+
def toggle_admin
19+
if @user == current_user
20+
return redirect_to admin_users_path, alert: "You cannot change your own admin status."
21+
end
22+
23+
@user.update!(admin: !@user.admin?)
24+
redirect_to admin_users_path, notice: "#{@user.username || 'User'} is #{@user.admin? ? 'now' : 'no longer'} an admin."
25+
end
26+
27+
def new_email
28+
end
29+
30+
def confirm_email
31+
@email = params[:email].to_s.strip.downcase
32+
if @email.blank?
33+
return redirect_to new_email_admin_user_path(@user), alert: "Email address is required."
34+
end
35+
36+
@existing_aliases = Alias.by_email(@email)
37+
@owned_by_other = @existing_aliases.where.not(user_id: [nil, @user.id]).exists?
38+
end
39+
40+
def add_email
41+
email = params[:email].to_s.strip.downcase
42+
if email.blank?
43+
return redirect_to admin_users_path, alert: "Email address is required."
44+
end
45+
46+
person = @user.person || Person.create!
47+
@user.update!(person_id: person.id) if @user.person_id.nil?
48+
49+
aliases = Alias.by_email(email)
50+
51+
if aliases.where.not(user_id: [nil, @user.id]).exists?
52+
return redirect_to admin_users_path, alert: "Email is linked to another account. Cannot associate."
53+
end
54+
55+
if aliases.exists?
56+
aliases.find_each do |al|
57+
person.attach_alias!(al, user: @user)
58+
al.update_columns(verified_at: Time.current)
59+
end
60+
61+
AdminEmailChange.create!(
62+
performed_by: current_user,
63+
target_user: @user,
64+
email: email,
65+
aliases_attached: aliases.count,
66+
created_new_alias: false
67+
)
68+
else
69+
al = Alias.create!(person: person, user: @user, name: email, email: email, verified_at: Time.current)
70+
person.update!(default_alias_id: al.id) if person.default_alias_id.nil?
71+
72+
AdminEmailChange.create!(
73+
performed_by: current_user,
74+
target_user: @user,
75+
email: email,
76+
aliases_attached: 0,
77+
created_new_alias: true
78+
)
79+
end
80+
81+
redirect_to admin_users_path, notice: "Email #{email} has been associated with #{@user.username || 'the user'}."
82+
end
83+
84+
private
85+
86+
def set_user
87+
@user = User.find(params[:id])
88+
end
89+
end

app/helpers/admin_helper.rb

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# frozen_string_literal: true
2+
3+
module AdminHelper
4+
def admin_nav_link_class(section)
5+
classes = ['settings-nav-link']
6+
classes << 'active' if active_admin_section == section
7+
classes.join(' ')
8+
end
9+
10+
def active_admin_section
11+
controller.respond_to?(:active_admin_section) ? controller.active_admin_section : :users
12+
end
13+
end

app/models/admin_email_change.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# frozen_string_literal: true
2+
3+
class AdminEmailChange < ApplicationRecord
4+
belongs_to :performed_by, class_name: "User"
5+
belongs_to :target_user, class_name: "User"
6+
7+
validates :email, presence: true
8+
end

0 commit comments

Comments
 (0)