From ebb5f3f4564fc92f263a7fe06fd9831522e1187a Mon Sep 17 00:00:00 2001 From: Gabriel Cebrian Date: Thu, 21 Feb 2013 15:01:52 -0800 Subject: [PATCH] Initial implementation of admin console and site settings --- Gemfile | 2 + Gemfile.lock | 12 +++ app/assets/javascripts/admin/admin.js.coffee | 2 + app/assets/javascripts/application.js | 1 + .../javascripts/backbone/kandan.js.coffee.erb | 16 --- app/assets/javascripts/layout.js.coffee | 5 + app/controllers/admin/admin_controller.rb | 27 +++++ app/controllers/admin/base_controller.rb | 12 +++ app/helpers/admin/admin_helper.rb | 9 ++ app/models/setting.rb | 63 ++++++++++++ app/models/user.rb | 33 +++++++ app/views/admin/admin/index.html.erb | 99 +++++++++++++++++++ app/views/devise/registrations/edit.html.erb | 6 ++ app/views/devise/registrations/new.html.erb | 6 ++ app/views/layouts/application.html.erb | 71 ++++++------- app/views/main/index.html.erb | 84 +++++++++------- config/application.rb | 2 + config/routes.rb | 6 ++ .../20130221014127_add_admin_flag_to_users.rb | 5 + db/migrate/20130221041719_create_settings.rb | 9 ++ .../20130221173650_add_status_to_users.rb | 5 + db/schema.rb | 10 +- lib/tasks/kandan.rake | 5 + 23 files changed, 399 insertions(+), 91 deletions(-) create mode 100644 app/assets/javascripts/admin/admin.js.coffee create mode 100644 app/assets/javascripts/layout.js.coffee create mode 100644 app/controllers/admin/admin_controller.rb create mode 100644 app/controllers/admin/base_controller.rb create mode 100644 app/helpers/admin/admin_helper.rb create mode 100644 app/models/setting.rb create mode 100644 app/views/admin/admin/index.html.erb create mode 100644 db/migrate/20130221014127_add_admin_flag_to_users.rb create mode 100644 db/migrate/20130221041719_create_settings.rb create mode 100644 db/migrate/20130221173650_add_status_to_users.rb diff --git a/Gemfile b/Gemfile index aa317b47..82a720c9 100644 --- a/Gemfile +++ b/Gemfile @@ -21,6 +21,7 @@ gem 'aws-sdk' gem 'paperclip' gem 'remotipart' gem 'jquery-rails' +gem 'enumerize' # Making the world a better, more stable place gem 'airbrake' @@ -42,6 +43,7 @@ group :development do gem 'awesome_print' gem 'better_errors' gem 'binding_of_caller' + gem 'debugger' end group :test do diff --git a/Gemfile.lock b/Gemfile.lock index 7a47d2b8..5a67ca98 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -74,9 +74,17 @@ GEM coffee-script-source execjs coffee-script-source (1.4.0) + columnize (0.3.6) cookiejar (0.3.0) daemons (1.1.9) database_cleaner (0.9.1) + debugger (1.2.3) + columnize (>= 0.3.1) + debugger-linecache (~> 1.1.1) + debugger-ruby_core_source (~> 1.1.5) + debugger-linecache (1.1.2) + debugger-ruby_core_source (>= 1.1.1) + debugger-ruby_core_source (1.1.6) devise (1.5.3) bcrypt-ruby (~> 3.0) orm_adapter (~> 0.0.3) @@ -98,6 +106,8 @@ GEM http_parser.rb (>= 0.5.3) em-socksify (0.2.1) eventmachine (>= 1.0.0.beta.4) + enumerize (0.5.1) + activesupport (>= 3.2) erubis (2.7.0) eventmachine (1.0.0) execjs (1.4.0) @@ -271,9 +281,11 @@ DEPENDENCIES cloudfuji coffee-rails database_cleaner + debugger devise devise_cloudfuji_authenticatable eco + enumerize execjs factory_girl_rails faker diff --git a/app/assets/javascripts/admin/admin.js.coffee b/app/assets/javascripts/admin/admin.js.coffee new file mode 100644 index 00000000..0aa20a9f --- /dev/null +++ b/app/assets/javascripts/admin/admin.js.coffee @@ -0,0 +1,2 @@ +$(document).ready -> + diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 3c976233..0ce01f95 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -19,5 +19,6 @@ //= require lib/backbone //= require backbone/kandan //= require_tree . +//= stub admin/admin //= require lib/jquery.atwho //= require lib/jquery.caret \ No newline at end of file diff --git a/app/assets/javascripts/backbone/kandan.js.coffee.erb b/app/assets/javascripts/backbone/kandan.js.coffee.erb index 24323672..ab7ef9de 100644 --- a/app/assets/javascripts/backbone/kandan.js.coffee.erb +++ b/app/assets/javascripts/backbone/kandan.js.coffee.erb @@ -123,29 +123,13 @@ window.Kandan = Kandan.Plugins.Mentions.initUsersMentions(Kandan.Helpers.ActiveUsers.all()) return - setCurrentUser: ()-> - template = JST['current_user'] - currentUser = Kandan.Helpers.Users.currentUser() - displayName = "#{currentUser.first_name} #{currentUser.last_name}" if currentUser.first_name? - displayName ||= currentUser.email - $(".header .user").html template({ - gravatarHash: currentUser.gravatar_hash, - name: displayName - }) - registerUtilityEvents: ()-> window.setInterval(=> for el in $(".posted_at") $(el).text (new Date($(el).data("timestamp"))).toRelativeTime(@options.nowThreshold) , @options.timestampRefreshInterval) - $(".user_menu_link").click (e)-> - e.preventDefault() - $(".user_menu").toggle() - false - init: -> - @setCurrentUser() channels = new Kandan.Collections.Channels() channels.fetch({ success: (channelsCollection)=> diff --git a/app/assets/javascripts/layout.js.coffee b/app/assets/javascripts/layout.js.coffee new file mode 100644 index 00000000..1784d49b --- /dev/null +++ b/app/assets/javascripts/layout.js.coffee @@ -0,0 +1,5 @@ +$(document).ready -> + $(".user_menu_link").click (e)-> + e.preventDefault() + $(".user_menu").toggle() + false \ No newline at end of file diff --git a/app/controllers/admin/admin_controller.rb b/app/controllers/admin/admin_controller.rb new file mode 100644 index 00000000..60a0ccd5 --- /dev/null +++ b/app/controllers/admin/admin_controller.rb @@ -0,0 +1,27 @@ +module Admin + class AdminController < BaseController + + def index + @settings = Setting.my_settings + @all_users = User.find(:all, :conditions => ["id != ?", current_user.id]) + + # Note that this reject! will remove users from all_users in order to show users in 2 different tables + @waiting_for_approval_users = @all_users.reject!{|user| user.status.waiting_approval? } || [] + end + + def update + + max_rooms = params[:setting][:max_rooms].to_i + public_site = params[:setting][:public_site] == "1" + + Setting.set_values(:max_rooms => max_rooms, :public_site => public_site) + + redirect_to :admin_root + end + + def update_users + + end + + end +end \ No newline at end of file diff --git a/app/controllers/admin/base_controller.rb b/app/controllers/admin/base_controller.rb new file mode 100644 index 00000000..120005f8 --- /dev/null +++ b/app/controllers/admin/base_controller.rb @@ -0,0 +1,12 @@ +module Admin + class BaseController < ApplicationController + before_filter :authenticate_admin! + + private + + def authenticate_admin! + redirect_to root_url unless current_user.try(:is_admin?) + end + + end +end \ No newline at end of file diff --git a/app/helpers/admin/admin_helper.rb b/app/helpers/admin/admin_helper.rb new file mode 100644 index 00000000..d5a3ac83 --- /dev/null +++ b/app/helpers/admin/admin_helper.rb @@ -0,0 +1,9 @@ +module Admin::AdminHelper + def user_status user + "
#{user.status}
".html_safe + end + + def user_action user + + end +end \ No newline at end of file diff --git a/app/models/setting.rb b/app/models/setting.rb new file mode 100644 index 00000000..308a9641 --- /dev/null +++ b/app/models/setting.rb @@ -0,0 +1,63 @@ +class Setting < ActiveRecord::Base + attr_accessible :values + serialize :values, Hash + + before_create :set_default_values + before_create :ensure_only_one_settings + before_destroy :ensure_only_one_settings + + before_save :validate_max_rooms, :validate_public_site + + def max_rooms + self.values[:max_rooms] + end + + def public_site? + self.values[:public_site] + end + + alias_method :public_site, :public_site? + + def set_default_values + self.values.merge!(self.class.default_values) + end + + def ensure_only_one_settings + return false if Setting.count >= 1 + end + + # Making sure the max_rooms is an integer and is never less than the current number of rooms + def validate_max_rooms + self.values[:max_rooms].is_a?(Integer) && self.values[:max_rooms] >= Channel.count unless self.new_record? + end + + # Making sure the public site is a boolean + def validate_public_site + !!self.values[:public_site] == self.values[:public_site] unless self.new_record? + end + + def self.default_values + return {:max_rooms => 99, :public_site => false } + end + + # Helper methods to be used while we don't need to deal with multi-tenancy + def self.my_settings + self.first_or_create + end + + def self.get_value(k) + setting = self.my_settings + setting.values[k] + end + + def self.set_values(values) + setting = self.my_settings + + values.each do |k,v| + setting.values[k.to_sym] = v + end + + setting.save! + end + +end diff --git a/app/models/user.rb b/app/models/user.rb index 07771d20..6a75b140 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,10 +1,20 @@ class User < ActiveRecord::Base + extend Enumerize + + # Being pesimistic here and making the default waiting for approval for security reasons + enumerize :status, in: [:active, :suspended, :waiting_approval], :default => :waiting_approval has_many :activities before_save :ensure_authentication_token before_save :ensure_gravatar_hash + before_save :mark_status_depending_on_app_settings + after_create :ensure_at_least_one_admin + after_destroy :ensure_at_least_one_admin + validates :username, :presence => true, :uniqueness => true + validates :first_name, :presence => true + validates :last_name, :presence => true # Kandan.devise_modules is defined in config/initializers/kandan.rb @@ -13,6 +23,14 @@ class User < ActiveRecord::Base # Setup accessible (or protected) attributes for your model attr_accessible :id, :username, :email, :password, :password_confirmation, :remember_me, :first_name, :last_name, :locale, :gravatar_hash + def full_name + "#{self.first_name.to_s} #{self.last_name.to_s}".titlecase + end + + def full_name_or_username + self.full_name.blank? ? self.username : self.full_name + end + def cloudfuji_extra_attributes(extra_attributes) self.first_name = extra_attributes["first_name"].to_s self.last_name = extra_attributes["last_name"].to_s @@ -20,10 +38,25 @@ def cloudfuji_extra_attributes(extra_attributes) self.locale = extra_attributes["locale"] end + # Callback to mark the user status depending on the settings of the app + def mark_status_depending_on_app_settings + # If the site is public we will make the user active. Otherwise we will make the user as waiting_approval + self.status = Setting.my_settings.public_site? ? :active : :waiting_approval + end + def ensure_gravatar_hash self.gravatar_hash = Digest::MD5.hexdigest self.email end + # We never want an app without an admin so let's ensure there is at least one user + def ensure_at_least_one_admin + if User.count == 1 + u = User.first + u.is_admin = true + u.save! + end + end + def active_for_authentication? super && active? end diff --git a/app/views/admin/admin/index.html.erb b/app/views/admin/admin/index.html.erb new file mode 100644 index 00000000..0cf90d40 --- /dev/null +++ b/app/views/admin/admin/index.html.erb @@ -0,0 +1,99 @@ +<% content_for :javascript_includes do %> + <%= javascript_include_tag "admin/admin" %> +<% end %> + +
+ <%= form_for @settings, :url => admin_update_path do |f| %> + <%= f.label :max_rooms, 'Max number of rooms' %>: + <%= f.text_field :max_rooms %>
+ <%= f.label :public_site, 'Is this a public site?' %>: + <%= f.check_box :public_site %>
+ <%= f.submit %> + <% end %> + +
+ <% if @waiting_for_approval_users.any? %> + + + + + + + + + + + + + <% @all_users.each do |user| %> + + + + + + + + + <% end %> + +
UsernameFirst NameLast NameEmailStatusAction
+ <%= user.username %> + + <%= user.first_name %> + + <%= user.last_name %> + + <%= user.email %> + + <%= user_status(user) %> + + +
+ <% else %> +
There are no users. Invite others to join Kandan!
+ <% end %> +
+ +
+ <% if @all_users.any? %> + + + + + + + + + + + + + <% @all_users.each do |user| %> + + + + + + + + + <% end %> + +
UsernameFirst NameLast NameEmailStatusAction
+ <%= user.username %> + + <%= user.first_name %> + + <%= user.last_name %> + + <%= user.email %> + + <%= user_status(user) %> + + +
+ <% else %> +
There are no users. Invite others to join Kandan!
+ <% end %> +
+
\ No newline at end of file diff --git a/app/views/devise/registrations/edit.html.erb b/app/views/devise/registrations/edit.html.erb index b6e25cb2..4edc3f8d 100644 --- a/app/views/devise/registrations/edit.html.erb +++ b/app/views/devise/registrations/edit.html.erb @@ -5,6 +5,12 @@ <%= form_for(resource, :as => resource_name, :url => registration_path(resource_name), :html => { :method => :put }) do |f| %> <%= devise_error_messages! %> +

<%= f.label :first_name %>
+ <%= f.text_field :first_name %>

+ +

<%= f.label :last_name %>
+ <%= f.text_field :last_name %>

+

<%= f.label :email %>
<%= f.email_field :email %>

diff --git a/app/views/devise/registrations/new.html.erb b/app/views/devise/registrations/new.html.erb index 508b0c8f..449cd698 100644 --- a/app/views/devise/registrations/new.html.erb +++ b/app/views/devise/registrations/new.html.erb @@ -5,6 +5,12 @@ <%= form_for(resource, :as => resource_name, :url => registration_path(resource_name)) do |f| %> <%= devise_error_messages! %> +

<%= f.label :first_name %>
+ <%= f.text_field :first_name %>

+ +

<%= f.label :last_name %>
+ <%= f.text_field :last_name %>

+

<%= f.label :email %>
<%= f.email_field :email %>

diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index cbfbc1fc..a740deac 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -6,52 +6,43 @@ <%= stylesheet_link_tag "http://fonts.googleapis.com/css?family=PT+Sans:400,700" %> <%= stylesheet_link_tag "application", :media => "all" %> <%= favicon_link_tag %> - - <%= airbrake_javascript_notifier %> - - <%- Kandan::Config.broadcaster.assets.each do |asset| %> - <%= javascript_include_tag asset %> - <%- end %> - <%= javascript_include_tag "application" %> - <%= javascript_tag do %> - <%- if user_signed_in? %> - <%- current_user_data = { - :id => current_user.id, - :first_name => current_user.first_name, - :last_name => current_user.last_name, - :email => current_user.email, - :username => current_user.username, - :auth_token => current_user.authentication_token, - :gravatar_hash => current_user.gravatar_hash - } - %> - $.data(document, "current-user", <%= current_user_data.to_json.html_safe %>); - <%- end %> - $(document).data("active-users", []) - <%- end %> + <%= airbrake_javascript_notifier %> + <%= yield :javascript_includes %> <%= csrf_meta_tags %> +
+ +
+ <%= link_to 'Admin console', admin_root_path if current_user.is_admin? %> + <%= link_to 'Edit Account', users_edit_path, @users_edit %>
+ <%= link_to 'Logout', destroy_user_session_path, :method => :delete %>
+
+ <%= link_to 'About Kandan', about_path, @about %> +
+ <% else %> +
+ <% end %> +
- <%= yield %> - - - - <%- if user_signed_in? && Cloudfuji::Platform.on_cloudfuji? %> - <%= javascript_tag do %> - <%- cloudfuji_help_vars = { - "HUBOT_KANDAN_TOKEN" => User.find_by_email("hubot@cloudfuji.com").try(:authentication_token), - "HUBOT_KANDAN_HOST" => ENV['CLOUDFUJI_DOMAIN'] - } - %> + <%= yield %> - window._cloudfuji_help = <%= cloudfuji_help_vars.to_json.html_safe %>; - <%- end %> - <%- end %> +
+ diff --git a/app/views/main/index.html.erb b/app/views/main/index.html.erb index c2efd176..b500042f 100644 --- a/app/views/main/index.html.erb +++ b/app/views/main/index.html.erb @@ -4,47 +4,63 @@ }) <%- end %> -
-
- <%= image_tag "logo.png", :class=>"logo" %> - - -
-
+<%- Kandan::Config.broadcaster.assets.each do |asset| %> + <%= javascript_include_tag asset %> +<%- end %> + +<%= javascript_tag do %> + <%- if user_signed_in? %> + <%- current_user_data = { + :id => current_user.id, + :first_name => current_user.first_name, + :last_name => current_user.last_name, + :email => current_user.email, + :username => current_user.username, + :auth_token => current_user.authentication_token, + :gravatar_hash => current_user.gravatar_hash + } + %> + $.data(document, "current-user", <%= current_user_data.to_json.html_safe %>); + <%- end %> + $(document).data("active-users", []) +<%- end %> - - - -
-
+<% end %> - -
-
-
-
-
-
+
+
+
+
+
+
- - + +
+
 
+ -
 
+<%- if user_signed_in? && Cloudfuji::Platform.on_cloudfuji? %> + <%= javascript_tag do %> + <%- cloudfuji_help_vars = { + "HUBOT_KANDAN_TOKEN" => User.find_by_email("hubot@cloudfuji.com").try(:authentication_token), + "HUBOT_KANDAN_HOST" => ENV['CLOUDFUJI_DOMAIN'] + } + %> -
-
- <%= link_to 'Edit Account', users_edit_path, @users_edit %>
- <%= link_to 'Logout', destroy_user_session_path, :method => :delete %>
-
- <%= link_to 'About Kandan', about_path, @about %> -
\ No newline at end of file + window._cloudfuji_help = <%= cloudfuji_help_vars.to_json.html_safe %>; + <%- end %> +<%- end %> \ No newline at end of file diff --git a/config/application.rb b/config/application.rb index 8d3c63f9..e39bdbd6 100644 --- a/config/application.rb +++ b/config/application.rb @@ -50,6 +50,8 @@ class Application < Rails::Application # Configure sensitive parameters which will be filtered from the log file. config.filter_parameters += [:password] + config.assets.precompile += ["lib/admin.js"] + # Use SQL instead of Active Record's schema dumper when creating the database. # This is necessary if your schema can't be completely dumped by the schema dumper, # like if you have constraints or database-specific column types diff --git a/config/routes.rb b/config/routes.rb index fc5b598f..80012fda 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -15,6 +15,12 @@ get "/about" =>"main#about" get "/users/edit" =>"main#users_edit" + namespace :admin do + root :to => "admin#index" + put "/update", :to => "admin#update", :as => "update" + put "/update_users", :to => "admin#update_users", :as => "update_users" + end + # The priority is based upon order of creation: # first created -> highest priority. diff --git a/db/migrate/20130221014127_add_admin_flag_to_users.rb b/db/migrate/20130221014127_add_admin_flag_to_users.rb new file mode 100644 index 00000000..385abd02 --- /dev/null +++ b/db/migrate/20130221014127_add_admin_flag_to_users.rb @@ -0,0 +1,5 @@ +class AddAdminFlagToUsers < ActiveRecord::Migration + def change + add_column :users, :is_admin, :boolean + end +end diff --git a/db/migrate/20130221041719_create_settings.rb b/db/migrate/20130221041719_create_settings.rb new file mode 100644 index 00000000..c4e79201 --- /dev/null +++ b/db/migrate/20130221041719_create_settings.rb @@ -0,0 +1,9 @@ +class CreateSettings < ActiveRecord::Migration + def change + create_table :settings do |t| + t.text :values, :limit => nil + + t.timestamps + end + end +end diff --git a/db/migrate/20130221173650_add_status_to_users.rb b/db/migrate/20130221173650_add_status_to_users.rb new file mode 100644 index 00000000..902a4bc6 --- /dev/null +++ b/db/migrate/20130221173650_add_status_to_users.rb @@ -0,0 +1,5 @@ +class AddStatusToUsers < ActiveRecord::Migration + def change + add_column :users, :status, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index b4b5129a..47e9e26a 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20130125210407) do +ActiveRecord::Schema.define(:version => 20130221173650) do create_table "activities", :force => true do |t| t.text "content" @@ -50,6 +50,12 @@ add_index "sessions", ["session_id"], :name => "index_sessions_on_session_id" add_index "sessions", ["updated_at"], :name => "index_sessions_on_updated_at" + create_table "settings", :force => true do |t| + t.text "values" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + end + create_table "users", :force => true do |t| t.string "email", :default => "", :null => false t.string "encrypted_password", :limit => 128, :default => "", :null => false @@ -71,6 +77,8 @@ t.text "gravatar_hash" t.boolean "active", :default => true t.string "username" + t.boolean "is_admin" + t.string "status" end add_index "users", ["authentication_token"], :name => "index_users_on_authentication_token", :unique => true diff --git a/lib/tasks/kandan.rake b/lib/tasks/kandan.rake index c201fc37..1e2f1179 100644 --- a/lib/tasks/kandan.rake +++ b/lib/tasks/kandan.rake @@ -1,6 +1,10 @@ namespace :kandan do desc "Bootstrap an initial install of Kandan. Not strictly necessary, but faster." task :bootstrap => :environment do + + # Initialize default settings + Setting.my_settings + user = User.first if user.nil? @@ -12,6 +16,7 @@ namespace :kandan do user.last_name = "OfKandan" user.password = "kandanappadmin" user.password_confirmation = "kandanappadmin" + user.is_admin = true user.save! end