tina4ruby 0.4.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 (74) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +80 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +768 -0
  5. data/exe/tina4 +4 -0
  6. data/lib/tina4/api.rb +152 -0
  7. data/lib/tina4/auth.rb +139 -0
  8. data/lib/tina4/cli.rb +349 -0
  9. data/lib/tina4/crud.rb +124 -0
  10. data/lib/tina4/database.rb +135 -0
  11. data/lib/tina4/database_result.rb +89 -0
  12. data/lib/tina4/debug.rb +83 -0
  13. data/lib/tina4/dev.rb +15 -0
  14. data/lib/tina4/dev_reload.rb +68 -0
  15. data/lib/tina4/drivers/firebird_driver.rb +94 -0
  16. data/lib/tina4/drivers/mssql_driver.rb +112 -0
  17. data/lib/tina4/drivers/mysql_driver.rb +90 -0
  18. data/lib/tina4/drivers/postgres_driver.rb +99 -0
  19. data/lib/tina4/drivers/sqlite_driver.rb +85 -0
  20. data/lib/tina4/env.rb +55 -0
  21. data/lib/tina4/field_types.rb +84 -0
  22. data/lib/tina4/graphql.rb +837 -0
  23. data/lib/tina4/localization.rb +100 -0
  24. data/lib/tina4/middleware.rb +59 -0
  25. data/lib/tina4/migration.rb +124 -0
  26. data/lib/tina4/orm.rb +168 -0
  27. data/lib/tina4/public/css/tina4.css +2286 -0
  28. data/lib/tina4/public/css/tina4.min.css +2 -0
  29. data/lib/tina4/public/js/tina4.js +134 -0
  30. data/lib/tina4/public/js/tina4helper.js +387 -0
  31. data/lib/tina4/queue.rb +117 -0
  32. data/lib/tina4/queue_backends/kafka_backend.rb +80 -0
  33. data/lib/tina4/queue_backends/lite_backend.rb +79 -0
  34. data/lib/tina4/queue_backends/rabbitmq_backend.rb +73 -0
  35. data/lib/tina4/rack_app.rb +150 -0
  36. data/lib/tina4/request.rb +158 -0
  37. data/lib/tina4/response.rb +172 -0
  38. data/lib/tina4/router.rb +148 -0
  39. data/lib/tina4/scss/tina4css/_alerts.scss +34 -0
  40. data/lib/tina4/scss/tina4css/_badges.scss +22 -0
  41. data/lib/tina4/scss/tina4css/_buttons.scss +69 -0
  42. data/lib/tina4/scss/tina4css/_cards.scss +49 -0
  43. data/lib/tina4/scss/tina4css/_forms.scss +156 -0
  44. data/lib/tina4/scss/tina4css/_grid.scss +81 -0
  45. data/lib/tina4/scss/tina4css/_modals.scss +84 -0
  46. data/lib/tina4/scss/tina4css/_nav.scss +149 -0
  47. data/lib/tina4/scss/tina4css/_reset.scss +94 -0
  48. data/lib/tina4/scss/tina4css/_tables.scss +54 -0
  49. data/lib/tina4/scss/tina4css/_typography.scss +55 -0
  50. data/lib/tina4/scss/tina4css/_utilities.scss +197 -0
  51. data/lib/tina4/scss/tina4css/_variables.scss +117 -0
  52. data/lib/tina4/scss/tina4css/base.scss +1 -0
  53. data/lib/tina4/scss/tina4css/colors.scss +48 -0
  54. data/lib/tina4/scss/tina4css/tina4.scss +17 -0
  55. data/lib/tina4/scss_compiler.rb +131 -0
  56. data/lib/tina4/seeder.rb +529 -0
  57. data/lib/tina4/session.rb +145 -0
  58. data/lib/tina4/session_handlers/file_handler.rb +55 -0
  59. data/lib/tina4/session_handlers/mongo_handler.rb +49 -0
  60. data/lib/tina4/session_handlers/redis_handler.rb +43 -0
  61. data/lib/tina4/swagger.rb +123 -0
  62. data/lib/tina4/template.rb +478 -0
  63. data/lib/tina4/templates/base.twig +26 -0
  64. data/lib/tina4/templates/errors/403.twig +22 -0
  65. data/lib/tina4/templates/errors/404.twig +22 -0
  66. data/lib/tina4/templates/errors/500.twig +22 -0
  67. data/lib/tina4/testing.rb +213 -0
  68. data/lib/tina4/version.rb +5 -0
  69. data/lib/tina4/webserver.rb +101 -0
  70. data/lib/tina4/websocket.rb +167 -0
  71. data/lib/tina4/wsdl.rb +164 -0
  72. data/lib/tina4.rb +259 -0
  73. data/lib/tina4ruby.rb +4 -0
  74. metadata +324 -0
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+ require "json"
3
+ require "fileutils"
4
+
5
+ module Tina4
6
+ module SessionHandlers
7
+ class FileHandler
8
+ def initialize(options = {})
9
+ @dir = options[:dir] || File.join(Dir.pwd, "sessions")
10
+ @ttl = options[:ttl] || 86400
11
+ FileUtils.mkdir_p(@dir)
12
+ end
13
+
14
+ def read(session_id)
15
+ path = session_path(session_id)
16
+ return nil unless File.exist?(path)
17
+
18
+ # Check expiry
19
+ if File.mtime(path) + @ttl < Time.now
20
+ File.delete(path)
21
+ return nil
22
+ end
23
+
24
+ data = File.read(path)
25
+ JSON.parse(data)
26
+ rescue JSON::ParserError
27
+ nil
28
+ end
29
+
30
+ def write(session_id, data)
31
+ path = session_path(session_id)
32
+ File.write(path, JSON.generate(data))
33
+ end
34
+
35
+ def destroy(session_id)
36
+ path = session_path(session_id)
37
+ File.delete(path) if File.exist?(path)
38
+ end
39
+
40
+ def cleanup
41
+ return unless Dir.exist?(@dir)
42
+ Dir.glob(File.join(@dir, "sess_*")).each do |file|
43
+ File.delete(file) if File.mtime(file) + @ttl < Time.now
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ def session_path(session_id)
50
+ safe_id = session_id.gsub(/[^a-zA-Z0-9_-]/, "")
51
+ File.join(@dir, "sess_#{safe_id}.json")
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+ require "json"
3
+
4
+ module Tina4
5
+ module SessionHandlers
6
+ class MongoHandler
7
+ def initialize(options = {})
8
+ require "mongo"
9
+ @ttl = options[:ttl] || 86400
10
+ client = Mongo::Client.new(
11
+ options[:uri] || "mongodb://localhost:27017",
12
+ database: options[:database] || "tina4_sessions"
13
+ )
14
+ @collection = client[options[:collection] || "sessions"]
15
+ # Ensure TTL index
16
+ @collection.indexes.create_one(
17
+ { updated_at: 1 },
18
+ expire_after_seconds: @ttl
19
+ )
20
+ rescue LoadError
21
+ raise "MongoDB session handler requires the 'mongo' gem. Install with: gem install mongo"
22
+ rescue Mongo::Error => e
23
+ Tina4::Debug.error("MongoDB session setup failed: #{e.message}")
24
+ end
25
+
26
+ def read(session_id)
27
+ doc = @collection.find(_id: session_id).first
28
+ return nil unless doc
29
+ doc["data"]
30
+ end
31
+
32
+ def write(session_id, data)
33
+ @collection.update_one(
34
+ { _id: session_id },
35
+ { "$set" => { data: data, updated_at: Time.now } },
36
+ upsert: true
37
+ )
38
+ end
39
+
40
+ def destroy(session_id)
41
+ @collection.delete_one(_id: session_id)
42
+ end
43
+
44
+ def cleanup
45
+ # MongoDB TTL index handles cleanup
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+ require "json"
3
+
4
+ module Tina4
5
+ module SessionHandlers
6
+ class RedisHandler
7
+ def initialize(options = {})
8
+ require "redis"
9
+ @prefix = options[:prefix] || "tina4:session:"
10
+ @ttl = options[:ttl] || 86400
11
+ @redis = Redis.new(
12
+ host: options[:host] || "localhost",
13
+ port: options[:port] || 6379,
14
+ db: options[:db] || 0,
15
+ password: options[:password]
16
+ )
17
+ rescue LoadError
18
+ raise "Redis session handler requires the 'redis' gem. Install with: gem install redis"
19
+ end
20
+
21
+ def read(session_id)
22
+ data = @redis.get("#{@prefix}#{session_id}")
23
+ return nil unless data
24
+ JSON.parse(data)
25
+ rescue JSON::ParserError
26
+ nil
27
+ end
28
+
29
+ def write(session_id, data)
30
+ key = "#{@prefix}#{session_id}"
31
+ @redis.setex(key, @ttl, JSON.generate(data))
32
+ end
33
+
34
+ def destroy(session_id)
35
+ @redis.del("#{@prefix}#{session_id}")
36
+ end
37
+
38
+ def cleanup
39
+ # Redis handles TTL automatically
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,123 @@
1
+ # frozen_string_literal: true
2
+ require "json"
3
+
4
+ module Tina4
5
+ module Swagger
6
+ class << self
7
+ def generate
8
+ spec = base_spec
9
+ Tina4::Router.routes.each do |route|
10
+ add_route_to_spec(spec, route)
11
+ end
12
+ spec
13
+ end
14
+
15
+ private
16
+
17
+ def base_spec
18
+ {
19
+ "openapi" => "3.0.3",
20
+ "info" => {
21
+ "title" => ENV["PROJECT_NAME"] || "Tina4 Ruby API",
22
+ "version" => ENV["VERSION"] || Tina4::VERSION,
23
+ "description" => "Auto-generated API documentation"
24
+ },
25
+ "servers" => [
26
+ { "url" => "/" }
27
+ ],
28
+ "paths" => {},
29
+ "components" => {
30
+ "securitySchemes" => {
31
+ "bearerAuth" => {
32
+ "type" => "http",
33
+ "scheme" => "bearer",
34
+ "bearerFormat" => "JWT"
35
+ }
36
+ }
37
+ }
38
+ }
39
+ end
40
+
41
+ def add_route_to_spec(spec, route)
42
+ path = convert_path(route.path)
43
+ method = route.method.downcase
44
+ return if method == "any"
45
+
46
+ spec["paths"][path] ||= {}
47
+ operation = {
48
+ "summary" => route.swagger_meta[:summary] || "#{method.upcase} #{route.path}",
49
+ "description" => route.swagger_meta[:description] || "",
50
+ "tags" => route.swagger_meta[:tags] || [extract_tag(route.path)],
51
+ "parameters" => build_parameters(route),
52
+ "responses" => route.swagger_meta[:responses] || default_responses
53
+ }
54
+
55
+ if route.auth_handler
56
+ operation["security"] = [{ "bearerAuth" => [] }]
57
+ end
58
+
59
+ if %w[post put patch].include?(method) && route.swagger_meta[:request_body]
60
+ operation["requestBody"] = route.swagger_meta[:request_body]
61
+ elsif %w[post put patch].include?(method)
62
+ operation["requestBody"] = default_request_body
63
+ end
64
+
65
+ spec["paths"][path][method] = operation
66
+ end
67
+
68
+ def convert_path(path)
69
+ # Convert {id:int} to {id}
70
+ path.gsub(/\{(\w+)(?::\w+)?\}/, '{\1}')
71
+ end
72
+
73
+ def extract_tag(path)
74
+ parts = path.split("/").reject(&:empty?)
75
+ parts.first || "default"
76
+ end
77
+
78
+ def build_parameters(route)
79
+ params = []
80
+ route.param_names.each do |param|
81
+ params << {
82
+ "name" => param[:name].to_s,
83
+ "in" => "path",
84
+ "required" => true,
85
+ "schema" => param_schema(param[:type])
86
+ }
87
+ end
88
+ params
89
+ end
90
+
91
+ def param_schema(type)
92
+ case type
93
+ when "int", "integer"
94
+ { "type" => "integer" }
95
+ when "float", "number"
96
+ { "type" => "number" }
97
+ else
98
+ { "type" => "string" }
99
+ end
100
+ end
101
+
102
+ def default_responses
103
+ {
104
+ "200" => { "description" => "Successful response" },
105
+ "400" => { "description" => "Bad request" },
106
+ "401" => { "description" => "Unauthorized" },
107
+ "404" => { "description" => "Not found" },
108
+ "500" => { "description" => "Internal server error" }
109
+ }
110
+ end
111
+
112
+ def default_request_body
113
+ {
114
+ "content" => {
115
+ "application/json" => {
116
+ "schema" => { "type" => "object" }
117
+ }
118
+ }
119
+ }
120
+ end
121
+ end
122
+ end
123
+ end