tzispa 0.6.1 → 0.7.0

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