tina4ruby 0.5.2 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1 -1
- data/README.md +360 -559
- data/exe/{tina4 → tina4ruby} +1 -0
- data/lib/tina4/ai.rb +312 -0
- data/lib/tina4/auth.rb +44 -3
- data/lib/tina4/auto_crud.rb +163 -0
- data/lib/tina4/cli.rb +242 -77
- data/lib/tina4/constants.rb +46 -0
- data/lib/tina4/cors.rb +74 -0
- data/lib/tina4/database/sqlite3_adapter.rb +139 -0
- data/lib/tina4/database.rb +43 -7
- data/lib/tina4/debug.rb +4 -79
- data/lib/tina4/dev_admin.rb +1162 -0
- data/lib/tina4/dev_mailbox.rb +191 -0
- data/lib/tina4/dev_reload.rb +9 -9
- data/lib/tina4/drivers/firebird_driver.rb +19 -3
- data/lib/tina4/drivers/mssql_driver.rb +3 -3
- data/lib/tina4/drivers/mysql_driver.rb +4 -4
- data/lib/tina4/drivers/postgres_driver.rb +9 -2
- data/lib/tina4/drivers/sqlite_driver.rb +1 -1
- data/lib/tina4/env.rb +42 -2
- data/lib/tina4/error_overlay.rb +252 -0
- data/lib/tina4/events.rb +90 -0
- data/lib/tina4/field_types.rb +4 -0
- data/lib/tina4/frond.rb +1336 -0
- data/lib/tina4/gallery/auth/meta.json +1 -0
- data/lib/tina4/gallery/auth/src/routes/api/gallery_auth.rb +114 -0
- data/lib/tina4/gallery/database/meta.json +1 -0
- data/lib/tina4/gallery/database/src/routes/api/gallery_db.rb +43 -0
- data/lib/tina4/gallery/error-overlay/meta.json +1 -0
- data/lib/tina4/gallery/error-overlay/src/routes/api/gallery_crash.rb +17 -0
- data/lib/tina4/gallery/orm/meta.json +1 -0
- data/lib/tina4/gallery/orm/src/routes/api/gallery_products.rb +16 -0
- data/lib/tina4/gallery/queue/meta.json +1 -0
- data/lib/tina4/gallery/queue/src/routes/api/gallery_queue.rb +27 -0
- data/lib/tina4/gallery/rest-api/meta.json +1 -0
- data/lib/tina4/gallery/rest-api/src/routes/api/gallery_hello.rb +14 -0
- data/lib/tina4/gallery/templates/meta.json +1 -0
- data/lib/tina4/gallery/templates/src/routes/gallery_page.rb +12 -0
- data/lib/tina4/gallery/templates/src/templates/gallery_page.twig +257 -0
- data/lib/tina4/health.rb +39 -0
- data/lib/tina4/html_element.rb +148 -0
- data/lib/tina4/localization.rb +2 -2
- data/lib/tina4/log.rb +203 -0
- data/lib/tina4/messenger.rb +484 -0
- data/lib/tina4/migration.rb +132 -29
- data/lib/tina4/orm.rb +337 -31
- data/lib/tina4/public/css/tina4.css +178 -1
- data/lib/tina4/public/css/tina4.min.css +1 -2
- data/lib/tina4/public/favicon.ico +0 -0
- data/lib/tina4/public/images/logo.svg +5 -0
- data/lib/tina4/public/images/tina4-logo-icon.webp +0 -0
- data/lib/tina4/public/js/frond.min.js +420 -0
- data/lib/tina4/public/js/tina4-dev-admin.min.js +367 -0
- data/lib/tina4/public/js/tina4.min.js +93 -0
- data/lib/tina4/public/swagger/index.html +90 -0
- data/lib/tina4/public/swagger/oauth2-redirect.html +63 -0
- data/lib/tina4/queue.rb +40 -4
- data/lib/tina4/queue_backends/lite_backend.rb +88 -0
- data/lib/tina4/rack_app.rb +314 -23
- data/lib/tina4/rate_limiter.rb +123 -0
- data/lib/tina4/request.rb +61 -15
- data/lib/tina4/response.rb +54 -24
- data/lib/tina4/response_cache.rb +134 -0
- data/lib/tina4/router.rb +90 -15
- data/lib/tina4/scss_compiler.rb +2 -2
- data/lib/tina4/seeder.rb +56 -61
- data/lib/tina4/service_runner.rb +303 -0
- data/lib/tina4/session.rb +85 -0
- data/lib/tina4/session_handlers/mongo_handler.rb +1 -1
- data/lib/tina4/session_handlers/valkey_handler.rb +43 -0
- data/lib/tina4/shutdown.rb +84 -0
- data/lib/tina4/sql_translation.rb +295 -0
- data/lib/tina4/template.rb +36 -6
- data/lib/tina4/templates/base.twig +2 -2
- data/lib/tina4/templates/errors/302.twig +14 -0
- data/lib/tina4/templates/errors/401.twig +9 -0
- data/lib/tina4/templates/errors/403.twig +22 -15
- data/lib/tina4/templates/errors/404.twig +22 -15
- data/lib/tina4/templates/errors/500.twig +31 -15
- data/lib/tina4/templates/errors/502.twig +9 -0
- data/lib/tina4/templates/errors/503.twig +12 -0
- data/lib/tina4/templates/errors/base.twig +37 -0
- data/lib/tina4/version.rb +1 -1
- data/lib/tina4/webserver.rb +28 -18
- data/lib/tina4.rb +57 -21
- metadata +51 -19
- data/lib/tina4/public/js/tina4.js +0 -134
- data/lib/tina4/public/js/tina4helper.js +0 -387
data/lib/tina4/cli.rb
CHANGED
|
@@ -1,32 +1,92 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
require "optparse"
|
|
3
4
|
require "fileutils"
|
|
4
5
|
|
|
5
6
|
module Tina4
|
|
6
|
-
class CLI
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
def
|
|
10
|
-
|
|
7
|
+
class CLI
|
|
8
|
+
COMMANDS = %w[init start migrate seed seed:create test version routes console ai help].freeze
|
|
9
|
+
|
|
10
|
+
def self.start(argv)
|
|
11
|
+
new.run(argv)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def run(argv)
|
|
15
|
+
command = argv.shift || "help"
|
|
16
|
+
case command
|
|
17
|
+
when "init" then cmd_init(argv)
|
|
18
|
+
when "start" then cmd_start(argv)
|
|
19
|
+
when "migrate" then cmd_migrate(argv)
|
|
20
|
+
when "seed" then cmd_seed(argv)
|
|
21
|
+
when "seed:create" then cmd_seed_create(argv)
|
|
22
|
+
when "test" then cmd_test(argv)
|
|
23
|
+
when "version" then cmd_version
|
|
24
|
+
when "routes" then cmd_routes
|
|
25
|
+
when "console" then cmd_console
|
|
26
|
+
when "ai" then cmd_ai(argv)
|
|
27
|
+
when "help", "-h", "--help" then cmd_help
|
|
28
|
+
else
|
|
29
|
+
puts "Unknown command: #{command}"
|
|
30
|
+
cmd_help
|
|
31
|
+
exit 1
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
# ── init ──────────────────────────────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
def cmd_init(argv)
|
|
40
|
+
options = { template: "default" }
|
|
41
|
+
parser = OptionParser.new do |opts|
|
|
42
|
+
opts.banner = "Usage: tina4ruby init [PATH] [options]"
|
|
43
|
+
opts.on("--template TEMPLATE", "Project template (default: default)") { |v| options[:template] = v }
|
|
44
|
+
end
|
|
45
|
+
parser.parse!(argv)
|
|
46
|
+
|
|
47
|
+
name = argv.shift || "."
|
|
48
|
+
dir = File.expand_path(name)
|
|
11
49
|
FileUtils.mkdir_p(dir)
|
|
12
50
|
|
|
51
|
+
project_name = File.basename(dir)
|
|
13
52
|
create_project_structure(dir)
|
|
14
|
-
create_sample_files(dir,
|
|
53
|
+
create_sample_files(dir, project_name)
|
|
15
54
|
|
|
16
|
-
puts "
|
|
17
|
-
|
|
55
|
+
puts "\nProject scaffolded at #{dir}"
|
|
56
|
+
if name == "."
|
|
57
|
+
puts " bundle install"
|
|
58
|
+
puts " ruby app.rb"
|
|
59
|
+
else
|
|
60
|
+
puts " cd #{dir}"
|
|
61
|
+
puts " bundle install"
|
|
62
|
+
puts " ruby app.rb"
|
|
63
|
+
end
|
|
18
64
|
end
|
|
19
65
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
66
|
+
# ── start ─────────────────────────────────────────────────────────────
|
|
67
|
+
|
|
68
|
+
def cmd_start(argv)
|
|
69
|
+
options = { port: nil, host: nil, dev: false }
|
|
70
|
+
parser = OptionParser.new do |opts|
|
|
71
|
+
opts.banner = "Usage: tina4ruby start [options]"
|
|
72
|
+
opts.on("-p", "--port PORT", Integer, "Port (default: 7147)") { |v| options[:port] = v }
|
|
73
|
+
opts.on("-h", "--host HOST", "Host (default: 0.0.0.0)") { |v| options[:host] = v }
|
|
74
|
+
opts.on("-d", "--dev", "Enable dev mode with auto-reload") { options[:dev] = true }
|
|
75
|
+
end
|
|
76
|
+
parser.parse!(argv)
|
|
77
|
+
|
|
78
|
+
# Priority: CLI flag > ENV var > default
|
|
79
|
+
options[:port] = resolve_config(:port, options[:port])
|
|
80
|
+
options[:host] = resolve_config(:host, options[:host])
|
|
81
|
+
|
|
25
82
|
require_relative "../tina4"
|
|
26
83
|
|
|
27
84
|
root_dir = Dir.pwd
|
|
28
85
|
Tina4.initialize!(root_dir)
|
|
29
86
|
|
|
87
|
+
# Register health check endpoint
|
|
88
|
+
Tina4::Health.register!
|
|
89
|
+
|
|
30
90
|
# Load route files
|
|
31
91
|
load_routes(root_dir)
|
|
32
92
|
|
|
@@ -56,20 +116,31 @@ module Tina4
|
|
|
56
116
|
user_config.quiet
|
|
57
117
|
end
|
|
58
118
|
|
|
59
|
-
Tina4::
|
|
119
|
+
Tina4::Log.info("Starting Puma server on http://#{puma_host}:#{puma_port}")
|
|
120
|
+
|
|
121
|
+
# Setup graceful shutdown (Puma manages its own signals, but we handle DB cleanup)
|
|
122
|
+
Tina4::Shutdown.setup
|
|
123
|
+
|
|
60
124
|
launcher = Puma::Launcher.new(config)
|
|
61
125
|
launcher.run
|
|
62
126
|
rescue LoadError
|
|
63
|
-
Tina4::
|
|
127
|
+
Tina4::Log.info("Puma not found, falling back to WEBrick")
|
|
64
128
|
server = Tina4::WebServer.new(app, host: options[:host], port: options[:port])
|
|
65
129
|
server.start
|
|
66
130
|
end
|
|
67
131
|
end
|
|
68
132
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
133
|
+
# ── migrate ───────────────────────────────────────────────────────────
|
|
134
|
+
|
|
135
|
+
def cmd_migrate(argv)
|
|
136
|
+
options = {}
|
|
137
|
+
parser = OptionParser.new do |opts|
|
|
138
|
+
opts.banner = "Usage: tina4ruby migrate [options]"
|
|
139
|
+
opts.on("--create NAME", "Create a new migration") { |v| options[:create] = v }
|
|
140
|
+
opts.on("--rollback N", Integer, "Rollback N migrations") { |v| options[:rollback] = v }
|
|
141
|
+
end
|
|
142
|
+
parser.parse!(argv)
|
|
143
|
+
|
|
73
144
|
require_relative "../tina4"
|
|
74
145
|
Tina4.initialize!(Dir.pwd)
|
|
75
146
|
|
|
@@ -100,8 +171,59 @@ module Tina4
|
|
|
100
171
|
end
|
|
101
172
|
end
|
|
102
173
|
|
|
103
|
-
|
|
104
|
-
|
|
174
|
+
# ── seed ──────────────────────────────────────────────────────────────
|
|
175
|
+
|
|
176
|
+
def cmd_seed(argv)
|
|
177
|
+
options = { clear: false }
|
|
178
|
+
parser = OptionParser.new do |opts|
|
|
179
|
+
opts.banner = "Usage: tina4ruby seed [options]"
|
|
180
|
+
opts.on("--clear", "Clear tables before seeding") { options[:clear] = true }
|
|
181
|
+
end
|
|
182
|
+
parser.parse!(argv)
|
|
183
|
+
|
|
184
|
+
require_relative "../tina4"
|
|
185
|
+
Tina4.initialize!(Dir.pwd)
|
|
186
|
+
load_routes(Dir.pwd)
|
|
187
|
+
Tina4.seed(seed_folder: "seeds", clear: options[:clear])
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# ── seed:create ───────────────────────────────────────────────────────
|
|
191
|
+
|
|
192
|
+
def cmd_seed_create(argv)
|
|
193
|
+
name = argv.shift
|
|
194
|
+
unless name
|
|
195
|
+
puts "Usage: tina4ruby seed:create NAME"
|
|
196
|
+
exit 1
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
dir = File.join(Dir.pwd, "seeds")
|
|
200
|
+
FileUtils.mkdir_p(dir)
|
|
201
|
+
|
|
202
|
+
existing = Dir.glob(File.join(dir, "*.rb")).select { |f| File.basename(f)[0] =~ /\d/ }.sort
|
|
203
|
+
numbers = existing.map { |f| File.basename(f).match(/^(\d+)/)[1].to_i }
|
|
204
|
+
next_num = numbers.empty? ? 1 : numbers.max + 1
|
|
205
|
+
|
|
206
|
+
clean_name = name.strip.downcase.gsub(/[^a-z0-9]+/, "_").gsub(/^_|_$/, "")
|
|
207
|
+
filename = format("%03d_%s.rb", next_num, clean_name)
|
|
208
|
+
filepath = File.join(dir, filename)
|
|
209
|
+
|
|
210
|
+
File.write(filepath, <<~RUBY)
|
|
211
|
+
# Seed: #{name.strip}
|
|
212
|
+
#
|
|
213
|
+
# This file is executed by `tina4ruby seed`.
|
|
214
|
+
# Use Tina4.seed_orm or Tina4.seed_table to populate data.
|
|
215
|
+
#
|
|
216
|
+
# Examples:
|
|
217
|
+
# Tina4.seed_orm(User, count: 50)
|
|
218
|
+
# Tina4.seed_table("audit_log", { action: :string, created_at: :datetime }, count: 100)
|
|
219
|
+
RUBY
|
|
220
|
+
|
|
221
|
+
puts "Created seed file: #{filepath}"
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# ── test ──────────────────────────────────────────────────────────────
|
|
225
|
+
|
|
226
|
+
def cmd_test(argv)
|
|
105
227
|
require_relative "../tina4"
|
|
106
228
|
Tina4.initialize!(Dir.pwd)
|
|
107
229
|
|
|
@@ -121,14 +243,16 @@ module Tina4
|
|
|
121
243
|
exit(1) if results[:failed] > 0 || results[:errors] > 0
|
|
122
244
|
end
|
|
123
245
|
|
|
124
|
-
|
|
125
|
-
|
|
246
|
+
# ── version ───────────────────────────────────────────────────────────
|
|
247
|
+
|
|
248
|
+
def cmd_version
|
|
126
249
|
require_relative "version"
|
|
127
250
|
puts "Tina4 Ruby v#{Tina4::VERSION}"
|
|
128
251
|
end
|
|
129
252
|
|
|
130
|
-
|
|
131
|
-
|
|
253
|
+
# ── routes ────────────────────────────────────────────────────────────
|
|
254
|
+
|
|
255
|
+
def cmd_routes
|
|
132
256
|
require_relative "../tina4"
|
|
133
257
|
Tina4.initialize!(Dir.pwd)
|
|
134
258
|
load_routes(Dir.pwd)
|
|
@@ -143,53 +267,92 @@ module Tina4
|
|
|
143
267
|
puts "Total: #{Tina4::Router.routes.length} routes\n"
|
|
144
268
|
end
|
|
145
269
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
def
|
|
270
|
+
# ── console ───────────────────────────────────────────────────────────
|
|
271
|
+
|
|
272
|
+
def cmd_console
|
|
149
273
|
require_relative "../tina4"
|
|
150
274
|
Tina4.initialize!(Dir.pwd)
|
|
151
275
|
load_routes(Dir.pwd)
|
|
152
|
-
|
|
276
|
+
|
|
277
|
+
require "irb"
|
|
278
|
+
IRB.start
|
|
153
279
|
end
|
|
154
280
|
|
|
155
|
-
|
|
156
|
-
def seed_create(name)
|
|
157
|
-
dir = File.join(Dir.pwd, "seeds")
|
|
158
|
-
FileUtils.mkdir_p(dir)
|
|
281
|
+
# ── ai ────────────────────────────────────────────────────────────────
|
|
159
282
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
283
|
+
def cmd_ai(argv)
|
|
284
|
+
options = { all: false, force: false }
|
|
285
|
+
parser = OptionParser.new do |opts|
|
|
286
|
+
opts.banner = "Usage: tina4ruby ai [options]"
|
|
287
|
+
opts.on("--all", "Install context for ALL AI tools (not just detected ones)") { options[:all] = true }
|
|
288
|
+
opts.on("--force", "Overwrite existing context files") { options[:force] = true }
|
|
289
|
+
end
|
|
290
|
+
parser.parse!(argv)
|
|
163
291
|
|
|
164
|
-
|
|
165
|
-
filename = format("%03d_%s.rb", next_num, clean_name)
|
|
166
|
-
filepath = File.join(dir, filename)
|
|
292
|
+
require_relative "ai"
|
|
167
293
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
#
|
|
171
|
-
# This file is executed by `tina4 seed`.
|
|
172
|
-
# Use Tina4.seed_orm or Tina4.seed_table to populate data.
|
|
173
|
-
#
|
|
174
|
-
# Examples:
|
|
175
|
-
# Tina4.seed_orm(User, count: 50)
|
|
176
|
-
# Tina4.seed_table("audit_log", { action: :string, created_at: :datetime }, count: 100)
|
|
177
|
-
RUBY
|
|
294
|
+
root_dir = Dir.pwd
|
|
295
|
+
puts Tina4::AI.status_report(root_dir)
|
|
178
296
|
|
|
179
|
-
|
|
297
|
+
if options[:all]
|
|
298
|
+
created = Tina4::AI.install_all(root_dir, force: options[:force])
|
|
299
|
+
else
|
|
300
|
+
created = Tina4::AI.install_ai_context(root_dir, force: options[:force])
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
if created.any?
|
|
304
|
+
puts "Created/updated context files:"
|
|
305
|
+
created.each { |f| puts " #{f}" }
|
|
306
|
+
else
|
|
307
|
+
puts "No context files were created (files already exist; use --force to overwrite)."
|
|
308
|
+
end
|
|
180
309
|
end
|
|
181
310
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
311
|
+
# ── help ──────────────────────────────────────────────────────────────
|
|
312
|
+
|
|
313
|
+
def cmd_help
|
|
314
|
+
puts <<~HELP
|
|
315
|
+
Tina4 Ruby CLI
|
|
316
|
+
|
|
317
|
+
Usage: tina4ruby COMMAND [options]
|
|
318
|
+
|
|
319
|
+
Commands:
|
|
320
|
+
init [NAME] Initialize a new Tina4 project
|
|
321
|
+
start Start the Tina4 web server
|
|
322
|
+
migrate Run database migrations
|
|
323
|
+
seed Run all seed files in seeds/
|
|
324
|
+
seed:create NAME Create a new seed file
|
|
325
|
+
test Run inline tests
|
|
326
|
+
version Show Tina4 version
|
|
327
|
+
routes List all registered routes
|
|
328
|
+
console Start an interactive console
|
|
329
|
+
ai Detect AI tools and install context files
|
|
330
|
+
help Show this help message
|
|
331
|
+
|
|
332
|
+
Run 'tina4ruby COMMAND --help' for more information on a command.
|
|
333
|
+
HELP
|
|
334
|
+
end
|
|
187
335
|
|
|
188
|
-
|
|
189
|
-
|
|
336
|
+
# ── config resolution ──────────────────────────────────────────────────
|
|
337
|
+
|
|
338
|
+
DEFAULT_PORT = 7147
|
|
339
|
+
DEFAULT_HOST = "0.0.0.0"
|
|
340
|
+
|
|
341
|
+
# Priority: CLI flag > ENV var > default
|
|
342
|
+
def resolve_config(key, cli_value)
|
|
343
|
+
case key
|
|
344
|
+
when :port
|
|
345
|
+
return cli_value if cli_value
|
|
346
|
+
return ENV["PORT"].to_i if ENV["PORT"] && !ENV["PORT"].empty?
|
|
347
|
+
DEFAULT_PORT
|
|
348
|
+
when :host
|
|
349
|
+
return cli_value if cli_value
|
|
350
|
+
return ENV["HOST"] if ENV["HOST"] && !ENV["HOST"].empty?
|
|
351
|
+
DEFAULT_HOST
|
|
352
|
+
end
|
|
190
353
|
end
|
|
191
354
|
|
|
192
|
-
|
|
355
|
+
# ── shared helpers ────────────────────────────────────────────────────
|
|
193
356
|
|
|
194
357
|
def load_routes(root_dir)
|
|
195
358
|
route_dirs = %w[routes src/routes src/api api]
|
|
@@ -209,8 +372,9 @@ module Tina4
|
|
|
209
372
|
|
|
210
373
|
def create_project_structure(dir)
|
|
211
374
|
%w[
|
|
212
|
-
routes
|
|
213
|
-
|
|
375
|
+
src/routes src/orm src/templates src/templates/errors
|
|
376
|
+
src/public src/public/css src/public/js src/public/images
|
|
377
|
+
migrations logs
|
|
214
378
|
].each do |subdir|
|
|
215
379
|
FileUtils.mkdir_p(File.join(dir, subdir))
|
|
216
380
|
end
|
|
@@ -221,14 +385,9 @@ module Tina4
|
|
|
221
385
|
unless File.exist?(File.join(dir, "app.rb"))
|
|
222
386
|
File.write(File.join(dir, "app.rb"), <<~RUBY)
|
|
223
387
|
require "tina4"
|
|
224
|
-
|
|
225
|
-
Tina4.
|
|
226
|
-
|
|
227
|
-
end
|
|
228
|
-
|
|
229
|
-
Tina4.get "/api/hello" do |request, response|
|
|
230
|
-
response.json({ message: "Hello from Tina4!", timestamp: Time.now.iso8601 })
|
|
231
|
-
end
|
|
388
|
+
Tina4.initialize!(__dir__)
|
|
389
|
+
app = Tina4::RackApp.new
|
|
390
|
+
Tina4::WebServer.new(app, port: 7147).start
|
|
232
391
|
RUBY
|
|
233
392
|
end
|
|
234
393
|
|
|
@@ -236,10 +395,18 @@ module Tina4
|
|
|
236
395
|
unless File.exist?(File.join(dir, "Gemfile"))
|
|
237
396
|
File.write(File.join(dir, "Gemfile"), <<~RUBY)
|
|
238
397
|
source "https://rubygems.org"
|
|
239
|
-
gem "
|
|
398
|
+
gem "tina4-ruby", "~> 3.0"
|
|
240
399
|
RUBY
|
|
241
400
|
end
|
|
242
401
|
|
|
402
|
+
# .env
|
|
403
|
+
unless File.exist?(File.join(dir, ".env"))
|
|
404
|
+
File.write(File.join(dir, ".env"), <<~TEXT)
|
|
405
|
+
TINA4_DEBUG=true
|
|
406
|
+
TINA4_LOG_LEVEL=ALL
|
|
407
|
+
TEXT
|
|
408
|
+
end
|
|
409
|
+
|
|
243
410
|
# .gitignore
|
|
244
411
|
unless File.exist?(File.join(dir, ".gitignore"))
|
|
245
412
|
File.write(File.join(dir, ".gitignore"), <<~TEXT)
|
|
@@ -291,7 +458,7 @@ module Tina4
|
|
|
291
458
|
# Copy application code
|
|
292
459
|
COPY --from=builder /app /app
|
|
293
460
|
|
|
294
|
-
EXPOSE
|
|
461
|
+
EXPOSE 7147
|
|
295
462
|
|
|
296
463
|
# Swagger defaults (override with env vars in docker-compose/k8s if needed)
|
|
297
464
|
ENV SWAGGER_TITLE="Tina4 API"
|
|
@@ -299,9 +466,8 @@ module Tina4
|
|
|
299
466
|
ENV SWAGGER_DESCRIPTION="Auto-generated API documentation"
|
|
300
467
|
|
|
301
468
|
# Start the server on all interfaces
|
|
302
|
-
CMD ["bundle", "exec", "
|
|
469
|
+
CMD ["bundle", "exec", "tina4ruby", "start", "-p", "7147", "-h", "0.0.0.0"]
|
|
303
470
|
DOCKERFILE
|
|
304
|
-
puts " Created Dockerfile"
|
|
305
471
|
end
|
|
306
472
|
|
|
307
473
|
# .dockerignore
|
|
@@ -319,11 +485,10 @@ module Tina4
|
|
|
319
485
|
spec/
|
|
320
486
|
vendor/bundle
|
|
321
487
|
TEXT
|
|
322
|
-
puts " Created .dockerignore"
|
|
323
488
|
end
|
|
324
489
|
|
|
325
490
|
# Base template
|
|
326
|
-
templates_dir = File.join(dir, "templates")
|
|
491
|
+
templates_dir = File.join(dir, "src", "templates")
|
|
327
492
|
unless File.exist?(File.join(templates_dir, "base.twig"))
|
|
328
493
|
File.write(File.join(templates_dir, "base.twig"), <<~HTML)
|
|
329
494
|
<!DOCTYPE html>
|
|
@@ -337,8 +502,8 @@ module Tina4
|
|
|
337
502
|
</head>
|
|
338
503
|
<body>
|
|
339
504
|
{% block content %}{% endblock %}
|
|
340
|
-
<script src="/js/tina4.js"></script>
|
|
341
|
-
<script src="/js/
|
|
505
|
+
<script src="/js/tina4.min.js"></script>
|
|
506
|
+
<script src="/js/frond.min.js"></script>
|
|
342
507
|
{% block scripts %}{% endblock %}
|
|
343
508
|
</body>
|
|
344
509
|
</html>
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Tina4 Constants — HTTP status codes and content types.
|
|
2
|
+
#
|
|
3
|
+
# Standard constants for use in route handlers across all Tina4 frameworks.
|
|
4
|
+
#
|
|
5
|
+
# Tina4.get "/api/users" do |request, response|
|
|
6
|
+
# response.call(users, Tina4::HTTP_OK)
|
|
7
|
+
# end
|
|
8
|
+
|
|
9
|
+
module Tina4
|
|
10
|
+
# ── HTTP Status Codes ──
|
|
11
|
+
|
|
12
|
+
HTTP_OK = 200
|
|
13
|
+
HTTP_CREATED = 201
|
|
14
|
+
HTTP_ACCEPTED = 202
|
|
15
|
+
HTTP_NO_CONTENT = 204
|
|
16
|
+
|
|
17
|
+
HTTP_MOVED = 301
|
|
18
|
+
HTTP_REDIRECT = 302
|
|
19
|
+
HTTP_NOT_MODIFIED = 304
|
|
20
|
+
|
|
21
|
+
HTTP_BAD_REQUEST = 400
|
|
22
|
+
HTTP_UNAUTHORIZED = 401
|
|
23
|
+
HTTP_FORBIDDEN = 403
|
|
24
|
+
HTTP_NOT_FOUND = 404
|
|
25
|
+
HTTP_METHOD_NOT_ALLOWED = 405
|
|
26
|
+
HTTP_CONFLICT = 409
|
|
27
|
+
HTTP_GONE = 410
|
|
28
|
+
HTTP_UNPROCESSABLE = 422
|
|
29
|
+
HTTP_TOO_MANY = 429
|
|
30
|
+
|
|
31
|
+
HTTP_SERVER_ERROR = 500
|
|
32
|
+
HTTP_BAD_GATEWAY = 502
|
|
33
|
+
HTTP_UNAVAILABLE = 503
|
|
34
|
+
|
|
35
|
+
# ── Content Types ──
|
|
36
|
+
|
|
37
|
+
APPLICATION_JSON = "application/json"
|
|
38
|
+
APPLICATION_XML = "application/xml"
|
|
39
|
+
APPLICATION_FORM = "application/x-www-form-urlencoded"
|
|
40
|
+
APPLICATION_OCTET = "application/octet-stream"
|
|
41
|
+
|
|
42
|
+
TEXT_HTML = "text/html; charset=utf-8"
|
|
43
|
+
TEXT_PLAIN = "text/plain; charset=utf-8"
|
|
44
|
+
TEXT_CSV = "text/csv"
|
|
45
|
+
TEXT_XML = "text/xml"
|
|
46
|
+
end
|
data/lib/tina4/cors.rb
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tina4
|
|
4
|
+
module CorsMiddleware
|
|
5
|
+
class << self
|
|
6
|
+
def config
|
|
7
|
+
@config ||= load_config
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def reset!
|
|
11
|
+
@config = nil
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Handle OPTIONS preflight request, returns a Rack response array
|
|
15
|
+
def preflight_response(env = {})
|
|
16
|
+
origin = resolve_origin(env)
|
|
17
|
+
[
|
|
18
|
+
204,
|
|
19
|
+
{
|
|
20
|
+
"access-control-allow-origin" => origin,
|
|
21
|
+
"access-control-allow-methods" => config[:methods],
|
|
22
|
+
"access-control-allow-headers" => config[:headers],
|
|
23
|
+
"access-control-max-age" => config[:max_age],
|
|
24
|
+
"access-control-allow-credentials" => config[:credentials]
|
|
25
|
+
},
|
|
26
|
+
[""]
|
|
27
|
+
]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Apply CORS headers to a response headers hash
|
|
31
|
+
def apply_headers(response_headers, env = {})
|
|
32
|
+
origin = resolve_origin(env)
|
|
33
|
+
response_headers["access-control-allow-origin"] = origin
|
|
34
|
+
response_headers["access-control-allow-methods"] = config[:methods]
|
|
35
|
+
response_headers["access-control-allow-headers"] = config[:headers]
|
|
36
|
+
response_headers["access-control-max-age"] = config[:max_age]
|
|
37
|
+
response_headers["access-control-allow-credentials"] = config[:credentials] if config[:credentials] == "true"
|
|
38
|
+
response_headers
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Check if a given origin is allowed
|
|
42
|
+
def origin_allowed?(origin)
|
|
43
|
+
return true if config[:origins] == "*"
|
|
44
|
+
|
|
45
|
+
allowed = config[:origins].split(",").map(&:strip)
|
|
46
|
+
allowed.include?(origin)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def load_config
|
|
52
|
+
{
|
|
53
|
+
origins: ENV["TINA4_CORS_ORIGINS"] || "*",
|
|
54
|
+
methods: ENV["TINA4_CORS_METHODS"] || "GET, POST, PUT, PATCH, DELETE, OPTIONS",
|
|
55
|
+
headers: ENV["TINA4_CORS_HEADERS"] || "Content-Type, Authorization, Accept",
|
|
56
|
+
max_age: ENV["TINA4_CORS_MAX_AGE"] || "86400",
|
|
57
|
+
credentials: ENV["TINA4_CORS_CREDENTIALS"] || "false"
|
|
58
|
+
}.freeze
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def resolve_origin(env)
|
|
62
|
+
request_origin = env["HTTP_ORIGIN"] || env["HTTP_REFERER"]
|
|
63
|
+
|
|
64
|
+
if config[:origins] == "*"
|
|
65
|
+
"*"
|
|
66
|
+
elsif request_origin && origin_allowed?(request_origin.chomp("/"))
|
|
67
|
+
request_origin.chomp("/")
|
|
68
|
+
else
|
|
69
|
+
config[:origins].split(",").first&.strip || "*"
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|