tzispa 0.6.1 → 0.7.0

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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +12 -0
  3. data/lib/tzispa/api/handler.rb +54 -23
  4. data/lib/tzispa/app.rb +60 -68
  5. data/lib/tzispa/cli.rb +42 -3
  6. data/lib/tzispa/commands/api.rb +55 -0
  7. data/lib/tzispa/commands/app.rb +83 -0
  8. data/lib/tzispa/commands/cli/generate.rb +60 -0
  9. data/lib/tzispa/commands/command.rb +28 -0
  10. data/lib/tzispa/commands/console.rb +62 -0
  11. data/lib/tzispa/commands/helpers/i18n.rb +67 -0
  12. data/lib/tzispa/commands/helpers/project.rb +69 -0
  13. data/lib/tzispa/commands/helpers/repository.rb +46 -0
  14. data/lib/tzispa/commands/project.rb +104 -0
  15. data/lib/tzispa/commands/repository.rb +66 -0
  16. data/lib/tzispa/commands/rig.rb +28 -0
  17. data/lib/tzispa/commands/server.rb +26 -0
  18. data/lib/tzispa/config/{appconfig.rb → app_config.rb} +12 -32
  19. data/lib/tzispa/config/base.rb +7 -5
  20. data/lib/tzispa/config/db_config.rb +67 -0
  21. data/lib/tzispa/config/yaml.rb +9 -10
  22. data/lib/tzispa/context.rb +3 -2
  23. data/lib/tzispa/controller/api.rb +66 -60
  24. data/lib/tzispa/controller/auth_layout.rb +4 -28
  25. data/lib/tzispa/controller/base.rb +61 -24
  26. data/lib/tzispa/controller/exceptions.rb +3 -4
  27. data/lib/tzispa/controller/http_error.rb +0 -3
  28. data/lib/tzispa/controller/layout.rb +4 -4
  29. data/lib/tzispa/domain.rb +27 -23
  30. data/lib/tzispa/env.rb +34 -0
  31. data/lib/tzispa/environment.rb +231 -0
  32. data/lib/tzispa/http/context.rb +65 -80
  33. data/lib/tzispa/http/request.rb +29 -17
  34. data/lib/tzispa/http/response.rb +45 -12
  35. data/lib/tzispa/route_set.rb +100 -0
  36. data/lib/tzispa/server.rb +61 -0
  37. data/lib/tzispa/tzisparc.rb +80 -0
  38. data/lib/tzispa/version.rb +1 -1
  39. data/lib/tzispa.rb +3 -1
  40. data/tzispa.gemspec +12 -6
  41. metadata +68 -17
  42. data/lib/tzispa/command/api.rb +0 -24
  43. data/lib/tzispa/command/app.rb +0 -95
  44. data/lib/tzispa/command/cli/generate.rb +0 -51
  45. data/lib/tzispa/command/project.rb +0 -258
  46. data/lib/tzispa/command/rig.rb +0 -26
  47. data/lib/tzispa/controller/signed_api.rb +0 -13
  48. data/lib/tzispa/http/session_flash_bag.rb +0 -62
  49. data/lib/tzispa/middleware.rb +0 -48
  50. data/lib/tzispa/routes.rb +0 -69
@@ -0,0 +1,231 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thread'
4
+ require 'pathname'
5
+ require 'singleton'
6
+ require 'tzispa/env'
7
+ require 'tzispa/tzisparc'
8
+ require 'tzispa/utils/hash'
9
+
10
+ module Tzispa
11
+
12
+ class Environment
13
+ include Singleton
14
+ using Tzispa::Utils::TzHash
15
+
16
+ LOCK = Mutex.new
17
+
18
+ RACK_ENV = 'RACK_ENV'
19
+
20
+ TZISPA_ENV = 'TZISPA_ENV'
21
+
22
+ DEVELOPMENT_ENV = 'development'
23
+
24
+ DEFAULT_ENV = 'development'
25
+
26
+ PRODUCTION_ENV = 'deployment'
27
+
28
+ RACK_ENV_DEPLOYMENT = 'deployment'
29
+
30
+ DEFAULT_DOTENV_ENV = '.env.%s'
31
+
32
+ DEFAULT_CONFIG = 'config'
33
+
34
+ TZISPA_HOST = 'TZISPA_HOST'
35
+
36
+ TZISPA_SSL = 'TZISPA_SSL'
37
+
38
+ TZISPA_SERVER_HOST = 'TZISPA_SERVER_HOST'
39
+
40
+ DEFAULT_HOST = 'localhost'
41
+
42
+ TZISPA_PORT = 'TZISPA_PORT'
43
+
44
+ TZISPA_SERVER_PORT = 'TZISPA_SERVER_PORT'
45
+
46
+ DEFAULT_PORT = 9412
47
+
48
+ DEFAULT_RACKUP = 'tzispa.ru'
49
+
50
+ DEFAULT_ENVIRONMENT_CONFIG = 'environment'
51
+
52
+ DEFAULT_DOMAINS_PATH = 'apps'
53
+
54
+ DOMAINS = 'domains'
55
+
56
+ DOMAINS_PATH = 'apps/%s'
57
+
58
+ APPLICATION = 'application'
59
+
60
+ APPLICATION_PATH = 'app'
61
+
62
+ # rubocop:disable Style/ClassVars
63
+ @@opts = {}
64
+
65
+ def initialize
66
+ @env = Tzispa::Env.new(env: @@opts.delete(:env) || ENV)
67
+ @options = Tzispa::Tzisparc.new(root).options
68
+ @options.merge! @@opts.clone.symbolize!
69
+ LOCK.synchronize { set_env_vars! }
70
+ end
71
+
72
+ def self.opts=(hash)
73
+ @@opts = hash.to_h.dup
74
+ end
75
+
76
+ def self.[](key)
77
+ instance[key]
78
+ end
79
+
80
+ def self.development?
81
+ instance.development?
82
+ end
83
+
84
+ def [](key)
85
+ @env[key]
86
+ end
87
+
88
+ def environment
89
+ @environment ||= env[TZISPA_ENV] || rack_env || DEFAULT_ENV
90
+ end
91
+
92
+ def development?
93
+ environment == DEVELOPMENT_ENV
94
+ end
95
+
96
+ def code_reloading?
97
+ development?
98
+ end
99
+
100
+ def environment?(*names)
101
+ names.map(&:to_s).include?(environment)
102
+ end
103
+
104
+ def bundler_groups
105
+ [:default, environment.to_sym]
106
+ end
107
+
108
+ def project_name
109
+ @options.fetch(:project)
110
+ end
111
+
112
+ def architecture
113
+ @options.fetch(:architecture) do
114
+ puts "Tzispa architecture unknown: see `.tzisparc'"
115
+ exit 1
116
+ end
117
+ end
118
+
119
+ def root
120
+ @root ||= Pathname.new(Dir.pwd)
121
+ end
122
+
123
+ def apps_path
124
+ @options.fetch(:path) do
125
+ case architecture
126
+ when DOMAINS
127
+ DOMAINS_PATH
128
+ when APPLICATION
129
+ APPLICATION_PATH
130
+ end
131
+ end
132
+ end
133
+
134
+ def config
135
+ @config ||= root.join(@options.fetch(:config) { DEFAULT_CONFIG })
136
+ end
137
+
138
+ def host
139
+ @host ||= @options.fetch(:host) do
140
+ env[TZISPA_HOST] || DEFAULT_HOST
141
+ end
142
+ end
143
+
144
+ def server_host
145
+ @server_host ||= @options.fetch(:server_host) do
146
+ env[TZISPA_SERVER_HOST] || host
147
+ end
148
+ end
149
+
150
+ def port
151
+ @port ||= @options.fetch(:port) do
152
+ env[TZISPA_PORT] || DEFAULT_PORT
153
+ end.to_i
154
+ end
155
+
156
+ def server_port
157
+ @server_port ||= @options.fetch(:server_port) do
158
+ env[TZISPA_SERVER_PORT] || port
159
+ end.to_i
160
+ end
161
+
162
+ def uri_port
163
+ if ssl?
164
+ ":#{port}" unless port == 443
165
+ else
166
+ ":#{port}" unless port == 80
167
+ end
168
+ end
169
+
170
+ def domains_path
171
+ @domains_path ||= @options.fetch(:domains_path) do
172
+ env[DOMAINS_PATH] || DEFAULT_DOMAINS_PATH
173
+ end
174
+ end
175
+
176
+ def default_port?
177
+ port == DEFAULT_PORT
178
+ end
179
+
180
+ def ssl?
181
+ env[TZISPA_SSL] == 'yes'
182
+ end
183
+
184
+ def rackup
185
+ root.join(@options.fetch(:rackup) { DEFAULT_RACKUP })
186
+ end
187
+
188
+ def daemonize?
189
+ @options.key?(:daemonize) && @options.fetch(:daemonize)
190
+ end
191
+
192
+ def to_options
193
+ @options.to_h.merge(
194
+ environment: environment,
195
+ apps_path: apps_path,
196
+ rackup: rackup,
197
+ host: server_host,
198
+ port: server_port
199
+ )
200
+ end
201
+
202
+ private
203
+
204
+ attr_reader :env
205
+
206
+ def set_env_vars!
207
+ set_application_env_vars!
208
+ set_tzispa_env_vars!
209
+ end
210
+
211
+ def set_tzispa_env_vars!
212
+ env[TZISPA_ENV] = env[RACK_ENV] = environment
213
+ env[TZISPA_HOST] = host
214
+ env[TZISPA_PORT] = port.to_s
215
+ end
216
+
217
+ def set_application_env_vars!
218
+ dotenv = root.join(DEFAULT_DOTENV_ENV % environment)
219
+ env.load!(dotenv) if dotenv.exist?
220
+ end
221
+
222
+ def rack_env
223
+ case env[RACK_ENV]
224
+ when RACK_ENV_DEPLOYMENT
225
+ PRODUCTION_ENV
226
+ else
227
+ env[RACK_ENV]
228
+ end
229
+ end
230
+ end
231
+ end
@@ -1,10 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'forwardable'
2
- require 'securerandom'
3
4
  require 'tzispa/context'
4
5
  require 'tzispa/http/response'
5
6
  require 'tzispa/http/request'
6
- require 'tzispa/http/session_flash_bag'
7
7
  require 'tzispa/helpers/response'
8
+ require 'tzispa/helpers/session'
8
9
  require 'tzispa/helpers/security'
9
10
 
10
11
  module Tzispa
@@ -15,118 +16,111 @@ module Tzispa
15
16
 
16
17
  include Tzispa::Helpers::Response
17
18
  include Tzispa::Helpers::Security
19
+ include Tzispa::Helpers::Session
18
20
 
19
21
  attr_reader :request, :response
20
22
  def_delegators :@request, :session
21
23
 
22
- SESSION_LAST_ACCESS = :__last_access
23
- SESSION_ID = :__session_id
24
- SESSION_AUTH_USER = :__auth__user
25
- GLOBAL_MESSAGE_FLASH = :__global_message_flash
26
-
24
+ def initialize(app, env)
25
+ super(app, env)
26
+ @request = Request.new(env)
27
+ @response = Response.new
28
+ init_session
29
+ end
27
30
 
28
- def initialize(app, environment)
29
- super(app, environment)
30
- @request = Tzispa::Http::Request.new(environment)
31
- @response = Tzispa::Http::Response.new
32
- generate_session_id unless session[SESSION_ID]
31
+ def request_method
32
+ if request.request_method == 'POST' && request['_method']
33
+ env[Request::REQUEST_METHOD] = request['_method']
34
+ request['_method']
35
+ else
36
+ request.request_method
37
+ end
33
38
  end
34
39
 
35
40
  def router_params
36
- env['router.params'] || Hash.new
41
+ env['router.params'] || {}
37
42
  end
38
43
 
39
44
  def layout
40
45
  router_params&.fetch(:layout, nil)
41
46
  end
42
47
 
43
- def set_last_access
44
- session[SESSION_LAST_ACCESS] = Time.now.utc.iso8601
45
- end
46
-
47
- def last_access
48
- session[SESSION_LAST_ACCESS]
49
- end
50
-
51
- def flash
52
- @flash ||= SessionFlashBag.new(session, GLOBAL_MESSAGE_FLASH)
53
- end
54
-
55
- def session?
56
- (not session[SESSION_ID].nil?) and (session[SESSION_ID] == session.id)
57
- end
58
-
59
- def logged?
60
- session? and (not session[SESSION_AUTH_USER].nil?)
61
- end
62
-
63
- def login=(user)
64
- session[SESSION_AUTH_USER] = user unless user.nil?
65
- end
66
-
67
- def login
68
- session[SESSION_AUTH_USER]
48
+ def login_redirect
49
+ redirect(layout_path(config.login_layout.to_sym), true, response) if login_redirect?
69
50
  end
70
51
 
71
- def logout
72
- session.delete(SESSION_AUTH_USER)
52
+ def login_redirect?
53
+ !logged? && (layout != config.login_layout)
73
54
  end
74
55
 
75
- def error_500(str)
76
- 500.tap { |code|
77
- response.body = str if str
78
- }
56
+ def unauthorized_but_logged
57
+ not_authorized unless logged?
79
58
  end
80
59
 
81
- def path(path_id, params={})
60
+ def path(path_id, params = {})
82
61
  app.routes.path path_id, params
83
62
  end
84
63
 
85
- def app_path(app_name, path_id, params={})
64
+ def app_path(app_name, path_id, params = {})
86
65
  app[app_name].routes.path path_id, params
87
66
  end
88
67
 
89
- def canonical_url(path_id, params={})
90
- app.config.canonical_url + path(path_id, params)
68
+ def canonical_root
69
+ @canonical_root ||= begin
70
+ http_proto = Tzispa::Environment.instance.ssl? ? 'https://' : 'http://'
71
+ http_host = Tzispa::Environment.instance.host
72
+ http_port = Tzispa::Environment.instance.uri_port
73
+ "#{http_proto}#{http_host}#{http_port}"
74
+ end
91
75
  end
92
76
 
93
- def app_canonical_url(app_name, path_id, params={})
94
- app[app_name].config.canonical_url + app_path(app_name, path_id, params)
77
+ def canonical_url(path_id, params = {})
78
+ "#{canonical_root}#{path(path_id, params)}"
95
79
  end
96
80
 
97
- def layout_path(layout, params={})
98
- params = params.merge(layout: layout) unless app.config.default_layout&.to_sym == layout
99
- app.routes.path layout, normalize_format(params)
81
+ def app_canonical_url(app_name, path_id, params = {})
82
+ "#{canonical_root}#{app_path(app_name, path_id, params)}"
100
83
  end
101
84
 
102
- def app_layout_path(app_name, layout, params={})
103
- params = params.merge(layout: layout) unless app[app_name].config.default_layout&.to_sym == layout
104
- app[app_name].routes.path layout, normalize_format(params)
85
+ def layout_path(layout, params = {})
86
+ is_default = app.default_layout? layout
87
+ params = normalize_format(params.merge(layout: layout)) unless is_default
88
+ app.routes.path layout, params
105
89
  end
106
90
 
107
- def layout_canonical_url(layout, params={})
108
- app.config.canonical_url + layout_path(layout, params)
91
+ def app_layout_path(app_name, layout, params = {})
92
+ is_default = app[app_name].default_layout? == layout
93
+ params = normalize_format(params.merge(layout: layout)) unless is_default
94
+ app[app_name].routes.path layout, params
109
95
  end
110
96
 
111
- def app_layout_canonical_url(app_name, layout, params={})
112
- app[app_name].config.canonical_url + app_layout_path(app_name, layout, params)
97
+ def layout_canonical_url(layout, params = {})
98
+ "#{canonical_root}#{layout_path(layout, params)}"
99
+ end
100
+
101
+ def app_layout_canonical_url(app_name, layout, params = {})
102
+ "#{canonical_root}#{app_layout_path(app_name, layout, params)}"
113
103
  end
114
104
 
115
105
  def api(handler, verb, predicate, sufix, app_name = nil)
116
- unless app_name
117
- canonical_url :api, handler: handler, verb: verb, predicate: predicate, sufix: sufix
106
+ if app_name
107
+ app_canonical_url app_name, :api, handler: handler, verb: verb,
108
+ predicate: predicate, sufix: sufix
118
109
  else
119
- app_canonical_url app_name, :api, handler: handler, verb: verb, predicate: predicate, sufix: sufix
110
+ canonical_url :api, handler: handler, verb: verb,
111
+ predicate: predicate, sufix: sufix
120
112
  end
121
113
  end
122
114
 
123
115
  def sapi(handler, verb, predicate, sufix, app_name = nil)
124
- unless app_name
125
- sign = sign_array [handler, verb, predicate], app.config.salt
126
- canonical_url :sapi, sign: sign, handler: handler, verb: verb, predicate: predicate, sufix: sufix
127
- else
116
+ if app_name
128
117
  sign = sign_array [handler, verb, predicate], app[:app_name].config.salt
129
- app_canonical_url app_name, :sapi, sign: sign, handler: handler, verb: verb, predicate: predicate, sufix: sufix
118
+ app_canonical_url app_name, :sapi, sign: sign, handler: handler,
119
+ verb: verb, predicate: predicate, sufix: sufix
120
+ else
121
+ sign = sign_array [handler, verb, predicate], app.config.salt
122
+ canonical_url :sapi, sign: sign, handler: handler,
123
+ verb: verb, predicate: predicate, sufix: sufix
130
124
  end
131
125
  end
132
126
 
@@ -136,19 +130,10 @@ module Tzispa
136
130
 
137
131
  private
138
132
 
139
- def generate_session_id
140
- SecureRandom.uuid.tap { |uuid|
141
- session.id = uuid
142
- session[SESSION_ID] = uuid
143
- }
144
- end
145
-
146
133
  def normalize_format(params)
147
- params.tap { |pmm|
148
- pmm[:format] = config.default_format unless pmm[:format]
149
- }
134
+ params.tap { |pmm| pmm[:format] = config.default_format unless pmm[:format] }
150
135
  end
151
-
152
136
  end
137
+
153
138
  end
154
139
  end
@@ -2,34 +2,46 @@
2
2
 
3
3
  require 'rack'
4
4
 
5
-
6
5
  module Tzispa
7
6
  module Http
7
+
8
8
  class Request < Rack::Request
9
+ ALLOWED_HTTP_VERSIONS = ['HTTP/1.1', 'HTTP/2.0'].freeze
10
+
11
+ HTTP_X_FORWARDED_HOST = 'HTTP_X_FORWARDED_HOST'
9
12
 
10
- alias secure? ssl?
13
+ REQUEST_METHOD = Rack::REQUEST_METHOD
11
14
 
15
+ alias secure? ssl?
12
16
 
13
- def forwarded?
14
- env.include? "HTTP_X_FORWARDED_HOST"
15
- end
17
+ def forwarded?
18
+ env.include? HTTP_X_FORWARDED_HOST
19
+ end
16
20
 
17
- def safe?
18
- get? or head? or options? or trace?
19
- end
21
+ def http_version
22
+ env['HTTP_VERSION']
23
+ end
20
24
 
21
- def idempotent?
22
- safe? or put? or delete? or link? or unlink?
23
- end
25
+ def allowed_http_version?
26
+ ALLOWED_HTTP_VERSIONS.include? http_version
27
+ end
24
28
 
25
- def link?
26
- request_method == "LINK"
27
- end
29
+ def safe?
30
+ get? || head? || options? || trace?
31
+ end
28
32
 
29
- def unlink?
30
- request_method == "UNLINK"
31
- end
33
+ def idempotent?
34
+ safe? || put? || delete? || link? || unlink?
35
+ end
32
36
 
37
+ def link?
38
+ request_method == 'LINK'
39
+ end
40
+
41
+ def unlink?
42
+ request_method == 'UNLINK'
43
+ end
33
44
  end
45
+
34
46
  end
35
47
  end
@@ -6,8 +6,7 @@ module Tzispa
6
6
  module Http
7
7
 
8
8
  class Response < Rack::Response
9
-
10
- DROP_BODY_RESPONSES = [204, 205, 304]
9
+ DROP_BODY_RESPONSES = [204, 205, 304].freeze
11
10
 
12
11
  def initialize(*)
13
12
  super
@@ -15,8 +14,8 @@ module Tzispa
15
14
  end
16
15
 
17
16
  def body=(value)
18
- value = value.body while Rack::Response === value
19
- @body = String === value ? [value.to_str] : value
17
+ value = value.body while value.is_a?(Rack::Response)
18
+ @body = value.is_a?(String) ? [value.to_str] : value
20
19
  end
21
20
 
22
21
  def each
@@ -27,8 +26,8 @@ module Tzispa
27
26
  result = body
28
27
 
29
28
  if drop_content_info?
30
- headers.delete "Content-Length"
31
- headers.delete "Content-Type"
29
+ headers.delete 'Content-Length'
30
+ headers.delete 'Content-Type'
32
31
  end
33
32
 
34
33
  if drop_body?
@@ -39,27 +38,61 @@ module Tzispa
39
38
  if calculate_content_length?
40
39
  # if some other code has already set Content-Length, don't muck with it
41
40
  # currently, this would be the static file-handler
42
- headers["Content-Length"] = body.inject(0) { |l, p| l + p.bytesize }.to_s
41
+ headers['Content-Length'] = body.inject(0) { |l, p| l + p.bytesize }.to_s
43
42
  end
44
- headers['X-Frame-Options'] = 'SAMEORIGIN'
45
- headers['X-Powered-By'] = "#{Tzispa::FRAMEWORK_NAME} #{Tzispa::VERSION}"
43
+ custom_headers
46
44
  [status.to_i, headers, result]
47
45
  end
48
46
 
49
- private
47
+ def custom_headers
48
+ headers['X-Frame-Options'] = 'SAMEORIGIN'
49
+ headers['X-Powered-By'] = "#{Tzispa::FRAMEWORK_NAME} #{Tzispa::VERSION}"
50
+ end
51
+
52
+ def cache_control
53
+ headers['Cache-control']
54
+ end
55
+
56
+ def cache_private
57
+ add_cache_control 'private'
58
+ end
59
+
60
+ def no_store
61
+ add_cache_control 'no-store'
62
+ end
63
+
64
+ def no_cache
65
+ add_cache_control 'no-cache'
66
+ end
67
+
68
+ def must_revalidate
69
+ add_cache_control 'must-revalidate'
70
+ end
71
+
72
+ def max_age(seconds)
73
+ add_cache_control "max-age=#{seconds}"
74
+ end
50
75
 
51
76
  def calculate_content_length?
52
- headers["Content-Type"] and not headers["Content-Length"] and Array === body
77
+ headers['Content-Type'] && !headers['Content-Length'] && body.is_a?(Array)
53
78
  end
54
79
 
55
80
  def drop_content_info?
56
- status.to_i / 100 == 1 or drop_body?
81
+ status.to_i / 100 == 1 || drop_body?
57
82
  end
58
83
 
59
84
  def drop_body?
60
85
  DROP_BODY_RESPONSES.include?(status.to_i)
61
86
  end
62
87
 
88
+ private
89
+
90
+ def add_cache_control(policy)
91
+ acache = (cache_control || String.new).split(',').map(&:strip)
92
+ acache << policy unless acache.include?(policy)
93
+ headers['Cache-control'] = acache.join(', ')
94
+ self
95
+ end
63
96
  end
64
97
 
65
98
  end