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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +44 -5
- data/.github/workflows/claude-code-review.yml +53 -0
- data/.github/workflows/claude.yml +49 -0
- data/.gitignore +3 -0
- data/.rubocop.yml +24 -345
- data/CHANGELOG.rst +83 -0
- data/CLAUDE.md +56 -0
- data/Gemfile +21 -5
- data/Gemfile.lock +69 -31
- data/README.md +2 -0
- data/bin/rspec +16 -0
- data/changelog.d/20250911_235619_delano_next.rst +28 -0
- data/changelog.d/20250912_123055_delano_remove_ostruct.rst +21 -0
- data/changelog.d/20250912_175625_claude_delano_remove_ostruct.rst +21 -0
- data/changelog.d/README.md +120 -0
- data/changelog.d/scriv.ini +5 -0
- data/docs/.gitignore +1 -0
- data/docs/migrating/v2.0.0-pre1.md +276 -0
- data/examples/.gitignore +1 -0
- data/examples/advanced_routes/README.md +33 -0
- data/examples/advanced_routes/app/controllers/handlers/async.rb +9 -0
- data/examples/advanced_routes/app/controllers/handlers/dynamic.rb +9 -0
- data/examples/advanced_routes/app/controllers/handlers/static.rb +9 -0
- data/examples/advanced_routes/app/controllers/modules/auth.rb +9 -0
- data/examples/advanced_routes/app/controllers/modules/transformer.rb +9 -0
- data/examples/advanced_routes/app/controllers/modules/validator.rb +9 -0
- data/examples/advanced_routes/app/controllers/routes_app.rb +232 -0
- data/examples/advanced_routes/app/controllers/v2/admin.rb +9 -0
- data/examples/advanced_routes/app/controllers/v2/config.rb +9 -0
- data/examples/advanced_routes/app/controllers/v2/settings.rb +9 -0
- data/examples/advanced_routes/app/logic/admin/logic/manager.rb +27 -0
- data/examples/advanced_routes/app/logic/admin/panel.rb +27 -0
- data/examples/advanced_routes/app/logic/analytics_processor.rb +25 -0
- data/examples/advanced_routes/app/logic/complex/business/handler.rb +27 -0
- data/examples/advanced_routes/app/logic/data_logic.rb +23 -0
- data/examples/advanced_routes/app/logic/data_processor.rb +25 -0
- data/examples/advanced_routes/app/logic/input_validator.rb +24 -0
- data/examples/advanced_routes/app/logic/nested/feature/logic.rb +27 -0
- data/examples/advanced_routes/app/logic/reports_generator.rb +27 -0
- data/examples/advanced_routes/app/logic/simple_logic.rb +25 -0
- data/examples/advanced_routes/app/logic/system/config/manager.rb +27 -0
- data/examples/advanced_routes/app/logic/test_logic.rb +23 -0
- data/examples/advanced_routes/app/logic/transform_logic.rb +23 -0
- data/examples/advanced_routes/app/logic/upload_logic.rb +23 -0
- data/examples/advanced_routes/app/logic/v2/logic/dashboard.rb +27 -0
- data/examples/advanced_routes/app/logic/v2/logic/processor.rb +27 -0
- data/examples/advanced_routes/app.rb +33 -0
- data/examples/advanced_routes/config.rb +23 -0
- data/examples/advanced_routes/config.ru +7 -0
- data/examples/advanced_routes/puma.rb +20 -0
- data/examples/advanced_routes/routes +167 -0
- data/examples/advanced_routes/run.rb +39 -0
- data/examples/advanced_routes/test.rb +58 -0
- data/examples/authentication_strategies/README.md +32 -0
- data/examples/authentication_strategies/app/auth.rb +68 -0
- data/examples/authentication_strategies/app/controllers/auth_controller.rb +29 -0
- data/examples/authentication_strategies/app/controllers/main_controller.rb +28 -0
- data/examples/authentication_strategies/config.ru +24 -0
- data/examples/authentication_strategies/routes +37 -0
- data/examples/basic/README.md +29 -0
- data/examples/basic/app.rb +7 -35
- data/examples/basic/routes +0 -9
- data/examples/mcp_demo/README.md +87 -0
- data/examples/mcp_demo/app.rb +51 -0
- data/examples/mcp_demo/config.ru +17 -0
- data/examples/mcp_demo/routes +9 -0
- data/examples/security_features/README.md +46 -0
- data/examples/security_features/app.rb +23 -24
- data/examples/security_features/config.ru +8 -10
- data/lib/otto/core/configuration.rb +167 -0
- data/lib/otto/core/error_handler.rb +86 -0
- data/lib/otto/core/file_safety.rb +61 -0
- data/lib/otto/core/middleware_stack.rb +157 -0
- data/lib/otto/core/router.rb +183 -0
- data/lib/otto/core/uri_generator.rb +44 -0
- data/lib/otto/design_system.rb +7 -5
- data/lib/otto/helpers/base.rb +3 -0
- data/lib/otto/helpers/request.rb +10 -8
- data/lib/otto/helpers/response.rb +5 -4
- data/lib/otto/helpers/validation.rb +85 -0
- data/lib/otto/mcp/auth/token.rb +77 -0
- data/lib/otto/mcp/protocol.rb +164 -0
- data/lib/otto/mcp/rate_limiting.rb +155 -0
- data/lib/otto/mcp/registry.rb +100 -0
- data/lib/otto/mcp/route_parser.rb +77 -0
- data/lib/otto/mcp/server.rb +206 -0
- data/lib/otto/mcp/validation.rb +123 -0
- data/lib/otto/response_handlers/auto.rb +39 -0
- data/lib/otto/response_handlers/base.rb +16 -0
- data/lib/otto/response_handlers/default.rb +16 -0
- data/lib/otto/response_handlers/factory.rb +39 -0
- data/lib/otto/response_handlers/json.rb +28 -0
- data/lib/otto/response_handlers/redirect.rb +25 -0
- data/lib/otto/response_handlers/view.rb +24 -0
- data/lib/otto/response_handlers.rb +9 -135
- data/lib/otto/route.rb +9 -9
- data/lib/otto/route_definition.rb +30 -33
- data/lib/otto/route_handlers/base.rb +121 -0
- data/lib/otto/route_handlers/class_method.rb +89 -0
- data/lib/otto/route_handlers/factory.rb +29 -0
- data/lib/otto/route_handlers/instance_method.rb +69 -0
- data/lib/otto/route_handlers/lambda.rb +59 -0
- data/lib/otto/route_handlers/logic_class.rb +93 -0
- data/lib/otto/route_handlers.rb +10 -376
- data/lib/otto/security/authentication/auth_strategy.rb +44 -0
- data/lib/otto/security/authentication/authentication_middleware.rb +123 -0
- data/lib/otto/security/authentication/failure_result.rb +36 -0
- data/lib/otto/security/authentication/strategies/api_key_strategy.rb +40 -0
- data/lib/otto/security/authentication/strategies/permission_strategy.rb +47 -0
- data/lib/otto/security/authentication/strategies/public_strategy.rb +19 -0
- data/lib/otto/security/authentication/strategies/role_strategy.rb +57 -0
- data/lib/otto/security/authentication/strategies/session_strategy.rb +41 -0
- data/lib/otto/security/authentication/strategy_result.rb +223 -0
- data/lib/otto/security/authentication.rb +28 -282
- data/lib/otto/security/config.rb +15 -11
- data/lib/otto/security/configurator.rb +219 -0
- data/lib/otto/security/csrf.rb +8 -143
- data/lib/otto/security/middleware/csrf_middleware.rb +151 -0
- data/lib/otto/security/middleware/rate_limit_middleware.rb +38 -0
- data/lib/otto/security/middleware/validation_middleware.rb +252 -0
- data/lib/otto/security/rate_limiter.rb +86 -0
- data/lib/otto/security/rate_limiting.rb +16 -0
- data/lib/otto/security/validator.rb +8 -292
- data/lib/otto/static.rb +3 -0
- data/lib/otto/utils.rb +14 -0
- data/lib/otto/version.rb +3 -1
- data/lib/otto.rb +184 -414
- data/otto.gemspec +11 -6
- metadata +134 -25
- data/examples/dynamic_pages/app.rb +0 -115
- data/examples/dynamic_pages/config.ru +0 -30
- data/examples/dynamic_pages/routes +0 -21
- data/examples/helpers_demo/app.rb +0 -244
- data/examples/helpers_demo/config.ru +0 -26
- 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 =
|
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
|
-
|
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:
|
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:
|
13
|
+
name: rack
|
14
14
|
requirement: !ruby/object:Gem::Requirement
|
15
15
|
requirements:
|
16
|
-
- - "
|
16
|
+
- - "~>"
|
17
17
|
- !ruby/object:Gem::Version
|
18
|
-
version: 3.
|
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.
|
28
|
+
version: '3.1'
|
29
|
+
- - "<"
|
30
|
+
- !ruby/object:Gem::Version
|
31
|
+
version: '4.0'
|
26
32
|
- !ruby/object:Gem::Dependency
|
27
|
-
name:
|
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:
|
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:
|
59
|
+
version: 3.3.6
|
40
60
|
- !ruby/object:Gem::Dependency
|
41
|
-
name:
|
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:
|
75
|
+
name: loofah
|
62
76
|
requirement: !ruby/object:Gem::Requirement
|
63
77
|
requirements:
|
64
78
|
- - "~>"
|
65
79
|
- !ruby/object:Gem::Version
|
66
|
-
version: '
|
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: '
|
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/
|
96
|
-
- examples/
|
97
|
-
- examples/
|
98
|
-
- examples/
|
99
|
-
- examples/
|
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
|