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.
- checksums.yaml +7 -0
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +1 -0
- data/bin/shaf +57 -0
- data/lib/shaf.rb +9 -0
- data/lib/shaf/api_doc.rb +124 -0
- data/lib/shaf/api_doc/comment.rb +27 -0
- data/lib/shaf/api_doc/document.rb +133 -0
- data/lib/shaf/app.rb +22 -0
- data/lib/shaf/command.rb +42 -0
- data/lib/shaf/command/console.rb +17 -0
- data/lib/shaf/command/generate.rb +19 -0
- data/lib/shaf/command/new.rb +79 -0
- data/lib/shaf/command/server.rb +15 -0
- data/lib/shaf/command/templates/Gemfile.erb +30 -0
- data/lib/shaf/doc_model.rb +54 -0
- data/lib/shaf/errors.rb +77 -0
- data/lib/shaf/extensions.rb +11 -0
- data/lib/shaf/extensions/authorize.rb +42 -0
- data/lib/shaf/extensions/resource_uris.rb +153 -0
- data/lib/shaf/formable.rb +188 -0
- data/lib/shaf/generator.rb +69 -0
- data/lib/shaf/generator/controller.rb +106 -0
- data/lib/shaf/generator/migration.rb +122 -0
- data/lib/shaf/generator/migration/add_column.rb +49 -0
- data/lib/shaf/generator/migration/create_table.rb +40 -0
- data/lib/shaf/generator/migration/drop_column.rb +45 -0
- data/lib/shaf/generator/migration/empty.rb +21 -0
- data/lib/shaf/generator/migration/rename_column.rb +48 -0
- data/lib/shaf/generator/model.rb +68 -0
- data/lib/shaf/generator/policy.rb +43 -0
- data/lib/shaf/generator/scaffold.rb +26 -0
- data/lib/shaf/generator/serializer.rb +258 -0
- data/lib/shaf/generator/templates/api/controller.rb.erb +62 -0
- data/lib/shaf/generator/templates/api/model.rb.erb +20 -0
- data/lib/shaf/generator/templates/api/policy.rb.erb +26 -0
- data/lib/shaf/generator/templates/api/serializer.rb.erb +24 -0
- data/lib/shaf/generator/templates/spec/integration_spec.rb.erb +98 -0
- data/lib/shaf/generator/templates/spec/model.rb.erb +40 -0
- data/lib/shaf/generator/templates/spec/serializer_spec.rb.erb +46 -0
- data/lib/shaf/helpers.rb +15 -0
- data/lib/shaf/helpers/json_html.rb +65 -0
- data/lib/shaf/helpers/paginate.rb +24 -0
- data/lib/shaf/helpers/payload.rb +115 -0
- data/lib/shaf/helpers/session.rb +53 -0
- data/lib/shaf/middleware.rb +1 -0
- data/lib/shaf/middleware/request_id.rb +16 -0
- data/lib/shaf/registrable_factory.rb +71 -0
- data/lib/shaf/settings.rb +33 -0
- data/lib/shaf/spec.rb +6 -0
- data/lib/shaf/spec/http_method_utils.rb +24 -0
- data/lib/shaf/spec/integration_spec.rb +53 -0
- data/lib/shaf/spec/model.rb +17 -0
- data/lib/shaf/spec/payload_test.rb +78 -0
- data/lib/shaf/spec/payload_utils.rb +176 -0
- data/lib/shaf/spec/serializer_spec.rb +24 -0
- data/lib/shaf/tasks.rb +4 -0
- data/lib/shaf/tasks/db.rb +61 -0
- data/lib/shaf/tasks/test.rb +43 -0
- data/lib/shaf/utils.rb +53 -0
- data/lib/shaf/version.rb +3 -0
- data/templates/Rakefile +13 -0
- data/templates/api/controllers/base_controller.rb +57 -0
- data/templates/api/controllers/docs_controller.rb +16 -0
- data/templates/api/controllers/root_controller.rb +8 -0
- data/templates/api/serializers/error_serializer.rb +10 -0
- data/templates/api/serializers/form_serializer.rb +42 -0
- data/templates/api/serializers/root_serializer.rb +16 -0
- data/templates/config.ru +4 -0
- data/templates/config/bootstrap.rb +12 -0
- data/templates/config/constants.rb +5 -0
- data/templates/config/customize.rb +3 -0
- data/templates/config/database.rb +40 -0
- data/templates/config/directories.rb +32 -0
- data/templates/config/helpers.rb +18 -0
- data/templates/config/initializers.rb +12 -0
- data/templates/config/initializers/db_migrations.rb +18 -0
- data/templates/config/initializers/hal_presenter.rb +6 -0
- data/templates/config/initializers/logging.rb +7 -0
- data/templates/config/initializers/sequel.rb +4 -0
- data/templates/config/settings.yml +19 -0
- data/templates/frontend/assets/css/main.css +70 -0
- data/templates/frontend/views/form.erb +16 -0
- data/templates/frontend/views/layout.erb +11 -0
- data/templates/frontend/views/payload.erb +8 -0
- data/templates/spec/integration/root_spec.rb +14 -0
- data/templates/spec/serializers/root_serializer_spec.rb +12 -0
- data/templates/spec/spec_helper.rb +4 -0
- metadata +348 -0
- 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
|
data/lib/shaf/tasks.rb
ADDED
@@ -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
|
+
|
data/lib/shaf/utils.rb
ADDED
@@ -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
|
data/lib/shaf/version.rb
ADDED
data/templates/Rakefile
ADDED
@@ -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,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
|
data/templates/config.ru
ADDED
@@ -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,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
|
+
|