tina4 0.3.0 → 0.5.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/version.rb +1 -1
- data/lib/tina4ruby.rb +4 -0
- metadata +14 -258
- data/CHANGELOG.md +0 -73
- data/LICENSE.txt +0 -21
- data/README.md +0 -662
- data/exe/tina4 +0 -4
- data/lib/tina4/api.rb +0 -152
- data/lib/tina4/auth.rb +0 -139
- data/lib/tina4/cli.rb +0 -243
- data/lib/tina4/crud.rb +0 -124
- data/lib/tina4/database.rb +0 -135
- data/lib/tina4/database_result.rb +0 -89
- data/lib/tina4/debug.rb +0 -83
- data/lib/tina4/dev_reload.rb +0 -68
- data/lib/tina4/drivers/firebird_driver.rb +0 -94
- data/lib/tina4/drivers/mssql_driver.rb +0 -112
- data/lib/tina4/drivers/mysql_driver.rb +0 -90
- data/lib/tina4/drivers/postgres_driver.rb +0 -99
- data/lib/tina4/drivers/sqlite_driver.rb +0 -85
- data/lib/tina4/env.rb +0 -55
- data/lib/tina4/field_types.rb +0 -84
- data/lib/tina4/graphql.rb +0 -837
- data/lib/tina4/localization.rb +0 -100
- data/lib/tina4/middleware.rb +0 -59
- data/lib/tina4/migration.rb +0 -124
- data/lib/tina4/orm.rb +0 -168
- data/lib/tina4/queue.rb +0 -117
- data/lib/tina4/queue_backends/kafka_backend.rb +0 -80
- data/lib/tina4/queue_backends/lite_backend.rb +0 -79
- data/lib/tina4/queue_backends/rabbitmq_backend.rb +0 -73
- data/lib/tina4/rack_app.rb +0 -150
- data/lib/tina4/request.rb +0 -158
- data/lib/tina4/response.rb +0 -172
- data/lib/tina4/router.rb +0 -142
- data/lib/tina4/scss_compiler.rb +0 -131
- data/lib/tina4/session.rb +0 -145
- data/lib/tina4/session_handlers/file_handler.rb +0 -55
- data/lib/tina4/session_handlers/mongo_handler.rb +0 -49
- data/lib/tina4/session_handlers/redis_handler.rb +0 -43
- data/lib/tina4/swagger.rb +0 -123
- data/lib/tina4/template.rb +0 -478
- data/lib/tina4/templates/base.twig +0 -25
- data/lib/tina4/templates/errors/403.twig +0 -22
- data/lib/tina4/templates/errors/404.twig +0 -22
- data/lib/tina4/templates/errors/500.twig +0 -22
- data/lib/tina4/testing.rb +0 -213
- data/lib/tina4/webserver.rb +0 -101
- data/lib/tina4/websocket.rb +0 -167
- data/lib/tina4/wsdl.rb +0 -164
- data/lib/tina4.rb +0 -234
data/lib/tina4/router.rb
DELETED
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Tina4
|
|
4
|
-
class Route
|
|
5
|
-
attr_reader :method, :path, :handler, :auth_handler, :swagger_meta, :path_regex, :param_names
|
|
6
|
-
|
|
7
|
-
def initialize(method, path, handler, auth_handler: nil, swagger_meta: {})
|
|
8
|
-
@method = method.to_s.upcase.freeze
|
|
9
|
-
@path = normalize_path(path).freeze
|
|
10
|
-
@handler = handler
|
|
11
|
-
@auth_handler = auth_handler
|
|
12
|
-
@swagger_meta = swagger_meta
|
|
13
|
-
@param_names = []
|
|
14
|
-
@path_regex = compile_pattern(@path)
|
|
15
|
-
@param_names.freeze
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
# Returns params hash if matched, false otherwise
|
|
19
|
-
def match_path(request_path)
|
|
20
|
-
match = @path_regex.match(request_path)
|
|
21
|
-
return false unless match
|
|
22
|
-
|
|
23
|
-
if @param_names.empty?
|
|
24
|
-
# Static route — no params to extract
|
|
25
|
-
{}
|
|
26
|
-
else
|
|
27
|
-
params = {}
|
|
28
|
-
@param_names.each_with_index do |param_def, i|
|
|
29
|
-
raw_value = match[i + 1]
|
|
30
|
-
params[param_def[:name]] = cast_param(raw_value, param_def[:type])
|
|
31
|
-
end
|
|
32
|
-
params
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
private
|
|
37
|
-
|
|
38
|
-
def normalize_path(path)
|
|
39
|
-
p = path.to_s.gsub("\\", "/")
|
|
40
|
-
p = "/#{p}" unless p.start_with?("/")
|
|
41
|
-
p = p.chomp("/") unless p == "/"
|
|
42
|
-
p
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
def compile_pattern(path)
|
|
46
|
-
return Regexp.new("\\A/\\z") if path == "/"
|
|
47
|
-
|
|
48
|
-
parts = path.split("/").reject(&:empty?)
|
|
49
|
-
regex_parts = parts.map do |part|
|
|
50
|
-
if part =~ /\A\{(\w+)(?::(\w+))?\}\z/
|
|
51
|
-
name = Regexp.last_match(1)
|
|
52
|
-
type = Regexp.last_match(2) || "string"
|
|
53
|
-
@param_names << { name: name.to_sym, type: type }
|
|
54
|
-
case type
|
|
55
|
-
when "int", "integer"
|
|
56
|
-
'(\d+)'
|
|
57
|
-
when "float", "number"
|
|
58
|
-
'([\d.]+)'
|
|
59
|
-
when "path"
|
|
60
|
-
'(.+)'
|
|
61
|
-
else
|
|
62
|
-
'([^/]+)'
|
|
63
|
-
end
|
|
64
|
-
else
|
|
65
|
-
Regexp.escape(part)
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
Regexp.new("\\A/#{regex_parts.join("/")}\\z")
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
def cast_param(value, type)
|
|
72
|
-
case type
|
|
73
|
-
when "int", "integer"
|
|
74
|
-
value.to_i
|
|
75
|
-
when "float", "number"
|
|
76
|
-
value.to_f
|
|
77
|
-
else
|
|
78
|
-
value
|
|
79
|
-
end
|
|
80
|
-
end
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
module Router
|
|
84
|
-
class << self
|
|
85
|
-
def routes
|
|
86
|
-
@routes ||= []
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
# Routes indexed by HTTP method for O(1) method lookup
|
|
90
|
-
def method_index
|
|
91
|
-
@method_index ||= Hash.new { |h, k| h[k] = [] }
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
def add_route(method, path, handler, auth_handler: nil, swagger_meta: {})
|
|
95
|
-
route = Route.new(method, path, handler, auth_handler: auth_handler, swagger_meta: swagger_meta)
|
|
96
|
-
routes << route
|
|
97
|
-
method_index[route.method] << route
|
|
98
|
-
Tina4::Debug.debug("Route registered: #{method.upcase} #{path}")
|
|
99
|
-
route
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
def find_route(path, method)
|
|
103
|
-
normalized_method = method.upcase
|
|
104
|
-
# Normalize path once (not per-route)
|
|
105
|
-
normalized_path = path.gsub("\\", "/")
|
|
106
|
-
normalized_path = "/#{normalized_path}" unless normalized_path.start_with?("/")
|
|
107
|
-
normalized_path = normalized_path.chomp("/") unless normalized_path == "/"
|
|
108
|
-
|
|
109
|
-
# Only scan routes matching this HTTP method
|
|
110
|
-
candidates = method_index[normalized_method]
|
|
111
|
-
candidates.each do |route|
|
|
112
|
-
params = route.match_path(normalized_path)
|
|
113
|
-
return [route, params] if params
|
|
114
|
-
end
|
|
115
|
-
nil
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
def clear!
|
|
119
|
-
@routes = []
|
|
120
|
-
@method_index = Hash.new { |h, k| h[k] = [] }
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
def group(prefix, auth_handler: nil, &block)
|
|
124
|
-
GroupContext.new(prefix, auth_handler).instance_eval(&block)
|
|
125
|
-
end
|
|
126
|
-
end
|
|
127
|
-
|
|
128
|
-
class GroupContext
|
|
129
|
-
def initialize(prefix, auth_handler = nil)
|
|
130
|
-
@prefix = prefix.chomp("/")
|
|
131
|
-
@auth_handler = auth_handler
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
%w[get post put patch delete any].each do |m|
|
|
135
|
-
define_method(m) do |path, swagger_meta: {}, &handler|
|
|
136
|
-
full_path = "#{@prefix}#{path}"
|
|
137
|
-
Tina4::Router.add_route(m, full_path, handler, auth_handler: @auth_handler, swagger_meta: swagger_meta)
|
|
138
|
-
end
|
|
139
|
-
end
|
|
140
|
-
end
|
|
141
|
-
end
|
|
142
|
-
end
|
data/lib/tina4/scss_compiler.rb
DELETED
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
require "fileutils"
|
|
3
|
-
|
|
4
|
-
module Tina4
|
|
5
|
-
module ScssCompiler
|
|
6
|
-
SCSS_DIRS = %w[src/scss scss src/styles styles].freeze
|
|
7
|
-
CSS_OUTPUT = "public/css"
|
|
8
|
-
|
|
9
|
-
class << self
|
|
10
|
-
def compile_all(root_dir = Dir.pwd)
|
|
11
|
-
output_dir = File.join(root_dir, CSS_OUTPUT)
|
|
12
|
-
FileUtils.mkdir_p(output_dir)
|
|
13
|
-
|
|
14
|
-
SCSS_DIRS.each do |dir|
|
|
15
|
-
scss_dir = File.join(root_dir, dir)
|
|
16
|
-
next unless Dir.exist?(scss_dir)
|
|
17
|
-
|
|
18
|
-
Dir.glob(File.join(scss_dir, "**/*.scss")).each do |scss_file|
|
|
19
|
-
next if File.basename(scss_file).start_with?("_") # Skip partials
|
|
20
|
-
compile_file(scss_file, output_dir, scss_dir)
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def compile_file(scss_file, output_dir, base_dir)
|
|
26
|
-
relative = scss_file.sub(base_dir, "").sub(/\.scss$/, ".css")
|
|
27
|
-
css_file = File.join(output_dir, relative)
|
|
28
|
-
FileUtils.mkdir_p(File.dirname(css_file))
|
|
29
|
-
|
|
30
|
-
scss_content = File.read(scss_file)
|
|
31
|
-
css_content = compile_scss(scss_content, File.dirname(scss_file))
|
|
32
|
-
File.write(css_file, css_content)
|
|
33
|
-
|
|
34
|
-
Tina4::Debug.debug("Compiled SCSS: #{scss_file} -> #{css_file}")
|
|
35
|
-
rescue => e
|
|
36
|
-
Tina4::Debug.error("SCSS compilation failed: #{scss_file} - #{e.message}")
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
def compile_scss(content, base_dir)
|
|
40
|
-
# Try sassc gem first
|
|
41
|
-
begin
|
|
42
|
-
require "sassc"
|
|
43
|
-
return SassC::Engine.new(content, style: :expanded, load_paths: [base_dir]).render
|
|
44
|
-
rescue LoadError
|
|
45
|
-
# Fall through to basic compiler
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
# Basic SCSS to CSS conversion (handles common patterns)
|
|
49
|
-
basic_compile(content, base_dir)
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
private
|
|
53
|
-
|
|
54
|
-
def basic_compile(content, base_dir)
|
|
55
|
-
# Handle @import
|
|
56
|
-
content = process_imports(content, base_dir)
|
|
57
|
-
|
|
58
|
-
# Handle variables
|
|
59
|
-
variables = {}
|
|
60
|
-
content = content.gsub(/\$([a-zA-Z_][\w-]*)\s*:\s*(.+?);/) do
|
|
61
|
-
variables[Regexp.last_match(1)] = Regexp.last_match(2).strip
|
|
62
|
-
""
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
# Replace variable references
|
|
66
|
-
variables.each do |name, value|
|
|
67
|
-
content = content.gsub("$#{name}", value)
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
# Handle nesting (basic single-level)
|
|
71
|
-
content = flatten_nesting(content)
|
|
72
|
-
|
|
73
|
-
# Handle & parent selector
|
|
74
|
-
content = content.gsub(/&/, "")
|
|
75
|
-
|
|
76
|
-
content
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
def process_imports(content, base_dir)
|
|
80
|
-
content.gsub(/@import\s+["'](.+?)["']\s*;/) do
|
|
81
|
-
import_path = Regexp.last_match(1)
|
|
82
|
-
candidates = [
|
|
83
|
-
File.join(base_dir, "#{import_path}.scss"),
|
|
84
|
-
File.join(base_dir, "_#{import_path}.scss"),
|
|
85
|
-
File.join(base_dir, import_path)
|
|
86
|
-
]
|
|
87
|
-
found = candidates.find { |c| File.exist?(c) }
|
|
88
|
-
if found
|
|
89
|
-
imported = File.read(found)
|
|
90
|
-
process_imports(imported, File.dirname(found))
|
|
91
|
-
else
|
|
92
|
-
"/* import not found: #{import_path} */"
|
|
93
|
-
end
|
|
94
|
-
end
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
def flatten_nesting(content)
|
|
98
|
-
# Very basic nesting flattener - handles single level
|
|
99
|
-
result = ""
|
|
100
|
-
content.scan(/([^{]+)\{([^{}]*(?:\{[^{}]*\}[^{}]*)*)\}/m) do |selector, body|
|
|
101
|
-
selector = selector.strip
|
|
102
|
-
# Check for nested rules
|
|
103
|
-
if body =~ /\{/
|
|
104
|
-
# Has nested content
|
|
105
|
-
props = ""
|
|
106
|
-
nested = ""
|
|
107
|
-
body.scan(/([^{;]+(?:\{[^}]*\}|;))/m) do |part_arr|
|
|
108
|
-
part = part_arr[0].strip
|
|
109
|
-
if part.include?("{")
|
|
110
|
-
# Nested rule
|
|
111
|
-
if part =~ /\A(.+?)\s*\{(.*?)\}\s*\z/m
|
|
112
|
-
nested_sel = Regexp.last_match(1).strip
|
|
113
|
-
nested_body = Regexp.last_match(2).strip
|
|
114
|
-
full_sel = "#{selector} #{nested_sel}"
|
|
115
|
-
nested += "#{full_sel} { #{nested_body} }\n"
|
|
116
|
-
end
|
|
117
|
-
else
|
|
118
|
-
props += " #{part}\n" unless part.empty?
|
|
119
|
-
end
|
|
120
|
-
end
|
|
121
|
-
result += "#{selector} {\n#{props}}\n" unless props.strip.empty?
|
|
122
|
-
result += nested
|
|
123
|
-
else
|
|
124
|
-
result += "#{selector} {\n#{body}\n}\n"
|
|
125
|
-
end
|
|
126
|
-
end
|
|
127
|
-
result.empty? ? content : result
|
|
128
|
-
end
|
|
129
|
-
end
|
|
130
|
-
end
|
|
131
|
-
end
|
data/lib/tina4/session.rb
DELETED
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
require "securerandom"
|
|
3
|
-
require "json"
|
|
4
|
-
|
|
5
|
-
module Tina4
|
|
6
|
-
class Session
|
|
7
|
-
DEFAULT_OPTIONS = {
|
|
8
|
-
cookie_name: "tina4_session",
|
|
9
|
-
secret: nil,
|
|
10
|
-
max_age: 86400,
|
|
11
|
-
handler: :file,
|
|
12
|
-
handler_options: {}
|
|
13
|
-
}.freeze
|
|
14
|
-
|
|
15
|
-
attr_reader :id, :data
|
|
16
|
-
|
|
17
|
-
def initialize(env, options = {})
|
|
18
|
-
@options = DEFAULT_OPTIONS.merge(options)
|
|
19
|
-
@options[:secret] ||= ENV["SECRET"] || "tina4-default-secret"
|
|
20
|
-
@handler = create_handler
|
|
21
|
-
@id = extract_session_id(env) || SecureRandom.hex(32)
|
|
22
|
-
@data = load_session
|
|
23
|
-
@modified = false
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def [](key)
|
|
27
|
-
@data[key.to_s]
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def []=(key, value)
|
|
31
|
-
@data[key.to_s] = value
|
|
32
|
-
@modified = true
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
def delete(key)
|
|
36
|
-
@data.delete(key.to_s)
|
|
37
|
-
@modified = true
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
def clear
|
|
41
|
-
@data = {}
|
|
42
|
-
@modified = true
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
def to_hash
|
|
46
|
-
@data.dup
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
def save
|
|
50
|
-
return unless @modified
|
|
51
|
-
@handler.write(@id, @data)
|
|
52
|
-
@modified = false
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
def destroy
|
|
56
|
-
@handler.destroy(@id)
|
|
57
|
-
@data = {}
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
def cookie_header
|
|
61
|
-
"#{@options[:cookie_name]}=#{@id}; Path=/; HttpOnly; SameSite=Lax; Max-Age=#{@options[:max_age]}"
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
private
|
|
65
|
-
|
|
66
|
-
def extract_session_id(env)
|
|
67
|
-
cookie_str = env["HTTP_COOKIE"] || ""
|
|
68
|
-
cookie_str.split(";").each do |pair|
|
|
69
|
-
key, value = pair.strip.split("=", 2)
|
|
70
|
-
return value if key == @options[:cookie_name]
|
|
71
|
-
end
|
|
72
|
-
nil
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
def load_session
|
|
76
|
-
existing = @handler.read(@id)
|
|
77
|
-
existing || {}
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
def create_handler
|
|
81
|
-
case @options[:handler].to_sym
|
|
82
|
-
when :file
|
|
83
|
-
Tina4::SessionHandlers::FileHandler.new(@options[:handler_options])
|
|
84
|
-
when :redis
|
|
85
|
-
Tina4::SessionHandlers::RedisHandler.new(@options[:handler_options])
|
|
86
|
-
when :mongo, :mongodb
|
|
87
|
-
Tina4::SessionHandlers::MongoHandler.new(@options[:handler_options])
|
|
88
|
-
else
|
|
89
|
-
Tina4::SessionHandlers::FileHandler.new(@options[:handler_options])
|
|
90
|
-
end
|
|
91
|
-
end
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
class LazySession
|
|
95
|
-
def initialize(env, options = {})
|
|
96
|
-
@env = env
|
|
97
|
-
@options = options
|
|
98
|
-
@session = nil
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
def [](key)
|
|
102
|
-
ensure_loaded
|
|
103
|
-
@session[key]
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
def []=(key, value)
|
|
107
|
-
ensure_loaded
|
|
108
|
-
@session[key] = value
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
def delete(key)
|
|
112
|
-
ensure_loaded
|
|
113
|
-
@session.delete(key)
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
def clear
|
|
117
|
-
ensure_loaded
|
|
118
|
-
@session.clear
|
|
119
|
-
end
|
|
120
|
-
|
|
121
|
-
def save
|
|
122
|
-
@session&.save
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
def destroy
|
|
126
|
-
@session&.destroy
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
def cookie_header
|
|
130
|
-
ensure_loaded
|
|
131
|
-
@session.cookie_header
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
def to_hash
|
|
135
|
-
ensure_loaded
|
|
136
|
-
@session.to_hash
|
|
137
|
-
end
|
|
138
|
-
|
|
139
|
-
private
|
|
140
|
-
|
|
141
|
-
def ensure_loaded
|
|
142
|
-
@session ||= Session.new(@env, @options)
|
|
143
|
-
end
|
|
144
|
-
end
|
|
145
|
-
end
|
|
@@ -1,55 +0,0 @@
|
|
|
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
|
|
@@ -1,49 +0,0 @@
|
|
|
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
|
|
@@ -1,43 +0,0 @@
|
|
|
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
|
data/lib/tina4/swagger.rb
DELETED
|
@@ -1,123 +0,0 @@
|
|
|
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
|