shaf 0.1.0.beta

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.
Files changed (90) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +1 -0
  4. data/bin/shaf +57 -0
  5. data/lib/shaf.rb +9 -0
  6. data/lib/shaf/api_doc.rb +124 -0
  7. data/lib/shaf/api_doc/comment.rb +27 -0
  8. data/lib/shaf/api_doc/document.rb +133 -0
  9. data/lib/shaf/app.rb +22 -0
  10. data/lib/shaf/command.rb +42 -0
  11. data/lib/shaf/command/console.rb +17 -0
  12. data/lib/shaf/command/generate.rb +19 -0
  13. data/lib/shaf/command/new.rb +79 -0
  14. data/lib/shaf/command/server.rb +15 -0
  15. data/lib/shaf/command/templates/Gemfile.erb +30 -0
  16. data/lib/shaf/doc_model.rb +54 -0
  17. data/lib/shaf/errors.rb +77 -0
  18. data/lib/shaf/extensions.rb +11 -0
  19. data/lib/shaf/extensions/authorize.rb +42 -0
  20. data/lib/shaf/extensions/resource_uris.rb +153 -0
  21. data/lib/shaf/formable.rb +188 -0
  22. data/lib/shaf/generator.rb +69 -0
  23. data/lib/shaf/generator/controller.rb +106 -0
  24. data/lib/shaf/generator/migration.rb +122 -0
  25. data/lib/shaf/generator/migration/add_column.rb +49 -0
  26. data/lib/shaf/generator/migration/create_table.rb +40 -0
  27. data/lib/shaf/generator/migration/drop_column.rb +45 -0
  28. data/lib/shaf/generator/migration/empty.rb +21 -0
  29. data/lib/shaf/generator/migration/rename_column.rb +48 -0
  30. data/lib/shaf/generator/model.rb +68 -0
  31. data/lib/shaf/generator/policy.rb +43 -0
  32. data/lib/shaf/generator/scaffold.rb +26 -0
  33. data/lib/shaf/generator/serializer.rb +258 -0
  34. data/lib/shaf/generator/templates/api/controller.rb.erb +62 -0
  35. data/lib/shaf/generator/templates/api/model.rb.erb +20 -0
  36. data/lib/shaf/generator/templates/api/policy.rb.erb +26 -0
  37. data/lib/shaf/generator/templates/api/serializer.rb.erb +24 -0
  38. data/lib/shaf/generator/templates/spec/integration_spec.rb.erb +98 -0
  39. data/lib/shaf/generator/templates/spec/model.rb.erb +40 -0
  40. data/lib/shaf/generator/templates/spec/serializer_spec.rb.erb +46 -0
  41. data/lib/shaf/helpers.rb +15 -0
  42. data/lib/shaf/helpers/json_html.rb +65 -0
  43. data/lib/shaf/helpers/paginate.rb +24 -0
  44. data/lib/shaf/helpers/payload.rb +115 -0
  45. data/lib/shaf/helpers/session.rb +53 -0
  46. data/lib/shaf/middleware.rb +1 -0
  47. data/lib/shaf/middleware/request_id.rb +16 -0
  48. data/lib/shaf/registrable_factory.rb +71 -0
  49. data/lib/shaf/settings.rb +33 -0
  50. data/lib/shaf/spec.rb +6 -0
  51. data/lib/shaf/spec/http_method_utils.rb +24 -0
  52. data/lib/shaf/spec/integration_spec.rb +53 -0
  53. data/lib/shaf/spec/model.rb +17 -0
  54. data/lib/shaf/spec/payload_test.rb +78 -0
  55. data/lib/shaf/spec/payload_utils.rb +176 -0
  56. data/lib/shaf/spec/serializer_spec.rb +24 -0
  57. data/lib/shaf/tasks.rb +4 -0
  58. data/lib/shaf/tasks/db.rb +61 -0
  59. data/lib/shaf/tasks/test.rb +43 -0
  60. data/lib/shaf/utils.rb +53 -0
  61. data/lib/shaf/version.rb +3 -0
  62. data/templates/Rakefile +13 -0
  63. data/templates/api/controllers/base_controller.rb +57 -0
  64. data/templates/api/controllers/docs_controller.rb +16 -0
  65. data/templates/api/controllers/root_controller.rb +8 -0
  66. data/templates/api/serializers/error_serializer.rb +10 -0
  67. data/templates/api/serializers/form_serializer.rb +42 -0
  68. data/templates/api/serializers/root_serializer.rb +16 -0
  69. data/templates/config.ru +4 -0
  70. data/templates/config/bootstrap.rb +12 -0
  71. data/templates/config/constants.rb +5 -0
  72. data/templates/config/customize.rb +3 -0
  73. data/templates/config/database.rb +40 -0
  74. data/templates/config/directories.rb +32 -0
  75. data/templates/config/helpers.rb +18 -0
  76. data/templates/config/initializers.rb +12 -0
  77. data/templates/config/initializers/db_migrations.rb +18 -0
  78. data/templates/config/initializers/hal_presenter.rb +6 -0
  79. data/templates/config/initializers/logging.rb +7 -0
  80. data/templates/config/initializers/sequel.rb +4 -0
  81. data/templates/config/settings.yml +19 -0
  82. data/templates/frontend/assets/css/main.css +70 -0
  83. data/templates/frontend/views/form.erb +16 -0
  84. data/templates/frontend/views/layout.erb +11 -0
  85. data/templates/frontend/views/payload.erb +8 -0
  86. data/templates/spec/integration/root_spec.rb +14 -0
  87. data/templates/spec/serializers/root_serializer_spec.rb +12 -0
  88. data/templates/spec/spec_helper.rb +4 -0
  89. metadata +348 -0
  90. metadata.gz.sig +0 -0
@@ -0,0 +1,24 @@
1
+ module Shaf
2
+ module Spec
3
+ class SerializerSpec < Minitest::Spec
4
+ include Minitest::Hooks
5
+ include PayloadUtils
6
+
7
+ TRANSACTION_OPTIONS = {
8
+ rollback: :always,
9
+ savepoint: true,
10
+ auto_savepoint: true
11
+ }.freeze
12
+
13
+ register_spec_type self do |desc, args|
14
+ next true if desc.to_s =~ /Serializer$/
15
+ next unless args && args.is_a?(Hash)
16
+ args[:type]&.to_s == 'serializer'
17
+ end
18
+
19
+ around do |&block|
20
+ DB.transaction(TRANSACTION_OPTIONS) { super(&block) }
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,4 @@
1
+ require 'shaf/settings'
2
+ require 'shaf/tasks/db'
3
+ require 'shaf/tasks/test'
4
+
@@ -0,0 +1,61 @@
1
+ namespace :db do
2
+ desc "Prints current schema version"
3
+ task :version do
4
+ require 'config/database'
5
+ if DB.tables.include?(:schema_migrations)
6
+ migration = DB[:schema_migrations].order(:filename).last
7
+ filename = migration && migration[:filename]
8
+ if match = /(\d*)_(.*).rb/.match(filename)
9
+ puts "Schema version: #{match[1]} (#{match[2]})"
10
+ else
11
+ puts "No migration found"
12
+ end
13
+ else
14
+ puts "No schema_info table"
15
+ end
16
+ end
17
+
18
+ desc "Prints all schema versions"
19
+ task :versions do
20
+ require 'config/database'
21
+ if DB.tables.include?(:schema_migrations)
22
+ DB[:schema_migrations].each do |migration|
23
+ filename = migration && migration[:filename]
24
+ if match = /(\d*)_(.*).rb/.match(filename)
25
+ puts "#{match[1]}: #{match[2]}"
26
+ end
27
+ end
28
+ else
29
+ puts "No schema_info table"
30
+ end
31
+ end
32
+
33
+ desc "Run migrations"
34
+ task :migrate, [:version] do |t, args|
35
+ require 'config/database'
36
+ Sequel.extension :migration
37
+ if args[:version]
38
+ puts "Migrating to version #{args[:version]}"
39
+ Sequel::Migrator.run(DB, MIGRATIONS_DIR, target: args[:version].to_i)
40
+ else
41
+ puts "Migrating to latest"
42
+ Sequel::Migrator.run(DB, MIGRATIONS_DIR)
43
+ end
44
+ end
45
+
46
+ desc "Perform rollback to specified target or full rollback as default"
47
+ task :rollback, :target do |t, args|
48
+ args.with_defaults(target: 0)
49
+ require 'config/database'
50
+ Sequel.extension :migration
51
+ Sequel::Migrator.run(DB, MIGRATIONS_DIR, target: args[:target].to_i)
52
+ end
53
+
54
+ Rake::Task["db:migrate"].enhance do
55
+ Rake::Task["db:version"].invoke
56
+ end
57
+
58
+ Rake::Task["db:rollback"].enhance do
59
+ Rake::Task["db:version"].invoke
60
+ end
61
+ end
@@ -0,0 +1,43 @@
1
+ require 'rake/testtask'
2
+
3
+ namespace :test do |ns|
4
+ Rake::TestTask.new(:integration) do |t|
5
+ t.libs = %w(. api spec)
6
+ t.pattern = "spec/integration/**/*_spec.rb"
7
+ t.verbose = true
8
+ end
9
+
10
+ Rake::TestTask.new(:models) do |t|
11
+ t.libs = %w(. api spec)
12
+ t.pattern = "spec/models/**/*_spec.rb"
13
+ t.verbose = true
14
+ end
15
+
16
+ Rake::TestTask.new(:serializers) do |t|
17
+ t.libs = %w(. api spec)
18
+ t.pattern = "spec/serializers/**/*_spec.rb"
19
+ t.verbose = true
20
+ end
21
+
22
+ Rake::TestTask.new(:lib) do |t|
23
+ t.libs = %w(. api spec)
24
+ t.pattern = "spec/lib/**/*_spec.rb"
25
+ t.verbose = true
26
+ end
27
+
28
+ Rake::TestTask.new(:all) do |t|
29
+ t.libs = %w(. api spec)
30
+ t.pattern = [
31
+ "spec/lib/**/*_spec.rb",
32
+ "spec/models/**/*_spec.rb",
33
+ "spec/serializers/**/*_spec.rb",
34
+ "spec/integration/**/*_spec.rb"
35
+ ]
36
+ t.verbose = true
37
+ end
38
+
39
+ end
40
+
41
+ desc "Run all tests"
42
+ task test: 'test:all'
43
+
@@ -0,0 +1,53 @@
1
+ module Shaf
2
+ module Utils
3
+ class ProjectRootNotFound < StandardError; end
4
+
5
+ def self.pluralize(noun)
6
+ noun + 's' #FIXME
7
+ end
8
+
9
+ def project_root
10
+ dir = Dir.pwd
11
+ 20.times do
12
+ if is_project_root?(dir)
13
+ return dir
14
+ elsif dir == '/'
15
+ break
16
+ end
17
+ dir = File.expand_path("..", dir)
18
+ end
19
+ end
20
+
21
+ def project_root!
22
+ project_root or raise ProjectRootNotFound
23
+ end
24
+
25
+ def is_project_root?(dir)
26
+ File.exist? File.expand_path(".shaf", dir)
27
+ end
28
+
29
+ def in_project_root?
30
+ is_project_root?(Dir.pwd)
31
+ end
32
+
33
+ def in_project_root
34
+ return unless block_given?
35
+ Dir.chdir(project_root!) do
36
+ $:.unshift Dir.pwd
37
+ yield
38
+ end
39
+ end
40
+
41
+ def bootstrap
42
+ in_project_root do
43
+ ENV['RACK_ENV'] ||= 'development'
44
+ require 'config/bootstrap'
45
+ yield if block_given?
46
+ end
47
+ end
48
+
49
+ def pluralize(noun)
50
+ Utils::pluralize(noun)
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,3 @@
1
+ module Shaf
2
+ VERSION = "0.1.0.beta"
3
+ end
@@ -0,0 +1,13 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ Bundler.setup(:default, :development)
4
+ $:.unshift __dir__
5
+ require 'shaf/tasks'
6
+ require 'shaf/settings'
7
+ require 'shaf/api_doc'
8
+
9
+ Shaf::ApiDoc::Task.new do |api_doc|
10
+ api_doc.source_dir = File.join(%w(api serializers))
11
+ api_doc.html_output_dir = File.join(Shaf::Settings.public_folder, "doc")
12
+ api_doc.yaml_output_dir = Shaf::Settings.documents_dir || "doc/api"
13
+ end
@@ -0,0 +1,57 @@
1
+ class BaseController < Sinatra::Base
2
+
3
+ include Shaf::Errors
4
+
5
+ configure do
6
+ disable :static
7
+ enable :logging
8
+ enable :method_override
9
+ mime_type :hal, 'application/hal+json'
10
+ set :views, VIEWS_DIR
11
+ set :static, !production?
12
+ set :public_folder, ASSETS_DIR
13
+ disable :dump_errors
14
+ set :show_exceptions, :after_handler
15
+ end
16
+
17
+ use Rack::Deflater
18
+ register(*Shaf.extensions)
19
+ helpers(*Shaf.helpers)
20
+
21
+ def self.inherited(controller)
22
+ super
23
+ Shaf::App.use controller
24
+ end
25
+
26
+ def log
27
+ $logger
28
+ end
29
+
30
+ before do
31
+ log.info "Processing: #{request.request_method} #{request.path_info}"
32
+ log.debug "Payload: #{payload || 'empty'}"
33
+ end
34
+
35
+ error StandardError do
36
+ err = env['sinatra.error']
37
+ log.error err.message
38
+ err.backtrace.each(&log.method(:error))
39
+
40
+ api_error = to_api_error(err)
41
+
42
+ respond_with api_error,
43
+ status: api_error.http_status,
44
+ serializer: ErrorSerializer
45
+ end
46
+
47
+ def to_api_error(err)
48
+ case err
49
+ when Shaf::Authorize::PolicyViolationError
50
+ ForbiddenError.new
51
+ when ServerError
52
+ err
53
+ else
54
+ ServerError.new(err.message)
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,16 @@
1
+ class DocsController < BaseController
2
+
3
+ register_uri :doc_curie, '/doc/:resource/rels/{rel}'
4
+
5
+ get '/doc/:resource/rels/:rel' do
6
+ doc.link(params[:rel])
7
+ end
8
+
9
+ get '/doc/:resource' do
10
+ doc.to_s
11
+ end
12
+
13
+ def doc
14
+ Shaf::DocModel.find!(params[:resource])
15
+ end
16
+ end
@@ -0,0 +1,8 @@
1
+ class RootController < BaseController
2
+
3
+ register_uri :root, '/'
4
+
5
+ get '/' do
6
+ respond_with nil, serializer: RootSerializer
7
+ end
8
+ end
@@ -0,0 +1,10 @@
1
+ class ErrorSerializer
2
+ extend HALPresenter
3
+
4
+ model Shaf::Errors::ServerError
5
+
6
+ attribute :title
7
+ attribute :code
8
+ attribute :message
9
+
10
+ end
@@ -0,0 +1,42 @@
1
+ require 'shaf/formable'
2
+
3
+ class FormSerializer
4
+ extend HALPresenter
5
+
6
+ model Shaf::Formable::Form
7
+
8
+ attribute :method do
9
+ (options[:method] || resource&.method || 'POST').to_s.upcase
10
+ end
11
+
12
+ attribute :name do
13
+ options[:name] || resource&.name
14
+ end
15
+
16
+ attribute :title do
17
+ options[:title] || resource&.title
18
+ end
19
+
20
+ attribute :href do
21
+ options[:href] || resource&.href
22
+ end
23
+
24
+ attribute :type do
25
+ options[:type] || resource&.type
26
+ end
27
+
28
+ link :self do
29
+ options[:self_link] || resource&.self_link
30
+ end
31
+
32
+ post_serialize do |hash|
33
+ fields = resource&.fields
34
+ break if fields.nil? || fields.empty?
35
+ hash[:fields] = fields.map do |field|
36
+ { name: field.name, type: field.type }.tap do |f|
37
+ f[:value] = field.value if field.has_value?
38
+ f[:label] = field.label
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,16 @@
1
+ class RootSerializer
2
+ extend HALPresenter
3
+ extend Shaf::UriHelper
4
+
5
+ # Auto generated doc:
6
+ # Link to the root resource. All clients should fetch this resource
7
+ # when starting to interact with this API.
8
+ # Method: GET
9
+ # Example:
10
+ # ```
11
+ # curl -H "Accept: application/json" \
12
+ # -H "Authorization: abcdef" \
13
+ # /
14
+ #```
15
+ link :self, root_uri
16
+ end
@@ -0,0 +1,4 @@
1
+ $:.unshift __dir__
2
+ require 'config/bootstrap'
3
+
4
+ run Shaf::App.instance
@@ -0,0 +1,12 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ Bundler.require(:default, ENV['RACK_ENV'])
4
+
5
+ $logger = nil
6
+ require 'shaf/settings'
7
+ require 'config/constants'
8
+ require 'config/database'
9
+ require 'config/initializers'
10
+ require 'api/controllers/base_controller'
11
+ require 'config/directories'
12
+ require 'config/helpers'
@@ -0,0 +1,5 @@
1
+ APP_ROOT = File.expand_path('../', __dir__)
2
+ APP_DIR = File.expand_path('api', APP_ROOT)
3
+ VIEWS_DIR = File.join(APP_ROOT, Shaf::Settings.views_folder)
4
+ ASSETS_DIR = File.join(APP_ROOT, Shaf::Settings.public_folder)
5
+ PAGINATION_PER_PAGE = Shaf::Settings.paginate_per_page || 25
@@ -0,0 +1,3 @@
1
+ # Load your custom commands and generators in this file. E.g.
2
+ # require 'lib/my_command'
3
+ # require 'lib/my_generator'
@@ -0,0 +1,40 @@
1
+ require 'sinatra/base'
2
+ require 'sequel'
3
+ require 'config/constants'
4
+ require 'fileutils'
5
+
6
+ CONFIG = {
7
+
8
+ production: {
9
+ adapter: 'postgres',
10
+ host: ENV['SHAF_DB_HOST'],
11
+ database: ENV['SHAF_DB_NAME'],
12
+ user: ENV['SHAF_DB_USER'],
13
+ password: ENV['SHAF_DB_PASS']
14
+ }.freeze,
15
+
16
+ development: {
17
+ adapter: 'sqlite',
18
+ database: File.join(APP_ROOT, 'db', 'development.sqlite3'),
19
+ }.freeze,
20
+
21
+ test: {
22
+ adapter: 'sqlite',
23
+ database: File.join(APP_ROOT, 'db', 'test.sqlite3'),
24
+ }.freeze
25
+
26
+ }.freeze
27
+
28
+
29
+ env = Sinatra::Application.settings.environment
30
+
31
+ unless CONFIG[env]
32
+ STDERR.puts "No Database config for environment '#{env}'"
33
+ exit 1
34
+ end
35
+
36
+ MIGRATIONS_DIR = File.join(APP_ROOT, Shaf::Settings.migrations_dir)
37
+ FileUtils.mkdir_p(MIGRATIONS_DIR) unless Dir.exist?(MIGRATIONS_DIR)
38
+
39
+ DB = Sequel.connect(CONFIG[env])
40
+
@@ -0,0 +1,32 @@
1
+ require 'config/constants'
2
+
3
+ $:.unshift APP_DIR
4
+
5
+ def sort_files(files)
6
+ files.sort_by do |file|
7
+ case file
8
+ when /\Alib/
9
+ 0
10
+ when /\Ahelpers/
11
+ 1
12
+ when /\Amodels/
13
+ 2
14
+ when /\Acontrollers/
15
+ 3
16
+ when /\Apolicies/
17
+ 4
18
+ else
19
+ 5
20
+ end
21
+ end
22
+ end
23
+
24
+ Dir.chdir(APP_DIR) do
25
+ files = Dir[File.join('**', '*.rb')]
26
+ sort_files(files).each do |file|
27
+ # load all files with .rb extension in subfolders of api
28
+ $logger&.debug "Require file: #{file}"
29
+ require file
30
+ end
31
+ end
32
+