hyraft 0.1.0.alpha1

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 (84) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +5 -0
  3. data/CODE_OF_CONDUCT.md +132 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +231 -0
  6. data/exe/hyraft +5 -0
  7. data/lib/hyraft/boot/asset_preloader.rb +185 -0
  8. data/lib/hyraft/boot/preloaded_static.rb +46 -0
  9. data/lib/hyraft/boot/preloader.rb +206 -0
  10. data/lib/hyraft/cli.rb +187 -0
  11. data/lib/hyraft/compiler/compiler.rb +34 -0
  12. data/lib/hyraft/compiler/html_purifier.rb +181 -0
  13. data/lib/hyraft/compiler/javascript_library.rb +281 -0
  14. data/lib/hyraft/compiler/javascript_obfuscator.rb +141 -0
  15. data/lib/hyraft/compiler/parser.rb +27 -0
  16. data/lib/hyraft/compiler/renderer.rb +217 -0
  17. data/lib/hyraft/engine/circuit.rb +35 -0
  18. data/lib/hyraft/engine/port.rb +17 -0
  19. data/lib/hyraft/engine/source.rb +19 -0
  20. data/lib/hyraft/engine.rb +11 -0
  21. data/lib/hyraft/router/api_router.rb +65 -0
  22. data/lib/hyraft/router/web_router.rb +136 -0
  23. data/lib/hyraft/system_info.rb +26 -0
  24. data/lib/hyraft/version.rb +5 -0
  25. data/lib/hyraft.rb +48 -0
  26. data/templates/do_app/Gemfile +50 -0
  27. data/templates/do_app/Rakefile +88 -0
  28. data/templates/do_app/adapter-intake/web-app/display/pages/home/home.hyr +174 -0
  29. data/templates/do_app/adapter-intake/web-app/request/home_web_adapter.rb +19 -0
  30. data/templates/do_app/boot.rb +41 -0
  31. data/templates/do_app/framework/adapters/server/server_api_adapter.rb +51 -0
  32. data/templates/do_app/framework/adapters/server/server_web_adapter.rb +178 -0
  33. data/templates/do_app/framework/compiler/style_resolver.rb +33 -0
  34. data/templates/do_app/framework/errors/error_handler.rb +75 -0
  35. data/templates/do_app/framework/errors/templates/304.html +22 -0
  36. data/templates/do_app/framework/errors/templates/400.html +22 -0
  37. data/templates/do_app/framework/errors/templates/401.html +22 -0
  38. data/templates/do_app/framework/errors/templates/403.html +22 -0
  39. data/templates/do_app/framework/errors/templates/404.html +62 -0
  40. data/templates/do_app/framework/errors/templates/500.html +73 -0
  41. data/templates/do_app/framework/middleware/cors_middleware.rb +37 -0
  42. data/templates/do_app/infra/config/environment.rb +86 -0
  43. data/templates/do_app/infra/config/error_config.rb +80 -0
  44. data/templates/do_app/infra/config/routes/api_routes.rb +2 -0
  45. data/templates/do_app/infra/config/routes/web_routes.rb +10 -0
  46. data/templates/do_app/infra/database/sequel_connection.rb +62 -0
  47. data/templates/do_app/infra/gems/database.rb +7 -0
  48. data/templates/do_app/infra/gems/load_all.rb +4 -0
  49. data/templates/do_app/infra/gems/utilities.rb +1 -0
  50. data/templates/do_app/infra/gems/web.rb +3 -0
  51. data/templates/do_app/infra/server/api-server.ru +13 -0
  52. data/templates/do_app/infra/server/web-server.ru +32 -0
  53. data/templates/do_app/package.json +9 -0
  54. data/templates/do_app/public/favicon.ico +0 -0
  55. data/templates/do_app/public/icons/docs.svg +10 -0
  56. data/templates/do_app/public/icons/expli.svg +13 -0
  57. data/templates/do_app/public/icons/git-repo.svg +13 -0
  58. data/templates/do_app/public/icons/hexagonal-arch.svg +15 -0
  59. data/templates/do_app/public/icons/template-engine.svg +26 -0
  60. data/templates/do_app/public/images/hyr-logo.png +0 -0
  61. data/templates/do_app/public/images/hyr-logo.webp +0 -0
  62. data/templates/do_app/public/index.html +22 -0
  63. data/templates/do_app/public/styles/css/main.css +418 -0
  64. data/templates/do_app/public/styles/css/spa.css +171 -0
  65. data/templates/do_app/shared/helpers/pagination_helper.rb +44 -0
  66. data/templates/do_app/shared/helpers/response_formatter.rb +25 -0
  67. data/templates/do_app/test/acceptance/api/articles_api_acceptance_test.rb +43 -0
  68. data/templates/do_app/test/acceptance/web/articles_acceptance_test.rb +31 -0
  69. data/templates/do_app/test/acceptance/web/home_acceptance_test.rb +17 -0
  70. data/templates/do_app/test/db.rb +106 -0
  71. data/templates/do_app/test/integration/adapter-exhaust/data-gateway/sequel_articles_gateway_test.rb +79 -0
  72. data/templates/do_app/test/integration/adapter-intake/api-app/request/articles_api_adapter_test.rb +61 -0
  73. data/templates/do_app/test/integration/adapter-intake/web-app/request/articles_web_adapter_test.rb +20 -0
  74. data/templates/do_app/test/integration/adapter-intake/web-app/request/home_web_adapter_test.rb +17 -0
  75. data/templates/do_app/test/integration/database/migration_test.rb +35 -0
  76. data/templates/do_app/test/support/mock_api_adapter.rb +82 -0
  77. data/templates/do_app/test/support/mock_articles_gateway.rb +41 -0
  78. data/templates/do_app/test/support/mock_web_adapter.rb +85 -0
  79. data/templates/do_app/test/support/test_patches.rb +33 -0
  80. data/templates/do_app/test/test_helper.rb +526 -0
  81. data/templates/do_app/test/unit/engine/circuit/articles_circuit_test.rb +167 -0
  82. data/templates/do_app/test/unit/engine/port/articles_gateway_port_test.rb +12 -0
  83. data/templates/do_app/test/unit/engine/source/article_test.rb +37 -0
  84. metadata +291 -0
@@ -0,0 +1,41 @@
1
+ # boot.rb
2
+ require 'bundler/setup'
3
+ require_relative 'infra/gems/load_all'
4
+
5
+ require 'hyraft'
6
+ require 'sequel'
7
+
8
+ require_relative 'framework/middleware/cors_middleware'
9
+
10
+ ROOT = File.expand_path(__dir__) unless defined?(ROOT)
11
+
12
+ def require_root(path)
13
+ full_path = File.join(ROOT, path)
14
+ require full_path
15
+ end
16
+
17
+ # Load environment configuration FIRST
18
+ require_relative 'infra/config/environment'
19
+
20
+ # Load middleware
21
+ Dir["#{ROOT}/framework/middleware/*.rb"].each { |file| require file }
22
+
23
+
24
+
25
+ Dir["#{ROOT}/**/*.rb"].each do |file|
26
+ # Skip files that should not be auto-loaded
27
+ next if file.include?('framework/middleware/') ||
28
+ file.include?('boot.rb') ||
29
+ file.end_with?('.ru') ||
30
+ file.end_with?('.hyr') ||
31
+ file.include?('infra/database/migrations/') ||
32
+ file.include?('test/') # Skip test files
33
+ require file
34
+ end
35
+
36
+
37
+ # Initialize the application after all files are loaded
38
+ Hyraft::Environment.load if defined?(Hyraft::Environment)
39
+
40
+ # Use the Environment module
41
+ puts "Starting #{Environment.environment_color} environment"
@@ -0,0 +1,51 @@
1
+ # framework/adapters/server/server_api_adapter.rb
2
+ require 'cgi'
3
+
4
+ class ServerApiAdapter
5
+ def initialize(router = Api)
6
+ @router = router
7
+ end
8
+
9
+ def call(rack_env)
10
+ req = Rack::Request.new(rack_env)
11
+
12
+ # Pass full path with query string to router
13
+ full_path = req.path_info + (req.query_string.empty? ? '' : "?#{req.query_string}")
14
+ route, route_params = @router.resolve(req.request_method, full_path)
15
+
16
+ if route
17
+ handler_class = route.handler_class
18
+ action = route.action_name
19
+ handler = handler_class.new
20
+
21
+ # Create clean request object
22
+ request_object = {
23
+ query_string: req.query_string,
24
+ query_params: CGI.parse(req.query_string).transform_values { |v| v.length == 1 ? v.first : v },
25
+ body: req.body&.read,
26
+ headers: {
27
+ content_type: req.content_type,
28
+ content_length: req.content_length
29
+ }
30
+ }
31
+
32
+ # Read body for POST/PUT
33
+ if req.post? || req.put?
34
+ request_object[:body] = req.body.read
35
+ req.body.rewind
36
+ end
37
+
38
+ # Call handler with clean request object
39
+ if [:show, :update, :delete].include?(action)
40
+ # Pass route param (id) as separate argument
41
+ id = route_params.first if route_params.any?
42
+ handler.public_send(action, request_object, id)
43
+ else
44
+ # No route params for index/create
45
+ handler.public_send(action, request_object)
46
+ end
47
+ else
48
+ [404, { 'Content-Type' => 'application/json' }, [{ error: 'Not Found' }.to_json]]
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,178 @@
1
+ # framework/adapters/server/server_web_adapter.rb
2
+
3
+ require_root 'framework/compiler/style_resolver'
4
+ require_root 'framework/errors/error_handler'
5
+ require_root 'infra/config/error_config'
6
+
7
+ class ServerWebAdapter
8
+ def initialize(router = Web)
9
+ @router = router
10
+ # Set up error config with template finder
11
+ ErrorConfig.set_template_finder(method(:find_template_anywhere))
12
+ end
13
+
14
+ def call(env)
15
+ req = Rack::Request.new(env)
16
+ method = env['REQUEST_METHOD']
17
+ path = env['PATH_INFO']
18
+
19
+ resolved = @router.resolve(method, path)
20
+
21
+ if resolved
22
+ route, params = resolved
23
+ handler_class, action = route.handler_class, route.action
24
+ handler = handler_class.new
25
+ req.update_param('route_params', params)
26
+
27
+ # Call handler
28
+ result = handler.public_send(action, req) || {}
29
+ status = (result[:status] || 200).to_i
30
+ locals = result[:locals] || {}
31
+ view_template = result[:display]
32
+
33
+ # EXPLICIT: If a display template is specified, ALWAYS use it
34
+ if view_template
35
+ begin
36
+ body = render_hyraft(view_template, locals)
37
+ return [status, { 'Content-Type' => 'text/html; charset=utf-8' }, [body]]
38
+ rescue StandardError => e
39
+ return handle_error(500, e)
40
+ end
41
+ end
42
+
43
+ # Handle redirects
44
+ if result[:status] && (300..399).include?(result[:status].to_i) && result[:headers] && result[:headers]['Location']
45
+ return [result[:status], result[:headers], ['']]
46
+ end
47
+
48
+ # If no explicit template, then handle errors
49
+ if status >= 400
50
+ return handle_error(status, nil, path, locals)
51
+ end
52
+
53
+ # Success with no template
54
+ return [status, { 'Content-Type' => 'text/html; charset=utf-8' }, ['']]
55
+ else
56
+ # route not found -> 404
57
+ return handle_error(404, nil, path)
58
+ end
59
+ rescue StandardError => e
60
+ return handle_error(500, e)
61
+ end
62
+
63
+ private
64
+
65
+ # ADD THIS MISSING METHOD
66
+ def handle_error(status, exception = nil, path = nil, locals = {})
67
+ # Check if custom template exists
68
+ if ErrorConfig.custom_template_exists?(status)
69
+ begin
70
+ template_path = ErrorConfig.custom_template_for(status)
71
+ error_locals = ErrorConfig.custom_locals_for(status, path || exception, locals)
72
+
73
+ body = render_hyraft(template_path, error_locals.merge(locals))
74
+ return [status, { 'Content-Type' => 'text/html; charset=utf-8' }, [body]]
75
+ rescue StandardError => e
76
+ # Fall back to default error handler if custom template fails
77
+ puts "Custom error template failed: #{e.message}"
78
+ end
79
+ end
80
+
81
+ # Fall back to default error handler
82
+ dispatch_error_status(status, path, locals.merge(exception: exception))
83
+ end
84
+
85
+ # Map status -> ErrorHandler method, allow fallback/defaults
86
+ def dispatch_error_status(status, request_path, locals)
87
+ # prefer explicit methods like render_404, render_401, etc.
88
+ method_name = "render_#{status}"
89
+
90
+ if ErrorHandler.respond_to?(method_name)
91
+ case status
92
+ when 404
93
+ return ErrorHandler.public_send(method_name, (locals[:path] || request_path))
94
+ when 304
95
+ return ErrorHandler.public_send(method_name) # usually no placeholders
96
+ when 400, 401, 403
97
+ # pass an Exception-like object when templates expect {{message}}/{{backtrace}}
98
+ exc = locals[:exception] || locals[:error] || StandardError.new(locals[:message].to_s)
99
+ return ErrorHandler.public_send(method_name, exc)
100
+ when 500..599
101
+ exc = locals[:exception] || locals[:error] || StandardError.new(locals[:message].to_s)
102
+ return ErrorHandler.public_send(method_name, exc)
103
+ else
104
+ # Generic: try to call with locals if available
105
+ return ErrorHandler.public_send(method_name, locals) rescue generic_fallback(status, locals)
106
+ end
107
+ end
108
+
109
+ # If ErrorHandler has a generic `render(status, options)` method, use it
110
+ if ErrorHandler.respond_to?(:render)
111
+ return ErrorHandler.render(status, locals)
112
+ end
113
+
114
+ # final fallback: plain text minimal response
115
+ body = locals[:message] || "HTTP #{status}"
116
+ return [status, { 'Content-Type' => 'text/plain; charset=utf-8' }, [body]]
117
+ end
118
+
119
+ # Generic fallback used in rescue above
120
+ def generic_fallback(status, locals)
121
+ if ErrorHandler.respond_to?(:render)
122
+ return ErrorHandler.render(status, locals)
123
+ else
124
+ return [status, { 'Content-Type' => 'text/plain; charset=utf-8' }, [locals[:message] || "HTTP #{status}"]]
125
+ end
126
+ end
127
+
128
+ # Render .hyr templates using Hyraft compiler with auto-detect
129
+ def render_hyraft(template_path, locals = {})
130
+ template_key = template_path.gsub(/\.hyr$/, '')
131
+
132
+ # Use preloader if available
133
+ if defined?(Hyraft::Preloader) && Hyraft::Preloader.template_preloaded?(template_key)
134
+ return Hyraft::Preloader.render_template(template_key, locals)
135
+ end
136
+
137
+ # Fallback to file compilation
138
+ template_file = find_template_anywhere(template_path)
139
+
140
+ unless File.exist?(template_file)
141
+ raise "Template not found: #{template_path}. Searched in all apps under adapter-intake"
142
+ end
143
+
144
+ layout_file = File.join(ROOT, 'public', 'index.html')
145
+ compiler = HyraftCompiler.new(layout_file)
146
+ locals.each { |k, v| instance_variable_set("@#{k}", v) }
147
+ compiler.compile(template_file, locals)
148
+ end
149
+
150
+ # Search all apps under adapter-intake for the template
151
+ def find_template_anywhere(template_name)
152
+ # Remove .hyr extension if present for flexible matching
153
+ clean_name = template_name.gsub(/\.hyr$/, '')
154
+
155
+ # Search pattern: adapter-intake/*/display/**/{template_name}.hyr
156
+ search_pattern = File.join(ROOT, 'adapter-intake', '*', 'display', '**', "#{clean_name}.hyr")
157
+ matching_files = Dir.glob(search_pattern)
158
+
159
+ if matching_files.any?
160
+ # Return the first match (you could add priority logic here)
161
+ return matching_files.first
162
+ else
163
+ # Show available templates for better debugging
164
+ available_templates = Dir.glob(File.join(ROOT, 'adapter-intake', '*', 'display', '**', '*.hyr'))
165
+ # puts "DEBUG: Available templates:"
166
+ # available_templates.each { |f| puts " - #{f.gsub(ROOT + '/', '')}" }
167
+
168
+ raise "Template '#{template_name}' not found in any app under adapter-intake"
169
+ end
170
+ end
171
+
172
+ def compile_styles(hyr_file_path, style_src)
173
+ Compiler::StyleResolver.resolve_style_path(style_src, from: hyr_file_path)
174
+ end
175
+
176
+ # Optional: Keep alias for backward compatibility if needed
177
+ alias_method :render_hyraft_with_preloader, :render_hyraft
178
+ end
@@ -0,0 +1,33 @@
1
+ # framework/compiler/style_resolver.rb
2
+
3
+ require 'pathname'
4
+
5
+ module Compiler
6
+ module StyleResolver
7
+ module_function
8
+
9
+ def resolve_style_path(path, from:)
10
+ # normalize path (remove leading slash if present)
11
+ rel_path = path.start_with?('/') ? path.sub(%r{^/}, '') : path
12
+
13
+ # 1) public/ takes priority: public/styles/css/example.css -> /styles/css/example.css
14
+ public_path = File.join(ROOT, 'public', rel_path)
15
+ return "/#{rel_path}" if File.exist?(public_path)
16
+
17
+ # 2) FIXED: Update to new structure path
18
+ local_path = File.expand_path(File.join(File.dirname(from), rel_path))
19
+
20
+ # CORRECT: New structure path
21
+ display_root = File.expand_path(File.join(ROOT,'adapter-intake'))
22
+
23
+ if local_path.start_with?(display_root) && File.exist?(local_path)
24
+ # compute path relative to adapter-intake and return a browser URL
25
+ relative = Pathname.new(local_path).relative_path_from(Pathname.new(display_root)).to_s
26
+ return "/#{relative}"
27
+ end
28
+
29
+ # not found
30
+ raise "Stylesheet not found: #{path} (searched #{public_path} and #{local_path})"
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,75 @@
1
+ class ErrorHandler
2
+ # 400 - Bad Request
3
+ def self.render_400(exception = nil)
4
+ template_file = File.join(ROOT, 'framework', 'errors', 'templates', '400.html')
5
+ unless File.exist?(template_file)
6
+ msg = exception ? exception.message : "Bad Request"
7
+ return [400, { 'Content-Type' => 'text/html; charset=utf-8' }, ["400 - Bad Request: #{msg}"]]
8
+ end
9
+
10
+ body = File.read(template_file)
11
+ body = body.gsub('{{message}}', exception&.message.to_s)
12
+ [400, { 'Content-Type' => 'text/html; charset=utf-8' }, [body]]
13
+ end
14
+
15
+ # 401 - Unauthorized
16
+ def self.render_401(exception = nil)
17
+ template_file = File.join(ROOT, 'framework', 'errors', 'templates', '401.html')
18
+ unless File.exist?(template_file)
19
+ msg = exception ? exception.message : "Unauthorized"
20
+ return [401, { 'Content-Type' => 'text/html; charset=utf-8' }, ["401 - Unauthorized: #{msg}"]]
21
+ end
22
+
23
+ body = File.read(template_file)
24
+ body = body.gsub('{{message}}', exception&.message.to_s)
25
+ [401, { 'Content-Type' => 'text/html; charset=utf-8' }, [body]]
26
+ end
27
+
28
+ # 403 - Forbidden
29
+ def self.render_403(exception = nil)
30
+ template_file = File.join(ROOT, 'framework', 'errors', 'templates', '403.html')
31
+ unless File.exist?(template_file)
32
+ msg = exception ? exception.message : "Forbidden"
33
+ return [403, { 'Content-Type' => 'text/html; charset=utf-8' }, ["403 - Forbidden: #{msg}"]]
34
+ end
35
+
36
+ body = File.read(template_file)
37
+ body = body.gsub('{{message}}', exception&.message.to_s)
38
+ [403, { 'Content-Type' => 'text/html; charset=utf-8' }, [body]]
39
+ end
40
+
41
+ # 404 - Not Found
42
+ def self.render_404(path)
43
+ template_file = File.join(ROOT, 'framework', 'errors', 'templates', '404.html')
44
+ unless File.exist?(template_file)
45
+ return [404, { 'Content-Type' => 'text/html; charset=utf-8' }, ["404 - Page Not Found"]]
46
+ end
47
+
48
+ body = File.read(template_file).gsub('{{path}}', path)
49
+ [404, { 'Content-Type' => 'text/html; charset=utf-8' }, [body]]
50
+ end
51
+
52
+ # 500 - Internal Server Error
53
+ def self.render_500(exception)
54
+ template_file = File.join(ROOT, 'framework', 'errors', 'templates', '500.html')
55
+ unless File.exist?(template_file)
56
+ return [500, { 'Content-Type' => 'text/html; charset=utf-8' }, ["500 - Internal Server Error: #{exception.message}"]]
57
+ end
58
+
59
+ body = File.read(template_file)
60
+ .gsub('{{message}}', exception.message)
61
+ .gsub('{{backtrace}}', exception.backtrace.first(10).join("<br>"))
62
+ [500, { 'Content-Type' => 'text/html; charset=utf-8' }, [body]]
63
+ end
64
+
65
+ # 304 - Not Modified
66
+ def self.render_304
67
+ template_file = File.join(ROOT, 'framework', 'errors', 'templates', '304.html')
68
+ unless File.exist?(template_file)
69
+ return [304, { 'Content-Type' => 'text/html; charset=utf-8' }, ["304 - Not Modified"]]
70
+ end
71
+
72
+ body = File.read(template_file)
73
+ [304, { 'Content-Type' => 'text/html; charset=utf-8' }, [body]]
74
+ end
75
+ end
@@ -0,0 +1,22 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>304 - Not Modified</title>
6
+ <style>
7
+ * { box-sizing:border-box; margin:0; padding:0; }
8
+ body { font-family:Arial, sans-serif; background:#f8f8f8; display:flex; justify-content:center; align-items:center; height:100vh; overflow:hidden; }
9
+ .container { max-width:800px; background:#fff; padding:40px; border-radius:8px; box-shadow:0 0 20px rgba(0,0,0,0.1); text-align:center; animation:fadeIn 1s ease-in-out; }
10
+ h1 { color:#5bc0de; font-size:48px; margin-bottom:20px; animation:bounce 1s infinite alternate; }
11
+ p { font-size:18px; }
12
+ @keyframes fadeIn { from {opacity:0; transform:translateY(-20px);} to {opacity:1; transform:translateY(0);} }
13
+ @keyframes bounce { 0%{transform:translateY(0);} 50%{transform:translateY(-10px);} 100%{transform:translateY(0);} }
14
+ </style>
15
+ </head>
16
+ <body>
17
+ <div class="container">
18
+ <h1>304 - Not Modified</h1>
19
+ <p>The requested resource has not been modified since your last request.</p>
20
+ </div>
21
+ </body>
22
+ </html>
@@ -0,0 +1,22 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>400 - Bad Request</title>
6
+ <style>
7
+ * { box-sizing:border-box; margin:0; padding:0; }
8
+ body { font-family:Arial, sans-serif; background:#f8f8f8; display:flex; justify-content:center; align-items:center; height:100vh; overflow:hidden; }
9
+ .container { max-width:800px; background:#fff; padding:40px; border-radius:8px; box-shadow:0 0 20px rgba(0,0,0,0.1); text-align:center; animation:fadeIn 1s ease-in-out; }
10
+ h1 { color:#f0ad4e; font-size:48px; margin-bottom:20px; animation:bounce 1s infinite alternate; }
11
+ p { font-size:18px; }
12
+ @keyframes fadeIn { from {opacity:0; transform:translateY(-20px);} to {opacity:1; transform:translateY(0);} }
13
+ @keyframes bounce { 0%{transform:translateY(0);} 50%{transform:translateY(-10px);} 100%{transform:translateY(0);} }
14
+ </style>
15
+ </head>
16
+ <body>
17
+ <div class="container">
18
+ <h1>400 - Bad Request</h1>
19
+ <p>Your request cannot be processed by the server.</p>
20
+ </div>
21
+ </body>
22
+ </html>
@@ -0,0 +1,22 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>401 - Unauthorized</title>
6
+ <style>
7
+ * { box-sizing:border-box; margin:0; padding:0; }
8
+ body { font-family:Arial, sans-serif; background:#f8f8f8; display:flex; justify-content:center; align-items:center; height:100vh; overflow:hidden; }
9
+ .container { max-width:800px; background:#fff; padding:40px; border-radius:8px; box-shadow:0 0 20px rgba(0,0,0,0.1); text-align:center; animation:fadeIn 1s ease-in-out; }
10
+ h1 { color:#d9534f; font-size:48px; margin-bottom:20px; animation:bounce 1s infinite alternate; }
11
+ p { font-size:18px; }
12
+ @keyframes fadeIn { from {opacity:0; transform:translateY(-20px);} to {opacity:1; transform:translateY(0);} }
13
+ @keyframes bounce { 0%{transform:translateY(0);} 50%{transform:translateY(-10px);} 100%{transform:translateY(0);} }
14
+ </style>
15
+ </head>
16
+ <body>
17
+ <div class="container">
18
+ <h1>401 - Unauthorized</h1>
19
+ <p>You are not authorized to view this page.</p>
20
+ </div>
21
+ </body>
22
+ </html>
@@ -0,0 +1,22 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>403 - Forbidden</title>
6
+ <style>
7
+ * { box-sizing:border-box; margin:0; padding:0; }
8
+ body { font-family:Arial, sans-serif; background:#f8f8f8; display:flex; justify-content:center; align-items:center; height:100vh; overflow:hidden; }
9
+ .container { max-width:800px; background:#fff; padding:40px; border-radius:8px; box-shadow:0 0 20px rgba(0,0,0,0.1); text-align:center; animation:fadeIn 1s ease-in-out; }
10
+ h1 { color:#d9534f; font-size:48px; margin-bottom:20px; animation:bounce 1s infinite alternate; }
11
+ p { font-size:18px; }
12
+ @keyframes fadeIn { from {opacity:0; transform:translateY(-20px);} to {opacity:1; transform:translateY(0);} }
13
+ @keyframes bounce { 0%{transform:translateY(0);} 50%{transform:translateY(-10px);} 100%{transform:translateY(0);} }
14
+ </style>
15
+ </head>
16
+ <body>
17
+ <div class="container">
18
+ <h1>403 - Forbidden</h1>
19
+ <p>Access to this resource is denied.</p>
20
+ </div>
21
+ </body>
22
+ </html>
@@ -0,0 +1,62 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>404 - Not Found</title>
6
+ <style>
7
+ * {
8
+ box-sizing: border-box;
9
+ margin: 0;
10
+ padding: 0;
11
+ }
12
+
13
+ body {
14
+ font-family: Arial, sans-serif;
15
+ background: #f8f8f8;
16
+ display: flex;
17
+ justify-content: center;
18
+ align-items: center;
19
+ height: 100vh; /* full viewport height */
20
+ overflow: hidden; /* prevent scrollbars */
21
+ }
22
+
23
+ .container {
24
+ max-width: 800px;
25
+ background: #fff;
26
+ padding: 40px; /* spacing inside container */
27
+ border-radius: 8px;
28
+ box-shadow: 0 0 20px rgba(0,0,0,0.1);
29
+ text-align: center;
30
+ animation: fadeIn 1s ease-in-out;
31
+ }
32
+
33
+ h1 {
34
+ color: #f0ad4e;
35
+ font-size: 48px;
36
+ margin-bottom: 20px;
37
+ animation: bounce 1s infinite alternate;
38
+ }
39
+
40
+ p {
41
+ font-size: 18px;
42
+ }
43
+
44
+ @keyframes fadeIn {
45
+ from { opacity: 0; transform: translateY(-20px); }
46
+ to { opacity: 1; transform: translateY(0); }
47
+ }
48
+
49
+ @keyframes bounce {
50
+ 0% { transform: translateY(0); }
51
+ 50% { transform: translateY(-10px); }
52
+ 100% { transform: translateY(0); }
53
+ }
54
+ </style>
55
+ </head>
56
+ <body>
57
+ <div class="container">
58
+ <h1>404 - Page Not Found</h1>
59
+ <p>The requested URL <strong>{{path}}</strong> was not found on this server.</p>
60
+ </div>
61
+ </body>
62
+ </html>
@@ -0,0 +1,73 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>500 - Internal Server Error</title>
6
+ <style>
7
+ * {
8
+ box-sizing: border-box;
9
+ margin: 0;
10
+ padding: 0;
11
+ }
12
+
13
+ body {
14
+ font-family: Arial, sans-serif;
15
+ background: #f8f8f8;
16
+ display: flex;
17
+ justify-content: center;
18
+ align-items: center;
19
+ height: 100vh;
20
+ overflow: hidden;
21
+ }
22
+
23
+ .container {
24
+ max-width: 800px;
25
+ background: #fff;
26
+ padding: 40px;
27
+ border-radius: 8px;
28
+ box-shadow: 0 0 20px rgba(0,0,0,0.1);
29
+ text-align: center;
30
+ animation: fadeIn 1s ease-in-out;
31
+ }
32
+
33
+ h1 {
34
+ color: #d9534f;
35
+ font-size: 48px;
36
+ margin-bottom: 20px;
37
+ animation: bounce 1s infinite alternate;
38
+ }
39
+
40
+ p, pre {
41
+ font-size: 16px;
42
+ margin-bottom: 10px;
43
+ word-wrap: break-word;
44
+ }
45
+
46
+ pre {
47
+ background: #f0f0f0;
48
+ padding: 15px;
49
+ border-radius: 5px;
50
+ text-align: left;
51
+ overflow-x: auto;
52
+ }
53
+
54
+ @keyframes fadeIn {
55
+ from { opacity: 0; transform: translateY(-20px); }
56
+ to { opacity: 1; transform: translateY(0); }
57
+ }
58
+
59
+ @keyframes bounce {
60
+ 0% { transform: translateY(0); }
61
+ 50% { transform: translateY(-10px); }
62
+ 100% { transform: translateY(0); }
63
+ }
64
+ </style>
65
+ </head>
66
+ <body>
67
+ <div class="container">
68
+ <h1>500 - Internal Server Error</h1>
69
+ <p><strong>Error:</strong> {{message}}</p>
70
+ <pre>{{backtrace}}</pre>
71
+ </div>
72
+ </body>
73
+ </html>
@@ -0,0 +1,37 @@
1
+ # framework/middleware/cors_middleware.rb
2
+ class CorsMiddleware
3
+ def initialize(app)
4
+ @app = app
5
+ # puts "CORS Middleware initialized!"
6
+ end
7
+
8
+ def call(env)
9
+ # puts "CORS Middleware processing: #{env['REQUEST_METHOD']} #{env['PATH_INFO']}"
10
+
11
+ # Handle preflight OPTIONS request
12
+ if env['REQUEST_METHOD'] == 'OPTIONS'
13
+ # puts "Handling OPTIONS preflight request"
14
+ return [200, cors_headers, []]
15
+ end
16
+
17
+ status, headers, body = @app.call(env)
18
+
19
+ # Add CORS headers to all responses
20
+ # puts "Adding CORS headers to response"
21
+ headers.merge!(cors_headers)
22
+
23
+ [status, headers, body]
24
+ end
25
+
26
+ private
27
+
28
+ def cors_headers
29
+ {
30
+ 'Access-Control-Allow-Origin' => 'http://localhost:1091',
31
+ 'Access-Control-Allow-Methods' => 'GET, POST, PUT, DELETE, OPTIONS',
32
+ 'Access-Control-Allow-Headers' => 'Content-Type, Authorization, X-Requested-With',
33
+ 'Access-Control-Allow-Credentials' => 'true',
34
+ 'Access-Control-Max-Age' => '86400'
35
+ }
36
+ end
37
+ end