diff --git a/app/assets/javascripts/private/conversations.coffee b/app/assets/javascripts/private/conversations.coffee
new file mode 100644
index 0000000..24f83d1
--- /dev/null
+++ b/app/assets/javascripts/private/conversations.coffee
@@ -0,0 +1,3 @@
+# Place all the behaviors and hooks related to the matching controller here.
+# All this logic will automatically be available in application.js.
+# You can use CoffeeScript in this file: http://coffeescript.org/
diff --git a/app/assets/stylesheets/partials/posts/branch_page.scss b/app/assets/stylesheets/partials/posts/branch_page.scss
index 76f6bc7..4317f0b 100644
--- a/app/assets/stylesheets/partials/posts/branch_page.scss
+++ b/app/assets/stylesheets/partials/posts/branch_page.scss
@@ -95,6 +95,30 @@
.infinite-scroll {
display: none;
}
+
+ .send-message-to-user {
+ background-color: $navbarColor;
+ padding: 10px;
+ color: white;
+ border-radius: 10px;
+ margin-top: 10px;
+ &:hover {
+ background-color: black;
+ color: white;
+ }
+ }
+
+ .contact-user {
+ text-align: center;
+ }
+
+ .contacted-user {
+ display: inline-block;
+ border-radius: 10px;
+ padding: 10px;
+ background-color: $navbarColor;
+ color: white;
+ }
#branch-main-content {
background: white;
diff --git a/app/assets/stylesheets/private/conversations.scss b/app/assets/stylesheets/private/conversations.scss
new file mode 100644
index 0000000..aa55a82
--- /dev/null
+++ b/app/assets/stylesheets/private/conversations.scss
@@ -0,0 +1,3 @@
+// Place all the styles related to the private/conversations controller here.
+// They will automatically be included in application.css.
+// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb
index 146c44c..dbc1f22 100644
--- a/app/controllers/posts_controller.rb
+++ b/app/controllers/posts_controller.rb
@@ -17,7 +17,10 @@ def create
end
def show
- @post = Post.find(params[:id])
+ @post = Post.find(params[:id])
+ if user_signed_in?
+ @message_has_been_sent = conversation_exist?
+ end
end
def study
@@ -56,4 +59,8 @@ def get_posts
}).call
end
+
+ def conversation_exist?
+ Private::Conversation.between_users(current_user.id, @post.user.id).present?
+ end
end
diff --git a/app/controllers/private/conversations_controller.rb b/app/controllers/private/conversations_controller.rb
new file mode 100644
index 0000000..920570c
--- /dev/null
+++ b/app/controllers/private/conversations_controller.rb
@@ -0,0 +1,33 @@
+class Private::ConversationsController < ApplicationController
+ def create
+ recipient_id = Post.find(params[:post_id]).user.id
+ @conversation = Private::Conversation.new(sender_id: current_user.id,
+ recipient_id: recipient_id)
+ if @conversation.save
+ Private::Message.create(user_id: recipient_id,
+ conversation_id: @conversation.id,
+ body: params[:message_body])
+
+ add_to_conversations unless already_added?
+
+ respond_to do |format|
+ format.js {render partial: 'posts/show/contact_user/message_form/success'}
+ end
+ else
+ respond_to do |format|
+ format.js {render partial: 'posts/show/contact_user/message_form/fail'}
+ end
+ end
+ end
+
+ private
+
+ def add_to_conversations
+ session[:private_conversations] ||= []
+ session[:private_conversations] << @conversation.id
+ end
+
+ def already_added?
+ session[:private_conversations].include?(@conversation.id)
+ end
+end
diff --git a/app/helpers/posts_helper.rb b/app/helpers/posts_helper.rb
index 947b9d6..620f78c 100644
--- a/app/helpers/posts_helper.rb
+++ b/app/helpers/posts_helper.rb
@@ -38,4 +38,20 @@ def update_pagination_partial_path
'posts/posts_pagination_page/remove_pagination'
end
end
+
+ def contact_user_partial_path
+ if user_signed_in?
+ @post.user.id != current_user.id ? 'posts/show/contact_user' : 'shared/empty_partial'
+ else
+ 'posts/show/login_required'
+ end
+ end
+
+ def leave_message_partial_path
+ if @message_has_been_sent
+ 'posts/show/contact_user/already_in_touch'
+ else
+ 'posts/show/contact_user/message_form'
+ end
+ end
end
diff --git a/app/helpers/private/conversations_helper.rb b/app/helpers/private/conversations_helper.rb
new file mode 100644
index 0000000..88e6f5f
--- /dev/null
+++ b/app/helpers/private/conversations_helper.rb
@@ -0,0 +1,13 @@
+module Private::ConversationsHelper
+ def private_conv_recipient(conversation)
+ conversation.opposed_user(current_user)
+ end
+
+ def load_private_messages(conversation)
+ if conversation.messages.count > 0
+ 'private/conversations/conversation/messages_list/link_to_previous_messages'
+ else
+ 'shared/empty_partial'
+ end
+ end
+end
diff --git a/app/models/private/conversation.rb b/app/models/private/conversation.rb
new file mode 100644
index 0000000..e3122e1
--- /dev/null
+++ b/app/models/private/conversation.rb
@@ -0,0 +1,19 @@
+class Private::Conversation < ApplicationRecord
+ self.table_name = 'private_conversations'
+
+ has_many :messages,
+ class_name: "Private::Message",
+ foreign_key: :conversation_id
+ belongs_to :sender, foreign_key: :sender_id, class_name: 'User'
+ belongs_to :recipient, foreign_key: :recipient_id, class_name: 'User'
+
+ scope :between_users, -> (user1_id, user2_id) do
+ where(sender_id: user1_id, recipient_id: user2_id).or(
+ where(sender_id: user2_id, recipient_id: user1_id)
+ )
+ end
+
+ def opposed_user(user)
+ user == recipient ? sender : recipient
+ end
+end
diff --git a/app/models/private/message.rb b/app/models/private/message.rb
new file mode 100644
index 0000000..c9edcf5
--- /dev/null
+++ b/app/models/private/message.rb
@@ -0,0 +1,8 @@
+class Private::Message < ApplicationRecord
+ self.table_name = 'private_messages'
+
+ belongs_to :user
+ belongs_to :conversation,
+ class_name: "Private::Conversation",
+ foreign_key: :conversation_id
+end
diff --git a/app/models/user.rb b/app/models/user.rb
index 38100ee..8a0f07c 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -5,4 +5,8 @@ class User < ApplicationRecord
:recoverable, :rememberable, :validatable
has_many :posts, dependent: :destroy
+ has_many :private_messages, class_name: "Private::Message"
+ has_many :private_conversations,
+ foreign_key: :sender_id,
+ class_name: "Private::Conversation"
end
diff --git a/app/views/posts/show.html.erb b/app/views/posts/show.html.erb
index bbfccfa..08030dc 100644
--- a/app/views/posts/show.html.erb
+++ b/app/views/posts/show.html.erb
@@ -4,6 +4,7 @@
Posted by <%= @post.user.name %>
<%= @post.title %>
<%= @post.content %>
+ <%= render contact_user_partial_path %>
diff --git a/app/views/posts/show/_contact_user.html.erb b/app/views/posts/show/_contact_user.html.erb
new file mode 100644
index 0000000..deb3669
--- /dev/null
+++ b/app/views/posts/show/_contact_user.html.erb
@@ -0,0 +1,3 @@
+
+ <%= render leave_message_partial_path %>
+
\ No newline at end of file
diff --git a/app/views/posts/show/_login_required.html.erb b/app/views/posts/show/_login_required.html.erb
new file mode 100644
index 0000000..001efe4
--- /dev/null
+++ b/app/views/posts/show/_login_required.html.erb
@@ -0,0 +1,3 @@
+
+ To contact the user you have to <%= link_to 'Login', login_path %>
+
\ No newline at end of file
diff --git a/app/views/posts/show/contact_user/_already_in_touch.html.erb b/app/views/posts/show/contact_user/_already_in_touch.html.erb
new file mode 100644
index 0000000..2350ff7
--- /dev/null
+++ b/app/views/posts/show/contact_user/_already_in_touch.html.erb
@@ -0,0 +1,3 @@
+
+ You are already in touch with this user
+
\ No newline at end of file
diff --git a/app/views/posts/show/contact_user/_message_form.html.erb b/app/views/posts/show/contact_user/_message_form.html.erb
new file mode 100644
index 0000000..ee1b51a
--- /dev/null
+++ b/app/views/posts/show/contact_user/_message_form.html.erb
@@ -0,0 +1,12 @@
+<%= form_tag({controller: "private/conversations", action: "create"},
+ method: "post",
+ remote: true) do %>
+ <%= hidden_field_tag(:post_id, @post.id) %>
+ <%= text_area_tag(:message_body,
+ nil,
+ rows: 3,
+ class: 'form-control',
+ placeholder: 'Send a message to the user') %>
+ <%= submit_tag('Send a message', class: 'btn send-message-to-user') %>
+<% end %>
+
diff --git a/app/views/posts/show/contact_user/message_form/_fail.js b/app/views/posts/show/contact_user/message_form/_fail.js
new file mode 100644
index 0000000..25a1df2
--- /dev/null
+++ b/app/views/posts/show/contact_user/message_form/_fail.js
@@ -0,0 +1 @@
+$('.contact-user').replaceWith('Message has not been sent
');
\ No newline at end of file
diff --git a/app/views/posts/show/contact_user/message_form/_success.js.erb b/app/views/posts/show/contact_user/message_form/_success.js.erb
new file mode 100644
index 0000000..d4e007b
--- /dev/null
+++ b/app/views/posts/show/contact_user/message_form/_success.js.erb
@@ -0,0 +1,4 @@
+$('.contact-user').replaceWith('\
+ ');
diff --git a/app/views/private/conversations/_conversation.html.erb b/app/views/private/conversations/_conversation.html.erb
new file mode 100644
index 0000000..5436324
--- /dev/null
+++ b/app/views/private/conversations/_conversation.html.erb
@@ -0,0 +1,20 @@
+<% @recipient = private_conv_recipient(conversation) %>
+<% @is_messenger = false %>
+
+
+ <%= render 'private/conversations/conversation/heading',
+ conversation: conversation %>
+
+
+
+ <%= render 'private/conversations/conversation/messages_list',
+ conversation: conversation %>
+ <%= render 'private/conversations/conversation/new_message_form',
+ conversation: conversation,
+ user: user %>
+
+
+
\ No newline at end of file
diff --git a/app/views/private/conversations/conversation/_heading.html.erb b/app/views/private/conversations/conversation/_heading.html.erb
new file mode 100644
index 0000000..6499303
--- /dev/null
+++ b/app/views/private/conversations/conversation/_heading.html.erb
@@ -0,0 +1,11 @@
+
+ <%= @recipient.name %>
+
+
+
+<%= link_to "X",
+ close_private_conversation_path(conversation),
+ class: 'close-conversation',
+ title: 'Close',
+ remote: true,
+ method: :post %>
\ No newline at end of file
diff --git a/app/views/private/conversations/conversation/_messages_list.html.erb b/app/views/private/conversations/conversation/_messages_list.html.erb
new file mode 100644
index 0000000..3cc3c4e
--- /dev/null
+++ b/app/views/private/conversations/conversation/_messages_list.html.erb
@@ -0,0 +1,9 @@
+
+ <%= render load_private_messages(conversation), conversation: conversation %>
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/views/private/conversations/conversation/messages_list/_link_to_previous_messages.html.erb b/app/views/private/conversations/conversation/messages_list/_link_to_previous_messages.html.erb
new file mode 100644
index 0000000..22d7af5
--- /dev/null
+++ b/app/views/private/conversations/conversation/messages_list/_link_to_previous_messages.html.erb
@@ -0,0 +1,6 @@
+<%= link_to "Load messages",
+ private_messages_path(:conversation_id => conversation.id,
+ :messages_to_display_offset => @messages_to_display_offset,
+ :is_messenger => @is_messenger),
+ class: 'load-more-messages',
+ remote: true %>
\ No newline at end of file
diff --git a/config/routes.rb b/config/routes.rb
index fb13bc9..8965c43 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -15,4 +15,13 @@
get 'team'
end
end
+
+ namespace :private do
+ resources :conversations, only: [:create] do
+ member do
+ post :close
+ end
+ end
+ resources :messages, only: [:index, :create]
+ end
end
diff --git a/db/migrate/20211211044212_create_private_conversations.rb b/db/migrate/20211211044212_create_private_conversations.rb
new file mode 100644
index 0000000..0afed89
--- /dev/null
+++ b/db/migrate/20211211044212_create_private_conversations.rb
@@ -0,0 +1,14 @@
+class CreatePrivateConversations < ActiveRecord::Migration[5.1]
+ def change
+ create_table :private_conversations do |t|
+ t.integer :recipient_id
+ t.integer :sender_id
+
+ t.timestamps
+ end
+
+ add_index :private_conversations, :recipient_id
+ add_index :private_conversations, :sender_id
+ add_index :private_conversations, [:recipient_id, :sender_id], unique: true
+ end
+end
diff --git a/db/migrate/20211211044226_create_private_messages.rb b/db/migrate/20211211044226_create_private_messages.rb
new file mode 100644
index 0000000..8125e44
--- /dev/null
+++ b/db/migrate/20211211044226_create_private_messages.rb
@@ -0,0 +1,12 @@
+class CreatePrivateMessages < ActiveRecord::Migration[5.1]
+ def change
+ create_table :private_messages do |t|
+ t.text :body
+ t.references :user, foreign_key: true
+ t.belongs_to :conversation, index: true
+ t.boolean :seen, default: false
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 35fa3af..2b7d1c9 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20211111154915) do
+ActiveRecord::Schema.define(version: 20211211044226) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -31,6 +31,27 @@
t.index ["user_id"], name: "index_posts_on_user_id"
end
+ create_table "private_conversations", force: :cascade do |t|
+ t.integer "recipient_id"
+ t.integer "sender_id"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.index ["recipient_id", "sender_id"], name: "index_private_conversations_on_recipient_id_and_sender_id", unique: true
+ t.index ["recipient_id"], name: "index_private_conversations_on_recipient_id"
+ t.index ["sender_id"], name: "index_private_conversations_on_sender_id"
+ end
+
+ create_table "private_messages", force: :cascade do |t|
+ t.text "body"
+ t.bigint "user_id"
+ t.bigint "conversation_id"
+ t.boolean "seen", default: false
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.index ["conversation_id"], name: "index_private_messages_on_conversation_id"
+ t.index ["user_id"], name: "index_private_messages_on_user_id"
+ end
+
create_table "users", force: :cascade do |t|
t.string "name", default: "", null: false
t.string "email", default: "", null: false
@@ -44,4 +65,5 @@
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
+ add_foreign_key "private_messages", "users"
end
diff --git a/spec/controllers/private/conversations_controller_spec.rb b/spec/controllers/private/conversations_controller_spec.rb
new file mode 100644
index 0000000..70d5654
--- /dev/null
+++ b/spec/controllers/private/conversations_controller_spec.rb
@@ -0,0 +1,5 @@
+require 'rails_helper'
+
+RSpec.describe Private::ConversationsController, type: :controller do
+
+end
diff --git a/spec/factories/private_conversations.rb b/spec/factories/private_conversations.rb
new file mode 100644
index 0000000..1196e98
--- /dev/null
+++ b/spec/factories/private_conversations.rb
@@ -0,0 +1,17 @@
+FactoryGirl.define do
+ factory :private_conversation, class: 'Private::Conversation' do
+ association :recipient, factory: :user
+ association :sender, factory: :user
+
+ factory :private_conversation_with_messages do
+ transient do
+ messages_count 1
+ end
+
+ after(:create) do |private_conversation, evaluator|
+ create_list(:private_message, evaluator.messages_count,
+ conversation: private_conversation)
+ end
+ end
+ end
+end
diff --git a/spec/factories/private_messages.rb b/spec/factories/private_messages.rb
new file mode 100644
index 0000000..a1433e7
--- /dev/null
+++ b/spec/factories/private_messages.rb
@@ -0,0 +1,7 @@
+FactoryGirl.define do
+ factory :private_message, class: 'Private::Message' do
+ body 'a'*20
+ association :conversation, factory: :private_conversation
+ user
+ end
+end
diff --git a/spec/features/posts/contact_user_spec.rb b/spec/features/posts/contact_user_spec.rb
new file mode 100644
index 0000000..a053414
--- /dev/null
+++ b/spec/features/posts/contact_user_spec.rb
@@ -0,0 +1,42 @@
+require "rails_helper"
+
+RSpec.feature "Contact user", :type => :feature do
+ let(:user) { create(:user) }
+ let(:category) { create(:category, name: 'Arts', branch: 'hobby') }
+ let(:post) { create(:post, category_id: category.id) }
+
+ context 'logged in user' do
+ before(:each) do
+ sign_in user
+ end
+
+ scenario "successfully sends a message to a post's author", js: true do
+ visit post_path(post)
+ expect(page).to have_selector('.contact-user form')
+
+ fill_in('message_body', with: 'a'*20)
+ find('form .send-message-to-user').trigger('click')
+
+ expect(page).not_to have_selector('.contact-user form')
+ expect(page).to have_selector('.contacted-user',
+ text: 'Message has been sent')
+ end
+
+ scenario "sees an already contacted mesage", js: true do
+ create(:private_conversation_with_messages,
+ recipient_id: post.user.id,
+ sender_id: user.id)
+
+ visit post_path(post)
+ expect(page).to have_selector('.contact-user .contacted-user',
+ text: 'You are already in touch with this user')
+ end
+ end
+
+ context "user is not logged in" do
+ scenario "sees a login is required to contact user" do
+ visit post_path(post)
+ expect(page).to have_selector('div', text: 'To contact the user you have to Login')
+ end
+ end
+end
\ No newline at end of file
diff --git a/spec/helpers/posts_helper_spec.rb b/spec/helpers/posts_helper_spec.rb
index 915d8e9..4e74122 100644
--- a/spec/helpers/posts_helper_spec.rb
+++ b/spec/helpers/posts_helper_spec.rb
@@ -81,4 +81,44 @@
)
end
end
+ context "#contact_user_partial_path" do
+ before(:each) do
+ @current_user = create(:user, id: 1)
+ helper.stub(:current_user).and_return(@current_user)
+ end
+ it "returns a contact user's partial path" do
+ helper.stub(:user_signed_in?).and_return(true)
+ assign(:post, create(:post, user_id: create(:user, id: 2).id))
+ expect(helper.contact_user_partial_path).to(
+ eq 'posts/show/contact_user'
+ )
+ end
+ it "returns an empty partial's path when post is from current user" do
+ helper.stub(:user_signed_in?).and_return(true)
+ assign(:post, create(:post, user_id: @current_user.id))
+ expect(helper.contact_user_partial_path).to(
+ eq 'shared/empty_partial'
+ )
+ end
+ it "returns login required path when no current user logged in" do
+ helper.stub(:user_signed_in?).and_return(false)
+ expect(helper.contact_user_partial_path).to(
+ eq 'posts/show/login_required'
+ )
+ end
+ end
+ context "#leave_message_partial_path" do
+ it "returns an already in touch partial path" do
+ assign('message_has_been_sent', true)
+ expect(helper.leave_message_partial_path).to(
+ eq 'posts/show/contact_user/already_in_touch'
+ )
+ end
+ it "returns a message form partial path" do
+ assign('message_has_been_sent', false)
+ expect(helper.leave_message_partial_path).to(
+ eq 'posts/show/contact_user/message_form'
+ )
+ end
+ end
end
diff --git a/spec/helpers/private/conversations_helper_spec.rb b/spec/helpers/private/conversations_helper_spec.rb
new file mode 100644
index 0000000..5cb5e04
--- /dev/null
+++ b/spec/helpers/private/conversations_helper_spec.rb
@@ -0,0 +1,29 @@
+require 'rails_helper'
+
+# Specs in this file have access to a helper object that includes
+# the Private::ConversationsHelper. For example:
+#
+# describe Private::ConversationsHelper do
+# describe "string concat" do
+# it "concats two strings with spaces" do
+# expect(helper.concat_strings("this","that")).to eq("this that")
+# end
+# end
+# end
+RSpec.describe Private::ConversationsHelper, type: :helper do
+ context "#load_private_messages" do
+ let(:conversation) { create(:private_conversation) }
+
+ it "returns load_messages partial path" do
+ create(:private_message, conversation_id: conversation.id)
+ expect(helper.load_private_messages(conversation)).to eq(
+ 'private/conversations/conversation/messages_list/link_to_previous_messages'
+ )
+ end
+ it "returns empty partial when there are no private messages" do
+ expect(helper.load_private_messages(conversation)).to eq(
+ 'shared/empty_partial'
+ )
+ end
+ end
+end
diff --git a/spec/models/private/conversation_spec.rb b/spec/models/private/conversation_spec.rb
new file mode 100644
index 0000000..db0657f
--- /dev/null
+++ b/spec/models/private/conversation_spec.rb
@@ -0,0 +1,24 @@
+require 'rails_helper'
+
+RSpec.describe Private::Conversation, type: :model do
+ context "Scopes" do
+ it "gets a conversation between users" do
+ user1 = create(:user)
+ user2 = create(:user)
+ create(:private_conversation, recipient_id: user1.id, sender_id: user2.id)
+ conversation = Private::Conversation.between_users(user1.id, user2.id)
+ expect(conversation.count).to eq 1
+ end
+ end
+ context "Methods" do
+ it "gets an opposed user of the conversation" do
+ user1 = create(:user)
+ user2 = create(:user)
+ conversation = create(:private_conversation,
+ recipient_id: user1.id,
+ sender_id: user2.id)
+ opposed_user = conversation.opposed_user(user1)
+ expect(opposed_user).to eq user2
+ end
+ end
+end
diff --git a/spec/models/private/message_spec.rb b/spec/models/private/message_spec.rb
new file mode 100644
index 0000000..fb899c9
--- /dev/null
+++ b/spec/models/private/message_spec.rb
@@ -0,0 +1,5 @@
+require 'rails_helper'
+
+RSpec.describe Private::Message, type: :model do
+ pending "add some examples to (or delete) #{__FILE__}"
+end