sinatra-admin 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +16 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +176 -0
- data/Rakefile +2 -0
- data/dummy/config.ru +12 -0
- data/dummy/config/mongoid.yml +6 -0
- data/dummy/dummy.rb +19 -0
- data/dummy/models/admin.rb +6 -0
- data/dummy/models/comment.rb +5 -0
- data/dummy/models/user.rb +11 -0
- data/dummy/views/admin/customs/index.haml +4 -0
- data/dummy/views/admin/users/custom.haml +2 -0
- data/features/admin_login.feature +36 -0
- data/features/admin_logout.feature +14 -0
- data/features/creating_users.feature +44 -0
- data/features/custom_pages.feature +34 -0
- data/features/default_root.feature +43 -0
- data/features/editing_users.feature +46 -0
- data/features/listing_users.feature +46 -0
- data/features/main_menu_resources.feature +41 -0
- data/features/removing_users.feature +34 -0
- data/features/step_definitions/common_steps.rb +59 -0
- data/features/step_definitions/web_steps.rb +212 -0
- data/features/support/database_cleaner.rb +17 -0
- data/features/support/env.rb +18 -0
- data/features/support/paths.rb +30 -0
- data/features/support/sinatra_admin.rb +3 -0
- data/features/support/warden.rb +9 -0
- data/features/user_details.feature +31 -0
- data/lib/sinatra-admin.rb +42 -0
- data/lib/sinatra-admin/app.rb +74 -0
- data/lib/sinatra-admin/config.rb +45 -0
- data/lib/sinatra-admin/helpers/session.rb +24 -0
- data/lib/sinatra-admin/helpers/template_lookup.rb +7 -0
- data/lib/sinatra-admin/models/admin.rb +40 -0
- data/lib/sinatra-admin/register.rb +8 -0
- data/lib/sinatra-admin/register/base.rb +29 -0
- data/lib/sinatra-admin/register/custom.rb +10 -0
- data/lib/sinatra-admin/register/model.rb +75 -0
- data/lib/sinatra-admin/version.rb +3 -0
- data/lib/sinatra-admin/views/auth/login.haml +16 -0
- data/lib/sinatra-admin/views/edit.haml +21 -0
- data/lib/sinatra-admin/views/index.haml +34 -0
- data/lib/sinatra-admin/views/layout.haml +19 -0
- data/lib/sinatra-admin/views/new.haml +19 -0
- data/lib/sinatra-admin/views/show.haml +16 -0
- data/lib/sinatra-admin/warden_strategies/sinatra_admin.rb +27 -0
- data/sinatra-admin.gemspec +36 -0
- data/spec/sinatra-admin/app_spec.rb +15 -0
- data/spec/sinatra-admin/config_spec.rb +111 -0
- data/spec/sinatra-admin/models/admin_spec.rb +33 -0
- data/spec/sinatra-admin/register/base_spec.rb +13 -0
- data/spec/sinatra-admin/register/custom_spec.rb +40 -0
- data/spec/sinatra-admin/register/model_spec.rb +26 -0
- data/spec/sinatra-admin/register_spec.rb +15 -0
- data/spec/sinatra-admin/version_spec.rb +5 -0
- data/spec/sinatra-admin_spec.rb +73 -0
- data/spec/spec_helper.rb +81 -0
- 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,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,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,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
|