tina4ruby 3.11.13 → 3.11.15
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 +80 -80
- data/LICENSE.txt +21 -21
- data/README.md +137 -137
- data/exe/tina4ruby +5 -5
- data/lib/tina4/ai.rb +696 -696
- data/lib/tina4/api.rb +189 -189
- data/lib/tina4/auth.rb +305 -305
- data/lib/tina4/auto_crud.rb +244 -244
- data/lib/tina4/cache.rb +154 -154
- data/lib/tina4/cli.rb +1449 -1449
- data/lib/tina4/constants.rb +46 -46
- data/lib/tina4/container.rb +74 -74
- data/lib/tina4/cors.rb +74 -74
- data/lib/tina4/crud.rb +692 -692
- data/lib/tina4/database/sqlite3_adapter.rb +165 -165
- data/lib/tina4/database.rb +625 -625
- data/lib/tina4/database_result.rb +208 -208
- data/lib/tina4/debug.rb +8 -8
- data/lib/tina4/dev.rb +14 -14
- data/lib/tina4/dev_admin.rb +935 -935
- data/lib/tina4/dev_mailbox.rb +191 -191
- data/lib/tina4/drivers/firebird_driver.rb +124 -110
- data/lib/tina4/drivers/mongodb_driver.rb +561 -561
- data/lib/tina4/drivers/mssql_driver.rb +112 -112
- data/lib/tina4/drivers/mysql_driver.rb +90 -90
- data/lib/tina4/drivers/odbc_driver.rb +191 -191
- data/lib/tina4/drivers/postgres_driver.rb +116 -106
- data/lib/tina4/drivers/sqlite_driver.rb +122 -122
- data/lib/tina4/env.rb +95 -95
- data/lib/tina4/error_overlay.rb +252 -252
- data/lib/tina4/events.rb +109 -109
- data/lib/tina4/field_types.rb +154 -154
- data/lib/tina4/frond.rb +2025 -2025
- data/lib/tina4/gallery/auth/meta.json +1 -1
- data/lib/tina4/gallery/auth/src/routes/api/gallery_auth.rb +114 -114
- data/lib/tina4/gallery/database/meta.json +1 -1
- data/lib/tina4/gallery/database/src/routes/api/gallery_db.rb +43 -43
- data/lib/tina4/gallery/error-overlay/meta.json +1 -1
- data/lib/tina4/gallery/error-overlay/src/routes/api/gallery_crash.rb +17 -17
- data/lib/tina4/gallery/orm/meta.json +1 -1
- data/lib/tina4/gallery/orm/src/routes/api/gallery_products.rb +16 -16
- data/lib/tina4/gallery/queue/meta.json +1 -1
- data/lib/tina4/gallery/queue/src/routes/api/gallery_queue.rb +325 -325
- data/lib/tina4/gallery/rest-api/meta.json +1 -1
- data/lib/tina4/gallery/rest-api/src/routes/api/gallery_hello.rb +14 -14
- data/lib/tina4/gallery/templates/meta.json +1 -1
- data/lib/tina4/gallery/templates/src/routes/gallery_page.rb +12 -12
- data/lib/tina4/gallery/templates/src/templates/gallery_page.twig +257 -257
- data/lib/tina4/graphql.rb +966 -966
- data/lib/tina4/health.rb +39 -39
- data/lib/tina4/html_element.rb +170 -170
- data/lib/tina4/job.rb +80 -80
- data/lib/tina4/localization.rb +168 -168
- data/lib/tina4/log.rb +203 -203
- data/lib/tina4/mcp.rb +696 -696
- data/lib/tina4/messenger.rb +587 -587
- data/lib/tina4/metrics.rb +793 -793
- data/lib/tina4/middleware.rb +445 -445
- data/lib/tina4/migration.rb +451 -451
- data/lib/tina4/orm.rb +790 -790
- data/lib/tina4/public/css/tina4.css +2463 -2463
- data/lib/tina4/public/css/tina4.min.css +1 -1
- data/lib/tina4/public/images/logo.svg +5 -5
- data/lib/tina4/public/js/frond.min.js +2 -2
- data/lib/tina4/public/js/tina4-dev-admin.js +565 -565
- data/lib/tina4/public/js/tina4-dev-admin.min.js +480 -480
- data/lib/tina4/public/js/tina4.min.js +92 -92
- data/lib/tina4/public/js/tina4js.min.js +48 -48
- data/lib/tina4/public/swagger/index.html +90 -90
- data/lib/tina4/public/swagger/oauth2-redirect.html +63 -63
- data/lib/tina4/query_builder.rb +380 -380
- data/lib/tina4/queue.rb +366 -366
- data/lib/tina4/queue_backends/kafka_backend.rb +80 -80
- data/lib/tina4/queue_backends/lite_backend.rb +298 -298
- data/lib/tina4/queue_backends/mongo_backend.rb +126 -126
- data/lib/tina4/queue_backends/rabbitmq_backend.rb +73 -73
- data/lib/tina4/rack_app.rb +817 -817
- data/lib/tina4/rate_limiter.rb +130 -130
- data/lib/tina4/request.rb +268 -255
- data/lib/tina4/response.rb +346 -346
- data/lib/tina4/response_cache.rb +551 -551
- data/lib/tina4/router.rb +406 -406
- data/lib/tina4/scss/tina4css/_alerts.scss +34 -34
- data/lib/tina4/scss/tina4css/_badges.scss +22 -22
- data/lib/tina4/scss/tina4css/_buttons.scss +69 -69
- data/lib/tina4/scss/tina4css/_cards.scss +49 -49
- data/lib/tina4/scss/tina4css/_forms.scss +156 -156
- data/lib/tina4/scss/tina4css/_grid.scss +81 -81
- data/lib/tina4/scss/tina4css/_modals.scss +84 -84
- data/lib/tina4/scss/tina4css/_nav.scss +149 -149
- data/lib/tina4/scss/tina4css/_reset.scss +94 -94
- data/lib/tina4/scss/tina4css/_tables.scss +54 -54
- data/lib/tina4/scss/tina4css/_typography.scss +55 -55
- data/lib/tina4/scss/tina4css/_utilities.scss +197 -197
- data/lib/tina4/scss/tina4css/_variables.scss +117 -117
- data/lib/tina4/scss/tina4css/base.scss +1 -1
- data/lib/tina4/scss/tina4css/colors.scss +48 -48
- data/lib/tina4/scss/tina4css/tina4.scss +17 -17
- data/lib/tina4/scss_compiler.rb +178 -178
- data/lib/tina4/seeder.rb +567 -567
- data/lib/tina4/service_runner.rb +303 -303
- data/lib/tina4/session.rb +297 -297
- data/lib/tina4/session_handlers/database_handler.rb +72 -72
- data/lib/tina4/session_handlers/file_handler.rb +67 -67
- data/lib/tina4/session_handlers/mongo_handler.rb +49 -49
- data/lib/tina4/session_handlers/redis_handler.rb +43 -43
- data/lib/tina4/session_handlers/valkey_handler.rb +43 -43
- data/lib/tina4/shutdown.rb +84 -84
- data/lib/tina4/sql_translation.rb +158 -158
- data/lib/tina4/swagger.rb +124 -124
- data/lib/tina4/template.rb +894 -894
- data/lib/tina4/templates/base.twig +26 -26
- data/lib/tina4/templates/errors/302.twig +14 -14
- data/lib/tina4/templates/errors/401.twig +9 -9
- data/lib/tina4/templates/errors/403.twig +29 -29
- data/lib/tina4/templates/errors/404.twig +29 -29
- data/lib/tina4/templates/errors/500.twig +38 -38
- data/lib/tina4/templates/errors/502.twig +9 -9
- data/lib/tina4/templates/errors/503.twig +12 -12
- data/lib/tina4/templates/errors/base.twig +37 -37
- data/lib/tina4/test_client.rb +159 -159
- data/lib/tina4/testing.rb +340 -340
- data/lib/tina4/validator.rb +174 -174
- data/lib/tina4/version.rb +1 -1
- data/lib/tina4/webserver.rb +312 -312
- data/lib/tina4/websocket.rb +343 -343
- data/lib/tina4/websocket_backplane.rb +190 -190
- data/lib/tina4/wsdl.rb +564 -564
- data/lib/tina4.rb +458 -458
- data/lib/tina4ruby.rb +4 -4
- metadata +3 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name": "Auth", "description": "JWT login form with token display", "try_url": "/gallery/auth"}
|
|
1
|
+
{"name": "Auth", "description": "JWT login form with token display", "try_url": "/gallery/auth"}
|
|
@@ -1,114 +1,114 @@
|
|
|
1
|
-
# Gallery: Auth — JWT login with a visual demo page.
|
|
2
|
-
|
|
3
|
-
Tina4::Router.get("/gallery/auth") do |request, response|
|
|
4
|
-
html = <<~HTML
|
|
5
|
-
<!DOCTYPE html>
|
|
6
|
-
<html lang="en">
|
|
7
|
-
<head>
|
|
8
|
-
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
|
9
|
-
<title>Auth Demo</title><link rel="stylesheet" href="/css/tina4.min.css">
|
|
10
|
-
</head>
|
|
11
|
-
<body class="bg-dark text-light">
|
|
12
|
-
<div class="container mt-5" style="max-width:600px;">
|
|
13
|
-
<h2 class="mb-4">JWT Authentication Demo</h2>
|
|
14
|
-
<div class="card mb-4">
|
|
15
|
-
<div class="card-header bg-primary text-white">Login</div>
|
|
16
|
-
<div class="card-body">
|
|
17
|
-
<div class="form-group mb-3">
|
|
18
|
-
<label class="form-label">Username</label>
|
|
19
|
-
<input type="text" id="username" class="form-control" placeholder="admin" value="admin">
|
|
20
|
-
</div>
|
|
21
|
-
<div class="form-group mb-3">
|
|
22
|
-
<label class="form-label">Password</label>
|
|
23
|
-
<input type="password" id="password" class="form-control" placeholder="secret" value="secret">
|
|
24
|
-
</div>
|
|
25
|
-
<button class="btn btn-primary" onclick="doLogin()">Login</button>
|
|
26
|
-
</div>
|
|
27
|
-
</div>
|
|
28
|
-
<div id="result" style="display:none;">
|
|
29
|
-
<div class="card mb-3">
|
|
30
|
-
<div class="card-header bg-success text-white">Token Received</div>
|
|
31
|
-
<div class="card-body">
|
|
32
|
-
<pre id="token" style="word-break:break-all;white-space:pre-wrap;color:#4ade80;background:#1e293b;padding:1rem;border-radius:0.5rem;"></pre>
|
|
33
|
-
</div>
|
|
34
|
-
</div>
|
|
35
|
-
<div class="card mb-3">
|
|
36
|
-
<div class="card-header">Token Payload (decoded)</div>
|
|
37
|
-
<div class="card-body">
|
|
38
|
-
<pre id="payload" style="color:#38bdf8;background:#1e293b;padding:1rem;border-radius:0.5rem;"></pre>
|
|
39
|
-
</div>
|
|
40
|
-
</div>
|
|
41
|
-
<button class="btn btn-outline-info" onclick="verifyToken()">Verify Token</button>
|
|
42
|
-
<span id="verify-result" class="ms-2"></span>
|
|
43
|
-
</div>
|
|
44
|
-
<div class="card bg-dark mt-4" style="border:1px solid #334155;">
|
|
45
|
-
<div class="card-body">
|
|
46
|
-
<h6 style="color:#e2e8f0;">How it works</h6>
|
|
47
|
-
<pre style="background:#0f172a;color:#4ade80;padding:1rem;border-radius:0.5rem;font-size:0.8rem;"><code>token = Tina4::Auth.create_token({ username: "admin" })
|
|
48
|
-
payload = Tina4::Auth.get_payload(token)
|
|
49
|
-
result = Tina4::Auth.validate_token(token)</code></pre>
|
|
50
|
-
</div>
|
|
51
|
-
</div>
|
|
52
|
-
</div>
|
|
53
|
-
<script>
|
|
54
|
-
var currentToken = '';
|
|
55
|
-
function doLogin() {
|
|
56
|
-
fetch('/api/gallery/auth/login', {
|
|
57
|
-
method: 'POST',
|
|
58
|
-
headers: {'Content-Type': 'application/json'},
|
|
59
|
-
body: JSON.stringify({
|
|
60
|
-
username: document.getElementById('username').value,
|
|
61
|
-
password: document.getElementById('password').value
|
|
62
|
-
})
|
|
63
|
-
}).then(r => r.json()).then(d => {
|
|
64
|
-
if (d.token) {
|
|
65
|
-
currentToken = d.token;
|
|
66
|
-
document.getElementById('token').textContent = d.token;
|
|
67
|
-
try {
|
|
68
|
-
var parts = d.token.split('.');
|
|
69
|
-
var payload = JSON.parse(atob(parts[1]));
|
|
70
|
-
document.getElementById('payload').textContent = JSON.stringify(payload, null, 2);
|
|
71
|
-
} catch(e) {
|
|
72
|
-
document.getElementById('payload').textContent = 'Could not decode';
|
|
73
|
-
}
|
|
74
|
-
document.getElementById('result').style.display = 'block';
|
|
75
|
-
document.getElementById('verify-result').textContent = '';
|
|
76
|
-
} else {
|
|
77
|
-
alert(d.error || 'Login failed');
|
|
78
|
-
}
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
function verifyToken() {
|
|
82
|
-
fetch('/api/gallery/auth/verify?token=' + encodeURIComponent(currentToken))
|
|
83
|
-
.then(r => r.json()).then(d => {
|
|
84
|
-
var el = document.getElementById('verify-result');
|
|
85
|
-
if (d.valid) {
|
|
86
|
-
el.innerHTML = '<span class="badge bg-success">Valid</span>';
|
|
87
|
-
} else {
|
|
88
|
-
el.innerHTML = '<span class="badge bg-danger">Invalid</span>';
|
|
89
|
-
}
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
</script>
|
|
93
|
-
</body></html>
|
|
94
|
-
HTML
|
|
95
|
-
response.html(html)
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
Tina4::Router.post("/api/gallery/auth/login") do |request, response|
|
|
99
|
-
body = request.body || {}
|
|
100
|
-
username = body["username"].to_s
|
|
101
|
-
password = body["password"].to_s
|
|
102
|
-
if !username.empty? && !password.empty?
|
|
103
|
-
token = Tina4::Auth.create_token({ username: username, role: "user" })
|
|
104
|
-
response.json({ token: token, message: "Welcome #{username}!" })
|
|
105
|
-
else
|
|
106
|
-
response.json({ error: "Username and password required" }, 401)
|
|
107
|
-
end
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
Tina4::Router.get("/api/gallery/auth/verify") do |request, response|
|
|
111
|
-
token = request.params["token"].to_s
|
|
112
|
-
result = Tina4::Auth.validate_token(token)
|
|
113
|
-
response.json({ valid: result[:valid] })
|
|
114
|
-
end
|
|
1
|
+
# Gallery: Auth — JWT login with a visual demo page.
|
|
2
|
+
|
|
3
|
+
Tina4::Router.get("/gallery/auth") do |request, response|
|
|
4
|
+
html = <<~HTML
|
|
5
|
+
<!DOCTYPE html>
|
|
6
|
+
<html lang="en">
|
|
7
|
+
<head>
|
|
8
|
+
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
|
9
|
+
<title>Auth Demo</title><link rel="stylesheet" href="/css/tina4.min.css">
|
|
10
|
+
</head>
|
|
11
|
+
<body class="bg-dark text-light">
|
|
12
|
+
<div class="container mt-5" style="max-width:600px;">
|
|
13
|
+
<h2 class="mb-4">JWT Authentication Demo</h2>
|
|
14
|
+
<div class="card mb-4">
|
|
15
|
+
<div class="card-header bg-primary text-white">Login</div>
|
|
16
|
+
<div class="card-body">
|
|
17
|
+
<div class="form-group mb-3">
|
|
18
|
+
<label class="form-label">Username</label>
|
|
19
|
+
<input type="text" id="username" class="form-control" placeholder="admin" value="admin">
|
|
20
|
+
</div>
|
|
21
|
+
<div class="form-group mb-3">
|
|
22
|
+
<label class="form-label">Password</label>
|
|
23
|
+
<input type="password" id="password" class="form-control" placeholder="secret" value="secret">
|
|
24
|
+
</div>
|
|
25
|
+
<button class="btn btn-primary" onclick="doLogin()">Login</button>
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
<div id="result" style="display:none;">
|
|
29
|
+
<div class="card mb-3">
|
|
30
|
+
<div class="card-header bg-success text-white">Token Received</div>
|
|
31
|
+
<div class="card-body">
|
|
32
|
+
<pre id="token" style="word-break:break-all;white-space:pre-wrap;color:#4ade80;background:#1e293b;padding:1rem;border-radius:0.5rem;"></pre>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
<div class="card mb-3">
|
|
36
|
+
<div class="card-header">Token Payload (decoded)</div>
|
|
37
|
+
<div class="card-body">
|
|
38
|
+
<pre id="payload" style="color:#38bdf8;background:#1e293b;padding:1rem;border-radius:0.5rem;"></pre>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
<button class="btn btn-outline-info" onclick="verifyToken()">Verify Token</button>
|
|
42
|
+
<span id="verify-result" class="ms-2"></span>
|
|
43
|
+
</div>
|
|
44
|
+
<div class="card bg-dark mt-4" style="border:1px solid #334155;">
|
|
45
|
+
<div class="card-body">
|
|
46
|
+
<h6 style="color:#e2e8f0;">How it works</h6>
|
|
47
|
+
<pre style="background:#0f172a;color:#4ade80;padding:1rem;border-radius:0.5rem;font-size:0.8rem;"><code>token = Tina4::Auth.create_token({ username: "admin" })
|
|
48
|
+
payload = Tina4::Auth.get_payload(token)
|
|
49
|
+
result = Tina4::Auth.validate_token(token)</code></pre>
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
<script>
|
|
54
|
+
var currentToken = '';
|
|
55
|
+
function doLogin() {
|
|
56
|
+
fetch('/api/gallery/auth/login', {
|
|
57
|
+
method: 'POST',
|
|
58
|
+
headers: {'Content-Type': 'application/json'},
|
|
59
|
+
body: JSON.stringify({
|
|
60
|
+
username: document.getElementById('username').value,
|
|
61
|
+
password: document.getElementById('password').value
|
|
62
|
+
})
|
|
63
|
+
}).then(r => r.json()).then(d => {
|
|
64
|
+
if (d.token) {
|
|
65
|
+
currentToken = d.token;
|
|
66
|
+
document.getElementById('token').textContent = d.token;
|
|
67
|
+
try {
|
|
68
|
+
var parts = d.token.split('.');
|
|
69
|
+
var payload = JSON.parse(atob(parts[1]));
|
|
70
|
+
document.getElementById('payload').textContent = JSON.stringify(payload, null, 2);
|
|
71
|
+
} catch(e) {
|
|
72
|
+
document.getElementById('payload').textContent = 'Could not decode';
|
|
73
|
+
}
|
|
74
|
+
document.getElementById('result').style.display = 'block';
|
|
75
|
+
document.getElementById('verify-result').textContent = '';
|
|
76
|
+
} else {
|
|
77
|
+
alert(d.error || 'Login failed');
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
function verifyToken() {
|
|
82
|
+
fetch('/api/gallery/auth/verify?token=' + encodeURIComponent(currentToken))
|
|
83
|
+
.then(r => r.json()).then(d => {
|
|
84
|
+
var el = document.getElementById('verify-result');
|
|
85
|
+
if (d.valid) {
|
|
86
|
+
el.innerHTML = '<span class="badge bg-success">Valid</span>';
|
|
87
|
+
} else {
|
|
88
|
+
el.innerHTML = '<span class="badge bg-danger">Invalid</span>';
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
</script>
|
|
93
|
+
</body></html>
|
|
94
|
+
HTML
|
|
95
|
+
response.html(html)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
Tina4::Router.post("/api/gallery/auth/login") do |request, response|
|
|
99
|
+
body = request.body || {}
|
|
100
|
+
username = body["username"].to_s
|
|
101
|
+
password = body["password"].to_s
|
|
102
|
+
if !username.empty? && !password.empty?
|
|
103
|
+
token = Tina4::Auth.create_token({ username: username, role: "user" })
|
|
104
|
+
response.json({ token: token, message: "Welcome #{username}!" })
|
|
105
|
+
else
|
|
106
|
+
response.json({ error: "Username and password required" }, 401)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
Tina4::Router.get("/api/gallery/auth/verify") do |request, response|
|
|
111
|
+
token = request.params["token"].to_s
|
|
112
|
+
result = Tina4::Auth.validate_token(token)
|
|
113
|
+
response.json({ valid: result[:valid] })
|
|
114
|
+
end
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name": "Database", "description": "Raw SQL queries with the Database class", "try_url": "/api/gallery/db/tables"}
|
|
1
|
+
{"name": "Database", "description": "Raw SQL queries with the Database class", "try_url": "/api/gallery/db/tables"}
|
|
@@ -1,43 +1,43 @@
|
|
|
1
|
-
# Gallery: Database — raw SQL query demo.
|
|
2
|
-
|
|
3
|
-
Tina4::Router.get("/api/gallery/db/tables") do |request, response|
|
|
4
|
-
begin
|
|
5
|
-
db = Tina4::Database.new("sqlite://data/gallery.db")
|
|
6
|
-
db.execute(<<~SQL)
|
|
7
|
-
CREATE TABLE IF NOT EXISTS gallery_notes (
|
|
8
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
9
|
-
title TEXT NOT NULL,
|
|
10
|
-
body TEXT,
|
|
11
|
-
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
12
|
-
)
|
|
13
|
-
SQL
|
|
14
|
-
tables = db.tables
|
|
15
|
-
response.json({ tables: tables, engine: "sqlite" })
|
|
16
|
-
rescue => e
|
|
17
|
-
response.json({ error: e.message }, 500)
|
|
18
|
-
end
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
Tina4::Router.post("/api/gallery/db/notes") do |request, response|
|
|
22
|
-
begin
|
|
23
|
-
db = Tina4::Database.new("sqlite://data/gallery.db")
|
|
24
|
-
body = request.body || {}
|
|
25
|
-
db.insert("gallery_notes", {
|
|
26
|
-
title: body["title"] || "Untitled",
|
|
27
|
-
body: body["body"] || ""
|
|
28
|
-
})
|
|
29
|
-
response.json({ created: true }, 201)
|
|
30
|
-
rescue => e
|
|
31
|
-
response.json({ error: e.message }, 500)
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
Tina4::Router.get("/api/gallery/db/notes") do |request, response|
|
|
36
|
-
begin
|
|
37
|
-
db = Tina4::Database.new("sqlite://data/gallery.db")
|
|
38
|
-
result = db.fetch("SELECT * FROM gallery_notes ORDER BY id DESC", [], limit: 50)
|
|
39
|
-
response.json(result.to_a)
|
|
40
|
-
rescue => e
|
|
41
|
-
response.json({ error: e.message }, 500)
|
|
42
|
-
end
|
|
43
|
-
end
|
|
1
|
+
# Gallery: Database — raw SQL query demo.
|
|
2
|
+
|
|
3
|
+
Tina4::Router.get("/api/gallery/db/tables") do |request, response|
|
|
4
|
+
begin
|
|
5
|
+
db = Tina4::Database.new("sqlite://data/gallery.db")
|
|
6
|
+
db.execute(<<~SQL)
|
|
7
|
+
CREATE TABLE IF NOT EXISTS gallery_notes (
|
|
8
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
9
|
+
title TEXT NOT NULL,
|
|
10
|
+
body TEXT,
|
|
11
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
12
|
+
)
|
|
13
|
+
SQL
|
|
14
|
+
tables = db.tables
|
|
15
|
+
response.json({ tables: tables, engine: "sqlite" })
|
|
16
|
+
rescue => e
|
|
17
|
+
response.json({ error: e.message }, 500)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
Tina4::Router.post("/api/gallery/db/notes") do |request, response|
|
|
22
|
+
begin
|
|
23
|
+
db = Tina4::Database.new("sqlite://data/gallery.db")
|
|
24
|
+
body = request.body || {}
|
|
25
|
+
db.insert("gallery_notes", {
|
|
26
|
+
title: body["title"] || "Untitled",
|
|
27
|
+
body: body["body"] || ""
|
|
28
|
+
})
|
|
29
|
+
response.json({ created: true }, 201)
|
|
30
|
+
rescue => e
|
|
31
|
+
response.json({ error: e.message }, 500)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
Tina4::Router.get("/api/gallery/db/notes") do |request, response|
|
|
36
|
+
begin
|
|
37
|
+
db = Tina4::Database.new("sqlite://data/gallery.db")
|
|
38
|
+
result = db.fetch("SELECT * FROM gallery_notes ORDER BY id DESC", [], limit: 50)
|
|
39
|
+
response.json(result.to_a)
|
|
40
|
+
rescue => e
|
|
41
|
+
response.json({ error: e.message }, 500)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name": "Error Overlay", "description": "See the rich debug error page with stack trace and source code", "try_url": "/api/gallery/crash"}
|
|
1
|
+
{"name": "Error Overlay", "description": "See the rich debug error page with stack trace and source code", "try_url": "/api/gallery/crash"}
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
# Gallery: Error Overlay — deliberately crash to demo the debug overlay.
|
|
2
|
-
#
|
|
3
|
-
# This route deliberately raises an error to showcase the error overlay.
|
|
4
|
-
#
|
|
5
|
-
# In debug mode (TINA4_DEBUG=true), you will see:
|
|
6
|
-
# - Exception type and message
|
|
7
|
-
# - Stack trace with syntax-highlighted source code
|
|
8
|
-
# - The exact line that caused the error (highlighted)
|
|
9
|
-
# - Request details (method, path, headers)
|
|
10
|
-
# - Environment info (framework version, Ruby version)
|
|
11
|
-
|
|
12
|
-
Tina4::Router.get("/api/gallery/crash") do |request, response|
|
|
13
|
-
# Simulate a realistic error — accessing a missing key
|
|
14
|
-
user = { name: "Alice", email: "alice@example.com" }
|
|
15
|
-
role = user.fetch(:role) # KeyError: key not found: :role — this line will be highlighted in the overlay
|
|
16
|
-
response.json({ role: role })
|
|
17
|
-
end
|
|
1
|
+
# Gallery: Error Overlay — deliberately crash to demo the debug overlay.
|
|
2
|
+
#
|
|
3
|
+
# This route deliberately raises an error to showcase the error overlay.
|
|
4
|
+
#
|
|
5
|
+
# In debug mode (TINA4_DEBUG=true), you will see:
|
|
6
|
+
# - Exception type and message
|
|
7
|
+
# - Stack trace with syntax-highlighted source code
|
|
8
|
+
# - The exact line that caused the error (highlighted)
|
|
9
|
+
# - Request details (method, path, headers)
|
|
10
|
+
# - Environment info (framework version, Ruby version)
|
|
11
|
+
|
|
12
|
+
Tina4::Router.get("/api/gallery/crash") do |request, response|
|
|
13
|
+
# Simulate a realistic error — accessing a missing key
|
|
14
|
+
user = { name: "Alice", email: "alice@example.com" }
|
|
15
|
+
role = user.fetch(:role) # KeyError: key not found: :role — this line will be highlighted in the overlay
|
|
16
|
+
response.json({ role: role })
|
|
17
|
+
end
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name": "ORM", "description": "Product model with CRUD endpoints", "try_url": "/api/gallery/products"}
|
|
1
|
+
{"name": "ORM", "description": "Product model with CRUD endpoints", "try_url": "/api/gallery/products"}
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
# Gallery: ORM — Product CRUD endpoints.
|
|
2
|
-
|
|
3
|
-
Tina4::Router.get("/api/gallery/products") do |request, response|
|
|
4
|
-
response.json({
|
|
5
|
-
products: [
|
|
6
|
-
{ id: 1, name: "Widget", price: 9.99 },
|
|
7
|
-
{ id: 2, name: "Gadget", price: 24.99 }
|
|
8
|
-
],
|
|
9
|
-
note: "Connect a database and deploy the ORM model for live data"
|
|
10
|
-
})
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
Tina4::Router.post("/api/gallery/products") do |request, response|
|
|
14
|
-
data = request.body || {}
|
|
15
|
-
response.json({ created: data, id: 3 }, 201)
|
|
16
|
-
end
|
|
1
|
+
# Gallery: ORM — Product CRUD endpoints.
|
|
2
|
+
|
|
3
|
+
Tina4::Router.get("/api/gallery/products") do |request, response|
|
|
4
|
+
response.json({
|
|
5
|
+
products: [
|
|
6
|
+
{ id: 1, name: "Widget", price: 9.99 },
|
|
7
|
+
{ id: 2, name: "Gadget", price: 24.99 }
|
|
8
|
+
],
|
|
9
|
+
note: "Connect a database and deploy the ORM model for live data"
|
|
10
|
+
})
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
Tina4::Router.post("/api/gallery/products") do |request, response|
|
|
14
|
+
data = request.body || {}
|
|
15
|
+
response.json({ created: data, id: 3 }, 201)
|
|
16
|
+
end
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name": "Queue", "description": "Background job producer and consumer", "try_url": "/gallery/queue"}
|
|
1
|
+
{"name": "Queue", "description": "Background job producer and consumer", "try_url": "/gallery/queue"}
|