Skip to content

Commit

Permalink
Merge pull request #223 from mjtko/kandan-131
Browse files Browse the repository at this point in the history
Add ownership to channels and associated enhancements
  • Loading branch information
fusion94 committed Mar 19, 2013
2 parents df8f166 + 83de39a commit 646bc8f
Show file tree
Hide file tree
Showing 25 changed files with 341 additions and 113 deletions.
1 change: 1 addition & 0 deletions Gemfile
Expand Up @@ -11,6 +11,7 @@ gem 'pg'

# Auth gems
gem 'devise'
gem 'cancan'

# Server/transport gems
gem 'thin'
Expand Down
2 changes: 2 additions & 0 deletions Gemfile.lock
Expand Up @@ -49,6 +49,7 @@ GEM
bourne (1.2.1)
mocha (= 0.12.7)
builder (3.0.4)
cancan (1.6.9)
capybara (2.0.2)
mime-types (>= 1.16)
nokogiri (>= 1.3.3)
Expand Down Expand Up @@ -295,6 +296,7 @@ DEPENDENCIES
binding_of_caller
bootstrap-sass (~> 2.3.0.1)
bourbon
cancan
capybara
coffee-rails
coveralls
Expand Down
3 changes: 2 additions & 1 deletion app/assets/javascripts/backbone/collections/users.js.coffee
@@ -1 +1,2 @@

class Kandan.Collections.Users extends Backbone.Collection
url: "users"
9 changes: 9 additions & 0 deletions app/assets/javascripts/backbone/helpers/channels.js.coffee
@@ -1,4 +1,13 @@
class Kandan.Helpers.Channels
@all: (options)->
$(document).data("channels")

@getCollection: ->
$(document).data("channelsCollection")

@setCollection: (collection)->
$(document).data("channelsCollection", collection)
$(document).data("channels", collection.toJSON())

@options:
autoScrollThreshold: 0.90
Expand Down
6 changes: 6 additions & 0 deletions app/assets/javascripts/backbone/helpers/users.js.coffee
@@ -1,3 +1,9 @@
class Kandan.Helpers.Users
@all: (options)->
$(document).data("users")

@setFromCollection: (collection)->
$(document).data("users", collection.toJSON())

@currentUser: ()->
$.data(document, 'current-user')
66 changes: 44 additions & 22 deletions app/assets/javascripts/backbone/kandan.js.coffee
Expand Up @@ -61,11 +61,9 @@ window.Kandan =
Kandan.Helpers.Utils.browserTabFocused = false
)


initBroadcasterAndSubscribe: ()->
Kandan.broadcaster = eval "new Kandan.Broadcasters.#{@options().broadcaster.name}Broadcaster()"
Kandan.broadcaster.subscribe "/channels/*"
@registerAppEvents()

initTabs: ()->
$('#kandan').tabs({
Expand Down Expand Up @@ -97,7 +95,6 @@ window.Kandan =
<span class="tab_left"></span>
<span class="tab_content">
<cite>#{label}</cite>
<cite class="close_channel" title="close channel">x</cite>
</span>
</a>
</li>
Expand All @@ -108,22 +105,22 @@ window.Kandan =
chatArea = new Kandan.Views.ChatArea({channels: channels})
$(".main-area").append(chatArea.render().el)

onFetchChannels: (callback) ->
(channels) ->
Kandan.Helpers.Channels.setCollection(channels)
callback()

onFetchActiveUsers: (channels)=>
return (activeUsers)=>
onFetchActiveUsers: (callback) ->
(activeUsers) ->
if not Kandan.Helpers.ActiveUsers.collectionHasCurrentUser(activeUsers)
activeUsers.add([Kandan.Helpers.Users.currentUser()])

Kandan.Helpers.ActiveUsers.setFromCollection(activeUsers)
Kandan.registerPlugins()
Kandan.Plugins.initAll()
Kandan.initChatArea(channels)
Kandan.initTabs()
Kandan.Widgets.initAll()
Kandan.Helpers.Channels.scrollToLatestMessage()
Kandan.Plugins.Mentions.initUsersMentions(Kandan.Helpers.ActiveUsers.all())
Kandan.Plugins.Emojis.attachToChatbox()
return
callback()

onFetchUsers: (callback) ->
(users) ->
Kandan.Helpers.Users.setFromCollection(users)
callback()

registerUtilityEvents: ()->
window.setInterval(=>
Expand All @@ -132,11 +129,36 @@ window.Kandan =
, @options().timestamp_refresh_interval)

init: ->
channels = new Kandan.Collections.Channels()
channels.fetch({
success: (channelsCollection)=>
@initBroadcasterAndSubscribe()
activeUsers = new Kandan.Collections.ActiveUsers()
activeUsers.fetch({success: @onFetchActiveUsers(channelsCollection)})
})
asyncInitializers = [
(callback) => new Kandan.Collections.Channels().fetch { success: @onFetchChannels(callback) }
(callback) => new Kandan.Collections.ActiveUsers().fetch { success: @onFetchActiveUsers(callback) }
(callback) => new Kandan.Collections.Users().fetch { success: @onFetchUsers(callback) }
]
# The initializer callback should only be called after all
# asynchronous initialization has been completed.
syncInitializer = @callAfter asyncInitializers.length, =>
@registerPlugins()
Kandan.Plugins.initAll()
@initChatArea(Kandan.Helpers.Channels.getCollection())
@initTabs()
Kandan.Widgets.initAll()
Kandan.Helpers.Channels.scrollToLatestMessage()
Kandan.Plugins.Mentions.initUsersMentions(Kandan.Helpers.ActiveUsers.all())
Kandan.Plugins.Emojis.attachToChatbox()

# Call the asynchronous initializers, passing the synchronous
# initializer in as the callback to execute after all asynchrnous
# initialization is complete.
_(asyncInitializers).each (f) -> f.call(@, syncInitializer)

# The following intiialization routines don't require deferred
# initialization and can be executed immediately.
@registerUtilityEvents()
@initBroadcasterAndSubscribe()
@registerAppEvents()

# Create a function that is fired only after it has been attempted
# `limit` times.
callAfter: (limit, callback) ->
finishedCalls = 0
-> callback() if ++finishedCalls == limit
9 changes: 8 additions & 1 deletion app/assets/javascripts/backbone/models/channel.js.coffee
Expand Up @@ -8,4 +8,11 @@ class Kandan.Models.Channel extends Backbone.Model
activities.add(response.activities)
@activities = activities
@moreActivities = response.more_activities
response
response

isDestroyable: ->
current_user = _(Kandan.Helpers.Users.all()).find (u) ->
u.id == Kandan.Helpers.Users.currentUser().id
@get('id') != 1 &&
current_user.is_admin ||
@get('user_id') == current_user.id
17 changes: 11 additions & 6 deletions app/assets/javascripts/backbone/views/channel_pane.js.coffee
Expand Up @@ -2,23 +2,28 @@ class Kandan.Views.ChannelPane extends Backbone.View
tagName: 'div'

render: (container)->
container = container || $(@el)
$(container).html @paginatedActivitiesView()
$(container).append @chatboxView()
@setIdAndData(container)
$container = $(container || @el)
$container.html @paginatedActivitiesView()
$container.append @chatboxView()
@setIdAndData($container)
$li = $("a[href=#channels-#{@options.channel.get('id')}]").parent()
if @options.channel.isDestroyable()
$li.addClass 'destroyable'
$li.find('cite').after '<cite class="close_channel" title="close channel">x</cite>'
else
$li.addClass 'protected'
Kandan.Helpers.Audio.createAudioChannel(@options.channel.get('id'))
@

setIdAndData: (container)->
$(container).attr "id", "channels-#{@options.channel.get("id")}"
$(container).attr "class", "channels-pane"
$(container).data "channel-id", @options.channel.get('id')

paginatedActivitiesView: ()->
view = new Kandan.Views.PaginatedActivities({channel: @options.channel})
view.render().el

chatboxView: ()->
view = new Kandan.Views.Chatbox({channel: @options.channel})
view.render().el

Expand Up @@ -33,7 +33,6 @@ class Kandan.Views.ChannelTabs extends Backbone.View
error: (model, response)->
_.each(JSON.parse(response.responseText), alert);
})
console.log "create channel: #{channelName}"
return false

deleteChannel: (event)->
Expand Down
3 changes: 0 additions & 3 deletions app/assets/templates/channel_tabs.jst.eco
Expand Up @@ -5,9 +5,6 @@
<span class="tab_left"></span>
<span class="tab_content">
<cite><%= channel.get('name') %></cite>
<% unless channel.get('id') == 1: %>
<cite class="close_channel" title="close channel">x</cite>
<% end %>
</span>
</a>
</li>
Expand Down
19 changes: 13 additions & 6 deletions app/controllers/channels_controller.rb
@@ -1,9 +1,11 @@
class ChannelsController < ApplicationController
before_filter :authenticate_user!
before_filter :find_channel_by_name, :only => :show
load_and_authorize_resource
before_filter :set_channel_owner, only: :create

def index
# NOTE Eager loading doesn't respect limit
@channels = Channel.find(:all)
nested_channel_data = []

# TODO this can be shortened
Expand All @@ -25,7 +27,6 @@ def index
end

def create
@channel = Channel.new(params[:channel])
respond_to do |format|
if @channel.save
format.json { render :json => @channel, :status => :created }
Expand All @@ -36,14 +37,12 @@ def create
end

def show
@channel = Channel.where("LOWER(name) = ?", params[:id].downcase).first || Channel.find(params[:id])
respond_to do |format|
format.json { render :json => @channel }
end
end

def update
@channel = Channel.find(params[:id])
respond_to do |format|
if @channel.update_attributes(params[:channel])
format.json { render :json => @channel, :status => :ok }
Expand All @@ -54,10 +53,18 @@ def update
end

def destroy
@channel = Channel.find params[:id]
@channel.destroy if not @channel.id == 1
@channel.destroy
respond_to do |format|
format.json { render :json => nil, :status => :ok}
end
end

private
def find_channel_by_name
@channel = Channel.where("LOWER(name) = ?", params['id'].downcase).first
end

def set_channel_owner
@channel.user = current_user
end
end
22 changes: 22 additions & 0 deletions app/controllers/users_controller.rb
@@ -0,0 +1,22 @@
class UsersController < ApplicationController
before_filter :authenticate_user!
before_filter :set_as_current_user_if_me, :only => :show
load_and_authorize_resource

def index
respond_to do |format|
format.json { render :json => @users }
end
end

def show
respond_to do |format|
format.json { render :json => @user }
end
end

private
def set_as_current_user_if_me
@user = current_user if params[:id] = 'me'
end
end
26 changes: 26 additions & 0 deletions app/models/ability.rb
@@ -0,0 +1,26 @@
#==============================================================================
# This file is part of Kandan.
# https://github.com/kandanapp/kandan
#
# Kandan's code and assets are dual-licensed. Kandan is available
# generally under the AGPL, and also under a custom license via
# special agreement. See LICENSE for the AGPL terms, and contact us
# at <admin@kandanapp.com> if you're interested in development of
# Kandan under a custom license.
#==============================================================================
class Ability
include CanCan::Ability

def initialize(user)
if user.is_admin
can :manage, :all
else
can [:read, :create], Channel
can :manage, Channel, :user => user
can :manage, User, :id => user.id # can manage themselves
end
can :read, User
# This goes last in order to override all other permissions.
cannot :destroy, Channel, :id => 1
end
end
4 changes: 3 additions & 1 deletion app/models/channel.rb
@@ -1,9 +1,11 @@
class Channel < ActiveRecord::Base
has_many :activities, :dependent => :destroy
has_many :attachments, :dependent => :destroy
belongs_to :user

validates :name, :presence => { :message => "Room name cannot be blank"}, :uniqueness => { :message => "Room name is already taken" }

validates :user, :presence => { :message => "Room must belong to a user"}

before_create :ensure_app_max_rooms

def ensure_app_max_rooms
Expand Down
2 changes: 2 additions & 0 deletions config/routes.rb
Expand Up @@ -19,6 +19,8 @@
resources :attachments
end

resources :users, :only => [:index, :show]

get "/active_users" => "apis#active_users"
get "/me" => "apis#me"

Expand Down
6 changes: 6 additions & 0 deletions db/migrate/20130315214129_add_user_to_channels.rb
@@ -0,0 +1,6 @@
class AddUserToChannels < ActiveRecord::Migration
def change
add_column :channels, :user_id, :integer
add_index :channels, :user_id
end
end
7 changes: 5 additions & 2 deletions db/schema.rb
Expand Up @@ -11,7 +11,7 @@
#
# It's strongly recommended to check this file into your version control system.

ActiveRecord::Schema.define(:version => 20130301022941) do
ActiveRecord::Schema.define(:version => 20130315214129) do

create_table "activities", :force => true do |t|
t.text "content"
Expand All @@ -38,8 +38,11 @@
t.text "name"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
t.integer "user_id"
end

add_index "channels", ["user_id"], :name => "index_channels_on_user_id"

create_table "sessions", :force => true do |t|
t.string "session_id", :null => false
t.text "data"
Expand Down Expand Up @@ -83,4 +86,4 @@
add_index "users", ["authentication_token"], :name => "index_users_on_authentication_token", :unique => true
add_index "users", ["email"], :name => "index_users_on_email", :unique => true

end
end
2 changes: 1 addition & 1 deletion lib/tasks/kandan.rake
Expand Up @@ -45,7 +45,7 @@ namespace :kandan do

if channel.nil?
puts "Creating default channel..."
channel = Channel.create :name => "Lobby"
channel = Channel.create! name: 'Lobby', user: user

["Welcome to Kandan, the slickest chat app out there. Brought to you by the good people of KandanApp (http://kandanapp.com) and friends",
"We think you'll really like Kandan, but if there's anything you would like to see, Kandan is fully open source, so you can dive into it or make suggestions.",
Expand Down

0 comments on commit 646bc8f

Please sign in to comment.