saucy 0.10.0 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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