otto 1.5.0 → 2.0.0.pre1

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 (136) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +44 -5
  3. data/.github/workflows/claude-code-review.yml +53 -0
  4. data/.github/workflows/claude.yml +49 -0
  5. data/.gitignore +3 -0
  6. data/.rubocop.yml +24 -345
  7. data/CHANGELOG.rst +83 -0
  8. data/CLAUDE.md +56 -0
  9. data/Gemfile +21 -5
  10. data/Gemfile.lock +69 -31
  11. data/README.md +2 -0
  12. data/bin/rspec +16 -0
  13. data/changelog.d/20250911_235619_delano_next.rst +28 -0
  14. data/changelog.d/20250912_123055_delano_remove_ostruct.rst +21 -0
  15. data/changelog.d/20250912_175625_claude_delano_remove_ostruct.rst +21 -0
  16. data/changelog.d/README.md +120 -0
  17. data/changelog.d/scriv.ini +5 -0
  18. data/docs/.gitignore +1 -0
  19. data/docs/migrating/v2.0.0-pre1.md +276 -0
  20. data/examples/.gitignore +1 -0
  21. data/examples/advanced_routes/README.md +33 -0
  22. data/examples/advanced_routes/app/controllers/handlers/async.rb +9 -0
  23. data/examples/advanced_routes/app/controllers/handlers/dynamic.rb +9 -0
  24. data/examples/advanced_routes/app/controllers/handlers/static.rb +9 -0
  25. data/examples/advanced_routes/app/controllers/modules/auth.rb +9 -0
  26. data/examples/advanced_routes/app/controllers/modules/transformer.rb +9 -0
  27. data/examples/advanced_routes/app/controllers/modules/validator.rb +9 -0
  28. data/examples/advanced_routes/app/controllers/routes_app.rb +232 -0
  29. data/examples/advanced_routes/app/controllers/v2/admin.rb +9 -0
  30. data/examples/advanced_routes/app/controllers/v2/config.rb +9 -0
  31. data/examples/advanced_routes/app/controllers/v2/settings.rb +9 -0
  32. data/examples/advanced_routes/app/logic/admin/logic/manager.rb +27 -0
  33. data/examples/advanced_routes/app/logic/admin/panel.rb +27 -0
  34. data/examples/advanced_routes/app/logic/analytics_processor.rb +25 -0
  35. data/examples/advanced_routes/app/logic/complex/business/handler.rb +27 -0
  36. data/examples/advanced_routes/app/logic/data_logic.rb +23 -0
  37. data/examples/advanced_routes/app/logic/data_processor.rb +25 -0
  38. data/examples/advanced_routes/app/logic/input_validator.rb +24 -0
  39. data/examples/advanced_routes/app/logic/nested/feature/logic.rb +27 -0
  40. data/examples/advanced_routes/app/logic/reports_generator.rb +27 -0
  41. data/examples/advanced_routes/app/logic/simple_logic.rb +25 -0
  42. data/examples/advanced_routes/app/logic/system/config/manager.rb +27 -0
  43. data/examples/advanced_routes/app/logic/test_logic.rb +23 -0
  44. data/examples/advanced_routes/app/logic/transform_logic.rb +23 -0
  45. data/examples/advanced_routes/app/logic/upload_logic.rb +23 -0
  46. data/examples/advanced_routes/app/logic/v2/logic/dashboard.rb +27 -0
  47. data/examples/advanced_routes/app/logic/v2/logic/processor.rb +27 -0
  48. data/examples/advanced_routes/app.rb +33 -0
  49. data/examples/advanced_routes/config.rb +23 -0
  50. data/examples/advanced_routes/config.ru +7 -0
  51. data/examples/advanced_routes/puma.rb +20 -0
  52. data/examples/advanced_routes/routes +167 -0
  53. data/examples/advanced_routes/run.rb +39 -0
  54. data/examples/advanced_routes/test.rb +58 -0
  55. data/examples/authentication_strategies/README.md +32 -0
  56. data/examples/authentication_strategies/app/auth.rb +68 -0
  57. data/examples/authentication_strategies/app/controllers/auth_controller.rb +29 -0
  58. data/examples/authentication_strategies/app/controllers/main_controller.rb +28 -0
  59. data/examples/authentication_strategies/config.ru +24 -0
  60. data/examples/authentication_strategies/routes +37 -0
  61. data/examples/basic/README.md +29 -0
  62. data/examples/basic/app.rb +7 -35
  63. data/examples/basic/routes +0 -9
  64. data/examples/mcp_demo/README.md +87 -0
  65. data/examples/mcp_demo/app.rb +51 -0
  66. data/examples/mcp_demo/config.ru +17 -0
  67. data/examples/mcp_demo/routes +9 -0
  68. data/examples/security_features/README.md +46 -0
  69. data/examples/security_features/app.rb +23 -24
  70. data/examples/security_features/config.ru +8 -10
  71. data/lib/otto/core/configuration.rb +167 -0
  72. data/lib/otto/core/error_handler.rb +86 -0
  73. data/lib/otto/core/file_safety.rb +61 -0
  74. data/lib/otto/core/middleware_stack.rb +157 -0
  75. data/lib/otto/core/router.rb +183 -0
  76. data/lib/otto/core/uri_generator.rb +44 -0
  77. data/lib/otto/design_system.rb +7 -5
  78. data/lib/otto/helpers/base.rb +3 -0
  79. data/lib/otto/helpers/request.rb +10 -8
  80. data/lib/otto/helpers/response.rb +5 -4
  81. data/lib/otto/helpers/validation.rb +85 -0
  82. data/lib/otto/mcp/auth/token.rb +77 -0
  83. data/lib/otto/mcp/protocol.rb +164 -0
  84. data/lib/otto/mcp/rate_limiting.rb +155 -0
  85. data/lib/otto/mcp/registry.rb +100 -0
  86. data/lib/otto/mcp/route_parser.rb +77 -0
  87. data/lib/otto/mcp/server.rb +206 -0
  88. data/lib/otto/mcp/validation.rb +123 -0
  89. data/lib/otto/response_handlers/auto.rb +39 -0
  90. data/lib/otto/response_handlers/base.rb +16 -0
  91. data/lib/otto/response_handlers/default.rb +16 -0
  92. data/lib/otto/response_handlers/factory.rb +39 -0
  93. data/lib/otto/response_handlers/json.rb +28 -0
  94. data/lib/otto/response_handlers/redirect.rb +25 -0
  95. data/lib/otto/response_handlers/view.rb +24 -0
  96. data/lib/otto/response_handlers.rb +9 -135
  97. data/lib/otto/route.rb +9 -9
  98. data/lib/otto/route_definition.rb +30 -33
  99. data/lib/otto/route_handlers/base.rb +121 -0
  100. data/lib/otto/route_handlers/class_method.rb +89 -0
  101. data/lib/otto/route_handlers/factory.rb +29 -0
  102. data/lib/otto/route_handlers/instance_method.rb +69 -0
  103. data/lib/otto/route_handlers/lambda.rb +59 -0
  104. data/lib/otto/route_handlers/logic_class.rb +93 -0
  105. data/lib/otto/route_handlers.rb +10 -376
  106. data/lib/otto/security/authentication/auth_strategy.rb +44 -0
  107. data/lib/otto/security/authentication/authentication_middleware.rb +123 -0
  108. data/lib/otto/security/authentication/failure_result.rb +36 -0
  109. data/lib/otto/security/authentication/strategies/api_key_strategy.rb +40 -0
  110. data/lib/otto/security/authentication/strategies/permission_strategy.rb +47 -0
  111. data/lib/otto/security/authentication/strategies/public_strategy.rb +19 -0
  112. data/lib/otto/security/authentication/strategies/role_strategy.rb +57 -0
  113. data/lib/otto/security/authentication/strategies/session_strategy.rb +41 -0
  114. data/lib/otto/security/authentication/strategy_result.rb +223 -0
  115. data/lib/otto/security/authentication.rb +28 -282
  116. data/lib/otto/security/config.rb +15 -11
  117. data/lib/otto/security/configurator.rb +219 -0
  118. data/lib/otto/security/csrf.rb +8 -143
  119. data/lib/otto/security/middleware/csrf_middleware.rb +151 -0
  120. data/lib/otto/security/middleware/rate_limit_middleware.rb +38 -0
  121. data/lib/otto/security/middleware/validation_middleware.rb +252 -0
  122. data/lib/otto/security/rate_limiter.rb +86 -0
  123. data/lib/otto/security/rate_limiting.rb +16 -0
  124. data/lib/otto/security/validator.rb +8 -292
  125. data/lib/otto/static.rb +3 -0
  126. data/lib/otto/utils.rb +14 -0
  127. data/lib/otto/version.rb +3 -1
  128. data/lib/otto.rb +184 -414
  129. data/otto.gemspec +11 -6
  130. metadata +134 -25
  131. data/examples/dynamic_pages/app.rb +0 -115
  132. data/examples/dynamic_pages/config.ru +0 -30
  133. data/examples/dynamic_pages/routes +0 -21
  134. data/examples/helpers_demo/app.rb +0 -244
  135. data/examples/helpers_demo/config.ru +0 -26
  136. data/examples/helpers_demo/routes +0 -7
data/otto.gemspec CHANGED
@@ -10,18 +10,23 @@ Gem::Specification.new do |spec|
10
10
  spec.email = 'gems@solutious.com'
11
11
  spec.authors = ['Delano Mandelbaum']
12
12
  spec.license = 'MIT'
13
- spec.files = Dir.chdir(File.expand_path(__dir__)) do
14
- `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
15
- end
13
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
16
14
  spec.homepage = 'https://github.com/delano/otto'
17
15
  spec.require_paths = ['lib']
18
16
 
19
17
  spec.required_ruby_version = ['>= 3.2', '< 4.0']
20
18
 
21
- # https://github.com/delano/otto/security/dependabot/5
22
- spec.add_dependency 'rexml', '>= 3.3.6'
23
- spec.add_dependency 'ostruct'
19
+
24
20
  spec.add_dependency 'rack', '~> 3.1', '< 4.0'
25
21
  spec.add_dependency 'rack-parser', '~> 0.7'
22
+ spec.add_dependency 'rexml', '>= 3.3.6'
23
+
24
+ # Security dependencies
25
+ spec.add_dependency 'facets', '~> 3.1'
26
+ spec.add_dependency 'loofah', '~> 2.20'
27
+
28
+ # Optional MCP dependencies
29
+ # spec.add_dependency 'json_schemer', '~> 2.0'
30
+ # spec.add_dependency 'rack-attack', '~> 6.7'
26
31
  spec.metadata['rubygems_mfa_required'] = 'true'
27
32
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: otto
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.0
4
+ version: 2.0.0.pre1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Delano Mandelbaum
@@ -10,43 +10,60 @@ cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
- name: rexml
13
+ name: rack
14
14
  requirement: !ruby/object:Gem::Requirement
15
15
  requirements:
16
- - - ">="
16
+ - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: 3.3.6
18
+ version: '3.1'
19
+ - - "<"
20
+ - !ruby/object:Gem::Version
21
+ version: '4.0'
19
22
  type: :runtime
20
23
  prerelease: false
21
24
  version_requirements: !ruby/object:Gem::Requirement
22
25
  requirements:
23
- - - ">="
26
+ - - "~>"
24
27
  - !ruby/object:Gem::Version
25
- version: 3.3.6
28
+ version: '3.1'
29
+ - - "<"
30
+ - !ruby/object:Gem::Version
31
+ version: '4.0'
26
32
  - !ruby/object:Gem::Dependency
27
- name: ostruct
33
+ name: rack-parser
34
+ requirement: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - "~>"
37
+ - !ruby/object:Gem::Version
38
+ version: '0.7'
39
+ type: :runtime
40
+ prerelease: false
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - "~>"
44
+ - !ruby/object:Gem::Version
45
+ version: '0.7'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rexml
28
48
  requirement: !ruby/object:Gem::Requirement
29
49
  requirements:
30
50
  - - ">="
31
51
  - !ruby/object:Gem::Version
32
- version: '0'
52
+ version: 3.3.6
33
53
  type: :runtime
34
54
  prerelease: false
35
55
  version_requirements: !ruby/object:Gem::Requirement
36
56
  requirements:
37
57
  - - ">="
38
58
  - !ruby/object:Gem::Version
39
- version: '0'
59
+ version: 3.3.6
40
60
  - !ruby/object:Gem::Dependency
41
- name: rack
61
+ name: facets
42
62
  requirement: !ruby/object:Gem::Requirement
43
63
  requirements:
44
64
  - - "~>"
45
65
  - !ruby/object:Gem::Version
46
66
  version: '3.1'
47
- - - "<"
48
- - !ruby/object:Gem::Version
49
- version: '4.0'
50
67
  type: :runtime
51
68
  prerelease: false
52
69
  version_requirements: !ruby/object:Gem::Requirement
@@ -54,23 +71,20 @@ dependencies:
54
71
  - - "~>"
55
72
  - !ruby/object:Gem::Version
56
73
  version: '3.1'
57
- - - "<"
58
- - !ruby/object:Gem::Version
59
- version: '4.0'
60
74
  - !ruby/object:Gem::Dependency
61
- name: rack-parser
75
+ name: loofah
62
76
  requirement: !ruby/object:Gem::Requirement
63
77
  requirements:
64
78
  - - "~>"
65
79
  - !ruby/object:Gem::Version
66
- version: '0.7'
80
+ version: '2.20'
67
81
  type: :runtime
68
82
  prerelease: false
69
83
  version_requirements: !ruby/object:Gem::Requirement
70
84
  requirements:
71
85
  - - "~>"
72
86
  - !ruby/object:Gem::Version
73
- version: '0.7'
87
+ version: '2.20'
74
88
  description: 'Otto: Auto-define your rack-apps in plaintext.'
75
89
  email: gems@solutious.com
76
90
  executables: []
@@ -79,42 +93,137 @@ extra_rdoc_files: []
79
93
  files:
80
94
  - ".github/dependabot.yml"
81
95
  - ".github/workflows/ci.yml"
96
+ - ".github/workflows/claude-code-review.yml"
97
+ - ".github/workflows/claude.yml"
82
98
  - ".gitignore"
83
99
  - ".pre-commit-config.yaml"
84
100
  - ".pre-push-config.yaml"
85
101
  - ".rspec"
86
102
  - ".rubocop.yml"
103
+ - CHANGELOG.rst
104
+ - CLAUDE.md
87
105
  - Gemfile
88
106
  - Gemfile.lock
89
107
  - LICENSE.txt
90
108
  - README.md
109
+ - bin/rspec
110
+ - changelog.d/20250911_235619_delano_next.rst
111
+ - changelog.d/20250912_123055_delano_remove_ostruct.rst
112
+ - changelog.d/20250912_175625_claude_delano_remove_ostruct.rst
113
+ - changelog.d/README.md
114
+ - changelog.d/scriv.ini
91
115
  - docs/.gitignore
116
+ - docs/migrating/v2.0.0-pre1.md
117
+ - examples/.gitignore
118
+ - examples/advanced_routes/README.md
119
+ - examples/advanced_routes/app.rb
120
+ - examples/advanced_routes/app/controllers/handlers/async.rb
121
+ - examples/advanced_routes/app/controllers/handlers/dynamic.rb
122
+ - examples/advanced_routes/app/controllers/handlers/static.rb
123
+ - examples/advanced_routes/app/controllers/modules/auth.rb
124
+ - examples/advanced_routes/app/controllers/modules/transformer.rb
125
+ - examples/advanced_routes/app/controllers/modules/validator.rb
126
+ - examples/advanced_routes/app/controllers/routes_app.rb
127
+ - examples/advanced_routes/app/controllers/v2/admin.rb
128
+ - examples/advanced_routes/app/controllers/v2/config.rb
129
+ - examples/advanced_routes/app/controllers/v2/settings.rb
130
+ - examples/advanced_routes/app/logic/admin/logic/manager.rb
131
+ - examples/advanced_routes/app/logic/admin/panel.rb
132
+ - examples/advanced_routes/app/logic/analytics_processor.rb
133
+ - examples/advanced_routes/app/logic/complex/business/handler.rb
134
+ - examples/advanced_routes/app/logic/data_logic.rb
135
+ - examples/advanced_routes/app/logic/data_processor.rb
136
+ - examples/advanced_routes/app/logic/input_validator.rb
137
+ - examples/advanced_routes/app/logic/nested/feature/logic.rb
138
+ - examples/advanced_routes/app/logic/reports_generator.rb
139
+ - examples/advanced_routes/app/logic/simple_logic.rb
140
+ - examples/advanced_routes/app/logic/system/config/manager.rb
141
+ - examples/advanced_routes/app/logic/test_logic.rb
142
+ - examples/advanced_routes/app/logic/transform_logic.rb
143
+ - examples/advanced_routes/app/logic/upload_logic.rb
144
+ - examples/advanced_routes/app/logic/v2/logic/dashboard.rb
145
+ - examples/advanced_routes/app/logic/v2/logic/processor.rb
146
+ - examples/advanced_routes/config.rb
147
+ - examples/advanced_routes/config.ru
148
+ - examples/advanced_routes/puma.rb
149
+ - examples/advanced_routes/routes
150
+ - examples/advanced_routes/run.rb
151
+ - examples/advanced_routes/test.rb
152
+ - examples/authentication_strategies/README.md
153
+ - examples/authentication_strategies/app/auth.rb
154
+ - examples/authentication_strategies/app/controllers/auth_controller.rb
155
+ - examples/authentication_strategies/app/controllers/main_controller.rb
156
+ - examples/authentication_strategies/config.ru
157
+ - examples/authentication_strategies/routes
158
+ - examples/basic/README.md
92
159
  - examples/basic/app.rb
93
160
  - examples/basic/config.ru
94
161
  - examples/basic/routes
95
- - examples/dynamic_pages/app.rb
96
- - examples/dynamic_pages/config.ru
97
- - examples/dynamic_pages/routes
98
- - examples/helpers_demo/app.rb
99
- - examples/helpers_demo/config.ru
100
- - examples/helpers_demo/routes
162
+ - examples/mcp_demo/README.md
163
+ - examples/mcp_demo/app.rb
164
+ - examples/mcp_demo/config.ru
165
+ - examples/mcp_demo/routes
166
+ - examples/security_features/README.md
101
167
  - examples/security_features/app.rb
102
168
  - examples/security_features/config.ru
103
169
  - examples/security_features/routes
104
170
  - lib/otto.rb
171
+ - lib/otto/core/configuration.rb
172
+ - lib/otto/core/error_handler.rb
173
+ - lib/otto/core/file_safety.rb
174
+ - lib/otto/core/middleware_stack.rb
175
+ - lib/otto/core/router.rb
176
+ - lib/otto/core/uri_generator.rb
105
177
  - lib/otto/design_system.rb
106
178
  - lib/otto/helpers/base.rb
107
179
  - lib/otto/helpers/request.rb
108
180
  - lib/otto/helpers/response.rb
181
+ - lib/otto/helpers/validation.rb
182
+ - lib/otto/mcp/auth/token.rb
183
+ - lib/otto/mcp/protocol.rb
184
+ - lib/otto/mcp/rate_limiting.rb
185
+ - lib/otto/mcp/registry.rb
186
+ - lib/otto/mcp/route_parser.rb
187
+ - lib/otto/mcp/server.rb
188
+ - lib/otto/mcp/validation.rb
109
189
  - lib/otto/response_handlers.rb
190
+ - lib/otto/response_handlers/auto.rb
191
+ - lib/otto/response_handlers/base.rb
192
+ - lib/otto/response_handlers/default.rb
193
+ - lib/otto/response_handlers/factory.rb
194
+ - lib/otto/response_handlers/json.rb
195
+ - lib/otto/response_handlers/redirect.rb
196
+ - lib/otto/response_handlers/view.rb
110
197
  - lib/otto/route.rb
111
198
  - lib/otto/route_definition.rb
112
199
  - lib/otto/route_handlers.rb
200
+ - lib/otto/route_handlers/base.rb
201
+ - lib/otto/route_handlers/class_method.rb
202
+ - lib/otto/route_handlers/factory.rb
203
+ - lib/otto/route_handlers/instance_method.rb
204
+ - lib/otto/route_handlers/lambda.rb
205
+ - lib/otto/route_handlers/logic_class.rb
113
206
  - lib/otto/security/authentication.rb
207
+ - lib/otto/security/authentication/auth_strategy.rb
208
+ - lib/otto/security/authentication/authentication_middleware.rb
209
+ - lib/otto/security/authentication/failure_result.rb
210
+ - lib/otto/security/authentication/strategies/api_key_strategy.rb
211
+ - lib/otto/security/authentication/strategies/permission_strategy.rb
212
+ - lib/otto/security/authentication/strategies/public_strategy.rb
213
+ - lib/otto/security/authentication/strategies/role_strategy.rb
214
+ - lib/otto/security/authentication/strategies/session_strategy.rb
215
+ - lib/otto/security/authentication/strategy_result.rb
114
216
  - lib/otto/security/config.rb
217
+ - lib/otto/security/configurator.rb
115
218
  - lib/otto/security/csrf.rb
219
+ - lib/otto/security/middleware/csrf_middleware.rb
220
+ - lib/otto/security/middleware/rate_limit_middleware.rb
221
+ - lib/otto/security/middleware/validation_middleware.rb
222
+ - lib/otto/security/rate_limiter.rb
223
+ - lib/otto/security/rate_limiting.rb
116
224
  - lib/otto/security/validator.rb
117
225
  - lib/otto/static.rb
226
+ - lib/otto/utils.rb
118
227
  - lib/otto/version.rb
119
228
  - otto.gemspec
120
229
  - public/favicon.ico
@@ -1,115 +0,0 @@
1
- # examples/basic/app.rb (Streamlined with Design System)
2
-
3
- require_relative '../../lib/otto/design_system'
4
-
5
- class App
6
- include Otto::DesignSystem
7
-
8
- attr_reader :req, :res
9
-
10
- def initialize(req, res)
11
- @req = req
12
- @res = res
13
- res.headers['content-type'] = 'text/html; charset=utf-8'
14
- end
15
-
16
- def index
17
- # This demonstrates nested heredocs in Ruby
18
- # The outer heredoc uses <<~HTML delimiter
19
- content = <<~HTML
20
- <div class="otto-card otto-text-center">
21
- <img src="/img/otto.jpg" alt="Otto Framework" class="otto-logo" />
22
- <h1>Otto Framework</h1>
23
- <p>Minimal Ruby web framework with style</p>
24
- </div>
25
-
26
- #{otto_card('Dynamic Pages') do
27
- # This is a nested heredoc within the outer HTML heredoc
28
- # It uses a different delimiter (EXAMPLES) to avoid conflicts
29
- # The #{} interpolation allows the inner heredoc to be embedded
30
- <<~EXAMPLES
31
- #{otto_link('Product #100', '/product/100')} - View product page<br>
32
- #{otto_link('Product #42', '/product/42')} - Different product<br>
33
- #{otto_link('API Data', '/product/100.json')} - JSON endpoint
34
- EXAMPLES
35
- end}
36
- HTML
37
-
38
- res.send_secure_cookie :sess, 1_234_567, 3600
39
- res.body = otto_page(content)
40
- end
41
-
42
- def receive_feedback
43
- message = req.params['msg']&.strip
44
-
45
- content = if message.nil? || message.empty?
46
- otto_alert('error', 'Empty Message', 'Please enter a message before submitting.')
47
- else
48
- otto_alert('success', 'Feedback Received', 'Thanks for your message!') +
49
- otto_card('Your Message') { otto_code_block(message, 'text') }
50
- end
51
-
52
- content += "<p>#{otto_link('← Back', '/')}</p>"
53
- res.body = otto_page(content, 'Feedback')
54
- end
55
-
56
- def redirect
57
- res.redirect '/robots.txt'
58
- end
59
-
60
- def robots_text
61
- res.headers['content-type'] = 'text/plain'
62
- res.body = ['User-agent: *', 'Disallow: /private'].join($/)
63
- end
64
-
65
- def display_product
66
- prodid = req.params[:prodid]
67
-
68
- # Check if JSON is requested via .json extension or Accept header
69
- wants_json = req.path_info.end_with?('.json') ||
70
- req.env['HTTP_ACCEPT']&.include?('application/json')
71
-
72
- if wants_json
73
- res.headers['content-type'] = 'application/json; charset=utf-8'
74
- res.body = format('{"product":%s,"msg":"Hint: try another value"}', prodid)
75
- else
76
- # Return HTML product page
77
- product_data = {
78
- 'Product ID' => prodid,
79
- 'Name' => "Sample Product ##{prodid}",
80
- 'Price' => "$#{rand(10..999)}.99",
81
- 'Description' => 'This is a demonstration product showing dynamic routing with parameter :prodid',
82
- 'Stock' => rand(0..50) > 5 ? 'In Stock' : 'Out of Stock',
83
- }
84
-
85
- product_html = product_data.map do |key, value|
86
- "<p><strong>#{key}:</strong> #{escape_html(value.to_s)}</p>"
87
- end.join
88
-
89
- content = <<~HTML
90
- #{otto_card('Product Details') { product_html }}
91
-
92
- <p>
93
- #{otto_link('← Back to Home', '/')} |
94
- #{otto_link('View as JSON', "/product/#{prodid}.json")}
95
- </p>
96
- HTML
97
-
98
- res.body = otto_page(content, "Product ##{prodid}")
99
- end
100
- end
101
-
102
- def not_found
103
- res.status = 404
104
- content = otto_alert('error', 'Not Found', 'The requested page could not be found.')
105
- content += "<p>#{otto_link('← Home', '/')}</p>"
106
- res.body = otto_page(content, '404')
107
- end
108
-
109
- def server_error
110
- res.status = 500
111
- content = otto_alert('error', 'Server Error', 'An internal server error occurred.')
112
- content += "<p>#{otto_link('← Home', '/')}</p>"
113
- res.body = otto_page(content, '500')
114
- end
115
- end
@@ -1,30 +0,0 @@
1
- # examples/basic/config.ru
2
-
3
- # OTTO EXAMPLE APP CONFIG - 2025-08-18
4
- #
5
- # Usage:
6
- #
7
- # $ thin -e dev -R config.ru -p 10770 start
8
-
9
- public_path = File.expand_path('../../public', __dir__)
10
-
11
- require_relative '../../lib/otto'
12
- require_relative 'app'
13
-
14
- app = Otto.new('routes')
15
-
16
- # DEV: Run web apps with extra logging and reloading
17
- if Otto.env?(:dev)
18
-
19
- map('/') do
20
- use Rack::CommonLogger
21
- use Rack::Reloader, 0
22
- app.option[:public] = public_path
23
- app.add_static_path '/favicon.ico'
24
- run app
25
- end
26
-
27
- # PROD: run the webapp on the metal
28
- else
29
- map('/') { run app }
30
- end
@@ -1,21 +0,0 @@
1
- # examples/basic/routes
2
-
3
- # OTTO - ROUTES EXAMPLE
4
-
5
- # Each route has three parts:
6
- # * HTTP verb (GET, POST, PUT, DELETE or HEAD)
7
- # * URI path
8
- # * Ruby class and method to call
9
-
10
- GET / App#index
11
- POST / App#receive_feedback
12
- GET /redirect App#redirect
13
- GET /robots.txt App#robots_text
14
- GET /product/:prodid App#display_product
15
-
16
- GET /bogus App#no_such_method
17
-
18
- # You can also define these handlers when no
19
- # route can be found or there's a server error. (optional)
20
- GET /404 App#not_found
21
- GET /500 App#server_error