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