diff --git a/app/javascript/chat/content.jsx b/app/javascript/chat/content.jsx
index 9cef6f48a..75b6bc102 100644
--- a/app/javascript/chat/content.jsx
+++ b/app/javascript/chat/content.jsx
@@ -2,11 +2,12 @@ import { h, Component } from 'preact';
import PropTypes from 'prop-types';
import CodeEditor from './codeEditor';
import GithubRepo from './githubRepo';
+import ChannelDetails from './channelDetails';
+import UserDetails from './userDetails';
export default class Content extends Component {
static propTypes = {
resource: PropTypes.object,
- onExit: PropTypes.func,
activeChannelId: PropTypes.number,
pusherKey: PropTypes.string,
};
@@ -15,10 +16,12 @@ export default class Content extends Component {
return ""
} else {
return (
-
+
{display(this.props)}
@@ -43,20 +46,7 @@ function display(props) {
display: "block",
backgroundColor: "#f5f6f7"}}>
} else if (props.resource.type_of === "user") {
- return

-
-
- {props.resource.summary}
-
-
+ return
} else if (props.resource.type_of === "article") {
return (
@@ -80,6 +70,11 @@ function display(props) {
githubToken={props.githubToken}
resource={props.resource}
/>
+ } else if (props.resource.type_of === "channel-details") {
+ return
} else if (props.resource.type_of === "code_editor") {
return
+
+
+ {user.summary}
+
+
+ }
+
+}
\ No newline at end of file
diff --git a/app/javascript/chat/view.jsx b/app/javascript/chat/view.jsx
new file mode 100644
index 000000000..5781a51b6
--- /dev/null
+++ b/app/javascript/chat/view.jsx
@@ -0,0 +1,26 @@
+
+
+import { h, Component } from 'preact';
+
+export default class View extends Component {
+
+ render() {
+ const channels = this.props.channels.map((channel) => {
+ return
{channel.channel_name}
+
+
+
+ });
+ return
+
+
Channel Invitations
+ Invitations are a work in progress ❤️
+ {channels}
+
+ }
+
+}
\ No newline at end of file
diff --git a/app/models/chat_channel.rb b/app/models/chat_channel.rb
index 76b3716e6..784acffe6 100644
--- a/app/models/chat_channel.rb
+++ b/app/models/chat_channel.rb
@@ -5,6 +5,13 @@ class ChatChannel < ApplicationRecord
has_many :messages
has_many :chat_channel_memberships
has_many :users, through: :chat_channel_memberships
+
+ has_many :active_memberships, -> { where status: "active" }, class_name: 'ChatChannelMembership'
+ has_many :pending_memberships, -> { where status: "pending" }, class_name: 'ChatChannelMembership'
+ has_many :rejected_memberships, -> { where status: "rejected" }, class_name: 'ChatChannelMembership'
+ has_many :active_users, :through => :active_memberships, class_name: "User", :source => :user
+ has_many :pending_users, :through => :pending_memberships, class_name: "User", :source => :user
+ has_many :rejected_users, :through => :rejected_memberships, class_name: "User", :source => :user
validates :channel_type, presence: true, inclusion: { in: %w(open invite_only direct) }
validates :status, presence: true, inclusion: { in: %w(active inactive) }
@@ -28,7 +35,7 @@ class ChatChannel < ApplicationRecord
end
def has_member?(user)
- users.include?(user)
+ active_users.include?(user)
end
def last_opened_at(user = nil)
@@ -79,7 +86,6 @@ class ChatChannel < ApplicationRecord
end
end
-
def adjusted_slug(user = nil, caller_type="reciever")
user ||= current_user
if channel_type == "direct" && caller_type == "reciever"
@@ -92,7 +98,7 @@ class ChatChannel < ApplicationRecord
end
def viewable_by
- chat_channel_memberships.pluck(:user_id)
+ active_memberships.pluck(:user_id)
end
def messages_count
@@ -100,15 +106,16 @@ class ChatChannel < ApplicationRecord
end
def channel_human_names
- chat_channel_memberships.
- order("last_opened_at DESC").limit(20).includes(:user).map do |m|
+ active_memberships.
+ order("last_opened_at DESC").limit(5).includes(:user).map do |m|
m.user.name
end
end
def channel_users
+ # Purely for algolia indexing
pics_obj = {}
- chat_channel_memberships.
+ active_memberships.
order("last_opened_at DESC").limit(80).includes(:user).each_with_index do |m, i|
pics_obj[m.user.username] = {
profile_image: i < 11 ? ProfileImage.new(m.user).get(90) : nil,
diff --git a/app/models/chat_channel_membership.rb b/app/models/chat_channel_membership.rb
index 95b4df4af..48959af92 100644
--- a/app/models/chat_channel_membership.rb
+++ b/app/models/chat_channel_membership.rb
@@ -4,6 +4,7 @@ class ChatChannelMembership < ApplicationRecord
validates :user_id, presence: true, uniqueness: { scope: :chat_channel_id }
validates :chat_channel_id, presence: true, uniqueness: { scope: :user_id }
+ validates :status, inclusion: { in: %w{active inactive pending rejected left_channel} }
validate :permission
private
diff --git a/app/policies/chat_channel_membership_policy.rb b/app/policies/chat_channel_membership_policy.rb
new file mode 100644
index 000000000..9f93edaf8
--- /dev/null
+++ b/app/policies/chat_channel_membership_policy.rb
@@ -0,0 +1,6 @@
+class ChatChannelMembershipPolicy < ApplicationPolicy
+
+ def update?
+ record.present? && user.id == record.user_id
+ end
+end
\ No newline at end of file
diff --git a/app/policies/chat_channel_policy.rb b/app/policies/chat_channel_policy.rb
index c3267c8c3..34eb5a1c4 100644
--- a/app/policies/chat_channel_policy.rb
+++ b/app/policies/chat_channel_policy.rb
@@ -4,6 +4,14 @@ class ChatChannelPolicy < ApplicationPolicy
user
end
+ def create?
+ true
+ end
+
+ def update?
+ user_can_edit_channel
+ end
+
def moderate?
!user_is_banned? && user_is_admin?
end
@@ -16,8 +24,17 @@ class ChatChannelPolicy < ApplicationPolicy
user_part_of_channel
end
+ def permitted_attributes
+ %i[channel_name slug command]
+ end
+
+
private
+ def user_can_edit_channel
+ record.present? && user.has_role?(:super_admin)
+ end
+
def user_part_of_channel_or_open
record.present? && (record.channel_type == "open" || record.has_member?(user))
end
diff --git a/app/services/chat_channel_creation_service.rb b/app/services/chat_channel_creation_service.rb
new file mode 100644
index 000000000..653f6dede
--- /dev/null
+++ b/app/services/chat_channel_creation_service.rb
@@ -0,0 +1,13 @@
+class ChatChannelCreationService
+ attr_accessor :user, :params
+
+ def initialize(user, params)
+ @user = user
+ @params = params
+ end
+
+ def create
+ user.chat_channels.create(channel_type: "invite_only",
+ channel_name: params[:channel_name], slug: params[:slug])
+ end
+end
\ No newline at end of file
diff --git a/app/services/chat_channel_update_service.rb b/app/services/chat_channel_update_service.rb
new file mode 100644
index 000000000..2501af339
--- /dev/null
+++ b/app/services/chat_channel_update_service.rb
@@ -0,0 +1,12 @@
+class ChatChannelUpdateService
+ attr_accessor :chat_channel, :params
+
+ def initialize(chat_channel, params)
+ @chat_channel = chat_channel
+ @params = params
+ end
+
+ def update
+ chat_channel.update(params)
+ end
+end
\ No newline at end of file
diff --git a/app/views/api/v0/users/show.json.jbuilder b/app/views/api/v0/users/show.json.jbuilder
index 87437810b..689836e79 100644
--- a/app/views/api/v0/users/show.json.jbuilder
+++ b/app/views/api/v0/users/show.json.jbuilder
@@ -1,4 +1,5 @@
json.type_of "user"
+json.id @user.id
json.username @user.username
json.name @user.name
json.summary @user.summary
diff --git a/app/views/chat_channels/index.json.jbuilder b/app/views/chat_channels/index.json.jbuilder
index 14b3141c9..90af37b19 100644
--- a/app/views/chat_channels/index.json.jbuilder
+++ b/app/views/chat_channels/index.json.jbuilder
@@ -7,4 +7,5 @@ json.array! (@chat_channels_memberships.sort_by { |m| m.chat_channel.last_messag
json.last_opened_at membership.chat_channel.last_opened_at
json.last_message_at membership.chat_channel.last_message_at
json.adjusted_slug membership.chat_channel.adjusted_slug(current_user)
+ json.membership_id membership.id
end
diff --git a/config/routes.rb b/config/routes.rb
index 76358a6f3..4c7d3e33b 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -72,7 +72,8 @@ Rails.application.routes.draw do
end
resources :messages, only: [:create]
- resources :chat_channels, only: [:index, :show]
+ resources :chat_channels, only: [:index, :show, :create, :update]
+ resources :chat_channel_memberships, only: [ :create, :update]
resources :articles, only: [:update,:create,:destroy]
resources :comments, only: [:create,:update,:destroy]
resources :users, only: [:update]
diff --git a/db/migrate/20180629201047_add_status_to_chat_channel_memberships.rb b/db/migrate/20180629201047_add_status_to_chat_channel_memberships.rb
new file mode 100644
index 000000000..8f19c4906
--- /dev/null
+++ b/db/migrate/20180629201047_add_status_to_chat_channel_memberships.rb
@@ -0,0 +1,5 @@
+class AddStatusToChatChannelMemberships < ActiveRecord::Migration[5.1]
+ def change
+ add_column :chat_channel_memberships, :status, :string, default: "active"
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index fa088fe27..f3aa2b194 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: 20180624230435) do
+ActiveRecord::Schema.define(version: 20180629201047) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -167,6 +167,7 @@ ActiveRecord::Schema.define(version: 20180624230435) do
t.datetime "created_at", null: false
t.boolean "has_unopened_messages", default: false
t.datetime "last_opened_at", default: "2017-01-01 05:00:00"
+ t.string "status", default: "active"
t.datetime "updated_at", null: false
t.bigint "user_id", null: false
t.index ["chat_channel_id"], name: "index_chat_channel_memberships_on_chat_channel_id"
diff --git a/spec/factories/chat_channel_memberships.rb b/spec/factories/chat_channel_memberships.rb
new file mode 100644
index 000000000..5537885f1
--- /dev/null
+++ b/spec/factories/chat_channel_memberships.rb
@@ -0,0 +1,4 @@
+FactoryBot.define do
+ factory :chat_channel_membership do
+ end
+end
diff --git a/spec/models/chat_channel_spec.rb b/spec/models/chat_channel_spec.rb
index b158eaded..03af3b849 100644
--- a/spec/models/chat_channel_spec.rb
+++ b/spec/models/chat_channel_spec.rb
@@ -17,4 +17,13 @@ RSpec.describe ChatChannel, type: :model do
expect(chat_channel.users.size).to eq(2)
expect(chat_channel.has_member?(User.first)).to eq(true)
end
+
+ it "lists active memberships" do
+ chat_channel = ChatChannel.create_with_users([create(:user),create(:user)])
+ expect(chat_channel.active_users.size).to eq(2)
+ expect(chat_channel.channel_users.size).to eq(2)
+ ChatChannelMembership.last.update(status: "left_channel")
+ expect(chat_channel.active_users.size).to eq(1)
+ expect(chat_channel.channel_users.size).to eq(1)
+ end
end
diff --git a/spec/policies/chat_channel_membership_policy_spec.rb b/spec/policies/chat_channel_membership_policy_spec.rb
new file mode 100644
index 000000000..f8d8980ea
--- /dev/null
+++ b/spec/policies/chat_channel_membership_policy_spec.rb
@@ -0,0 +1,27 @@
+require "rails_helper"
+
+RSpec.describe ChatChannelMembershipPolicy do
+ subject { described_class.new(user, chat_channel_membership) }
+
+ let(:chat_channel_membership) { build(:chat_channel_membership) }
+
+
+ context "when user is not signed-in" do
+ let(:user) { nil }
+ it { within_block_is_expected.to raise_error(Pundit::NotAuthorizedError) }
+ end
+
+ context "when user belongs to membership" do
+ let(:user) { create(:user) }
+ let(:chat_channel) { create(:chat_channel ) }
+ let(:chat_channel_membership) { create(:chat_channel_membership, user_id: user.id, chat_channel_id: chat_channel.id ) }
+ it { is_expected.to permit_actions(%i[update]) }
+ end
+ context "when user does not belong to membership" do
+ let(:user) { create(:user) }
+ let(:other_user) { create(:user) }
+ let(:chat_channel) { create(:chat_channel ) }
+ let(:chat_channel_membership) { create(:chat_channel_membership, user_id: other_user.id, chat_channel_id: chat_channel.id ) }
+ it { is_expected.to forbid_actions(%i[update]) }
+ end
+end
\ No newline at end of file
diff --git a/spec/policies/chat_channel_policy_spec.rb b/spec/policies/chat_channel_policy_spec.rb
index d9be73279..42e32dd04 100644
--- a/spec/policies/chat_channel_policy_spec.rb
+++ b/spec/policies/chat_channel_policy_spec.rb
@@ -14,20 +14,20 @@ RSpec.describe ChatChannelPolicy do
context "when user is not a part of channel" do
let(:user) { build(:user) }
it { is_expected.to permit_actions(%i[index]) }
- it { is_expected.to forbid_actions(%i[show open moderate]) }
+ it { is_expected.to forbid_actions(%i[show open moderate update]) }
end
context "when user is a part of channel" do
let(:user) { create(:user) }
before { chat_channel.add_users [user] }
it { is_expected.to permit_actions(%i[index show open]) }
- it { is_expected.to forbid_actions(%i[moderate]) }
+ it { is_expected.to forbid_actions(%i[moderate update]) }
end
context "when user is an admin but not part of channel" do
let(:user) { create(:user) }
before { user.add_role(:super_admin) }
- it { is_expected.to permit_actions(%i[index moderate]) }
+ it { is_expected.to permit_actions(%i[index moderate update]) }
it { is_expected.to forbid_actions(%i[show open]) }
end
end
diff --git a/spec/requests/chat_channel_memberships_spec.rb b/spec/requests/chat_channel_memberships_spec.rb
new file mode 100644
index 000000000..751b2a1c5
--- /dev/null
+++ b/spec/requests/chat_channel_memberships_spec.rb
@@ -0,0 +1,53 @@
+require "rails_helper"
+
+RSpec.describe "ChatChannelMemberships", type: :request do
+ let(:user) { create(:user) }
+ let(:second_user) { create(:user) }
+ let(:chat_channel) { create(:chat_channel) }
+
+ before do
+ sign_in user
+ chat_channel.add_users([user])
+ end
+
+ describe "POST /chat_channel_memberships" do
+ it "creates chat channel invitation" do
+ user.add_role(:super_admin)
+ mems_num = ChatChannelMembership.all.size
+ post "/chat_channel_memberships", params: {user_id: second_user.id, chat_channel_id: chat_channel.id}
+ expect(ChatChannelMembership.all.size).to eq(mems_num + 1)
+ expect(ChatChannelMembership.last.status).to eq("pending")
+ end
+ it "denies chat channel invitation to non-authorized user" do
+ expect do
+ post "/chat_channel_memberships", params: {user_id: second_user.id, chat_channel_id: chat_channel.id}
+ end.to raise_error(Pundit::NotAuthorizedError)
+ end
+ end
+
+ describe "PUT /chat_channel_memberships/:id" do
+ before do
+ user.add_role(:super_admin)
+ post "/chat_channel_memberships", params: {user_id: second_user.id, chat_channel_id: chat_channel.id}
+ end
+ it "accepts chat channel invitation" do
+ membership = ChatChannelMembership.last
+ sign_in second_user
+ put "/chat_channel_memberships/#{membership.id}", params: {user_action: "accept"}
+ expect(ChatChannelMembership.find(membership.id).status).to eq("active")
+ end
+ it "rejects chat channel invitation" do
+ membership = ChatChannelMembership.last
+ sign_in second_user
+ put "/chat_channel_memberships/#{membership.id}", params: {user_action: "reject"}
+ expect(ChatChannelMembership.find(membership.id).status).to eq("rejected")
+ end
+ it "disallows non-logged-user" do
+ membership = ChatChannelMembership.last
+ expect do
+ put "/chat_channel_memberships/#{membership.id}", params: {user_action: "accept"}
+ expect(ChatChannelMembership.find(membership.id).status).to eq("active")
+ end.to raise_error(Pundit::NotAuthorizedError)
+ end
+ end
+end
\ No newline at end of file
diff --git a/spec/requests/chat_channels_spec.rb b/spec/requests/chat_channels_spec.rb
index 4e2027773..a7642d465 100644
--- a/spec/requests/chat_channels_spec.rb
+++ b/spec/requests/chat_channels_spec.rb
@@ -49,6 +49,29 @@ RSpec.describe "ChatChannels", type: :request do
end
end
+ describe "get /chat_channels?state=pending" do
+ it "returns pending channels" do
+ pending_membership = ChatChannelMembership.create(chat_channel_id: invite_channel.id,
+ user_id:user.id, status: "pending")
+ sign_in user
+ get "/chat_channels?state=pending"
+ expect(response.body).to include(invite_channel.slug)
+ end
+ it "returns no pending channels if no invites" do
+ sign_in user
+ get "/chat_channels?state=pending"
+ expect(response.body).not_to include(invite_channel.slug)
+ end
+ it "returns no pending channels if not pending" do
+ pending_membership = ChatChannelMembership.create(chat_channel_id: invite_channel.id,
+ user_id:user.id, status: "rejected")
+ sign_in user
+ get "/chat_channels?state=pending"
+ expect(response.body).not_to include(invite_channel.slug)
+ end
+ end
+
+
describe "GET /chat_channels/:id" do
context "when request is valid" do
before do
@@ -71,6 +94,48 @@ RSpec.describe "ChatChannels", type: :request do
end
end
+ describe "POST /chat_channels" do
+ it "creates chat_channel for current user" do
+ post "/chat_channels",
+ params: { chat_channel: { channel_name: "Hello Channel", slug: "hello-channel" } },
+ headers: { HTTP_ACCEPT: "application/json" }
+ expect(ChatChannel.last.slug).to eq("hello-channel")
+ expect(ChatChannel.last.active_users).to include(user)
+ end
+ it "returns errors if channel is invalid" do
+ #slug should be taken
+ post "/chat_channels",
+ params: { chat_channel: { channel_name: "HEy hey hoho", slug: chat_channel.slug } },
+ headers: { HTTP_ACCEPT: "application/json" }
+ expect(response.body).to include("Slug has already been taken")
+ end
+ end
+
+ describe "PUT /chat_channels/:id" do
+ it "updates channel for valid user" do
+ user.add_role(:super_admin)
+ put "/chat_channels/#{chat_channel.id}",
+ params: { chat_channel: { channel_name: "Hello Channel", slug: "hello-channelly" } },
+ headers: { HTTP_ACCEPT: "application/json" }
+ expect(ChatChannel.last.slug).to eq("hello-channelly")
+ end
+ it "dissallows invalid users" do
+ expect do
+ put "/chat_channels/#{chat_channel.id}",
+ params: { chat_channel: { channel_name: "Hello Channel", slug: "hello-channelly" } },
+ headers: { HTTP_ACCEPT: "application/json" }
+ end.to raise_error(Pundit::NotAuthorizedError)
+ end
+ it "returns errors if channel is invalid" do
+ #slug should be taken
+ user.add_role(:super_admin)
+ put "/chat_channels/#{chat_channel.id}",
+ params: { chat_channel: { channel_name: "HEy hey hoho", slug: invite_channel.slug } },
+ headers: { HTTP_ACCEPT: "application/json" }
+ expect(response.body).to include("Slug has already been taken")
+ end
+ end
+
describe "POST /chat_channels/:id/moderate" do
it "raises NotAuthorizedError if user is not logged in" do
expect do