shaf 0.1.0.beta

Sign up to get free protection for your applications and to get access to all the features.
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
+