diff options
-rw-r--r-- | app/assets/stylesheets/leap.scss | 21 | ||||
-rw-r--r-- | app/controllers/identities_controller.rb | 28 | ||||
-rw-r--r-- | app/helpers/link_helper.rb | 25 | ||||
-rw-r--r-- | app/helpers/table_helper.rb | 13 | ||||
-rw-r--r-- | app/models/identity.rb | 20 | ||||
-rw-r--r-- | app/views/common/_action.html.haml | 2 | ||||
-rw-r--r-- | app/views/common/_navigation_item.html.haml | 3 | ||||
-rw-r--r-- | app/views/common/_table.html.haml | 18 | ||||
-rw-r--r-- | app/views/identities/_identity.html.haml | 7 | ||||
-rw-r--r-- | app/views/identities/index.html.haml | 4 | ||||
-rw-r--r-- | app/views/layouts/_admin_navigation_item.html.haml | 3 | ||||
-rw-r--r-- | app/views/layouts/_header.html.haml | 6 | ||||
-rw-r--r-- | config/locales/users.en.yml | 7 | ||||
-rw-r--r-- | config/routes.rb | 2 | ||||
-rw-r--r-- | engines/support/app/views/tickets/index.html.haml | 16 | ||||
-rw-r--r-- | test/factories.rb | 6 | ||||
-rw-r--r-- | test/functional/identities_controller_test.rb | 42 | ||||
-rw-r--r-- | test/integration/browser/admin_test.rb | 24 | ||||
-rw-r--r-- | test/support/browser_integration_test.rb | 2 |
19 files changed, 227 insertions, 22 deletions
diff --git a/app/assets/stylesheets/leap.scss b/app/assets/stylesheets/leap.scss index 77104e5..3b7075c 100644 --- a/app/assets/stylesheets/leap.scss +++ b/app/assets/stylesheets/leap.scss @@ -145,6 +145,27 @@ input, textarea { } // +// IDENTITIES +// + +// Color code for the identity labels +.identity{ + &.main_email .label { + @extend .label-info + } + &.alias .label { + @extend .label-success + } + &.forward .label { + @extend .label-warning + } + &.disabled .label { + @extend .label-default + } +} + + +// // BORING DEFAULT MASTHEAD // diff --git a/app/controllers/identities_controller.rb b/app/controllers/identities_controller.rb new file mode 100644 index 0000000..624e38a --- /dev/null +++ b/app/controllers/identities_controller.rb @@ -0,0 +1,28 @@ +class IdentitiesController < ApplicationController + + before_filter :require_login + before_filter :require_admin + before_filter :fetch_identity, only: :destroy + before_filter :protect_main_email, only: :destroy + + def index + @identities = Identity.all + end + + def destroy + @identity.destroy + redirect_to identities_path + end + + protected + def fetch_identity + @identity = Identity.find(params[:id]) + end + + def protect_main_email + if @identity.status == :main_email + flash[:error] = "You cannot destroy the main email. Remove or Rename the user instead." + redirect_to identities_path + end + end +end diff --git a/app/helpers/link_helper.rb b/app/helpers/link_helper.rb index 55e392b..ddb063e 100644 --- a/app/helpers/link_helper.rb +++ b/app/helpers/link_helper.rb @@ -1,5 +1,30 @@ module LinkHelper + Action = Struct.new(:target, :verb, :options) do + def to_partial_path; 'common/action'; end + def label; options[:label]; end + def class; verb; end + def url + case verb + when :show, :destroy then target + when :edit, :new then [verb, target] + end + end + + def html_options + if verb == :destroy + {method: :delete} + end + end + end + + def actions(target) + target.actions.map do |action| + Action.new target, action, + label: t(".#{action}", cascade: true) + end + end + # # markup for bootstrap button # diff --git a/app/helpers/table_helper.rb b/app/helpers/table_helper.rb new file mode 100644 index 0000000..16a7019 --- /dev/null +++ b/app/helpers/table_helper.rb @@ -0,0 +1,13 @@ +module TableHelper + + # we do the translation here so the .key lookup is relative + # to the partial the helper was called from. + def table(content, columns) + render 'common/table', + content: content, + columns: columns, + headers: columns.map {|h| t(".#{h}", cascading: true) }, + none: t('.none', cascading: true) + end + +end diff --git a/app/models/identity.rb b/app/models/identity.rb index e7b5785..e09d7f2 100644 --- a/app/models/identity.rb +++ b/app/models/identity.rb @@ -95,6 +95,18 @@ class Identity < CouchRest::Model::Base } end + def status + return :blocked if disabled? + case destination + when address + :main_email + when /@#{APP_CONFIG[:domain]}\Z/i, + :alias + else + :forward + end + end + def enabled? self.user_id end @@ -103,6 +115,14 @@ class Identity < CouchRest::Model::Base !enabled? end + def actions + if enabled? + [] # [:show, :edit] + else + [:destroy] + end + end + def disable self.destination = nil self.user_id = nil diff --git a/app/views/common/_action.html.haml b/app/views/common/_action.html.haml new file mode 100644 index 0000000..71ffd96 --- /dev/null +++ b/app/views/common/_action.html.haml @@ -0,0 +1,2 @@ +%li.action{class: action.class} + =link_to action.label, action.url, action.html_options diff --git a/app/views/common/_navigation_item.html.haml b/app/views/common/_navigation_item.html.haml new file mode 100644 index 0000000..02c54c8 --- /dev/null +++ b/app/views/common/_navigation_item.html.haml @@ -0,0 +1,3 @@ +- item = navigation_item.to_s +%li{:class => ("active" if controller?(item))} + = link_to t(".#{item}", cascade: true), polymorphic_url(item) diff --git a/app/views/common/_table.html.haml b/app/views/common/_table.html.haml new file mode 100644 index 0000000..3c6bf2e --- /dev/null +++ b/app/views/common/_table.html.haml @@ -0,0 +1,18 @@ +-# +-# A simple table with headers, content and a message if there is no +-# content. +-# You can use the table helper method to translate the headers and +-# error message. +-# +%table.table.table-striped.table-bordered + %thead + %tr + - headers.each do |header| + %th= header + %tbody + - if content.any? + = render content.all + - else + %tr + %td{:colspan=>headers.count}= none + diff --git a/app/views/identities/_identity.html.haml b/app/views/identities/_identity.html.haml new file mode 100644 index 0000000..7c4aeda --- /dev/null +++ b/app/views/identities/_identity.html.haml @@ -0,0 +1,7 @@ +%tr[identity]{class: identity.status} + %td + %span.label= t(".#{identity.status}", cascading: true) + %td= identity.login + %td= identity.destination + %td + %ul.list-inline= render actions(identity) diff --git a/app/views/identities/index.html.haml b/app/views/identities/index.html.haml new file mode 100644 index 0000000..cabcccd --- /dev/null +++ b/app/views/identities/index.html.haml @@ -0,0 +1,4 @@ +-@show_navigation = false + += table @identities, %w(type username destination actions) +-# = paginate @identities diff --git a/app/views/layouts/_admin_navigation_item.html.haml b/app/views/layouts/_admin_navigation_item.html.haml new file mode 100644 index 0000000..8dd8a39 --- /dev/null +++ b/app/views/layouts/_admin_navigation_item.html.haml @@ -0,0 +1,3 @@ +- item = admin_navigation_item.to_s +%li{:class => ("active" if controller?(item))} + = link_to t(".#{item}", cascade: true), path_for(item) diff --git a/app/views/layouts/_header.html.haml b/app/views/layouts/_header.html.haml index e827f60..ff14af9 100644 --- a/app/views/layouts/_header.html.haml +++ b/app/views/layouts/_header.html.haml @@ -1,10 +1,6 @@ - if admin? %ul.nav.nav-tabs - = # this navigation isn't quite right. also, we will want to active for an identity controller once it is added. - %li{:class => ("active" if controller?('users', 'overviews') || params[:user_id])} - = link_to t(".users"), users_path - %li{:class => ("active" if controller?('tickets') && !params[:user_id])} - = link_to t(".tickets", cascade: true), tickets_path + = render partial: 'common/navigation_item', collection: [:users, :identities, :tickets] %li = link_to t(:logout), logout_path, :method => :delete - if @user && @show_navigation diff --git a/config/locales/users.en.yml b/config/locales/users.en.yml index f0fcb3d..bebf1cf 100644 --- a/config/locales/users.en.yml +++ b/config/locales/users.en.yml @@ -1,6 +1,9 @@ en: - layouts: - users: "Users" + common: + navigation_item: + users: "Users" + identities: "Usernames" + tickets: "Tickets" user_control_panel: "user control panel" account_settings: "Account Settings" username: "Username" diff --git a/config/routes.rb b/config/routes.rb index 4ccfe62..468e14e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -48,6 +48,8 @@ LeapWeb::Application.routes.draw do post 'deactivate', on: :member post 'enable', on: :member end + + resources :identities, :only => [:index, :destroy] end get "/.well-known/host-meta" => 'webfinger#host_meta' diff --git a/engines/support/app/views/tickets/index.html.haml b/engines/support/app/views/tickets/index.html.haml index 526cd6d..d107ce2 100644 --- a/engines/support/app/views/tickets/index.html.haml +++ b/engines/support/app/views/tickets/index.html.haml @@ -1,19 +1,5 @@ - @show_navigation = params[:user_id].present? = render 'tickets/tabs' - -%table.table.table-striped.table-bordered - %thead - %tr - %th= t(".subject") - %th= t(".created") - %th= t(".updated") - %th= t(".voices") - %tbody - - if @tickets.any? - = render @tickets.all - - else - %tr - %td{:colspan=>4}= t(".none") - += table @tickets, %w(subject created updated voices) = paginate @tickets diff --git a/test/factories.rb b/test/factories.rb index bebda5c..a96d48c 100644 --- a/test/factories.rb +++ b/test/factories.rb @@ -29,6 +29,12 @@ FactoryGirl.define do end + # Identities can have a lot of different purposes - alias, forward, ... + # So far this is a blocked handle only. + factory :identity do + address {Faker::Internet.user_name + '@' + APP_CONFIG[:domain]} + end + factory :token do user end diff --git a/test/functional/identities_controller_test.rb b/test/functional/identities_controller_test.rb new file mode 100644 index 0000000..fcdeaa2 --- /dev/null +++ b/test/functional/identities_controller_test.rb @@ -0,0 +1,42 @@ +require 'test_helper' + +class IdentitiesControllerTest < ActionController::TestCase + + test "admin can list active and blocked ids" do + login :is_admin? => true + get :index + assert_response :success + assert ids = assigns(:identities) + end + + test "non-admin can't list usernames" do + login + get :index + assert_access_denied + end + + test "requires login" do + get :index + assert_login_required + end + + test "admin can unblock username" do + # an identity without user_id and destination is a blocked handle + identity = FactoryGirl.create :identity + login :is_admin? => true + delete :destroy, id: identity.id + assert_response :redirect + assert_nil Identity.find(identity.id) + end + + test "admin cannot remove main identity" do + user = FactoryGirl.create :user + identity = FactoryGirl.create :identity, + Identity.attributes_from_user(user) + login :is_admin? => true + delete :destroy, id: identity.id + assert_response :redirect + assert_equal identity, Identity.find(identity.id) + end + +end diff --git a/test/integration/browser/admin_test.rb b/test/integration/browser/admin_test.rb new file mode 100644 index 0000000..2d3f988 --- /dev/null +++ b/test/integration/browser/admin_test.rb @@ -0,0 +1,24 @@ +require 'test_helper' + +class AdminTest < BrowserIntegrationTest + + test "clear blocked handle" do + id = FactoryGirl.create :identity + submit_signup(id.login) + assert page.has_content?('has already been taken') + login + with_config admins: [@user.login] do + visit '/' + click_on "Usernames" + within "##{dom_id(id)}" do + assert page.has_content? id.login + click_on "Destroy" + end + assert page.has_no_content? id.login + click_on 'Log Out' + end + submit_signup(id.login) + assert page.has_content?("Welcome #{id.login}") + click_on 'Log Out' + end +end diff --git a/test/support/browser_integration_test.rb b/test/support/browser_integration_test.rb index 4fec59f..c01b8a1 100644 --- a/test/support/browser_integration_test.rb +++ b/test/support/browser_integration_test.rb @@ -5,6 +5,8 @@ # class BrowserIntegrationTest < ActionDispatch::IntegrationTest + # let's use dom_id inorder to identify sections + include ActionController::RecordIdentifier CONFIG_RU = (Rails.root + 'config.ru').to_s OUTER_APP = Rack::Builder.parse_file(CONFIG_RU).first |