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.
Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1 -1
  3. data/README.md +360 -559
  4. data/exe/{tina4 → tina4ruby} +1 -0
  5. data/lib/tina4/ai.rb +312 -0
  6. data/lib/tina4/auth.rb +44 -3
  7. data/lib/tina4/auto_crud.rb +163 -0
  8. data/lib/tina4/cli.rb +242 -77
  9. data/lib/tina4/constants.rb +46 -0
  10. data/lib/tina4/cors.rb +74 -0
  11. data/lib/tina4/database/sqlite3_adapter.rb +139 -0
  12. data/lib/tina4/database.rb +43 -7
  13. data/lib/tina4/debug.rb +4 -79
  14. data/lib/tina4/dev_admin.rb +1162 -0
  15. data/lib/tina4/dev_mailbox.rb +191 -0
  16. data/lib/tina4/dev_reload.rb +9 -9
  17. data/lib/tina4/drivers/firebird_driver.rb +19 -3
  18. data/lib/tina4/drivers/mssql_driver.rb +3 -3
  19. data/lib/tina4/drivers/mysql_driver.rb +4 -4
  20. data/lib/tina4/drivers/postgres_driver.rb +9 -2
  21. data/lib/tina4/drivers/sqlite_driver.rb +1 -1
  22. data/lib/tina4/env.rb +42 -2
  23. data/lib/tina4/error_overlay.rb +252 -0
  24. data/lib/tina4/events.rb +90 -0
  25. data/lib/tina4/field_types.rb +4 -0
  26. data/lib/tina4/frond.rb +1336 -0
  27. data/lib/tina4/gallery/auth/meta.json +1 -0
  28. data/lib/tina4/gallery/auth/src/routes/api/gallery_auth.rb +114 -0
  29. data/lib/tina4/gallery/database/meta.json +1 -0
  30. data/lib/tina4/gallery/database/src/routes/api/gallery_db.rb +43 -0
  31. data/lib/tina4/gallery/error-overlay/meta.json +1 -0
  32. data/lib/tina4/gallery/error-overlay/src/routes/api/gallery_crash.rb +17 -0
  33. data/lib/tina4/gallery/orm/meta.json +1 -0
  34. data/lib/tina4/gallery/orm/src/routes/api/gallery_products.rb +16 -0
  35. data/lib/tina4/gallery/queue/meta.json +1 -0
  36. data/lib/tina4/gallery/queue/src/routes/api/gallery_queue.rb +27 -0
  37. data/lib/tina4/gallery/rest-api/meta.json +1 -0
  38. data/lib/tina4/gallery/rest-api/src/routes/api/gallery_hello.rb +14 -0
  39. data/lib/tina4/gallery/templates/meta.json +1 -0
  40. data/lib/tina4/gallery/templates/src/routes/gallery_page.rb +12 -0
  41. data/lib/tina4/gallery/templates/src/templates/gallery_page.twig +257 -0
  42. data/lib/tina4/health.rb +39 -0
  43. data/lib/tina4/html_element.rb +148 -0
  44. data/lib/tina4/localization.rb +2 -2
  45. data/lib/tina4/log.rb +203 -0
  46. data/lib/tina4/messenger.rb +484 -0
  47. data/lib/tina4/migration.rb +132 -29
  48. data/lib/tina4/orm.rb +337 -31
  49. data/lib/tina4/public/css/tina4.css +178 -1
  50. data/lib/tina4/public/css/tina4.min.css +1 -2
  51. data/lib/tina4/public/favicon.ico +0 -0
  52. data/lib/tina4/public/images/logo.svg +5 -0
  53. data/lib/tina4/public/images/tina4-logo-icon.webp +0 -0
  54. data/lib/tina4/public/js/frond.min.js +420 -0
  55. data/lib/tina4/public/js/tina4-dev-admin.min.js +367 -0
  56. data/lib/tina4/public/js/tina4.min.js +93 -0
  57. data/lib/tina4/public/swagger/index.html +90 -0
  58. data/lib/tina4/public/swagger/oauth2-redirect.html +63 -0
  59. data/lib/tina4/queue.rb +40 -4
  60. data/lib/tina4/queue_backends/lite_backend.rb +88 -0
  61. data/lib/tina4/rack_app.rb +314 -23
  62. data/lib/tina4/rate_limiter.rb +123 -0
  63. data/lib/tina4/request.rb +61 -15
  64. data/lib/tina4/response.rb +54 -24
  65. data/lib/tina4/response_cache.rb +134 -0
  66. data/lib/tina4/router.rb +90 -15
  67. data/lib/tina4/scss_compiler.rb +2 -2
  68. data/lib/tina4/seeder.rb +56 -61
  69. data/lib/tina4/service_runner.rb +303 -0
  70. data/lib/tina4/session.rb +85 -0
  71. data/lib/tina4/session_handlers/mongo_handler.rb +1 -1
  72. data/lib/tina4/session_handlers/valkey_handler.rb +43 -0
  73. data/lib/tina4/shutdown.rb +84 -0
  74. data/lib/tina4/sql_translation.rb +295 -0
  75. data/lib/tina4/template.rb +36 -6
  76. data/lib/tina4/templates/base.twig +2 -2
  77. data/lib/tina4/templates/errors/302.twig +14 -0
  78. data/lib/tina4/templates/errors/401.twig +9 -0
  79. data/lib/tina4/templates/errors/403.twig +22 -15
  80. data/lib/tina4/templates/errors/404.twig +22 -15
  81. data/lib/tina4/templates/errors/500.twig +31 -15
  82. data/lib/tina4/templates/errors/502.twig +9 -0
  83. data/lib/tina4/templates/errors/503.twig +12 -0
  84. data/lib/tina4/templates/errors/base.twig +37 -0
  85. data/lib/tina4/version.rb +1 -1
  86. data/lib/tina4/webserver.rb +28 -18
  87. data/lib/tina4.rb +57 -21
  88. metadata +51 -19
  89. data/lib/tina4/public/js/tina4.js +0 -134
  90. data/lib/tina4/public/js/tina4helper.js +0 -387
@@ -3,41 +3,54 @@ require "fileutils"
3
3
 
4
4
  module Tina4
5
5
  class Migration
6
- TRACKING_TABLE = "tina4_migrations"
6
+ TRACKING_TABLE = "tina4_migration"
7
+
8
+ attr_reader :db, :migrations_dir
7
9
 
8
10
  def initialize(db, migrations_dir: nil)
9
11
  @db = db
10
- @migrations_dir = migrations_dir || File.join(Dir.pwd, "migrations")
12
+ @migrations_dir = migrations_dir || File.join(Dir.pwd, "src", "migrations")
11
13
  ensure_tracking_table
12
14
  end
13
15
 
14
- def run
16
+ # Run all pending migrations
17
+ def migrate
15
18
  pending = pending_migrations
16
19
  if pending.empty?
17
- Tina4::Debug.info("No pending migrations")
20
+ Tina4::Log.info("No pending migrations")
18
21
  return []
19
22
  end
20
23
 
24
+ batch = next_batch_number
21
25
  results = []
22
26
  pending.each do |file|
23
- result = run_migration(file)
27
+ result = run_migration(file, batch)
24
28
  results << result
29
+ # Stop on failure
30
+ break if result[:status] == "failed"
25
31
  end
26
32
  results
27
33
  end
28
34
 
35
+ alias run migrate
36
+
37
+ # Rollback last batch (or N steps)
29
38
  def rollback(steps = 1)
30
- completed = completed_migrations.last(steps)
31
- completed.reverse.each do |name|
32
- down_file = File.join(@migrations_dir, name.sub(".sql", ".down.sql"))
33
- if File.exist?(down_file)
34
- execute_sql_file(down_file)
35
- remove_migration_record(name)
36
- Tina4::Debug.info("Rolled back: #{name}")
37
- else
38
- Tina4::Debug.warning("No rollback file for: #{name}")
39
- end
39
+ completed = completed_migrations_with_batch
40
+ return [] if completed.empty?
41
+
42
+ # Get the last N unique batches
43
+ batches = completed.map { |m| m[:batch] }.uniq.sort.reverse
44
+ batches_to_rollback = batches.first(steps)
45
+
46
+ results = []
47
+ completed.select { |m| batches_to_rollback.include?(m[:batch]) }
48
+ .sort_by { |m| -m[:id] }
49
+ .each do |migration|
50
+ result = rollback_migration(migration[:migration_name])
51
+ results << result
40
52
  end
53
+ results
41
54
  end
42
55
 
43
56
  def status
@@ -47,17 +60,30 @@ module Tina4
47
60
  }
48
61
  end
49
62
 
63
+ # Create a new migration file
50
64
  def create(name)
51
65
  FileUtils.mkdir_p(@migrations_dir)
52
66
  timestamp = Time.now.strftime("%Y%m%d%H%M%S")
53
- filename = "#{timestamp}_#{name.gsub(/\s+/, '_')}.sql"
67
+ filename = "#{timestamp}_#{name.gsub(/\s+/, '_')}.rb"
54
68
  filepath = File.join(@migrations_dir, filename)
55
- File.write(filepath, "-- Migration: #{name}\n-- Created: #{Time.now}\n\n")
56
69
 
57
- down_filepath = filepath.sub(".sql", ".down.sql")
58
- File.write(down_filepath, "-- Rollback: #{name}\n-- Created: #{Time.now}\n\n")
70
+ File.write(filepath, <<~RUBY)
71
+ # frozen_string_literal: true
72
+ # Migration: #{name}
73
+ # Created: #{Time.now}
74
+
75
+ class #{classify(name)} < Tina4::MigrationBase
76
+ def up(db)
77
+ # db.exec("CREATE TABLE ...")
78
+ end
59
79
 
60
- Tina4::Debug.info("Created migration: #{filename}")
80
+ def down(db)
81
+ # db.exec("DROP TABLE IF EXISTS ...")
82
+ end
83
+ end
84
+ RUBY
85
+
86
+ Tina4::Log.info("Created migration: #{filename}")
61
87
  filepath
62
88
  end
63
89
 
@@ -69,10 +95,11 @@ module Tina4
69
95
  CREATE TABLE #{TRACKING_TABLE} (
70
96
  id INTEGER PRIMARY KEY,
71
97
  migration_name VARCHAR(255) NOT NULL,
98
+ batch INTEGER NOT NULL DEFAULT 1,
72
99
  executed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
73
100
  )
74
101
  SQL
75
- Tina4::Debug.info("Created migrations tracking table")
102
+ Tina4::Log.info("Created migrations tracking table")
76
103
  end
77
104
  end
78
105
 
@@ -81,29 +108,79 @@ module Tina4
81
108
  result.map { |r| r[:migration_name] }
82
109
  end
83
110
 
111
+ def completed_migrations_with_batch
112
+ result = @db.fetch("SELECT id, migration_name, batch FROM #{TRACKING_TABLE} ORDER BY id")
113
+ result.map { |r| { id: r[:id], migration_name: r[:migration_name], batch: r[:batch] } }
114
+ end
115
+
116
+ def next_batch_number
117
+ result = @db.fetch_one("SELECT MAX(batch) as max_batch FROM #{TRACKING_TABLE}")
118
+ (result && result[:max_batch] ? result[:max_batch].to_i : 0) + 1
119
+ end
120
+
84
121
  def pending_migrations
85
122
  return [] unless Dir.exist?(@migrations_dir)
86
123
 
87
124
  completed = completed_migrations
88
- Dir.glob(File.join(@migrations_dir, "*.sql"))
125
+ # Support both .rb and .sql migration files
126
+ Dir.glob(File.join(@migrations_dir, "*.{rb,sql}"))
89
127
  .reject { |f| f.end_with?(".down.sql") }
90
128
  .sort
91
129
  .reject { |f| completed.include?(File.basename(f)) }
92
130
  end
93
131
 
94
- def run_migration(file)
132
+ def run_migration(file, batch)
95
133
  name = File.basename(file)
96
- Tina4::Debug.info("Running migration: #{name}")
134
+ Tina4::Log.info("Running migration: #{name}")
97
135
  begin
98
- execute_sql_file(file)
99
- record_migration(name)
136
+ if file.end_with?(".rb")
137
+ execute_ruby_migration(file, :up)
138
+ else
139
+ execute_sql_file(file)
140
+ end
141
+ record_migration(name, batch)
100
142
  { name: name, status: "success" }
101
143
  rescue => e
102
- Tina4::Debug.error("Migration failed: #{name} - #{e.message}")
144
+ Tina4::Log.error("Migration failed: #{name} - #{e.message}")
103
145
  { name: name, status: "failed", error: e.message }
104
146
  end
105
147
  end
106
148
 
149
+ def rollback_migration(name)
150
+ Tina4::Log.info("Rolling back: #{name}")
151
+ begin
152
+ file = File.join(@migrations_dir, name)
153
+ if name.end_with?(".rb") && File.exist?(file)
154
+ execute_ruby_migration(file, :down)
155
+ elsif name.end_with?(".sql")
156
+ down_file = File.join(@migrations_dir, name.sub(".sql", ".down.sql"))
157
+ if File.exist?(down_file)
158
+ execute_sql_file(down_file)
159
+ else
160
+ Tina4::Log.warning("No rollback file for: #{name}")
161
+ end
162
+ end
163
+ remove_migration_record(name)
164
+ { name: name, status: "rolled_back" }
165
+ rescue => e
166
+ Tina4::Log.error("Rollback failed: #{name} - #{e.message}")
167
+ { name: name, status: "failed", error: e.message }
168
+ end
169
+ end
170
+
171
+ def execute_ruby_migration(file, direction)
172
+ # Load the migration class
173
+ content = File.read(file)
174
+ # Evaluate in a clean binding
175
+ eval(content, TOPLEVEL_BINDING, file)
176
+
177
+ # Find the migration class (last class defined that inherits from MigrationBase)
178
+ class_name = extract_class_name(content)
179
+ klass = Object.const_get(class_name)
180
+ migration = klass.new
181
+ migration.__send__(direction, @db)
182
+ end
183
+
107
184
  def execute_sql_file(file)
108
185
  sql = File.read(file)
109
186
  statements = sql.split(";").map(&:strip).reject(&:empty?)
@@ -113,12 +190,38 @@ module Tina4
113
190
  end
114
191
  end
115
192
 
116
- def record_migration(name)
117
- @db.insert(TRACKING_TABLE, { migration_name: name })
193
+ def record_migration(name, batch)
194
+ @db.insert(TRACKING_TABLE, { migration_name: name, batch: batch })
118
195
  end
119
196
 
120
197
  def remove_migration_record(name)
121
198
  @db.delete(TRACKING_TABLE, { migration_name: name })
122
199
  end
200
+
201
+ def classify(name)
202
+ name.gsub(/[^a-zA-Z0-9_]/, "_")
203
+ .split("_")
204
+ .map(&:capitalize)
205
+ .join
206
+ end
207
+
208
+ def extract_class_name(content)
209
+ if content =~ /class\s+(\w+)\s*<\s*Tina4::MigrationBase/
210
+ $1
211
+ else
212
+ raise "No migration class found inheriting from Tina4::MigrationBase"
213
+ end
214
+ end
215
+ end
216
+
217
+ # Base class for Ruby migrations
218
+ class MigrationBase
219
+ def up(db)
220
+ raise NotImplementedError, "Implement #up in your migration"
221
+ end
222
+
223
+ def down(db)
224
+ raise NotImplementedError, "Implement #down in your migration"
225
+ end
123
226
  end
124
227
  end