sinatra-admin 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +16 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +176 -0
  6. data/Rakefile +2 -0
  7. data/dummy/config.ru +12 -0
  8. data/dummy/config/mongoid.yml +6 -0
  9. data/dummy/dummy.rb +19 -0
  10. data/dummy/models/admin.rb +6 -0
  11. data/dummy/models/comment.rb +5 -0
  12. data/dummy/models/user.rb +11 -0
  13. data/dummy/views/admin/customs/index.haml +4 -0
  14. data/dummy/views/admin/users/custom.haml +2 -0
  15. data/features/admin_login.feature +36 -0
  16. data/features/admin_logout.feature +14 -0
  17. data/features/creating_users.feature +44 -0
  18. data/features/custom_pages.feature +34 -0
  19. data/features/default_root.feature +43 -0
  20. data/features/editing_users.feature +46 -0
  21. data/features/listing_users.feature +46 -0
  22. data/features/main_menu_resources.feature +41 -0
  23. data/features/removing_users.feature +34 -0
  24. data/features/step_definitions/common_steps.rb +59 -0
  25. data/features/step_definitions/web_steps.rb +212 -0
  26. data/features/support/database_cleaner.rb +17 -0
  27. data/features/support/env.rb +18 -0
  28. data/features/support/paths.rb +30 -0
  29. data/features/support/sinatra_admin.rb +3 -0
  30. data/features/support/warden.rb +9 -0
  31. data/features/user_details.feature +31 -0
  32. data/lib/sinatra-admin.rb +42 -0
  33. data/lib/sinatra-admin/app.rb +74 -0
  34. data/lib/sinatra-admin/config.rb +45 -0
  35. data/lib/sinatra-admin/helpers/session.rb +24 -0
  36. data/lib/sinatra-admin/helpers/template_lookup.rb +7 -0
  37. data/lib/sinatra-admin/models/admin.rb +40 -0
  38. data/lib/sinatra-admin/register.rb +8 -0
  39. data/lib/sinatra-admin/register/base.rb +29 -0
  40. data/lib/sinatra-admin/register/custom.rb +10 -0
  41. data/lib/sinatra-admin/register/model.rb +75 -0
  42. data/lib/sinatra-admin/version.rb +3 -0
  43. data/lib/sinatra-admin/views/auth/login.haml +16 -0
  44. data/lib/sinatra-admin/views/edit.haml +21 -0
  45. data/lib/sinatra-admin/views/index.haml +34 -0
  46. data/lib/sinatra-admin/views/layout.haml +19 -0
  47. data/lib/sinatra-admin/views/new.haml +19 -0
  48. data/lib/sinatra-admin/views/show.haml +16 -0
  49. data/lib/sinatra-admin/warden_strategies/sinatra_admin.rb +27 -0
  50. data/sinatra-admin.gemspec +36 -0
  51. data/spec/sinatra-admin/app_spec.rb +15 -0
  52. data/spec/sinatra-admin/config_spec.rb +111 -0
  53. data/spec/sinatra-admin/models/admin_spec.rb +33 -0
  54. data/spec/sinatra-admin/register/base_spec.rb +13 -0
  55. data/spec/sinatra-admin/register/custom_spec.rb +40 -0
  56. data/spec/sinatra-admin/register/model_spec.rb +26 -0
  57. data/spec/sinatra-admin/register_spec.rb +15 -0
  58. data/spec/sinatra-admin/version_spec.rb +5 -0
  59. data/spec/sinatra-admin_spec.rb +73 -0
  60. data/spec/spec_helper.rb +81 -0
  61. metadata +343 -0
@@ -0,0 +1,17 @@
1
+ begin
2
+ require 'database_cleaner'
3
+ require 'database_cleaner/cucumber'
4
+
5
+ DatabaseCleaner.orm = "mongoid"
6
+ DatabaseCleaner.strategy = :truncation
7
+ rescue NameError
8
+ raise "You need to add database_cleaner to your Gemfile (in the :test group) if you wish to use it."
9
+ end
10
+
11
+ Around do |scenario, block|
12
+ DatabaseCleaner.cleaning(&block)
13
+ end
14
+
15
+ Before do |scenario|
16
+ DatabaseCleaner.clean
17
+ end
@@ -0,0 +1,18 @@
1
+ ENV['RACK_ENV'] = 'test'
2
+
3
+ require File.join(File.dirname(__FILE__), '..', '..', 'dummy/dummy.rb')
4
+ require 'capybara'
5
+ require 'capybara/cucumber'
6
+ require 'rspec'
7
+
8
+ Capybara.app = Rack::Builder.parse_file('./dummy/config.ru').first
9
+
10
+ class DummyWorld
11
+ include Capybara::DSL
12
+ include RSpec::Matchers
13
+ include RSpec::Expectations
14
+ end
15
+
16
+ World do
17
+ DummyWorld.new
18
+ end
@@ -0,0 +1,30 @@
1
+ # Taken from the cucumber-rails project.
2
+
3
+ module NavigationHelpers
4
+ # Maps a name to a path. Used by the
5
+ #
6
+ # When /^I go to (.+)$/ do |page_name|
7
+ #
8
+ # step definition in web_steps.rb
9
+ #
10
+ def path_to(page_name)
11
+ case page_name
12
+
13
+ when /the home page/
14
+ '/admin'
15
+ when /the login page/
16
+ '/admin/login'
17
+ when /my custom page/
18
+ '/admin/customs'
19
+ when /users listing/
20
+ '/admin/users'
21
+ when /users custom page/
22
+ '/admin/users/custom'
23
+ else
24
+ raise "Can't find mapping from \"#{page_name}\" to a path.\n" +
25
+ "Now, go and add a mapping in #{__FILE__}"
26
+ end
27
+ end
28
+ end
29
+
30
+ World(NavigationHelpers)
@@ -0,0 +1,3 @@
1
+ Before do |scenario|
2
+ SinatraAdmin.config.reset!
3
+ end
@@ -0,0 +1,9 @@
1
+ After do |scenario|
2
+ Warden.test_reset!
3
+ end
4
+
5
+ module WardenWorld
6
+ include Warden::Test::Helpers
7
+ end
8
+
9
+ World(WardenWorld)
@@ -0,0 +1,31 @@
1
+ Feature: User show
2
+ In order to use SinatraAdmin
3
+ As an Admin
4
+ I want to see the details for each user when I register the "User" resource
5
+ And I clik on an id link
6
+
7
+ Scenario: Admin tries to see user details thithout login
8
+ Given I add SinatraAdmin as middleware
9
+ And I register "User" resource
10
+ And I am an Admin
11
+ And There are users
12
+ When I go to users listing
13
+ Then I should see "Login - SinatraAdmin"
14
+ And I should see "You must log in"
15
+
16
+ Scenario: Admin sees user details when clicks the _id link
17
+ Given I add SinatraAdmin as middleware
18
+ And I register "User" resource
19
+ And I am an Admin
20
+ And I am logged in as admin
21
+ And There are users
22
+ And I am on users listing
23
+ When I click on Carlo id
24
+ Then I should see "User - Show"
25
+ And I should see "_id"
26
+ And I should see "first_name"
27
+ And I should see "last_name"
28
+ And I should see "email"
29
+ And I should see "Carlo"
30
+ And I should see "Cajucom"
31
+ And I should see "carlo@herbalife.com"
@@ -0,0 +1,42 @@
1
+ require 'active_support/inflector'
2
+
3
+ require "sinatra-admin/version"
4
+ require 'sinatra-admin/app'
5
+ require 'sinatra-admin/register'
6
+ require 'sinatra-admin/config'
7
+ require 'sinatra-admin/models/admin'
8
+
9
+ module SinatraAdmin
10
+ class << self
11
+ def register(constant_name, &block)
12
+ begin
13
+ model = constant_name.constantize
14
+ Register::Model.add(model, &block)
15
+ rescue NameError => error #Model does not exist
16
+ Register::Custom.add(constant_name, &block)
17
+ end
18
+ end
19
+
20
+ def config
21
+ @config ||= Config.new
22
+ end
23
+
24
+ def root(default)
25
+ config.root = default
26
+ end
27
+
28
+ def admin_model(constant_name)
29
+ config.admin_model = constant_name.constantize
30
+ end
31
+
32
+ def extend_views_from(target)
33
+ if target.instance_of?(String)
34
+ SinatraAdmin::App.views << "#{target}/admin"
35
+ else #Sinatra app
36
+ Array(target.views).each do |view|
37
+ SinatraAdmin::App.views << "#{view}/admin"
38
+ end
39
+ end
40
+ end
41
+ end #class << self
42
+ end #SinatraAdmin
@@ -0,0 +1,74 @@
1
+ require 'mongoid'
2
+ require 'warden'
3
+ require 'sinatra/base'
4
+ require 'sinatra/namespace'
5
+ require 'sinatra/flash'
6
+ require 'sinatra-admin/warden_strategies/sinatra_admin'
7
+ require 'sinatra-admin/helpers/session'
8
+ require "sinatra-admin/helpers/template_lookup"
9
+
10
+ module SinatraAdmin
11
+ class App < Sinatra::Base
12
+ Mongoid.raise_not_found_error = false
13
+ I18n.config.enforce_available_locales = false
14
+
15
+ set :sessions, true
16
+ set :views, [views]
17
+ set :session_secret, ENV['SINATRA_ADMIN_SECRET']
18
+
19
+ register Sinatra::Flash
20
+ register Sinatra::Namespace
21
+
22
+ helpers SinatraAdmin::SessionHelper
23
+ helpers SinatraAdmin::TemplateLookupHelper
24
+
25
+ use Rack::MethodOverride
26
+ use Warden::Manager do |config|
27
+ config.serialize_into_session(:sinatra_admin){|admin| admin.id }
28
+ config.serialize_from_session(:sinatra_admin){|id| SinatraAdmin.config.admin_model.find(id) }
29
+ config.scope_defaults :sinatra_admin,
30
+ strategies: [:sinatra_admin],
31
+ action: '/admin/unauthenticated'
32
+ config.failure_app = SinatraAdmin::App
33
+ end
34
+
35
+ Warden::Manager.before_failure do |env,opts|
36
+ env['REQUEST_METHOD'] = 'POST'
37
+ end
38
+
39
+ before do
40
+ unless public_routes.include?(request.path)
41
+ authenticate!
42
+ end
43
+ end
44
+
45
+ get '/?' do
46
+ redirect SinatraAdmin.config.default_route
47
+ end
48
+
49
+ get '/login/?' do
50
+ haml 'auth/login'.to_sym, format: :html5, layout: false
51
+ end
52
+
53
+ post '/login/?' do
54
+ if warden.authenticate(:sinatra_admin, scope: :sinatra_admin)
55
+ redirect SinatraAdmin.config.default_route
56
+ else
57
+ flash.now[:error] = warden.message
58
+ haml 'auth/login'.to_sym, format: :html5, layout: false
59
+ end
60
+ end
61
+
62
+ get '/logout/?' do
63
+ warden.logout(:sinatra_admin)
64
+ flash[:success] = 'Successfully logged out'
65
+ redirect '/admin/login'
66
+ end
67
+
68
+
69
+ post '/unauthenticated/?' do
70
+ flash[:error] = warden.message || "You must log in"
71
+ redirect '/admin/login'
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,45 @@
1
+ module SinatraAdmin
2
+ class Config
3
+ ATTRIBUTES = [:root, :admin_model]
4
+
5
+ ATTRIBUTES.each do |attr|
6
+ attr_accessor attr
7
+ end
8
+
9
+ def default_route
10
+ @default_route ||=
11
+ if root
12
+ route = root.underscore.pluralize
13
+ raise RegistrationException, "The resource #{root} was not registered" unless routes.include?(route)
14
+ "/admin/#{route}"
15
+ else
16
+ raise RegistrationException, 'You should register at least one resource' if routes.empty?
17
+ "/admin/#{routes.first}"
18
+ end
19
+ end
20
+
21
+ def admin_model
22
+ @admin_model ||= SinatraAdmin::Admin
23
+ end
24
+
25
+ # Store all registered routes.
26
+ # Executing:
27
+ # SinatraAdmin.register('User')
28
+ # SinatraAdmin.register('Tags')
29
+ #
30
+ # The result is going to be:
31
+ # @routes => ['users', 'tags']
32
+ def routes
33
+ @routes ||= []
34
+ end
35
+
36
+ ########## USE THIS METHOD CAREFULLY! ###########
37
+ # Sets ALL config to nil and remove ALL registered
38
+ # routes. It means that this method is going to remove
39
+ # the WHOLE SinatraAdmin functionality
40
+ def reset!
41
+ ATTRIBUTES.each {|attr| send("#{attr.to_s}=", nil)}
42
+ routes.clear
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,24 @@
1
+ module SinatraAdmin
2
+ module SessionHelper
3
+ def public_routes
4
+ [
5
+ '/admin/login',
6
+ '/admin/login/',
7
+ '/admin/unauthenticated',
8
+ '/admin/unauthenticated/',
9
+ '/admin/not_found/'
10
+ ]
11
+ end
12
+
13
+ def authenticate!
14
+ unless warden.authenticated?(:sinatra_admin)
15
+ flash[:error] = "You must log in"
16
+ redirect to('login')
17
+ end
18
+ end
19
+
20
+ def warden
21
+ env['warden']
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,7 @@
1
+ module SinatraAdmin
2
+ module TemplateLookupHelper
3
+ def find_template(views, name, engine, &block)
4
+ Array(views).each { |v| super(v, name, engine, &block) }
5
+ end
6
+ end #TemplateLookupHelper
7
+ end #SinatraAdmin
@@ -0,0 +1,40 @@
1
+ require 'mongoid'
2
+ require 'bcrypt'
3
+
4
+ class EmailValidator < ActiveModel::EachValidator
5
+ def validate_each(record, attribute, value)
6
+ unless value =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
7
+ record.errors[attribute] << (options[:message] || "is not an email")
8
+ end
9
+ end
10
+ end
11
+
12
+ module SinatraAdmin
13
+ class Admin
14
+ include Mongoid::Document
15
+ include BCrypt
16
+
17
+ field :first_name, type: String
18
+ field :last_name, type: String
19
+ field :email, type: String
20
+ field :password_hash, type: String
21
+
22
+ validates :email,
23
+ :password_hash, presence: true
24
+
25
+ validates :email, email: true
26
+
27
+ def authenticate(attemp_password)
28
+ password == attemp_password
29
+ end
30
+
31
+ def password
32
+ @password ||= Password.new(password_hash)
33
+ end
34
+
35
+ def password=(new_password)
36
+ @password = Password.create(new_password)
37
+ self.password_hash = @password
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,8 @@
1
+ require 'sinatra-admin/register/base'
2
+ require 'sinatra-admin/register/model'
3
+ require 'sinatra-admin/register/custom'
4
+
5
+ module SinatraAdmin
6
+ class RegistrationException < Exception; end
7
+ module Register; end
8
+ end
@@ -0,0 +1,29 @@
1
+ module SinatraAdmin
2
+ module Register
3
+ class Base
4
+ class << self
5
+ def add(resource_constant, &block)
6
+ route = resource_constant.to_s.downcase.split.join('_').pluralize
7
+ if SinatraAdmin.config.routes.include?(route)
8
+ raise RegistrationException, "The resource #{resource_constant.to_s} is already registered"
9
+ else
10
+ SinatraAdmin.config.routes << route
11
+ new(resource_constant, route).generate!(&block)
12
+ end
13
+ end
14
+ end
15
+
16
+ attr_reader :app, :resource_constant, :route
17
+
18
+ def initialize(resource_constant, route)
19
+ @app = SinatraAdmin::App
20
+ @route = route
21
+ @resource_constant = resource_constant
22
+ end
23
+
24
+ def generate!
25
+ raise NotImplementedError, 'You must implement me!'
26
+ end
27
+ end #Base
28
+ end #Register
29
+ end #SinatraAdmin
@@ -0,0 +1,10 @@
1
+ module SinatraAdmin
2
+ module Register
3
+ class Custom < Base
4
+ def generate!(&block)
5
+ raise RegistrationException, "You should pass a block in order to register #{resource_constant} custom resource" unless block_given?
6
+ app.namespace("/#{route}", &block)
7
+ end
8
+ end #Custom
9
+ end #Register
10
+ end #SinatraAdmin
@@ -0,0 +1,75 @@
1
+ module SinatraAdmin
2
+ module Register
3
+ class Model < Base
4
+ def generate!(&block)
5
+ app.namespace("/#{route}", &block) if block_given?
6
+ app.instance_exec(resource_constant, route) do |model, route|
7
+ before "/#{route}/?*" do
8
+ @model = model
9
+ @route = route
10
+ end
11
+
12
+ #INDEX
13
+ get "/#{route}/?" do
14
+ @collection ||= model.all.entries
15
+ haml :index, format: :html5
16
+ end
17
+
18
+ #NEW
19
+ get "/#{route}/new/?" do
20
+ @resource = model.new
21
+ haml :new, format: :html5
22
+ end
23
+
24
+ #CREATE
25
+ post "/#{route}/?" do
26
+ @resource = model.new(params[:data])
27
+ if @resource.save
28
+ puts "Resource was created"
29
+ redirect "/admin/#{@route}/#{@resource.id}"
30
+ else
31
+ puts "Validation Errors"
32
+ haml :new, format: :html5
33
+ end
34
+ end
35
+
36
+ #SHOW
37
+ get "/#{route}/:id/?" do
38
+ @resource = model.find(params[:id])
39
+ haml :show, format: :html5
40
+ end
41
+
42
+ #EDIT
43
+ get "/#{route}/:id/edit/?" do
44
+ @resource = model.find(params[:id])
45
+ haml :edit, format: :html5
46
+ end
47
+
48
+ #UPDATE
49
+ put "/#{route}/:id/?" do
50
+ @resource = model.find(params[:id])
51
+ if @resource.update_attributes(params[:data])
52
+ puts "Resource was updated"
53
+ redirect "/admin/#{@route}/#{@resource.id}"
54
+ else
55
+ puts "Validation Errors"
56
+ haml :edit, format: :html5
57
+ end
58
+ end
59
+
60
+ #DESTROY
61
+ delete "/#{route}/:id/?" do
62
+ @resource = model.find(params[:id])
63
+ if @resource.destroy
64
+ puts "Resource was destroyed"
65
+ redirect "/admin/#{route}/"
66
+ else
67
+ puts "Something wrong"
68
+ status 400
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end #Model
74
+ end #Register
75
+ end #SinatraAdmin