ditty 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +14 -0
  3. data/.pryrc +6 -0
  4. data/.rspec +2 -0
  5. data/.rubocop.yml +15 -0
  6. data/.travis.yml +15 -0
  7. data/Gemfile.ci +19 -0
  8. data/License.txt +7 -0
  9. data/Rakefile +10 -0
  10. data/Readme.md +67 -0
  11. data/config.ru +33 -0
  12. data/ditty.gemspec +46 -0
  13. data/lib/ditty/components/app.rb +78 -0
  14. data/lib/ditty/controllers/application.rb +79 -0
  15. data/lib/ditty/controllers/audit_logs.rb +44 -0
  16. data/lib/ditty/controllers/component.rb +161 -0
  17. data/lib/ditty/controllers/main.rb +86 -0
  18. data/lib/ditty/controllers/roles.rb +16 -0
  19. data/lib/ditty/controllers/users.rb +183 -0
  20. data/lib/ditty/db.rb +12 -0
  21. data/lib/ditty/helpers/authentication.rb +58 -0
  22. data/lib/ditty/helpers/component.rb +63 -0
  23. data/lib/ditty/helpers/pundit.rb +34 -0
  24. data/lib/ditty/helpers/views.rb +50 -0
  25. data/lib/ditty/helpers/wisper.rb +14 -0
  26. data/lib/ditty/listener.rb +23 -0
  27. data/lib/ditty/models/audit_log.rb +14 -0
  28. data/lib/ditty/models/base.rb +7 -0
  29. data/lib/ditty/models/identity.rb +70 -0
  30. data/lib/ditty/models/role.rb +16 -0
  31. data/lib/ditty/models/user.rb +63 -0
  32. data/lib/ditty/policies/application_policy.rb +21 -0
  33. data/lib/ditty/policies/audit_log_policy.rb +41 -0
  34. data/lib/ditty/policies/identity_policy.rb +25 -0
  35. data/lib/ditty/policies/role_policy.rb +41 -0
  36. data/lib/ditty/policies/user_policy.rb +47 -0
  37. data/lib/ditty/rake_tasks.rb +85 -0
  38. data/lib/ditty/seed.rb +1 -0
  39. data/lib/ditty/services/logger.rb +48 -0
  40. data/lib/ditty/version.rb +5 -0
  41. data/lib/ditty.rb +142 -0
  42. data/migrate/20170207_base_tables.rb +40 -0
  43. data/migrate/20170208_audit_log.rb +12 -0
  44. data/migrate/20170416_audit_log_details.rb +9 -0
  45. data/public/browserconfig.xml +9 -0
  46. data/public/images/apple-icon.png +0 -0
  47. data/public/images/favicon-16x16.png +0 -0
  48. data/public/images/favicon-32x32.png +0 -0
  49. data/public/images/launcher-icon-1x.png +0 -0
  50. data/public/images/launcher-icon-2x.png +0 -0
  51. data/public/images/launcher-icon-4x.png +0 -0
  52. data/public/images/mstile-150x150.png +0 -0
  53. data/public/images/safari-pinned-tab.svg +43 -0
  54. data/public/manifest.json +25 -0
  55. data/views/404.haml +7 -0
  56. data/views/audit_logs/index.haml +30 -0
  57. data/views/error.haml +4 -0
  58. data/views/identity/login.haml +19 -0
  59. data/views/identity/register.haml +14 -0
  60. data/views/index.haml +1 -0
  61. data/views/layout.haml +55 -0
  62. data/views/partials/delete_form.haml +4 -0
  63. data/views/partials/footer.haml +5 -0
  64. data/views/partials/form_control.haml +20 -0
  65. data/views/partials/navbar.haml +24 -0
  66. data/views/partials/notifications.haml +24 -0
  67. data/views/partials/pager.haml +14 -0
  68. data/views/partials/sidebar.haml +35 -0
  69. data/views/roles/display.haml +18 -0
  70. data/views/roles/edit.haml +11 -0
  71. data/views/roles/form.haml +1 -0
  72. data/views/roles/index.haml +22 -0
  73. data/views/roles/new.haml +10 -0
  74. data/views/users/display.haml +50 -0
  75. data/views/users/edit.haml +11 -0
  76. data/views/users/identity.haml +3 -0
  77. data/views/users/index.haml +23 -0
  78. data/views/users/new.haml +11 -0
  79. data/views/users/profile.haml +39 -0
  80. data/views/users/user.haml +3 -0
  81. metadata +431 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9f88c10fb42b6cc8dd793998baaaf909a4dc6c0a
4
+ data.tar.gz: 5a72a520051492ee1a769cdeef56d943bbda6c55
5
+ SHA512:
6
+ metadata.gz: 2ba59d3510c55c01ca48b49cc1f601b2dcd2f4872670fedade8a2d0d8a8f36cc42382f05361f1cda1cd7646f2d3af71f9f105f4230bc5c9a56051e15b7a38817
7
+ data.tar.gz: ae4ea9466221bd370ce5de8b1e19993804c77c16f9070ca323ece9e6f8ebe4ba578ad207ec51741115303672fcb4006d346bea9b1e5c2a3a0ec5c49dd8e24838
data/.gitignore ADDED
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *_secret
11
+ /node_modules
12
+ .vagrant
13
+ *.db
14
+ /Gemfile.dev.lock
data/.pryrc ADDED
@@ -0,0 +1,6 @@
1
+ if defined?(PryByebug)
2
+ Pry.commands.alias_command 'c', 'continue'
3
+ Pry.commands.alias_command 's', 'step'
4
+ Pry.commands.alias_command 'n', 'next'
5
+ Pry.commands.alias_command 'f', 'finish'
6
+ end
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.rubocop.yml ADDED
@@ -0,0 +1,15 @@
1
+ Metrics/LineLength:
2
+ Max: 120
3
+
4
+ Style/Documentation:
5
+ Enabled: false
6
+
7
+ Style/NumericPredicate:
8
+ Enabled: false
9
+
10
+ Style/LeadingCommentSpace:
11
+ Exclude:
12
+ - 'config.ru'
13
+
14
+ AllCops:
15
+ TargetRubyVersion: 2.2
data/.travis.yml ADDED
@@ -0,0 +1,15 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.4.0
5
+ - 2.3.3
6
+ - 2.2.6
7
+ gemfile: Gemfile.ci
8
+ env:
9
+ - DATABASE_URL="sqlite::memory:" RACK_ENV=test
10
+ before_install: gem install bundler -v 1.12.5
11
+ addons:
12
+ code_climate:
13
+ repo_token: 289860573c6284a8e277de86848caba84d840be49e35f3601bcd672ab40d1e35
14
+ after_success:
15
+ - bundle exec codeclimate-test-reporter
data/Gemfile.ci ADDED
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+ source 'https://rubygems.org'
3
+
4
+ gemspec
5
+
6
+ gem 'rubocop'
7
+ gem 'simplecov', '~> 0.13.0'
8
+ gem 'sqlite3'
9
+
10
+ if RUBY_VERSION < '2.1'
11
+ gem 'sidekiq', '3.0.0'
12
+ gem 'activesupport', '<4.0.0'
13
+ gem 'omniauth', '~>1.4.2'
14
+ elsif RUBY_VERSION < '2.2.0'
15
+ gem 'sidekiq', '4.0.0'
16
+ gem 'activesupport', '<5.0.0'
17
+ else
18
+ gem 'activesupport'
19
+ end
data/License.txt ADDED
@@ -0,0 +1,7 @@
1
+ Copyright 2017 Jade IT cc
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rake'
4
+ require 'bundler/gem_tasks'
5
+ require 'rspec/core/rake_task'
6
+ require 'ditty/rake_tasks'
7
+
8
+ RSpec::Core::RakeTask.new(:spec)
9
+
10
+ task default: :spec
data/Readme.md ADDED
@@ -0,0 +1,67 @@
1
+ [![Build Status](https://travis-ci.org/EagerELK/ditty.svg?branch=master)](https://travis-ci.org/EagerELK/ditty)
2
+ [![Code Climate](https://codeclimate.com/github/EagerELK/ditty/badges/gpa.svg)](https://codeclimate.com/github/EagerELK/ditty)
3
+ [![Test Coverage](https://codeclimate.com/github/EagerELK/ditty/badges/coverage.svg)](https://codeclimate.com/github/EagerELK/ditty/coverage)
4
+
5
+ # Ditty
6
+
7
+ Ditty provides an extra layer of functionality on top of [Sinatra](http://sinatrarb.com/) to give structure and basic tools to an already great framework. You can get a new application, with user authentication and basic CRUD / REST interfaces, up and running in minutes.
8
+
9
+ ## Installation
10
+
11
+ Add these lines to your application's Gemfile:
12
+
13
+ ```ruby
14
+ gem 'ditty'
15
+ gem 'sqlite3'
16
+ ```
17
+
18
+ You can replace `sqlite3` with a DB adapter of your choice.
19
+
20
+ And then execute:
21
+
22
+ ```bash
23
+ bundle install
24
+ ```
25
+
26
+ Or install it yourself as:
27
+
28
+ ```bash
29
+ gem install ditty
30
+ ```
31
+
32
+ ## Usage
33
+
34
+ 1. Add the components to your rack config file. See the included [`config.ru`](https://github.com/EagerELK/ditty/blob/master/config.ru) file for an example setup
35
+ 2. Add the Ditty rake tasks to your Rakefile: `require 'ditty/rake_tasks'`
36
+ 3. Set the DB connection as the `DATABASE_URL` ENV variable: `DATABASE_URL=sqlite://development.db`
37
+ 4. Create and populate the DB and secret tokens:
38
+
39
+ ```bash
40
+ bundle exec rake proxes:prep
41
+ bundle exec rake proxes:generate_tokens
42
+ bundle exec rake proxes:migrate
43
+ bundle exec rake proxes:seed
44
+ ```
45
+
46
+ 4. Start up the web app: `bundle exec rackup`
47
+
48
+ ## Components
49
+
50
+ The application can now be further extended by creating components.
51
+
52
+ ## Development
53
+
54
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
55
+
56
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
57
+
58
+
59
+ ## Contributing
60
+
61
+ Bug reports and pull requests are welcome on GitHub at https://github.com/EagerELK/ditty.
62
+
63
+ ## License
64
+
65
+ The Ditty gem is an Open Source project licensed under the terms of
66
+ the MIT license. Please see [MIT license](License.txt)
67
+ for license text.
data/config.ru ADDED
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rack/protection'
4
+ use Rack::Session::Cookie,
5
+ key: '_Ditty_session',
6
+ # :secure=>!TEST_MODE, # Uncomment if only allowing https:// access
7
+ secret: File.read('.session_secret')
8
+ use Rack::Protection::RemoteToken
9
+ use Rack::Protection::SessionHijacking
10
+
11
+ require 'ditty/components/app'
12
+ Ditty.component :app
13
+
14
+ require 'omniauth'
15
+ require 'omniauth/identity'
16
+ OmniAuth.config.logger = Ditty::Services::Logger.instance
17
+ OmniAuth.config.on_failure = proc { |env|
18
+ OmniAuth::FailureEndpoint.new(env).redirect_to_failure
19
+ }
20
+ require 'ditty/controllers/main'
21
+ require 'ditty/models/identity'
22
+ use OmniAuth::Builder do
23
+ # The identity provider is used by the App.
24
+ provider :identity,
25
+ fields: [:username],
26
+ callback_path: '/auth/identity/callback',
27
+ model: Ditty::Identity,
28
+ on_login: Ditty::Main,
29
+ on_registration: Ditty::Main,
30
+ locate_conditions: ->(req) { { username: req['username'] } }
31
+ end
32
+
33
+ run Rack::URLMap.new Ditty::Components.routes
data/ditty.gemspec ADDED
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('../lib', __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'ditty/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = 'ditty'
9
+ spec.version = Ditty::VERSION
10
+ spec.authors = ['Jurgens du Toit']
11
+ spec.email = ['jrgns@jadeit.co.za']
12
+
13
+ spec.summary = 'Sinatra Based Application Framework'
14
+ spec.description = 'Sinatra Based Application Framework'
15
+ spec.homepage = 'https://github.com/eagerelk/ditty'
16
+ spec.license = 'LGPLv3'
17
+
18
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
19
+ spec.bindir = 'exe'
20
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
+ spec.require_paths = ['lib']
22
+
23
+ spec.add_development_dependency 'bundler', '~> 1.12'
24
+ spec.add_development_dependency 'database_cleaner'
25
+ spec.add_development_dependency 'factory_girl'
26
+ spec.add_development_dependency 'rack-test'
27
+ spec.add_development_dependency 'racksh'
28
+ spec.add_development_dependency 'rspec', '~> 3.0'
29
+ spec.add_development_dependency 'timecop'
30
+
31
+ spec.add_dependency 'activesupport', '>= 3'
32
+ spec.add_dependency 'bcrypt', '~> 3.0'
33
+ spec.add_dependency 'haml', '~> 5.0'
34
+ spec.add_dependency 'logger', '~> 1.0'
35
+ spec.add_dependency 'omniauth', '~> 1.0'
36
+ spec.add_dependency 'omniauth-identity', '~> 1.0'
37
+ spec.add_dependency 'pundit', '~> 1.0'
38
+ spec.add_dependency 'rack-contrib', '~> 1.0'
39
+ spec.add_dependency 'rake', '~> 12.0'
40
+ spec.add_dependency 'sequel', '~> 4.0'
41
+ spec.add_dependency 'sinatra', '~> 2.0'
42
+ spec.add_dependency 'sinatra-contrib', '~> 2.0'
43
+ spec.add_dependency 'sinatra-flash', '~> 0.3'
44
+ spec.add_dependency 'tilt', '>= 2'
45
+ spec.add_dependency 'wisper', '~> 2.0'
46
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ditty'
4
+
5
+ module Ditty
6
+ class App
7
+ def self.load_models
8
+ require 'ditty/models/user'
9
+ require 'ditty/models/role'
10
+ require 'ditty/models/identity'
11
+ require 'ditty/models/audit_log'
12
+ end
13
+
14
+ def self.configure(_container)
15
+ require 'ditty/db' unless defined? ::DB
16
+ Sequel::Model.plugin :auto_validations
17
+ Sequel::Model.plugin :update_or_create
18
+ Sequel::Model.plugin :timestamps, update_on_create: true
19
+
20
+ DB.extension(:pagination)
21
+ require 'ditty/listener'
22
+ end
23
+
24
+ def self.migrations
25
+ File.expand_path('../../../../migrate', __FILE__)
26
+ end
27
+
28
+ def self.view_folder
29
+ File.expand_path('../../../../views', __FILE__)
30
+ end
31
+
32
+ def self.routes
33
+ controllers = File.expand_path('../../controllers', __FILE__)
34
+ Dir.glob("#{controllers}/*.rb").each { |f| require f }
35
+ {
36
+ '/' => ::Ditty::Main,
37
+ '/users' => ::Ditty::Users,
38
+ '/roles' => ::Ditty::Roles,
39
+ '/audit-logs' => ::Ditty::AuditLogs
40
+ }
41
+ end
42
+
43
+ def self.navigation
44
+ load_models
45
+
46
+ [
47
+ {
48
+ group: 'User Management',
49
+ order: 10,
50
+ icon: 'lock',
51
+ items: [
52
+ { order: 10, link: '/users/', text: 'Users', target: ::Ditty::User, icon: 'user' },
53
+ { order: 20, link: '/roles/', text: 'Roles', target: ::Ditty::Role, icon: 'check-square' },
54
+ { order: 30, link: '/audit-logs/', text: 'Audit Logs', target: ::Ditty::AuditLog, icon: 'history' }
55
+ ]
56
+ }
57
+ ]
58
+ end
59
+
60
+ def self.seeder
61
+ proc do
62
+ load_models
63
+
64
+ ::Ditty::Role.find_or_create(name: 'super_admin')
65
+ ::Ditty::Role.find_or_create(name: 'admin')
66
+ user_role = ::Ditty::Role.find_or_create(name: 'user')
67
+
68
+ # Anonymous User
69
+ anon = ::Ditty::User.find_or_create(email: 'anonymous@ditty.io')
70
+ anon.remove_role user_role
71
+ anon_role = ::Ditty::Role.find_or_create(name: 'anonymous')
72
+ anon.add_role anon_role unless anon.role?('anonymous')
73
+ end
74
+ end
75
+ end
76
+ end
77
+
78
+ Ditty::Components.register_component(:app, Ditty::App)
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'wisper'
4
+ require 'sinatra/base'
5
+ require 'sinatra/flash'
6
+ require 'sinatra/respond_with'
7
+ require 'ditty/helpers/views'
8
+ require 'ditty/helpers/pundit'
9
+ require 'ditty/helpers/wisper'
10
+ require 'ditty/helpers/authentication'
11
+ require 'ditty/services/logger'
12
+ require 'active_support'
13
+ require 'active_support/inflector'
14
+ require 'rack/contrib'
15
+
16
+ module Ditty
17
+ class Application < Sinatra::Base
18
+ include ActiveSupport::Inflector
19
+
20
+ set :root, ENV['APP_ROOT'] || ::File.expand_path(::File.dirname(__FILE__) + '/../../../')
21
+ set :map_path, nil
22
+ set :view_location, nil
23
+ set :model_class, nil
24
+ # The order here is important, since Wisper has a deprecated method respond_with method
25
+ helpers Wisper::Publisher, Helpers::Wisper
26
+ helpers Helpers::Pundit, Helpers::Views, Helpers::Authentication
27
+
28
+ register Sinatra::Flash, Sinatra::RespondWith
29
+
30
+ use Rack::PostBodyContentTypeParser
31
+ use Rack::MethodOverride
32
+
33
+ def view_location
34
+ return settings.view_location if settings.view_location
35
+ return underscore(pluralize(demodulize(settings.model_class))) if settings.model_class
36
+ underscore(demodulize(self.class))
37
+ end
38
+
39
+ configure :production do
40
+ disable :show_exceptions
41
+ end
42
+
43
+ configure :development do
44
+ set :show_exceptions, :after_handler
45
+ end
46
+
47
+ configure :production, :development do
48
+ enable :logging
49
+ # use Rack::CommonLogger, Ditty::Services::Logger.instance
50
+ end
51
+
52
+ not_found do
53
+ haml :'404', locals: { title: '4 oh 4' }
54
+ end
55
+
56
+ error do
57
+ error = env['sinatra.error']
58
+ haml :error, locals: { title: 'Something went wrong', error: error }
59
+ end
60
+
61
+ error Helpers::NotAuthenticated do
62
+ flash[:warning] = 'Please log in first.'
63
+ redirect "#{settings.map_path}/auth/identity"
64
+ end
65
+
66
+ error ::Pundit::NotAuthorizedError do
67
+ flash[:warning] = 'Please log in first.'
68
+ redirect "#{settings.map_path}/auth/identity"
69
+ end
70
+
71
+ before(/.*/) do
72
+ ::Ditty::Services::Logger.instance.debug "Running with #{self.class}"
73
+ if request.url =~ /.json/
74
+ request.accept.unshift('application/json')
75
+ request.path_info = request.path_info.gsub(/.json/, '')
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ditty/controllers/component'
4
+ require 'ditty/models/audit_log'
5
+ require 'ditty/policies/audit_log_policy'
6
+
7
+ module Ditty
8
+ class AuditLogs < Ditty::Component
9
+ set model_class: AuditLog
10
+
11
+ def find_template(views, name, engine, &block)
12
+ super(views, name, engine, &block) # Root
13
+ super(::Ditty::App.view_folder, name, engine, &block) # Ditty
14
+ end
15
+
16
+ def list
17
+ super.order(:created_at).reverse
18
+ end
19
+
20
+ get '/new' do
21
+ halt 404
22
+ end
23
+
24
+ post '/' do
25
+ halt 404
26
+ end
27
+
28
+ get '/:id' do
29
+ halt 404
30
+ end
31
+
32
+ get '/:id/edit' do
33
+ halt 404
34
+ end
35
+
36
+ put '/:id' do
37
+ halt 404
38
+ end
39
+
40
+ delete '/:id' do
41
+ halt 404
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,161 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ditty/controllers/application'
4
+ require 'ditty/helpers/component'
5
+ require 'sinatra/json'
6
+
7
+ module Ditty
8
+ class Component < Application
9
+ helpers Helpers::Component
10
+
11
+ set base_path: nil
12
+ set dehumanized: nil
13
+ set view_location: nil
14
+ set track_actions: false
15
+
16
+ # List
17
+ get '/', provides: %i[html json] do
18
+ authorize settings.model_class, :list
19
+
20
+ actions = {}
21
+ actions["#{base_path}/new"] = "New #{heading}" if policy(settings.model_class).create?
22
+
23
+ log_action("#{dehumanized}_list".to_sym) if settings.track_actions
24
+ respond_to do |format|
25
+ format.html do
26
+ haml :"#{view_location}/index",
27
+ locals: { list: list, title: heading(:list), actions: actions }
28
+ end
29
+ format.json do
30
+ # TODO: Add links defined by actions (New #{heading})
31
+ json(
32
+ 'items' => list.map(&:for_json),
33
+ 'page' => params[:page],
34
+ 'count' => list.count,
35
+ 'total' => dataset.count
36
+ )
37
+ end
38
+ end
39
+ end
40
+
41
+ # Create Form
42
+ get '/new' do
43
+ authorize settings.model_class, :create
44
+
45
+ entity = settings.model_class.new(permitted_attributes(settings.model_class, :create))
46
+ haml :"#{view_location}/new", locals: { entity: entity, title: heading(:new) }
47
+ end
48
+
49
+ # Create
50
+ post '/' do
51
+ authorize settings.model_class, :create
52
+ entity = settings.model_class.new(permitted_attributes(settings.model_class, :create))
53
+ success = entity.valid? && entity.save
54
+
55
+ log_action("#{dehumanized}_create".to_sym) if success && settings.track_actions
56
+ respond_to do |format|
57
+ format.html do
58
+ if success
59
+ flash[:success] = "#{heading} Created"
60
+ redirect "#{base_path}/#{entity.id}"
61
+ else
62
+ haml :"#{view_location}/new", locals: { entity: entity, title: heading(:new) }
63
+ end
64
+ end
65
+ format.json do
66
+ headers 'Content-Type' => 'application/json'
67
+ if success
68
+ redirect "#{base_path}/#{entity.id}", 201
69
+ else
70
+ 400
71
+ end
72
+ end
73
+ end
74
+ end
75
+
76
+ # Read
77
+ get '/:id' do |id|
78
+ entity = dataset.first(settings.model_class.primary_key => id)
79
+ halt 404 unless entity
80
+ authorize entity, :read
81
+
82
+ actions = {}
83
+ actions["#{base_path}/#{entity.id}/edit"] = "Edit #{heading}" if policy(entity).update?
84
+
85
+ log_action("#{dehumanized}_read".to_sym) if settings.track_actions
86
+ respond_to do |format|
87
+ format.html do
88
+ haml :"#{view_location}/display",
89
+ locals: { entity: entity, title: heading, actions: actions }
90
+ end
91
+ format.json do
92
+ # TODO: Add links defined by actions (Edit #{heading})
93
+ json entity.for_json
94
+ end
95
+ end
96
+ end
97
+
98
+ # Update Form
99
+ get '/:id/edit' do |id|
100
+ entity = dataset.first(settings.model_class.primary_key => id)
101
+ halt 404 unless entity
102
+ authorize entity, :update
103
+
104
+ haml :"#{view_location}/edit", locals: { entity: entity, title: heading(:edit) }
105
+ end
106
+
107
+ # Update
108
+ put '/:id' do |id|
109
+ entity = dataset.first(settings.model_class.primary_key => id)
110
+ halt 404 unless entity
111
+ authorize entity, :update
112
+
113
+ entity.set(permitted_attributes(settings.model_class, :update))
114
+
115
+ success = entity.valid? && entity.save
116
+ log_action("#{dehumanized}_update".to_sym) if success && settings.track_actions
117
+ if success
118
+ respond_to do |format|
119
+ format.html do
120
+ flash[:success] = "#{heading} Updated"
121
+ redirect "#{base_path}/#{entity.id}"
122
+ end
123
+ format.json do
124
+ headers 'Location' => "#{base_path}/#{entity.id}"
125
+ json body entity.for_json
126
+ end
127
+ end
128
+ else
129
+ respond_to do |format|
130
+ format.html do
131
+ haml :"#{view_location}/edit", locals: { entity: entity, title: heading(:edit) }
132
+ end
133
+ format.json do
134
+ 400
135
+ end
136
+ end
137
+ end
138
+ end
139
+
140
+ delete '/:id' do |id|
141
+ entity = dataset.first(settings.model_class.primary_key => id)
142
+ halt 404 unless entity
143
+ authorize entity, :delete
144
+
145
+ entity.destroy
146
+
147
+ log_action("#{dehumanized}_delete".to_sym) if settings.track_actions
148
+ respond_to do |format|
149
+ format.html do
150
+ flash[:success] = "#{heading} Deleted"
151
+ redirect base_path.to_s
152
+ end
153
+ format.json do
154
+ content_type 'application/json'
155
+ headers 'Location' => '/users'
156
+ status 204
157
+ end
158
+ end
159
+ end
160
+ end
161
+ end