tina4ruby 3.11.35 → 3.12.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/lib/tina4/auth.rb +5 -5
- data/lib/tina4/background.rb +81 -0
- data/lib/tina4/constants.rb +40 -0
- data/lib/tina4/container.rb +1 -1
- data/lib/tina4/database.rb +37 -10
- data/lib/tina4/dev_admin.rb +464 -2
- data/lib/tina4/docs.rb +636 -0
- data/lib/tina4/drivers/postgres_driver.rb +38 -4
- data/lib/tina4/env.rb +74 -3
- data/lib/tina4/field_types.rb +1 -1
- data/lib/tina4/frond.rb +62 -0
- data/lib/tina4/mcp.rb +191 -1
- data/lib/tina4/messenger.rb +13 -14
- data/lib/tina4/orm.rb +85 -12
- data/lib/tina4/plan.rb +471 -0
- data/lib/tina4/project_index.rb +366 -0
- data/lib/tina4/public/js/frond.js +600 -0
- data/lib/tina4/public/js/frond.min.js +1 -1
- data/lib/tina4/public/js/tina4-dev-admin.js +1086 -238
- data/lib/tina4/public/js/tina4-dev-admin.min.js +1142 -209
- data/lib/tina4/rack_app.rb +98 -16
- data/lib/tina4/response.rb +3 -0
- data/lib/tina4/session.rb +1 -1
- data/lib/tina4/session_handlers/database_handler.rb +1 -1
- data/lib/tina4/shutdown.rb +10 -0
- data/lib/tina4/swagger.rb +3 -3
- data/lib/tina4/version.rb +1 -1
- data/lib/tina4/webserver.rb +3 -0
- data/lib/tina4.rb +15 -1
- metadata +6 -1
data/lib/tina4/rack_app.rb
CHANGED
|
@@ -200,7 +200,7 @@ module Tina4
|
|
|
200
200
|
end
|
|
201
201
|
|
|
202
202
|
# API_KEY bypass — matches tina4_python behavior
|
|
203
|
-
api_key = ENV["TINA4_API_KEY"]
|
|
203
|
+
api_key = ENV["TINA4_API_KEY"]
|
|
204
204
|
if api_key && !api_key.empty? && token == api_key
|
|
205
205
|
env["tina4.auth_payload"] = { "api_key" => true }
|
|
206
206
|
elsif token
|
|
@@ -330,18 +330,28 @@ module Tina4
|
|
|
330
330
|
end
|
|
331
331
|
|
|
332
332
|
def handle_404(path)
|
|
333
|
-
# Try serving a template file (e.g. /hello -> src/templates/hello.twig
|
|
333
|
+
# Try serving a template file (e.g. /hello -> src/templates/pages/hello.twig)
|
|
334
334
|
template_response = try_serve_template(path)
|
|
335
335
|
return template_response if template_response
|
|
336
336
|
|
|
337
|
-
# Show landing page for GET "/"
|
|
338
|
-
|
|
337
|
+
# Show landing page for GET "/" — but ONLY in dev mode.
|
|
338
|
+
# Production never shows it, so the framework version, dev-admin link,
|
|
339
|
+
# and gallery never leak to real users. Symmetric with /__dev/* gating.
|
|
340
|
+
return render_landing_page if path == "/" && dev_mode?
|
|
339
341
|
|
|
340
342
|
Tina4::Log.warning("404 Not Found: #{path}")
|
|
341
343
|
body = Tina4::Template.render_error(404, { "path" => path }) rescue "404 Not Found"
|
|
342
344
|
[404, { "content-type" => "text/html" }, [body]]
|
|
343
345
|
end
|
|
344
346
|
|
|
347
|
+
# Honour TINA4_TEMPLATE_ROUTING=off|false|0|no|disabled as an explicit
|
|
348
|
+
# kill switch. Default: enabled. Drop a file in src/templates/pages/ and
|
|
349
|
+
# it serves at the matching URL — the zero-config Tina4 convention.
|
|
350
|
+
def template_auto_routing_enabled?
|
|
351
|
+
val = ENV.fetch("TINA4_TEMPLATE_ROUTING", "on").to_s.strip.downcase
|
|
352
|
+
!%w[off false 0 no disabled].include?(val)
|
|
353
|
+
end
|
|
354
|
+
|
|
345
355
|
def should_show_landing_page?
|
|
346
356
|
# Check if any index template exists in src/templates/
|
|
347
357
|
templates_dir = File.join(@root_dir, "src", "templates")
|
|
@@ -357,19 +367,39 @@ module Tina4
|
|
|
357
367
|
[200, { "content-type" => "text/html" }, [body]]
|
|
358
368
|
end
|
|
359
369
|
|
|
360
|
-
# Resolve a URL path to a template file
|
|
370
|
+
# Resolve a URL path to a template file in src/templates/pages/.
|
|
371
|
+
#
|
|
372
|
+
# Only files inside ``src/templates/pages/`` auto-route from a URL.
|
|
373
|
+
# Anything in ``src/templates/`` outside ``pages/`` (partials, layouts,
|
|
374
|
+
# base.twig, errors, components) is never served standalone — those
|
|
375
|
+
# remain renderable via {% include %} / {% extends %} / explicit render
|
|
376
|
+
# calls but never auto-serve from a URL.
|
|
377
|
+
#
|
|
378
|
+
# Files starting with ``_`` (e.g. pages/_helper.twig) are always skipped
|
|
379
|
+
# even within pages/ — Hugo/Jekyll convention for private partials.
|
|
380
|
+
#
|
|
361
381
|
# Dev mode: checks filesystem every time for live changes.
|
|
362
382
|
# Production: uses a cached lookup built once at startup.
|
|
383
|
+
#
|
|
384
|
+
# The whole feature can be turned off with ``TINA4_TEMPLATE_ROUTING=off``.
|
|
363
385
|
def resolve_template(path)
|
|
386
|
+
return nil unless template_auto_routing_enabled?
|
|
387
|
+
|
|
364
388
|
clean_path = path.sub(%r{^/}, "")
|
|
365
389
|
clean_path = "index" if clean_path.empty?
|
|
390
|
+
|
|
391
|
+
# Skip underscore-prefixed segments — private files even within pages/.
|
|
392
|
+
return nil if clean_path.split("/").any? { |seg| seg.start_with?("_") }
|
|
393
|
+
|
|
366
394
|
is_dev = %w[true 1 yes].include?(ENV.fetch("TINA4_DEBUG", "false").downcase)
|
|
367
395
|
|
|
368
396
|
if is_dev
|
|
369
|
-
|
|
397
|
+
pages_dir = File.join(@root_dir, "src", "templates", "pages")
|
|
370
398
|
%w[.twig .html].each do |ext|
|
|
371
|
-
|
|
372
|
-
|
|
399
|
+
candidate_inside_pages = clean_path + ext
|
|
400
|
+
if File.file?(File.join(pages_dir, candidate_inside_pages))
|
|
401
|
+
return File.join("pages", candidate_inside_pages)
|
|
402
|
+
end
|
|
373
403
|
end
|
|
374
404
|
return nil
|
|
375
405
|
end
|
|
@@ -379,15 +409,22 @@ module Tina4
|
|
|
379
409
|
@template_cache[clean_path]
|
|
380
410
|
end
|
|
381
411
|
|
|
412
|
+
# Scan src/templates/pages/ once and build url_path -> template_file lookup.
|
|
413
|
+
# Only files under pages/ are eligible — partials, layouts, base.twig,
|
|
414
|
+
# errors etc. remain renderable via explicit render calls but never
|
|
415
|
+
# auto-serve from a URL.
|
|
382
416
|
def build_template_cache
|
|
383
417
|
cache = {}
|
|
384
|
-
|
|
385
|
-
return cache unless File.directory?(
|
|
386
|
-
|
|
387
|
-
Dir.glob(File.join(
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
418
|
+
pages_dir = File.join(@root_dir, "src", "templates", "pages")
|
|
419
|
+
return cache unless File.directory?(pages_dir)
|
|
420
|
+
|
|
421
|
+
Dir.glob(File.join(pages_dir, "**", "*.{twig,html}")).each do |f|
|
|
422
|
+
rel_inside_pages = f.sub(pages_dir + File::SEPARATOR, "").tr("\\", "/")
|
|
423
|
+
# Skip private files even within pages/ (e.g. pages/_helper.twig)
|
|
424
|
+
next if rel_inside_pages.split("/").any? { |seg| seg.start_with?("_") }
|
|
425
|
+
url_path = rel_inside_pages.sub(/\.(twig|html)$/, "")
|
|
426
|
+
rel_from_templates = "pages/#{rel_inside_pages}"
|
|
427
|
+
cache[url_path] ||= rel_from_templates
|
|
391
428
|
end
|
|
392
429
|
cache
|
|
393
430
|
end
|
|
@@ -714,10 +751,55 @@ module Tina4
|
|
|
714
751
|
<span style="color:#ffeb3b;">req:#{request_id}</span>
|
|
715
752
|
<span style="color:#90caf9;">#{route_count} routes</span>
|
|
716
753
|
<span style="color:#888;">Ruby #{RUBY_VERSION}</span>
|
|
717
|
-
<a href="#" onclick="
|
|
754
|
+
<a href="#" onclick="window.__tina4ToggleOverlay(event)" style="color:#ef9a9a;margin-left:auto;text-decoration:none;cursor:pointer;">Dashboard ↗</a>
|
|
718
755
|
<span onclick="this.parentElement.style.display='none'" style="cursor:pointer;color:#888;margin-left:8px;">✕</span>
|
|
719
756
|
</div>
|
|
720
757
|
<script>
|
|
758
|
+
// Overlay open/toggle helper + auto-restore. Persist the dev-admin
|
|
759
|
+
// iframe's open/closed state across parent reloads so saving a
|
|
760
|
+
// file doesn't lose the user's dev-admin context. Cross-framework
|
|
761
|
+
// parity with PHP / Python / Node — same localStorage key.
|
|
762
|
+
(function(){
|
|
763
|
+
var STATE_KEY = 'tina4_dev_overlay_open';
|
|
764
|
+
function buildOverlay() {
|
|
765
|
+
var c = document.createElement('div');
|
|
766
|
+
c.id = 'tina4-dev-panel';
|
|
767
|
+
c.style.cssText = 'position:fixed;top:3rem;left:0;right:0;bottom:2rem;z-index:99998;transition:all 0.2s';
|
|
768
|
+
var f = document.createElement('iframe');
|
|
769
|
+
f.src = '/__dev';
|
|
770
|
+
f.style.cssText = 'width:100%;height:100%;border:1px solid #CC342D;border-radius:0.5rem;box-shadow:0 8px 32px rgba(0,0,0,0.5);background:#0f172a';
|
|
771
|
+
c.appendChild(f);
|
|
772
|
+
document.body.appendChild(c);
|
|
773
|
+
return c;
|
|
774
|
+
}
|
|
775
|
+
window.__tina4ToggleOverlay = function(e) {
|
|
776
|
+
if (e) e.preventDefault();
|
|
777
|
+
var p = document.getElementById('tina4-dev-panel');
|
|
778
|
+
if (p) {
|
|
779
|
+
var hide = p.style.display !== 'none';
|
|
780
|
+
p.style.display = hide ? 'none' : 'block';
|
|
781
|
+
try { localStorage.setItem(STATE_KEY, hide ? '0' : '1'); } catch (_) {}
|
|
782
|
+
return;
|
|
783
|
+
}
|
|
784
|
+
buildOverlay();
|
|
785
|
+
try { localStorage.setItem(STATE_KEY, '1'); } catch (_) {}
|
|
786
|
+
};
|
|
787
|
+
function restoreIfOpen() {
|
|
788
|
+
try {
|
|
789
|
+
if (location.pathname.indexOf('/__dev') === 0) return;
|
|
790
|
+
if (localStorage.getItem(STATE_KEY) === '1' && !document.getElementById('tina4-dev-panel')) {
|
|
791
|
+
buildOverlay();
|
|
792
|
+
}
|
|
793
|
+
} catch (_) {}
|
|
794
|
+
}
|
|
795
|
+
if (document.readyState === 'loading') {
|
|
796
|
+
document.addEventListener('DOMContentLoaded', restoreIfOpen);
|
|
797
|
+
} else {
|
|
798
|
+
restoreIfOpen();
|
|
799
|
+
}
|
|
800
|
+
})();
|
|
801
|
+
</script>
|
|
802
|
+
<script>
|
|
721
803
|
function tina4VersionModal(){
|
|
722
804
|
var m=document.getElementById('tina4-ver-modal');
|
|
723
805
|
if(m.style.display==='block'){m.style.display='none';return;}
|
data/lib/tina4/response.rb
CHANGED
|
@@ -151,6 +151,9 @@ module Tina4
|
|
|
151
151
|
self
|
|
152
152
|
end
|
|
153
153
|
|
|
154
|
+
# Render a Frond/Twig template file with data and return self. Tries
|
|
155
|
+
# the user template directory first, falling back to the framework's
|
|
156
|
+
# built-in templates. Sets the response body to the rendered HTML.
|
|
154
157
|
def render(template_path, data = {}, status: 200, template_dir: nil)
|
|
155
158
|
@status_code = status
|
|
156
159
|
@headers["content-type"] = HTML_CONTENT_TYPE
|
data/lib/tina4/session.rb
CHANGED
|
@@ -16,7 +16,7 @@ module Tina4
|
|
|
16
16
|
|
|
17
17
|
def initialize(env, options = {})
|
|
18
18
|
@options = DEFAULT_OPTIONS.merge(options)
|
|
19
|
-
@options[:secret] ||= ENV["
|
|
19
|
+
@options[:secret] ||= ENV["TINA4_SECRET"] || "tina4-default-secret"
|
|
20
20
|
@handler = create_handler
|
|
21
21
|
@id = extract_session_id(env) || SecureRandom.hex(32)
|
|
22
22
|
@data = load_session
|
data/lib/tina4/shutdown.rb
CHANGED
|
@@ -53,6 +53,16 @@ module Tina4
|
|
|
53
53
|
end
|
|
54
54
|
end
|
|
55
55
|
|
|
56
|
+
# Stop background tasks
|
|
57
|
+
if defined?(Tina4::Background)
|
|
58
|
+
begin
|
|
59
|
+
Tina4::Background.stop_all
|
|
60
|
+
Tina4::Log.info("Background tasks stopped")
|
|
61
|
+
rescue => e
|
|
62
|
+
Tina4::Log.error("Error stopping background tasks: #{e.message}")
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
56
66
|
# Close database connections
|
|
57
67
|
if Tina4.database
|
|
58
68
|
begin
|
data/lib/tina4/swagger.rb
CHANGED
|
@@ -19,9 +19,9 @@ module Tina4
|
|
|
19
19
|
{
|
|
20
20
|
"openapi" => "3.0.3",
|
|
21
21
|
"info" => {
|
|
22
|
-
"title" => ENV["
|
|
23
|
-
"version" => ENV["
|
|
24
|
-
"description" => "Auto-generated API documentation"
|
|
22
|
+
"title" => ENV["TINA4_SWAGGER_TITLE"] || ENV["PROJECT_NAME"] || "Tina4 API",
|
|
23
|
+
"version" => ENV["TINA4_SWAGGER_VERSION"] || Tina4::VERSION,
|
|
24
|
+
"description" => ENV["TINA4_SWAGGER_DESCRIPTION"] || "Auto-generated API documentation"
|
|
25
25
|
},
|
|
26
26
|
"servers" => [
|
|
27
27
|
{ "url" => "/" }
|
data/lib/tina4/version.rb
CHANGED
data/lib/tina4/webserver.rb
CHANGED
data/lib/tina4.rb
CHANGED
|
@@ -35,11 +35,14 @@ require_relative "tina4/cors"
|
|
|
35
35
|
require_relative "tina4/rate_limiter"
|
|
36
36
|
require_relative "tina4/health"
|
|
37
37
|
require_relative "tina4/shutdown"
|
|
38
|
+
require_relative "tina4/background"
|
|
38
39
|
require_relative "tina4/localization"
|
|
39
40
|
require_relative "tina4/container"
|
|
40
41
|
require_relative "tina4/queue"
|
|
41
42
|
require_relative "tina4/service_runner"
|
|
42
43
|
require_relative "tina4/events"
|
|
44
|
+
require_relative "tina4/plan"
|
|
45
|
+
require_relative "tina4/project_index"
|
|
43
46
|
require_relative "tina4/dev_admin"
|
|
44
47
|
require_relative "tina4/messenger"
|
|
45
48
|
require_relative "tina4/dev_mailbox"
|
|
@@ -50,6 +53,7 @@ require_relative "tina4/response_cache"
|
|
|
50
53
|
require_relative "tina4/html_element"
|
|
51
54
|
require_relative "tina4/error_overlay"
|
|
52
55
|
require_relative "tina4/test_client"
|
|
56
|
+
require_relative "tina4/docs"
|
|
53
57
|
require_relative "tina4/mcp"
|
|
54
58
|
|
|
55
59
|
module Tina4
|
|
@@ -383,6 +387,16 @@ module Tina4
|
|
|
383
387
|
Tina4::ServiceRunner.register(name, nil, options, &block)
|
|
384
388
|
end
|
|
385
389
|
|
|
390
|
+
# Register a periodic background task.
|
|
391
|
+
# Mirrors Python's tina4_python.core.server.background(fn, interval) and
|
|
392
|
+
# PHP's $app->background($callback, $interval).
|
|
393
|
+
#
|
|
394
|
+
# Tina4.background(interval: 5.0) { drain_queue }
|
|
395
|
+
# Tina4.background(method(:health_check), interval: 30.0)
|
|
396
|
+
def background(callback = nil, interval: 1.0, &block)
|
|
397
|
+
Tina4::Background.register(callback, interval: interval, &block)
|
|
398
|
+
end
|
|
399
|
+
|
|
386
400
|
# DI container shortcuts
|
|
387
401
|
def register(name, instance = nil, &block)
|
|
388
402
|
Tina4::Container.register(name, instance, &block)
|
|
@@ -426,7 +440,7 @@ module Tina4
|
|
|
426
440
|
end
|
|
427
441
|
|
|
428
442
|
def setup_database
|
|
429
|
-
db_url = ENV["
|
|
443
|
+
db_url = ENV["TINA4_DATABASE_URL"]
|
|
430
444
|
if db_url && !db_url.empty?
|
|
431
445
|
begin
|
|
432
446
|
@database = Tina4::Database.new(db_url)
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: tina4ruby
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.
|
|
4
|
+
version: 3.12.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tina4 Team
|
|
@@ -281,6 +281,7 @@ files:
|
|
|
281
281
|
- lib/tina4/api.rb
|
|
282
282
|
- lib/tina4/auth.rb
|
|
283
283
|
- lib/tina4/auto_crud.rb
|
|
284
|
+
- lib/tina4/background.rb
|
|
284
285
|
- lib/tina4/cache.rb
|
|
285
286
|
- lib/tina4/cli.rb
|
|
286
287
|
- lib/tina4/constants.rb
|
|
@@ -294,6 +295,7 @@ files:
|
|
|
294
295
|
- lib/tina4/dev.rb
|
|
295
296
|
- lib/tina4/dev_admin.rb
|
|
296
297
|
- lib/tina4/dev_mailbox.rb
|
|
298
|
+
- lib/tina4/docs.rb
|
|
297
299
|
- lib/tina4/drivers/firebird_driver.rb
|
|
298
300
|
- lib/tina4/drivers/mongodb_driver.rb
|
|
299
301
|
- lib/tina4/drivers/mssql_driver.rb
|
|
@@ -333,11 +335,14 @@ files:
|
|
|
333
335
|
- lib/tina4/middleware.rb
|
|
334
336
|
- lib/tina4/migration.rb
|
|
335
337
|
- lib/tina4/orm.rb
|
|
338
|
+
- lib/tina4/plan.rb
|
|
339
|
+
- lib/tina4/project_index.rb
|
|
336
340
|
- lib/tina4/public/css/tina4.css
|
|
337
341
|
- lib/tina4/public/css/tina4.min.css
|
|
338
342
|
- lib/tina4/public/favicon.ico
|
|
339
343
|
- lib/tina4/public/images/logo.svg
|
|
340
344
|
- lib/tina4/public/images/tina4-logo-icon.webp
|
|
345
|
+
- lib/tina4/public/js/frond.js
|
|
341
346
|
- lib/tina4/public/js/frond.min.js
|
|
342
347
|
- lib/tina4/public/js/tina4-dev-admin.js
|
|
343
348
|
- lib/tina4/public/js/tina4-dev-admin.min.js
|