Skip to content

Commit

Permalink
130 - support user with multiple emails
Browse files Browse the repository at this point in the history
  • Loading branch information
gamesover committed Dec 17, 2024
1 parent 62efefc commit 96670de
Show file tree
Hide file tree
Showing 37 changed files with 702 additions and 37 deletions.
12 changes: 12 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# EditorConfig is awesome: https://EditorConfig.org

# top-most EditorConfig file
root = true

[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
2 changes: 2 additions & 0 deletions .tool-versions
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ruby 3.3.5
nodejs 22.11.0
3 changes: 3 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ gem 'bugsnag'
gem 'createsend'
gem 'decent_exposure'
gem 'devise'
gem 'devise-multi_email'
gem 'icalendar'
gem 'inline_svg', '~> 1.9.0'
gem 'jbuilder'
Expand All @@ -32,6 +33,7 @@ group :development, :test do
# Call 'byebug' anywhere in the code to stop execution and get a
# debugger console
gem 'byebug'
gem 'faker'
gem 'pry-rails'
gem 'pry-byebug'
gem 'rspec-rails', '~> 6.1.0'
Expand All @@ -54,6 +56,7 @@ group :development do
gem 'guard', require: false
gem 'guard-rspec', require: false
gem 'letter_opener'
gem 'letter_opener_web', '~> 3.0'
end

group :test do
Expand Down
12 changes: 12 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ GEM
railties (>= 4.1.0)
responders
warden (~> 1.2.3)
devise-multi_email (3.0.1)
devise
diff-lcs (1.5.1)
docile (1.4.1)
drb (2.2.1)
Expand All @@ -140,6 +142,8 @@ GEM
factory_bot_rails (6.4.4)
factory_bot (~> 6.5)
railties (>= 5.0.0)
faker (3.5.1)
i18n (>= 1.8.11, < 2)
ffi (1.17.0)
ffi (1.17.0-aarch64-linux-gnu)
ffi (1.17.0-arm64-darwin)
Expand Down Expand Up @@ -214,6 +218,11 @@ GEM
addressable (~> 2.8)
letter_opener (1.10.0)
launchy (>= 2.2, < 4)
letter_opener_web (3.0.0)
actionmailer (>= 6.1)
letter_opener (~> 1.9)
railties (>= 6.1)
rexml
listen (3.9.0)
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
Expand Down Expand Up @@ -476,14 +485,17 @@ DEPENDENCIES
dartsass-rails (~> 0.5.0)
decent_exposure
devise
devise-multi_email
factory_bot_rails
faker
guard
guard-rspec
icalendar
inline_svg (~> 1.9.0)
jbuilder
kaminari
letter_opener
letter_opener_web (~> 3.0)
listen
pg
premailer-rails
Expand Down
4 changes: 3 additions & 1 deletion app/controllers/admin/imported_members_controller.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
require 'csv'

class Admin::ImportedMembersController < Admin::ApplicationController
expose(:members) { ImportedMember.contactable }

Expand Down Expand Up @@ -28,6 +30,6 @@ def add_import(row)
end

def skip_row?(row)
row[:ticket_email].blank? || User.where(email: row[:ticket_email]).any?
row[:ticket_email].blank? || Email.where(email: row[:ticket_email]).any?
end
end
2 changes: 1 addition & 1 deletion app/controllers/mailing_lists_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def events
end

def handle_list_event(event)
user = User.find_by email: event["EmailAddress"]
user = Email.find_by(email: event["EmailAddress"])&.user
return if user.nil?

user.mailing_lists_will_change!
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/my/details_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def update

def user_params
params.require(:user).permit(\
:email, :full_name, :address, :visible,
:full_name, :address, :visible,
mailing_lists: MailingList.all.collect(&:name)
)
end
Expand Down
42 changes: 42 additions & 0 deletions app/controllers/my/emails_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
class My::EmailsController < My::ApplicationController
def new
@email = current_user.emails.new
end

def create
@email = current_user.emails.new(email_params)
if @email.save
redirect_to my_details_path
else
render :new
end
end

def set_primary
email = Email.find(params[:id])
ActiveRecord::Base.transaction do
current_user.emails.where(primary: true).update_all(primary: false)
email.update!(primary: true)
end

flash[:notice] = "Your primary email has been ukpdated to #{email.email}"
redirect_to my_details_path
end

def destroy
email = Email.find(params[:id])
if email.destroy
flash[:notice] = "Your email #{email.email} have been deleted."
else
flash[:error] = "Your email #{email.email} is failed to be deleted."
end

redirect_to my_details_path
end

private

def email_params
params.require(:email).permit(:email)
end
end
2 changes: 1 addition & 1 deletion app/controllers/reactivations_controller.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
class ReactivationsController < ApplicationController
expose(:user) do
params[:user].present? ? User.find_by(email: user_params[:email]) : User.new
params[:user].present? ? Email.find_by(email: user_params[:email])&.user : User.new
end

def create
Expand Down
2 changes: 1 addition & 1 deletion app/lib/mailing_list/sync.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def call
mark_users_as_unsubscribed

each_subscriber do |subscriber|
user = User.find_by email: subscriber['EmailAddress']
user = Email.find_by(email: subscriber['EmailAddress'])&.user
next if user.nil?

user.mailing_lists_will_change!
Expand Down
2 changes: 1 addition & 1 deletion app/lib/slack/events/team_join.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ def self.call(event_data = {})
email = event_data.dig(:user, :profile, :email)
name = event_data.dig(:user, :real_name)

return if !email || User.exists?(email: email)
return if !email || Email.exists?(email: email)

InvitationMailer.with(email: email, name: name)
.invite
Expand Down
20 changes: 20 additions & 0 deletions app/models/email.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
class Email < ActiveRecord::Base
belongs_to :user

validates :email, presence: true, uniqueness: true

after_save :trigger_after_confirmation, if: :saved_change_to_confirmed_at?

delegate :full_name, :address, to: :user

private

def send_devise_notification(notification, *args)
devise_mailer.send(notification, self, *args).deliver_now
end

def trigger_after_confirmation
email_update = saved_change_to_email? && email_before_last_save.present?
user.update_mailing_list_and_memberships(email_update: email_update)
end
end
2 changes: 1 addition & 1 deletion app/models/membership.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ class Membership < ApplicationRecord
belongs_to :user

validates :joined_at, presence: true
validate :single_current_membership
validate :single_current_membership, on: :create

scope :current, -> { where(left_at: nil) }
scope :visible, -> { joins(:user).where(users: { visible: true }) }
Expand Down
33 changes: 26 additions & 7 deletions app/models/user.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
class User < ApplicationRecord
has_many :emails, dependent: :destroy # must be declared before devise :multi_email_authenticatable

# Include devise modules. Others available are:
# :lockable, :timeoutable, and :omniauthable
devise :database_authenticatable, :registerable, :recoverable, :rememberable,
:validatable, :confirmable, :trackable
devise :multi_email_authenticatable,
:multi_email_confirmable,
:multi_email_validatable,
:registerable,
:recoverable,
:rememberable,
:trackable

has_many :memberships, dependent: :destroy

Expand Down Expand Up @@ -33,16 +40,28 @@ def deactivated?
def save_as_confirmed!
self.confirmed_at ||= Time.current
save!

after_confirmation
end

protected
def update_emails
# fetch the email attribute directly from the table
existing_email = self.read_attribute_before_type_cast('email')
if email == nil && existing_email != ''
email = Email.new(email: existing_email, user: self, primary: true)
email.skip_confirmation!
email.save
if email.errors.present?
return errors
end
logger.info 'Email Updated!'
else
logger.info 'Email already exists or no email saved on record'
end
end

def after_confirmation
def update_mailing_list_and_memberships(email_update: false)
subscribe_to_lists

if email_previously_changed? && previous_changes["email"].first.present?
if email_update
update_mailing_list_email_addresses
else
set_up_mailing_list_flags
Expand Down
4 changes: 4 additions & 0 deletions app/packs/styles/application.scss
Original file line number Diff line number Diff line change
Expand Up @@ -225,3 +225,7 @@ nav {
@apply pl-1/3;
}
}

.badge {
@apply inline-flex items-center rounded bg-green px-2 py-1 text-xs font-medium text-white ring-1 ring-inset;
}
9 changes: 0 additions & 9 deletions app/views/my/details/edit.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,6 @@
</div>
<% end %>

<div class="field">
<div class="label">
<%= form.label :email, class: 'required' %>
</div>
<div class="input">
<%= form.text_field :email, type: 'email' %>
</div>
</div>

<div class="field">
<div class="label">
<%= form.label :full_name, class: 'required' %>
Expand Down
23 changes: 22 additions & 1 deletion app/views/my/details/show.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,28 @@
<b>Full Name</b>: <%= @user.full_name %>
</li>
<li>
<b>Email</b>: <%= @user.email %>
<b>Emails</b>:
<ul>
<% @user.emails.order(primary: :desc, confirmed_at: :asc).each do |email| %>
<li>
<%= email.email %>
<% if email.confirmed_at? %>
<span class="badge">Confirmed</span>
<% else %>
<span class="badge">Unconfirmed</span>
<% end %>
<% if email.primary? %>
<span class="badge">Primary</span>
<% else %>
<% if email.confirmed_at? %>
<%= link_to "Make Primary", set_primary_my_email_path(email), method: :put, class: "btn btn-sm btn-primary", data: { confirm: "Are you sure you want to change primary email to #{email.email}?" } %>
<% end %>
<%= link_to "Remove", my_email_path(email), method: :delete, class: "btn btn-sm btn-danger", data: { confirm: "Are you sure you want to remove #{email.email}?" } %>
<% end %>
</li>
<% end %>
</ul>
<%= link_to "Add Email", new_my_email_path, class: "btn btn-sm btn-primary" %>
</li>
<li>
<b>Mailing Lists</b>: <%= subscribed_mailing_lists(@user) %>
Expand Down
32 changes: 32 additions & 0 deletions app/views/my/emails/new.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<%= content_for :heading do %>
Add New Email
<% end %>

<h2>Add New Email</h2>

<%= form_for @email, url: my_emails_path, html: { class: 'standard' } do |form| %>
<% if @email.errors.any? %>
<div id="error_explanation" class="ui visible error message">
<h2><%= pluralize(@email.errors.count, "error") %> prohibited this from being saved:</h2>

<ul>
<% @email.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>

<div class="field">
<div class="label">
<%= form.label :email, class: 'required' %>
</div>
<div class="input">
<%= form.email_field :email, type: 'email' %>
</div>
</div>

<div class="buttons">
<%= form.submit 'Add New Email', class: 'btn btn-primary' %>
</div>
<% end %>
7 changes: 7 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
Rails.application.routes.draw do
devise_for :users, controllers: { registrations: 'registrations' }

mount LetterOpenerWeb::Engine, at: "/letter_opener" if Rails.env.development?

namespace :my do
resource :details, only: [:show, :edit, :update]
resource :password, only: [:update]
resource :membership, only: [:destroy]
resources :meetings, only: [:index]
resources :access_requests, only: [:index]
resource :slack_invite, only: [:show]
resources :emails, only: [:new, :create, :destroy] do
member do
put :set_primary
end
end
end

resources :rsvps, only: [:show, :update, :destroy] do
Expand Down
17 changes: 17 additions & 0 deletions db/migrate/20241214235748_create_emails.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
class CreateEmails < ActiveRecord::Migration[7.2]
def change
create_table :emails do |t|
t.references :user, foreign_key: true
t.string :email, null: false, index: { unique: true }
t.boolean :primary, default: false, null: false

## Confirmable
t.string :unconfirmed_email
t.string :confirmation_token
t.datetime :confirmed_at
t.datetime :confirmation_sent_at

t.timestamps
end
end
end
Loading

0 comments on commit 96670de

Please sign in to comment.