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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +80 -0
- data/LICENSE.txt +21 -0
- data/README.md +768 -0
- data/exe/tina4 +4 -0
- data/lib/tina4/api.rb +152 -0
- data/lib/tina4/auth.rb +139 -0
- data/lib/tina4/cli.rb +349 -0
- data/lib/tina4/crud.rb +124 -0
- data/lib/tina4/database.rb +135 -0
- data/lib/tina4/database_result.rb +89 -0
- data/lib/tina4/debug.rb +83 -0
- data/lib/tina4/dev.rb +15 -0
- data/lib/tina4/dev_reload.rb +68 -0
- data/lib/tina4/drivers/firebird_driver.rb +94 -0
- data/lib/tina4/drivers/mssql_driver.rb +112 -0
- data/lib/tina4/drivers/mysql_driver.rb +90 -0
- data/lib/tina4/drivers/postgres_driver.rb +99 -0
- data/lib/tina4/drivers/sqlite_driver.rb +85 -0
- data/lib/tina4/env.rb +55 -0
- data/lib/tina4/field_types.rb +84 -0
- data/lib/tina4/graphql.rb +837 -0
- data/lib/tina4/localization.rb +100 -0
- data/lib/tina4/middleware.rb +59 -0
- data/lib/tina4/migration.rb +124 -0
- data/lib/tina4/orm.rb +168 -0
- data/lib/tina4/public/css/tina4.css +2286 -0
- data/lib/tina4/public/css/tina4.min.css +2 -0
- data/lib/tina4/public/js/tina4.js +134 -0
- data/lib/tina4/public/js/tina4helper.js +387 -0
- data/lib/tina4/queue.rb +117 -0
- data/lib/tina4/queue_backends/kafka_backend.rb +80 -0
- data/lib/tina4/queue_backends/lite_backend.rb +79 -0
- data/lib/tina4/queue_backends/rabbitmq_backend.rb +73 -0
- data/lib/tina4/rack_app.rb +150 -0
- data/lib/tina4/request.rb +158 -0
- data/lib/tina4/response.rb +172 -0
- data/lib/tina4/router.rb +148 -0
- data/lib/tina4/scss/tina4css/_alerts.scss +34 -0
- data/lib/tina4/scss/tina4css/_badges.scss +22 -0
- data/lib/tina4/scss/tina4css/_buttons.scss +69 -0
- data/lib/tina4/scss/tina4css/_cards.scss +49 -0
- data/lib/tina4/scss/tina4css/_forms.scss +156 -0
- data/lib/tina4/scss/tina4css/_grid.scss +81 -0
- data/lib/tina4/scss/tina4css/_modals.scss +84 -0
- data/lib/tina4/scss/tina4css/_nav.scss +149 -0
- data/lib/tina4/scss/tina4css/_reset.scss +94 -0
- data/lib/tina4/scss/tina4css/_tables.scss +54 -0
- data/lib/tina4/scss/tina4css/_typography.scss +55 -0
- data/lib/tina4/scss/tina4css/_utilities.scss +197 -0
- data/lib/tina4/scss/tina4css/_variables.scss +117 -0
- data/lib/tina4/scss/tina4css/base.scss +1 -0
- data/lib/tina4/scss/tina4css/colors.scss +48 -0
- data/lib/tina4/scss/tina4css/tina4.scss +17 -0
- data/lib/tina4/scss_compiler.rb +131 -0
- data/lib/tina4/seeder.rb +529 -0
- data/lib/tina4/session.rb +145 -0
- data/lib/tina4/session_handlers/file_handler.rb +55 -0
- data/lib/tina4/session_handlers/mongo_handler.rb +49 -0
- data/lib/tina4/session_handlers/redis_handler.rb +43 -0
- data/lib/tina4/swagger.rb +123 -0
- data/lib/tina4/template.rb +478 -0
- data/lib/tina4/templates/base.twig +26 -0
- data/lib/tina4/templates/errors/403.twig +22 -0
- data/lib/tina4/templates/errors/404.twig +22 -0
- data/lib/tina4/templates/errors/500.twig +22 -0
- data/lib/tina4/testing.rb +213 -0
- data/lib/tina4/version.rb +5 -0
- data/lib/tina4/webserver.rb +101 -0
- data/lib/tina4/websocket.rb +167 -0
- data/lib/tina4/wsdl.rb +164 -0
- data/lib/tina4.rb +259 -0
- data/lib/tina4ruby.rb +4 -0
- 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
|