Class: GRApiManager::Server

Inherits:
Object
  • Object
show all
Defined in:
lib/gr_api_manager.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(port: nil, bearer_token: nil, permitted_hosts: [], prefix: '') ⇒ Server

Initializes the server configuration.



10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# File 'lib/gr_api_manager.rb', line 10

def initialize(port: nil, bearer_token: nil, permitted_hosts: [], prefix: '')
  @port = port || ENV['PORT'] || 4000
  @token = bearer_token || ENV['API_TOKEN']
  @permitted_hosts = permitted_hosts.empty? ? [] : permitted_hosts
  @prefix = prefix
  
  @app_class = Class.new(Sinatra::Base) do
    
    # Logs HTTP requests with status-based color coding.
    def log_request(method, path, params, status_code)
      color = status_code.between?(200, 299) ? "\e[32m" : "\e[31m"
      puts "[#{Time.now.strftime('%H:%M:%S')}] #{color}#{method} #{path} - #{status_code}\e[0m"
    end

    # Casts string URL parameters to native Ruby types (Integer, Float, Boolean).
    def smart_parse(hash)
      hash.transform_values do |val|
        case val
        when 'true' then true
        when 'false' then false
        when /^[0-9]+$/ then val.to_i
        when /^[0-9]+\.[0-9]+$/ then val.to_f
        else val
        end
      end
    end
  end

  configure_app
end

Instance Attribute Details

#app_classObject (readonly)

Returns the value of attribute app_class.



7
8
9
# File 'lib/gr_api_manager.rb', line 7

def app_class
  @app_class
end

Instance Method Details

#register_route(verb, path, options = {}, &block) ⇒ Object

Core routing logic: auth validation, param parsing, and block execution.



88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/gr_api_manager.rb', line 88

def register_route(verb, path, options = {}, &block)
  verb = verb.to_s.upcase
  require_auth = options.fetch(:auth, true)
  required_params = options.fetch(:requires, [])
  
  # Construct the full path with the optional prefix.
  full_path = File.join('/', @prefix.to_s, path.to_s).gsub(%r{/+}, '/')

  handler = proc do
    
    # 1. Authentication check
    if require_auth
      auth_header = request.env["HTTP_AUTHORIZATION"]
      halt 401, { error: "Token required. Format: 'Bearer <token>'" }.to_json if auth_header.nil?
      halt 403, { error: "Invalid token" }.to_json if auth_header.split(" ").last != settings.token
    end

    # 2. Body parsing (for POST/PUT requests)
    parsed_body = {}
    if ['POST', 'PUT'].include?(verb)
      body_data = request.body.read.to_s
      unless body_data.empty?
        begin
          parsed_body = JSON.parse(body_data, symbolize_names: true)
        rescue JSON::ParserError
          halt 400, { error: "Invalid JSON body" }.to_json
        end
      end
    end

    # 3. Merge query parameters with parsed JSON body
    all_params = smart_parse(params).merge(parsed_body)

    # 4. Declarative parameter validation
    missing = required_params.select { |p| all_params[p.to_sym].nil? || all_params[p.to_sym].to_s.strip.empty? }
    if missing.any?
      status 400
      log_request(verb, full_path, all_params, 400)
      next { error: "Missing required parameters", required: missing }.to_json
    end

    # 5. Execute user-defined block
    result = instance_exec(all_params, &block)
    log_request(verb, full_path, all_params, response.status)
    result.to_json
  end

  @app_class.send(verb.downcase, full_path, &handler)
end

#run!Object

Starts the Sinatra server with a custom GR banner.



139
140
141
142
143
144
145
146
147
# File 'lib/gr_api_manager.rb', line 139

def run!
  puts "============================================="
  puts "  GR API MANAGER STARTED"
  puts "  Port   : #{@port}"
  puts "  Auth   : #{@token ? 'Enabled' : 'Public'}"
  puts "  Prefix : #{@prefix.empty? ? '/' : @prefix}"
  puts "============================================="
  @app_class.run!
end