sinatra-admin 0.1.0
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.
- 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
|