saucy 0.10.0 → 0.10.1

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ 0.10.1
2
+
3
+ Add admin reporting area at /admin/ to view accounts + users
4
+ * Added admin_reporting.feature stories
5
+ * Introduce admin_username and admin_password configuration options, for http auth protection
6
+
1
7
  0.10.0
2
8
 
3
9
  Bump to a newer version of Clearance.
@@ -0,0 +1,14 @@
1
+ class Admin::AccountsController < Admin::BaseController
2
+ def index
3
+ @accounts = Account.all
4
+ end
5
+
6
+ def search
7
+ @accounts = Account.search params[:query]
8
+ render :index
9
+ end
10
+
11
+ def show
12
+ @account = Account.find_by_keyword params[:id]
13
+ end
14
+ end
@@ -0,0 +1,15 @@
1
+ class Admin::BaseController < ApplicationController
2
+ layout 'admin'
3
+ before_filter :authenticate_admin
4
+
5
+ def show
6
+ end
7
+
8
+ protected
9
+
10
+ def authenticate_admin
11
+ authenticate_or_request_with_http_basic do |user_name, password|
12
+ user_name == Saucy::Configuration.admin_username && password == Saucy::Configuration.admin_password
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,14 @@
1
+ class Admin::UsersController < Admin::BaseController
2
+ def index
3
+ @users = User.all
4
+ end
5
+
6
+ def search
7
+ @users = User.search params[:query]
8
+ render :index
9
+ end
10
+
11
+ def show
12
+ @user = User.find params[:id]
13
+ end
14
+ end
@@ -0,0 +1,15 @@
1
+ <tr>
2
+ <td><%= account.id %></td>
3
+ <td><%= link_to account.name, admin_account_path(account) %></td>
4
+ <td><%= account.plan.name %></td>
5
+ <td><%= account.activated? %></td>
6
+ <td><%= link_to account.customer_token, "https://www.braintreegateway.com/merchants/#{Braintree::Configuration.merchant_id}/customers/#{account.customer_token}" %></td>
7
+ <td>
8
+ <% if account.subscription_token? %>
9
+ <%= link_to account.subscription_token, "https://www.braintreegateway.com/merchants/#{Braintree::Configuration.merchant_id}/subscriptions/#{account.subscription_token}" %>
10
+ <% else %>
11
+ N/A
12
+ <% end %>
13
+ </td>
14
+ <td><%= account.created_at %></td>
15
+ </tr>
@@ -0,0 +1,19 @@
1
+ <h2>Accounts</h2>
2
+
3
+ <%= form_tag search_admin_accounts_path, :method => :get do %>
4
+ <%= text_field_tag :query, params[:query], :placeholder => 'Search' %>
5
+ <%= submit_tag 'Search', :name => nil %>
6
+ <% end -%>
7
+
8
+ <table>
9
+ <tr>
10
+ <th>ID</th>
11
+ <th>Account Name</th>
12
+ <th>Plan</th>
13
+ <th>Activated?</th>
14
+ <th>Customer Token</th>
15
+ <th>Subscription Token</th>
16
+ <th>Creation Date</th>
17
+ </tr>
18
+ <%= render :partial => 'admin/accounts/account', :collection => @accounts %>
19
+ </table>
@@ -0,0 +1,49 @@
1
+ <h2>Account Detail</h2>
2
+
3
+ <table>
4
+ <tr>
5
+ <th>ID</th>
6
+ <th>Account Name</th>
7
+ <th>Plan</th>
8
+ <th>Activated?</th>
9
+ <th>Customer Token</th>
10
+ <th>Subscription Token</th>
11
+ <th>Creation Date</th>
12
+ </tr>
13
+ <%= render :partial => 'admin/accounts/account', :object => @account %>
14
+ </table>
15
+
16
+ <h3>Projects</h3>
17
+ <% unless @account.projects.empty? -%>
18
+ <table>
19
+ <tr>
20
+ <th>ID</th>
21
+ <th>Project Name</th>
22
+ <th>Creation Date</th>
23
+ </tr>
24
+ <% @account.projects.each do |project| -%>
25
+ <tr>
26
+ <td><%= project.id %></td>
27
+ <td><%= project.name %></td>
28
+ <td><%= project.created_at %></td>
29
+ </tr>
30
+ <% end -%>
31
+ </table>
32
+ <% else -%>
33
+ <p><em>This account has no projects.</em></p>
34
+ <% end -%>
35
+
36
+ <h3>Users</h3>
37
+ <% unless @account.users.empty? -%>
38
+ <table>
39
+ <tr>
40
+ <th>ID</th>
41
+ <th>User Name</th>
42
+ <th>Email</th>
43
+ <th>Creation Date</th>
44
+ </tr>
45
+ <%= render :partial => 'admin/users/user', :collection => @account.users %>
46
+ </table>
47
+ <% else -%>
48
+ <p><em>This account has no users.</em></p>
49
+ <% end -%>
@@ -0,0 +1,10 @@
1
+ <h3><%= link_to 'Accounts', admin_accounts_path %></h3>
2
+
3
+ <h3><%= link_to 'Users', admin_users_path %></h3>
4
+
5
+ <h2>TODO</h2>
6
+
7
+ <ul>
8
+ <li>Link to tender and/or some way for tender to link back to this thing</li>
9
+ <li>Saucy configuration for http basic auth - ship w/ defaults? seems dangerous.</li>
10
+ </ul>
@@ -0,0 +1,6 @@
1
+ <tr>
2
+ <td><%= user.id %></td>
3
+ <td><%= link_to user.name, admin_user_path(user) %></td>
4
+ <td><%= user.email %></td>
5
+ <td><%= user.created_at %></td>
6
+ </tr>
@@ -0,0 +1,16 @@
1
+ <h2>Users</h2>
2
+
3
+ <%= form_tag search_admin_users_path, :method => :get do %>
4
+ <%= text_field_tag :query, params[:query], :placeholder => 'Search' %>
5
+ <%= submit_tag 'Search', :name => nil %>
6
+ <% end -%>
7
+
8
+ <table>
9
+ <tr>
10
+ <th>ID</th>
11
+ <th>User Name</th>
12
+ <th>Email</th>
13
+ <th>Creation Date</th>
14
+ </tr>
15
+ <%= render :partial => 'admin/users/user', :collection => @users %>
16
+ </table>
@@ -0,0 +1,49 @@
1
+ <h2>User Detail</h2>
2
+
3
+ <table>
4
+ <tr>
5
+ <th>ID</th>
6
+ <th>User Name</th>
7
+ <th>Email</th>
8
+ <th>Creation Date</th>
9
+ </tr>
10
+ <%= render :partial => 'admin/users/user', :object => @user %>
11
+ </table>
12
+
13
+ <h3>Projects</h3>
14
+ <% unless @user.projects.empty? -%>
15
+ <table>
16
+ <tr>
17
+ <th>ID</th>
18
+ <th>Project Name</th>
19
+ <th>Creation Date</th>
20
+ </tr>
21
+ <% @user.projects.each do |project| -%>
22
+ <tr>
23
+ <td><%= project.id %></td>
24
+ <td><%= project.name %></td>
25
+ <td><%= project.created_at %></td>
26
+ </tr>
27
+ <% end -%>
28
+ </table>
29
+ <% else -%>
30
+ <p><em>This user has no projects.</em></p>
31
+ <% end -%>
32
+
33
+ <h3>Accounts</h3>
34
+ <% unless @user.accounts.empty? -%>
35
+ <table>
36
+ <tr>
37
+ <th>ID</th>
38
+ <th>Account Name</th>
39
+ <th>Plan</th>
40
+ <th>Activated?</th>
41
+ <th>Customer Token</th>
42
+ <th>Subscription Token</th>
43
+ <th>Creation Date</th>
44
+ </tr>
45
+ <%= render :partial => 'admin/accounts/account', :collection => @user.accounts %>
46
+ </table>
47
+ <% else -%>
48
+ <p><em>This user has no account.</em></p>
49
+ <% end -%>
@@ -0,0 +1,21 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <title><%= yield(:pagetitle) %></title>
6
+ <link rel="stylesheet" href="/saucy/stylesheets/flutie.css" />
7
+ <link rel="stylesheet" href="/saucy/stylesheets/screen.css" />
8
+ </head>
9
+ <body>
10
+ <div id="container">
11
+ <header>
12
+ <h1><%= link_to 'Admin', admin_path %></h1>
13
+ </header>
14
+ <div id="main" role="main">
15
+ <%= yield %>
16
+ </div>
17
+ <footer>
18
+ </footer>
19
+ </div>
20
+ </body>
21
+ </html>
data/config/routes.rb CHANGED
@@ -16,4 +16,14 @@ Rails.application.routes.draw do
16
16
  end
17
17
 
18
18
  resource :profile, :only => [:edit, :update]
19
+
20
+ namespace :admin do
21
+ resources :accounts, :only => [:index, :show] do
22
+ get :search, :on => :collection
23
+ end
24
+ resources :users, :only => [:index, :show] do
25
+ get :search, :on => :collection
26
+ end
27
+ match "/" => "base#show"
28
+ end
19
29
  end
@@ -28,6 +28,8 @@ DESC
28
28
 
29
29
  def create_paths
30
30
  paths = <<-PATHS
31
+ when 'the admin page'
32
+ admin_path
31
33
  when 'the list of accounts'
32
34
  accounts_path
33
35
  when 'the list of plans page'
@@ -0,0 +1,65 @@
1
+ Feature: Viewing reports
2
+ As a product manager
3
+ I want to be able to locate account details, user details
4
+ So I can perform support tasks successfully
5
+
6
+ Scenario: Viewing the reports dashboard
7
+ Given I authenticate as the site owner
8
+ When I go to the admin page
9
+ Then I should see "Admin"
10
+
11
+ Scenario: Viewing the accounts listing
12
+ Given an account exists with a name of "Test"
13
+ And I authenticate as the site owner
14
+ When I go to the admin page
15
+ And I follow "Accounts"
16
+ Then I should see "Test"
17
+
18
+ Scenario: Viewing an account
19
+ Given an account exists with a name of "Test"
20
+ And I authenticate as the site owner
21
+ When I go to the admin page
22
+ And I follow "Accounts"
23
+ And I follow "Test"
24
+ Then I should see "Test"
25
+ And I should see "Projects"
26
+ And I should see "Users"
27
+
28
+ Scenario: Searching to find an account
29
+ Given an account exists with a name of "Test"
30
+ And an account exists with a name of "Fun"
31
+ And I authenticate as the site owner
32
+ When I go to the admin page
33
+ And I follow "Accounts"
34
+ And I fill in "query" with "Fun"
35
+ And I press "Search"
36
+ Then I should see "Fun"
37
+ And I should not see "Test"
38
+
39
+ Scenario: Viewing the users listing
40
+ Given a user exists with a name of "Test"
41
+ And I authenticate as the site owner
42
+ When I go to the admin page
43
+ And I follow "Users"
44
+ Then I should see "Test"
45
+
46
+ Scenario: Viewing an user
47
+ Given a user exists with a name of "Test"
48
+ And I authenticate as the site owner
49
+ When I go to the admin page
50
+ And I follow "Users"
51
+ And I follow "Test"
52
+ Then I should see "Test"
53
+ And I should see "Projects"
54
+ And I should see "Accounts"
55
+
56
+ Scenario: Searching to find an user
57
+ Given a user exists with a name of "Test"
58
+ And an user exists with a name of "Fun"
59
+ And I authenticate as the site owner
60
+ When I go to the admin page
61
+ And I follow "Users"
62
+ And I fill in "query" with "Fun"
63
+ And I press "Search"
64
+ Then I should see "Fun"
65
+ And I should not see "Test"
@@ -29,3 +29,10 @@ Given /^I am signed in$/ do
29
29
  user = Factory(:user)
30
30
  When %{I sign in as "#{user.email}"}
31
31
  end
32
+
33
+ When /^I authenticate as the site owner$/ do
34
+ name, password = Saucy::Configuration.admin_username, Saucy::Configuration.admin_password
35
+ # This is done differently in various capybara versions, you may need to change it to
36
+ # page.driver.browser.basic_authorize - or - page.driver.basic_auth
37
+ page.driver.basic_authorize name, password
38
+ end
data/lib/saucy/account.rb CHANGED
@@ -88,6 +88,11 @@ module Saucy
88
88
  end
89
89
 
90
90
  module ClassMethods
91
+ def search(query)
92
+ return [] if query.nil?
93
+ where ['accounts.keyword like :query or accounts.name like :query', { :query => "%#{query}%" }]
94
+ end
95
+
91
96
  def deliver_new_unactivated_notifications
92
97
  new_unactivated.each do |account|
93
98
  BillingMailer.new_unactivated(account).deliver
@@ -6,11 +6,16 @@ module Saucy
6
6
  cattr_accessor :manager_email_address
7
7
  cattr_accessor :support_email_address
8
8
  cattr_accessor :merchant_account_id
9
+ cattr_accessor :admin_username
10
+ cattr_accessor :admin_password
9
11
 
10
12
  def initialize
11
13
  @@manager_email_address = 'manager@example.com'
12
14
  @@support_email_address = 'support@example.com'
13
- @@layouts = Layouts.new
15
+ @@layouts = Layouts.new
16
+ @@admin_username = 'admin'
17
+ @@admin_password = 'admin'
14
18
  end
19
+
15
20
  end
16
21
  end
data/lib/saucy/engine.rb CHANGED
@@ -23,6 +23,10 @@ module Saucy
23
23
  ActionView::Base.send :include, LimitsHelper
24
24
  end
25
25
 
26
+ initializer 'static assets' do |app|
27
+ app.middleware.insert_after ::ActionDispatch::Static, ::ActionDispatch::Static, "#{root}/public"
28
+ end
29
+
26
30
  initializer 'coupons.helper' do |app|
27
31
  ActionView::Base.send :include, CouponsHelper
28
32
  end
@@ -36,4 +40,3 @@ module Saucy
36
40
  end
37
41
  end
38
42
  end
39
-
data/lib/saucy/user.rb CHANGED
@@ -22,6 +22,10 @@ module Saucy
22
22
  end
23
23
 
24
24
  module ClassMethods
25
+ def search(query)
26
+ return [] if query.nil?
27
+ where ['users.email like :query or users.name like :query', { :query => "%#{query}%" }]
28
+ end
25
29
  def by_name
26
30
  order('users.name')
27
31
  end
@@ -0,0 +1,50 @@
1
+ require 'spec_helper'
2
+
3
+ describe Admin::AccountsController, "routes" do
4
+ it { should route(:get, "/admin/accounts").to(:action => :index) }
5
+ it { should route(:get, "/admin/accounts/123").to(:action => :show, :id => 123) }
6
+ end
7
+
8
+ describe Admin::AccountsController, "get index without http auth" do
9
+ before { get :index }
10
+ it { should respond_with(:unauthorized) }
11
+ end
12
+
13
+ describe Admin::AccountsController, "get index with http auth" do
14
+ before do
15
+ http_basic_auth_sign_in 'admin', 'admin'
16
+ get :index
17
+ end
18
+ it { should respond_with(:success) }
19
+ it { should render_template(:index) }
20
+ end
21
+
22
+ describe Admin::AccountsController, "get search without http auth" do
23
+ before { get :search }
24
+ it { should respond_with(:unauthorized) }
25
+ end
26
+
27
+ describe Admin::AccountsController, "get search with http auth" do
28
+ before do
29
+ http_basic_auth_sign_in 'admin', 'admin'
30
+ get :search
31
+ end
32
+ it { should respond_with(:success) }
33
+ it { should render_template(:index) }
34
+ end
35
+
36
+ describe Admin::AccountsController, "get show without http auth" do
37
+ let(:account) { Factory(:account) }
38
+ before { get :show, :id => account.to_param }
39
+ it { should respond_with(:unauthorized) }
40
+ end
41
+
42
+ describe Admin::AccountsController, "get show with http auth" do
43
+ let(:account) { Factory(:account) }
44
+ before do
45
+ http_basic_auth_sign_in 'admin', 'admin'
46
+ get :show, :id => account.to_param
47
+ end
48
+ it { should respond_with(:success) }
49
+ it { should render_template(:show) }
50
+ end
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+
3
+ describe Admin::BaseController, "routes" do
4
+ it { should route(:get, "/admin").to(:action => :show) }
5
+ end
6
+
7
+ describe Admin::BaseController, "get show without http auth" do
8
+ before { get :show }
9
+ it { should respond_with(:unauthorized) }
10
+ end
11
+
12
+ describe Admin::BaseController, "get show with http auth" do
13
+ before do
14
+ http_basic_auth_sign_in 'admin', 'admin'
15
+ get :show
16
+ end
17
+ it { should respond_with(:success) }
18
+ it { should render_template(:show) }
19
+ end
@@ -0,0 +1,51 @@
1
+ require 'spec_helper'
2
+
3
+ describe Admin::UsersController, "routes" do
4
+ it { should route(:get, "/admin/users").to(:action => :index) }
5
+ it { should route(:get, "/admin/users/123").to(:action => :show, :id => 123) }
6
+ end
7
+
8
+ describe Admin::UsersController, "get index without http auth" do
9
+ before { get :index }
10
+ it { should respond_with(:unauthorized) }
11
+ end
12
+
13
+ describe Admin::UsersController, "get index with http auth" do
14
+ before do
15
+ http_basic_auth_sign_in 'admin', 'admin'
16
+ get :index
17
+ end
18
+ it { should respond_with(:success) }
19
+ it { should render_template(:index) }
20
+ end
21
+
22
+ describe Admin::UsersController, "get search without http auth" do
23
+ before { get :search }
24
+ it { should respond_with(:unauthorized) }
25
+ end
26
+
27
+ describe Admin::UsersController, "get search with http auth" do
28
+ before do
29
+ http_basic_auth_sign_in 'admin', 'admin'
30
+ get :search
31
+ end
32
+ it { should respond_with(:success) }
33
+ it { should render_template(:index) }
34
+ end
35
+
36
+ describe Admin::UsersController, "get show without http auth" do
37
+ let(:user) { Factory(:user) }
38
+ before { get :show, :id => user.to_param }
39
+ it { should respond_with(:unauthorized) }
40
+ end
41
+
42
+ describe Admin::UsersController, "get show with http auth" do
43
+ let(:user) { Factory(:user) }
44
+ before do
45
+ http_basic_auth_sign_in 'admin', 'admin'
46
+ get :show, :id => user.to_param
47
+ end
48
+ before { get :show, :id => user.to_param }
49
+ it { should respond_with(:success) }
50
+ it { should render_template(:show) }
51
+ end
@@ -215,6 +215,18 @@ describe Account do
215
215
 
216
216
  unactivated.each { |account| account.reload.should be_asked_to_activate }
217
217
  end
218
+
219
+ it "searches records for keyword and name" do
220
+ name = Factory :account, :name => 'match'
221
+ keyword = Factory :account, :keyword => 'match'
222
+ nope = Factory :account
223
+
224
+ Account.search('match').should == [name, keyword]
225
+ end
226
+
227
+ it "should return nothing for nil search" do
228
+ Account.search(nil).should == []
229
+ end
218
230
  end
219
231
 
220
232
  describe Account, "with coupon" do
@@ -68,5 +68,16 @@ describe User, "valid" do
68
68
 
69
69
  User.by_name.map(&:name).should == %w(abc def ghi)
70
70
  end
71
- end
72
71
 
72
+ it "searches records for email and name" do
73
+ name = Factory :user, :email => 'match@example.com'
74
+ keyword = Factory :user, :name => 'match match'
75
+ nope = Factory :user
76
+
77
+ User.search('match').should == [name, keyword]
78
+ end
79
+
80
+ it "should return nothing for nil search" do
81
+ User.search(nil).should == []
82
+ end
83
+ end
@@ -6,6 +6,10 @@ module AuthenticationHelpers
6
6
  def sign_in
7
7
  sign_in_as(Factory(:user))
8
8
  end
9
+
10
+ def http_basic_auth_sign_in(user, pass)
11
+ request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials(user, pass)
12
+ end
9
13
 
10
14
  def sign_out
11
15
  controller.current_user = nil
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: saucy
3
3
  version: !ruby/object:Gem::Version
4
- hash: 55
4
+ hash: 53
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 10
9
- - 0
10
- version: 0.10.0
9
+ - 1
10
+ version: 0.10.1
11
11
  platform: ruby
12
12
  authors:
13
13
  - thoughtbot, inc.
@@ -20,7 +20,7 @@ autorequire:
20
20
  bindir: bin
21
21
  cert_chain: []
22
22
 
23
- date: 2011-06-29 00:00:00 -04:00
23
+ date: 2011-06-30 00:00:00 -04:00
24
24
  default_executable:
25
25
  dependencies:
26
26
  - !ruby/object:Gem::Dependency
@@ -151,6 +151,9 @@ files:
151
151
  - config/locales/en.yml
152
152
  - config/routes.rb
153
153
  - app/controllers/accounts_controller.rb
154
+ - app/controllers/admin/accounts_controller.rb
155
+ - app/controllers/admin/base_controller.rb
156
+ - app/controllers/admin/users_controller.rb
154
157
  - app/controllers/billings_controller.rb
155
158
  - app/controllers/invitations_controller.rb
156
159
  - app/controllers/memberships_controller.rb
@@ -173,6 +176,13 @@ files:
173
176
  - app/views/accounts/edit.html.erb
174
177
  - app/views/accounts/index.html.erb
175
178
  - app/views/accounts/new.html.erb
179
+ - app/views/admin/accounts/_account.html.erb
180
+ - app/views/admin/accounts/index.html.erb
181
+ - app/views/admin/accounts/show.html.erb
182
+ - app/views/admin/base/show.html.erb
183
+ - app/views/admin/users/_user.html.erb
184
+ - app/views/admin/users/index.html.erb
185
+ - app/views/admin/users/show.html.erb
176
186
  - app/views/billing_mailer/completed_trial.text.erb
177
187
  - app/views/billing_mailer/expiring_trial.text.erb
178
188
  - app/views/billing_mailer/new_unactivated.text.erb
@@ -186,6 +196,7 @@ files:
186
196
  - app/views/invitation_mailer/invitation.text.erb
187
197
  - app/views/invitations/new.html.erb
188
198
  - app/views/invitations/show.html.erb
199
+ - app/views/layouts/admin.html.erb
189
200
  - app/views/layouts/saucy.html.erb
190
201
  - app/views/limits/_meter.html.erb
191
202
  - app/views/memberships/edit.html.erb
@@ -248,6 +259,7 @@ files:
248
259
  - features/step_definitions/saucy_steps.rb
249
260
  - features/support/env.rb
250
261
  - features/support/file.rb
262
+ - lib/generators/saucy/features/templates/features/admin_reporting.feature
251
263
  - lib/generators/saucy/features/templates/features/edit_profile.feature
252
264
  - lib/generators/saucy/features/templates/features/edit_project_permissions.feature
253
265
  - lib/generators/saucy/features/templates/features/edit_user_permissions.feature
@@ -264,6 +276,9 @@ files:
264
276
  - lib/generators/saucy/features/templates/features/trial_plans.feature
265
277
  - lib/generators/saucy/features/templates/README
266
278
  - spec/controllers/accounts_controller_spec.rb
279
+ - spec/controllers/admin/accounts_controller_spec.rb
280
+ - spec/controllers/admin/base_controller_spec.rb
281
+ - spec/controllers/admin/users_controller_spec.rb
267
282
  - spec/controllers/application_controller_spec.rb
268
283
  - spec/controllers/invitations_controller_spec.rb
269
284
  - spec/controllers/memberships_controller_spec.rb
@@ -296,7 +311,7 @@ files:
296
311
  - spec/support/clearance_matchers.rb
297
312
  - spec/support/notifications.rb
298
313
  - spec/views/accounts/_account.html.erb_spec.rb
299
- has_rdoc: false
314
+ has_rdoc: true
300
315
  homepage: http://github.com/thoughtbot/saucy
301
316
  licenses: []
302
317