Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

User preferences, notifications and contributors #57

Merged
merged 41 commits into from
Jan 27, 2024
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
d57040b
add preferences model and table to store user preferences
madhums Jun 17, 2023
5c6bcfc
use after_confirmation devise hook to create user preference
madhums Jun 18, 2023
10f8e9a
add rake task to add preferences for already created users
madhums Jun 18, 2023
0213c0a
add DOMAIN_NAME test env variable
madhums Jun 18, 2023
0b1aa45
set values for required test env variables
madhums Jun 19, 2023
2652b42
add preferences ui
madhums Jun 20, 2023
7b6bfc8
add requests test for preferences
madhums Jan 2, 2024
796f2fc
add noticed
madhums Jan 3, 2024
deb4a08
use ApplicationMailer for devise emails so that we have the same layo…
madhums Jan 5, 2024
0fb6b4e
add default from email address rails config
madhums Jan 5, 2024
b6d0fb3
add user_mailer and it's tests
madhums Jan 6, 2024
be522ea
add noticed notification for notifying users of new stories
madhums Jan 6, 2024
2fde037
add a request test to check if mailer jobs are fired to notify new st…
madhums Jan 6, 2024
54a7c16
add notifications for new discussions, rename user_mailer to notifica…
madhums Jan 6, 2024
c3c8865
skip after_create_commit callback for tests, skip some tests
madhums Jan 7, 2024
ee06ad1
add notifications for when there are new posts on a discussion + refa…
madhums Jan 7, 2024
6e0cd3e
add preferences to nav
madhums Jan 7, 2024
f250c00
add notifications UI
madhums Jan 7, 2024
bb8386a
display unread notification indicator
madhums Jan 7, 2024
c3ea2c4
add tests for User.with_notify_new_story_preference scope
madhums Jan 8, 2024
aa8b1ae
add the ability to add story contributors
madhums Jan 8, 2024
a95fe43
send notification to the invitee
madhums Jan 8, 2024
fd46b95
make sure user has a preference created once his invitation is accepted
madhums Jan 8, 2024
79864ac
update mailer specs and preview
madhums Jan 9, 2024
926735c
display story contributors
madhums Jan 12, 2024
4e459d0
update translations
madhums Jan 12, 2024
802cf26
make sure story contributors have same permissions as creators
madhums Jan 14, 2024
3e6084b
add routes, controllers and tests for inviting and uninviting collabo…
madhums Jan 14, 2024
5caa40d
update i18n translations
madhums Jan 21, 2024
465fd92
crude fix. closes #61
madhums Jan 21, 2024
1a23650
add email validations, max contributors and respective tests
madhums Jan 22, 2024
e4b7549
add remove contributors link
madhums Jan 22, 2024
73a849e
first strip and then check for unique
madhums Jan 22, 2024
016e74f
allow contributors to manage sotry udpates
madhums Jan 25, 2024
3fe92cf
forbid contributors to delete a story
madhums Jan 25, 2024
ef2ecdc
add tooltip to indicate a contributor can leave a story
madhums Jan 25, 2024
6002186
use find_by instead of find
madhums Jan 25, 2024
fa21866
use scoped find
madhums Jan 26, 2024
3934ea0
use a sidebar partial
madhums Jan 27, 2024
7aab75b
misc bug fixes
madhums Jan 27, 2024
7d1292f
misc ui changes
madhums Jan 27, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ jobs:
RAILS_ENV: test
DATABASE_URL: postgres://test:test@localhost:5432/test
CI: "true"
DOMAIN_NAME: xxxx
DOMAIN_EMAIL_ADDRESS: xxxx

steps:
- name: Checkout code
Expand Down
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,4 @@ gem "devise-i18n", "~> 1.10"
gem "dockerfile-rails", ">= 1.5", group: :development

gem "bugsnag", "~> 6.26"
gem "noticed", "~> 1.6"
19 changes: 19 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ GEM
activerecord (>= 4.2, < 8)
dockerfile-rails (1.5.3)
rails
domain_name (0.6.20231109)
dotenv (2.8.1)
dotenv-rails (2.8.1)
dotenv (= 2.8.1)
Expand All @@ -166,6 +167,9 @@ GEM
faker (3.0.0)
i18n (>= 1.8.11, < 2)
ffi (1.16.3)
ffi-compiler (1.0.1)
ffi (>= 1.0.0)
rake
formatador (1.1.0)
globalid (1.0.1)
activesupport (>= 5.0)
Expand All @@ -183,6 +187,14 @@ GEM
guard (~> 2.1)
guard-compat (~> 1.1)
rspec (>= 2.99.0, < 4.0)
http (5.1.1)
addressable (~> 2.8)
http-cookie (~> 1.0)
http-form_data (~> 2.2)
llhttp-ffi (~> 0.4.0)
http-cookie (1.0.5)
domain_name (~> 0.5)
http-form_data (2.3.0)
i18n (1.14.1)
concurrent-ruby (~> 1.0)
image_processing (1.12.2)
Expand Down Expand Up @@ -224,6 +236,9 @@ GEM
listen (3.7.1)
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
llhttp-ffi (0.4.0)
ffi-compiler (~> 1.0)
rake (~> 13.0)
loofah (2.22.0)
crass (~> 1.0.2)
nokogiri (>= 1.12.0)
Expand Down Expand Up @@ -259,6 +274,9 @@ GEM
racc (~> 1.4)
nokogiri (1.16.0-x86_64-linux)
racc (~> 1.4)
noticed (1.6.3)
http (>= 4.0.0)
rails (>= 5.2.0)
notiffany (0.1.3)
nenv (~> 0.1)
shellany (~> 0.0)
Expand Down Expand Up @@ -492,6 +510,7 @@ DEPENDENCIES
kaminari (~> 1.2)
mobility (~> 1.2)
mobility-ransack (~> 1.2.2)
noticed (~> 1.6)
pg (~> 1.1)
puma (~> 6.4)
pundit (~> 2.2)
Expand Down
11 changes: 1 addition & 10 deletions app/assets/stylesheets/application.bootstrap.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,4 @@
@import 'bootstrap-icons/font/bootstrap-icons';
@import './direct_uploads';
@import 'trix/dist/trix';

trix-editor {
min-height: 100px;
}

.trix-button-group--block-tools,
.trix-button-group--file-tools,
.trix-button-group--history-tools {
display: none !important;
}
@import './socialchange';
36 changes: 36 additions & 0 deletions app/assets/stylesheets/socialchange.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
trix-editor {
min-height: 100px;
}

.trix-button-group--block-tools,
.trix-button-group--file-tools,
.trix-button-group--history-tools {
display: none !important;
}

.unread-dot {
content: '';
width: 8px; /* Adjust the width and height to control the size of the dot */
height: 8px;
background-color: #3498db; /* Blue color, adjust as needed */
border-radius: 50%;
position: absolute;
top: 50%; /* Adjust vertical positioning as needed */
left: -6px; /* Adjust horizontal positioning as needed */
transform: translateY(-50%); /* Center the dot vertically */
}

.nav-item.dropdown.nav-unread-notifications {
&> .nav-account-dropdown {
position: relative;
margin-left: 15px;
&:before {
@extend .unread-dot;
}
}

.dropdown-item.notifications {
font-weight: bold;
}
}

54 changes: 54 additions & 0 deletions app/controllers/contributions_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Contributions controller
#
class ContributionsController < ApplicationController
before_action :authenticate_user!
before_action :set_story
before_action :set_contribution, only: %i[destroy]

def new
# use story policy update permissions
authorize @story, :update?, policy_class: StoryPolicy

@contribution = Contribution.new(story: @story)
end

def create
# use story policy update permissions
authorize @story, :update?, policy_class: StoryPolicy

@story.invite_contributors(params[:contribution][:emails].split(","), current_user)
@story.reload
respond_to do |format|
format.turbo_stream do
render turbo_stream: turbo_stream.replace(:story_contributors, partial: "stories/contributors",
locals: {story: @story})
end
format.html { redirect_to story_url(@story), notice: t(".invited") }
format.json { head :no_content }
end
end

def destroy
# use story policy update permissions
authorize @story, :update?, policy_class: StoryPolicy

@contribution.destroy
respond_to do |format|
format.turbo_stream do
render turbo_stream: turbo_stream.remove(helpers.dom_id(@contribution))
end
format.html { redirect_to story_url(@story), notice: t(".removed") }
format.json { head :no_content }
end
end

private

def set_story
@story = policy_scope(Story).find(params[:story_id])
end

def set_contribution
@contribution = Contribution.find(params[:id])
madhums marked this conversation as resolved.
Show resolved Hide resolved
madhums marked this conversation as resolved.
Show resolved Hide resolved
madhums marked this conversation as resolved.
Show resolved Hide resolved
end
end
12 changes: 12 additions & 0 deletions app/controllers/notifications_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
class NotificationsController < ApplicationController
after_action :mark_notifications_as_read, only: :index
def index
@notifications = current_user.notifications.newest_first
end

private

def mark_notifications_as_read
current_user.notifications.mark_as_read!
end
end
36 changes: 36 additions & 0 deletions app/controllers/preferences_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Preferences controller
#
class PreferencesController < ApplicationController
before_action :authenticate_user!
before_action :set_preference, only: %i[index update]

def index
end

def update
respond_to do |format|
if @preference.update(**permitted_params)
format.html { redirect_to preferences_url, notice: I18n.t("preferences.updated") }
format.json { render :index, status: :ok, location: @preference }
else
format.html { render :edit, status: :unprocessable_entity }
format.json { render json: @preference.errors, status: :unprocessable_entity }
end
end
end

private

def set_preference
@preference = current_user.preference
end

def permitted_params
params.require(:preference).permit(
:notify_any_post_in_discussion,
:notify_new_discussion_on_story,
:notify_new_post_on_discussion,
:notify_new_story
)
end
end
2 changes: 1 addition & 1 deletion app/mailers/application_mailer.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
class ApplicationMailer < ActionMailer::Base
default from: "[email protected]"
default from: Rails.application.config.mailer_default_from
layout "mailer"
end
65 changes: 65 additions & 0 deletions app/mailers/notification_mailer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
class NotificationMailer < ApplicationMailer
layout "mailer"

def notify_new_story
@user = params[:recipient]
@story = params[:story]

# make sure that the email is always sent in the language preferred
# by the user
I18n.with_locale(@user.language) do
mail(
to: email_address_with_name(@user.email, @user.name),
subject: t(".subject", title: @story.title)
)
end
end

def notify_new_discussion
@user = params[:recipient]
@discussion = params[:discussion]
@story = params[:story]

# make sure that the email is always sent in the language preferred
# by the user
I18n.with_locale(@user.language) do
mail(
to: email_address_with_name(@user.email, @user.name),
subject: t(".subject", story_title: @story.title)
)
end
end

def notify_new_post
@user = params[:recipient]
@discussion = params[:discussion]
@story = params[:story]
@post = params[:post]

# make sure that the email is always sent in the language preferred
# by the user
I18n.with_locale(@user.language) do
mail(
to: email_address_with_name(@user.email, @user.name),
subject: t(".subject", title: @discussion.title)
)
end
end

def invite_contributor
@user = params[:recipient]
@story = params[:story]

unless @user.confirmed?
@user.invitation_sent_at = Time.zone.now
@token, enc = Devise.token_generator.generate(User, :invitation_token)
@user.invitation_token = enc
@user.save(validate: false)
end

mail(
to: @user.email,
subject: t(".subject", title: @story.title)
)
end
end
12 changes: 12 additions & 0 deletions app/models/contribution.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
class Contribution < ApplicationRecord
belongs_to :user
belongs_to :story

after_create_commit :invite_contributor, unless: -> { Rails.env.test? }

private

def invite_contributor
InviteContributorNotification.with(story:).deliver_later([user])
end
end
20 changes: 20 additions & 0 deletions app/models/discussion.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,19 @@ class Discussion < ApplicationRecord
belongs_to :story
belongs_to :updater, class_name: "User"
has_many :posts, dependent: :destroy
has_noticed_notifications

validates :title, :description, presence: true

# @todo remove the unless filter
# figure out why tests get stuck and thorw errors like these
# https://github.com/compassionprojects/socialchange/actions/runs/7434566400/job/20228849583?pr=57
# WARNING: there is already a transaction in progress
# ActiveRecord::StatementInvalid:
# PG::UnableToSend: insufficient data in "T" message
#
after_create_commit :notify, unless: -> { Rails.env.test? }

# After a topic is discarded, discard it's posts
#
after_discard do
Expand All @@ -19,4 +29,14 @@ class Discussion < ApplicationRecord
after_undiscard do
posts.undiscard_all
end

def participants
User.joins(:posts).merge(Post.kept.where(discussion_id: id)).distinct
end

private

def notify
NewDiscussionNotification.with(discussion: self, story:).deliver_later([story.user])
end
end
4 changes: 4 additions & 0 deletions app/models/notification.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
class Notification < ApplicationRecord
include Noticed::Model
belongs_to :recipient, polymorphic: true
end
13 changes: 13 additions & 0 deletions app/models/post.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,19 @@ class Post < ApplicationRecord
belongs_to :discussion
belongs_to :user
belongs_to :updater, class_name: "User"
has_noticed_notifications

validates :body, presence: true

# @todo remove the unless filter after fixing the tests
# same issue as discussion model
after_create_commit :notify, unless: -> { Rails.env.test? }

private

def notify
# notify discussion creator and anyone else participating in the discussion
participants = (discussion.participants + [discussion.user]).uniq
NewPostNotification.with(post: self, discussion:, story: discussion.story).deliver_later(participants)
end
end
3 changes: 3 additions & 0 deletions app/models/preference.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class Preference < ApplicationRecord
belongs_to :user
end
Loading
Loading