ru.Bee 1.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 7997ccfa7b0bf78ebf9c643a4ca6370263f6c4fb6be0d31125d7b22cc6cdd730
4
+ data.tar.gz: bbee43a818c09cd38fc2547b3b041d8e27c3f3b969080f7de29bef4e5e489d75
5
+ SHA512:
6
+ metadata.gz: 5c42d7683c253a9c49dcb74ef9ab59075a52a44c97e91c3af7630dd9ee21fef3ca1f9d1556c358fa27ae24171ea42145965e306e5e4b94da92883bdf313edbe8
7
+ data.tar.gz: c3ff336841bd2de71e04702b117270e6a8b0d5eaa051ea880fc6ce5686d48dcfd8475e9ac3eab26d3d883de51334b5795d07cd27e446955e5514bf1b4656160c
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Oleg Saltykov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/bin/rubee ADDED
@@ -0,0 +1,259 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'fileutils'
4
+ require 'date'
5
+ require 'irb'
6
+ require 'sequel'
7
+ require 'sqlite3'
8
+ require 'json'
9
+ require 'rack'
10
+ require 'rackup'
11
+
12
+ require_relative '../lib/version'
13
+ require_relative '../lib/inits/print_colors'
14
+ require_relative '../lib/rubee'
15
+
16
+ ENV['RACK_ENV'] ||= 'development'
17
+
18
+ LIB_ROOT = File.expand_path('../lib', File.dirname(__FILE__))
19
+ ENV['RACKUP_FILE'] = File.join(LIB_ROOT, 'config.ru')
20
+
21
+ LOGO = <<-'LOGO'
22
+ ____ _ _ ____ _____
23
+ | _ \| | | || __ )| ____|
24
+ | |_) | | | || _ \| _|
25
+ | _ <| |__| || |_) | |___
26
+ |_| \_\\____/ |____/|_____|
27
+ Ver: %s
28
+ LOGO
29
+
30
+ command = ARGV.first
31
+
32
+ def print_logo
33
+ puts "\e[36m" + (LOGO % VERSION) + "\e[0m" # Cyan color
34
+ end
35
+
36
+ if command =~ /^(start)$|^(start:(\d+))$/
37
+ command, port = ARGV.first&.split(':')
38
+
39
+ port ||= "7000"
40
+ print_logo
41
+ color_puts "Starting takeoff of ruBee server on port #{port}...", color: :yellow
42
+ exec("rackup #{ENV['RACKUP_FILE']} -p #{port}")
43
+ elsif command =~ /^(start_dev)$|^(start_dev:(\d+))$/
44
+ command, port = ARGV.first&.split(':')
45
+
46
+ port ||= "7000"
47
+ print_logo
48
+
49
+ color_puts "Starting takeoff of ruBee server on port #{port} in dev mode...", color: :yellow
50
+
51
+ exec("rerun -- rackup --port #{port} #{ENV['RACKUP_FILE']}")
52
+ elsif command == "stop"
53
+ exec("pkill -f rubee")
54
+ elsif command == "status"
55
+ exec("ps aux | grep rubee")
56
+ elsif command == "project"
57
+ project_name = ARGV[1]
58
+ if project_name.nil?
59
+ color_puts "Please indicate project name.", color: :red
60
+ exit 1
61
+ end
62
+
63
+ source_dir = File.expand_path("../lib", __dir__)
64
+ target_dir = File.expand_path("./#{project_name}", Dir.pwd)
65
+
66
+ if Dir.exist?(target_dir)
67
+ color_puts "Error: Project #{project_name} already exists!", color: :red
68
+ exit 1
69
+ end
70
+
71
+ # Create target directory
72
+ FileUtils.mkdir_p(target_dir)
73
+
74
+ # Define blacklist
75
+ blacklist_files = %w[rubee.rb print_colors.rb version.rb config.ru]
76
+ blacklist_dirs = %w[rubee]
77
+
78
+ # Copy files, excluding blacklisted ones
79
+ Dir.glob("#{source_dir}/**/*", File::FNM_DOTMATCH).each do |file|
80
+ relative_path = file.sub("#{source_dir}/", "")
81
+
82
+ # Skip blacklisted directories
83
+ next if blacklist_dirs.any? { |dir| relative_path.split('/').include?(dir) }
84
+
85
+ # Skip blacklisted files
86
+ next if blacklist_files.include?(File.basename(file))
87
+
88
+ target_path = File.join(target_dir, relative_path)
89
+
90
+ if File.directory?(file)
91
+ FileUtils.mkdir_p(target_path)
92
+ else
93
+ FileUtils.cp(file, target_path)
94
+ end
95
+ end
96
+
97
+ # create a gemfile context
98
+ gemfile = <<~GEMFILE
99
+ source 'https://rubygems.org'
100
+
101
+ gem 'rubee', path: '../rubee'
102
+ gem 'sequel'
103
+ gem 'sqlite3'
104
+ gem 'rake'
105
+ gem 'rack'
106
+ gem 'rackup'
107
+ gem 'pry'
108
+ gem 'pry-byebug'
109
+ gem 'puma'
110
+
111
+ group :development do
112
+ gem 'rerun'
113
+ gem 'minitest'
114
+ gem 'rack-test'
115
+ end
116
+ GEMFILE
117
+ # create a gemfile
118
+ File.open("#{target_dir}/Gemfile", 'w') do |file|
119
+ file.puts gemfile
120
+ end
121
+
122
+ # create a test folder
123
+ FileUtils.mkdir_p("#{target_dir}/tests")
124
+ # create a test_helper context
125
+ test_helper = <<~RUBY
126
+ require "bundler/setup"
127
+ Bundler.require(:test)
128
+
129
+ require 'minitest/autorun'
130
+ require 'rack/test'
131
+ require 'rubee'
132
+
133
+ Rubee::Autoload.call
134
+ RUBY
135
+ File.open("#{target_dir}/tests/test_helper.rb", 'w') do |file|
136
+ file.puts test_helper
137
+ end
138
+
139
+ color_puts "Project #{project_name} created successfully at #{target_dir}", color: :green
140
+
141
+ elsif command == "version"
142
+ puts "ruBee v#{VERSION}"
143
+ elsif command == "test"
144
+ ENV['RACK_ENV'] = 'test'
145
+ file_name = ARGV[1] # Get the first argument
146
+
147
+ if file_name
148
+ color_puts "Running #{file_name} test ...", color: :yellow
149
+ exec("ruby -Itest -e \"require './tests/#{file_name}'\"")
150
+ else
151
+ color_puts "Running all tests ...", color: :yellow
152
+ exec("ruby -Itest -e \"Dir.glob('./tests/**/*_test.rb').each { |file| require file }\"")
153
+ end
154
+ elsif ['generate', 'g'].include? command
155
+ method, path = ARGV[1..2]
156
+ ENV['RACK_ENV'] ||= 'development'
157
+
158
+ routes = eval(File.read('config/routes.rb'))
159
+ route = routes.find { |route| route[:path] == path.to_s && route[:method] == method.to_sym }
160
+ color_puts("Route not found with path: #{path} and method: #{method}", color: :red) unless route
161
+
162
+ Rubee::Generator.new(
163
+ route[:model]&.[](:name),
164
+ route[:model]&.[](:attributes),
165
+ "#{route[:controller]&.capitalize}Controller",
166
+ route[:action]
167
+ ).call
168
+ elsif command == "db"
169
+ Rubee::Autoload.call
170
+ ENV['RACK_ENV'] ||= 'development'
171
+
172
+ command, file_name = ARGV[1]&.split(':')
173
+
174
+
175
+ def ensure_database_exists(db_url)
176
+ uri = URI.parse(db_url)
177
+ case uri.scheme
178
+ when "sqlite"
179
+ begin
180
+ Sequel.connect(db_url)
181
+ color_puts "Database #{ENV['RACK_ENV']} exists", color: :cyan
182
+ rescue => _
183
+ if File.exist?(db_path = db_url.sub(/^sqlite:\/\//, ''))
184
+ color_puts "Database #{ENV['RACK_ENV']} exists", color: :cyan
185
+ else
186
+ Sequel.sqlite(db_path)
187
+ color_puts "Database #{ENV['RACK_ENV']} created", color: :green
188
+ end
189
+ end
190
+ when "postgres"
191
+ begin
192
+ Sequel.connect(db_url)
193
+ color_puts "Database #{ENV['RACK_ENV']} exists", color: :cyan
194
+ rescue => _
195
+ con = Sequel.connect(Rubee::Configuration.get_database_url.gsub(/(\/test|\/development|\/production)/, ''))
196
+ con.run("CREATE DATABASE #{ENV['RACK_ENV']}")
197
+ color_puts "Database #{ENV['RACK_ENV']} created", color: :green
198
+ end
199
+ else
200
+ color_puts "Unsupported database type: #{db_url}", color: :red
201
+ end
202
+ end
203
+
204
+ def generate_structure
205
+ schema_hash = {}
206
+
207
+ Rubee::SequelObject::DB.tables.each do |table|
208
+ schema_hash[table] = {}
209
+
210
+ Rubee::SequelObject::DB.schema(table).each do |column, details|
211
+ schema_hash[table][column] = details
212
+ end
213
+ end
214
+ formatted_hash = JSON.pretty_generate(schema_hash)
215
+ .gsub(/\"(\w+)\":/, '\1:') # Convert keys to symbols
216
+ .gsub(': null', ': nil') # Convert `null` to `nil`
217
+
218
+ File.open("db/structure.rb", 'w') do |file|
219
+ file.puts "STRUCTURE = #{formatted_hash}"
220
+ end
221
+
222
+ color_puts "db/structure.rb updated", color: :green
223
+ end
224
+
225
+
226
+ if command == 'run'
227
+ Rubee::Autoload.call
228
+ Rubee::Configuration.envs.each do |env|
229
+ ENV['RACK_ENV'] = env.to_s
230
+ color_puts "Run #{file_name} file for #{env} env", color: :cyan
231
+ Object.const_get(file_name.split('_').map(&:capitalize).join).new.call
232
+ end
233
+ color_puts "Migration #{file_name} completed", color: :green
234
+ color_puts "Regenerate schema file", color: :cyan
235
+ generate_structure
236
+ elsif command == 'init'
237
+ ensure_database_exists(Rubee::Configuration.get_database_url)
238
+ elsif command == 'structure'
239
+ generate_structure
240
+ else
241
+ color_puts "Unknown command: #{command}", color: :red
242
+ end
243
+ elsif ['console'].include? command
244
+ ARGV.clear
245
+ ENV['RACK_ENV'] ||= 'development'
246
+
247
+ Rubee::Autoload.call
248
+
249
+ def reload
250
+ app_files = Dir["./#{APP_ROOT}/**/*.rb"]
251
+ app_files.each { |file| load file }
252
+ color_puts "Reloaded ..", color: :green
253
+ end
254
+
255
+ # Start IRB
256
+ IRB.start
257
+ else
258
+ color_puts "Unknown command: #{command}", color: :red
259
+ end
data/lib/Dockerfile ADDED
@@ -0,0 +1,33 @@
1
+ # Use an official Ruby runtime as a parent image
2
+ FROM ruby:3.4.1
3
+
4
+ # Set the working directory inside the container
5
+ WORKDIR /app
6
+
7
+ # Install system dependencies required for your app (e.g., build tools for gems like pg, etc.)
8
+ RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs
9
+
10
+ # Copy Gemfile and Gemfile.lock, then install dependencies
11
+ COPY ../Gemfile Gemfile.lock ./
12
+ RUN bundle install
13
+
14
+ # Copy the rest of the application files into the container
15
+ COPY .. .
16
+
17
+ # Ensure scripts are executable (after the files are copied)
18
+ RUN chmod +x ./com/db
19
+ RUN chmod +x ./com/rubee
20
+
21
+ # Set the environment variable RACK_ENV to production
22
+ ENV RACK_ENV=production
23
+
24
+ # Initialize the database (make sure this script exists in your project structure)
25
+ RUN ./com/db init
26
+
27
+ RUN ./com/db run:create_users
28
+
29
+ # Expose the port the app will run on
30
+ EXPOSE 8080
31
+
32
+ # Run the app (ensure this command correctly starts your app)
33
+ CMD ./com/rubee start:8080
@@ -0,0 +1,5 @@
1
+ class WelcomeController < Rubee::BaseController
2
+ def show
3
+ response_with
4
+ end
5
+ end
@@ -0,0 +1,6 @@
1
+ # WARNING: User model is required for JWT authentification
2
+ # If you remove or modify it, make sure all changes are inlined
3
+ # with AuthTokenMiddleware and AuthTokenable modules
4
+ class User < Rubee::SequelObject
5
+ attr_accessor :id, :email, :password
6
+ end
@@ -0,0 +1,52 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Setup Complete</title>
7
+ <style>
8
+ body {
9
+ display: flex;
10
+ justify-content: center;
11
+ align-items: center;
12
+ height: 100vh;
13
+ margin: 0;
14
+ background-color: #fdf6a5;
15
+ text-align: center;
16
+ font-family: Arial, sans-serif;
17
+ }
18
+ .container {
19
+ padding: 20px;
20
+ background: white;
21
+ border-radius: 10px;
22
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
23
+ }
24
+ img {
25
+ width: 50px;
26
+ height: auto;
27
+ margin-top: 10px;
28
+ }
29
+ h1 {
30
+ font-size: 1.8rem;
31
+ margin: 10px 0;
32
+ }
33
+ @media (max-width: 600px) {
34
+ h1 {
35
+ font-size: 1.5rem;
36
+ }
37
+ img {
38
+ width: 60px;
39
+ }
40
+ }
41
+ </style>
42
+ </head>
43
+ <body>
44
+ <div class="container">
45
+ <h1>All set up and running!</h1>
46
+ <br/>
47
+ <img src="images/rubee.svg" alt="ruBee">
48
+ <br/>
49
+ <p>rubee homepage: <a href="https://github.com/nucleom42/rubee"><br/>https://github.com/nucleom42/rubee</a></p>
50
+ </div>
51
+ </body>
52
+ </html>
@@ -0,0 +1,11 @@
1
+ Rubee::Configuration.setup(env=:development) do |config|
2
+ config.database_url = { url: "sqlite://db/development.db", env: }
3
+ end
4
+
5
+ Rubee::Configuration.setup(env=:test) do |config|
6
+ config.database_url = { url: "sqlite://db/test.db", env: }
7
+ end
8
+
9
+ Rubee::Configuration.setup(env=:production) do |config|
10
+ config.database_url = { url: "sqlite://db/production.db", env: }
11
+ end
@@ -0,0 +1,3 @@
1
+ Rubee::Router.draw do |router|
2
+ router.get "/", to: "welcome#show" # override it for your app
3
+ end
data/lib/config.ru ADDED
@@ -0,0 +1,3 @@
1
+ require_relative './rubee'
2
+
3
+ run Rubee::Application.instance
@@ -0,0 +1,16 @@
1
+ # WARNING: This migration is a prerequisite for the inbuild JWT authentification logic
2
+ # Please make sure you executed it before using AuthTokenable module
3
+ class CreateUsers
4
+ def call
5
+ unless Rubee::SequelObject::DB.tables.include?(:users)
6
+ Rubee::SequelObject::DB.create_table :users do
7
+ primary_key :id
8
+ String :email
9
+ String :password
10
+ index :email
11
+ end
12
+
13
+ User.create(email: "ok@ok.com", password: "password")
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,34 @@
1
+ STRUCTURE = {
2
+ users: {
3
+ id: {
4
+ generated: false,
5
+ allow_null: false,
6
+ default: nil,
7
+ db_type: "INTEGER",
8
+ primary_key: true,
9
+ auto_increment: true,
10
+ type: "integer",
11
+ ruby_default: nil
12
+ },
13
+ email: {
14
+ generated: false,
15
+ allow_null: true,
16
+ default: nil,
17
+ db_type: "varchar(255)",
18
+ primary_key: false,
19
+ type: "string",
20
+ ruby_default: nil,
21
+ max_length: 255
22
+ },
23
+ password: {
24
+ generated: false,
25
+ allow_null: true,
26
+ default: nil,
27
+ db_type: "varchar(255)",
28
+ primary_key: false,
29
+ type: "string",
30
+ ruby_default: nil,
31
+ max_length: 255
32
+ }
33
+ }
34
+ }
data/lib/db/test.db ADDED
Binary file
@@ -0,0 +1,6 @@
1
+ <?xml version='1.0' encoding='iso-8859-1'?>
2
+ <!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'>
3
+ <!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
4
+ <svg fill="#000000" height="800px" width="800px" version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 235.32 235.32" xmlns:xlink="http://www.w3.org/1999/xlink" enable-background="new 0 0 235.32 235.32">
5
+ <path d="m204.738,118.109c-8.042-10.534-20.072-22.499-30.95-26.649-2.433-22.924-18.665-41.776-40.208-48.093l30.565-30.564c2.929-2.929 2.929-7.678 0-10.607-2.93-2.929-7.678-2.929-10.607,0l-35.786,35.785-35.476-35.474c-2.93-2.929-7.678-2.929-10.607,2.22045e-15-2.929,2.929-2.929,7.678 0,10.606l30.255,30.253c-21.521,6.312-37.741,25.132-40.199,48.024-10.928,4.082-23.052,16.124-31.142,26.719-8.511,11.147-22.788,33.768-22.788,60.021 0,18.913 15.388,34.299 34.301,34.299 16.951,0 31.063-12.365 33.807-28.547 8.722,9.659 20.739,16.275 34.26,18.085v25.854c0,4.142 3.357,7.5 7.5,7.5 4.143,0 7.5-3.358 7.5-7.5v-25.854c13.52-1.81 25.537-8.426 34.258-18.085 2.745,16.183 16.855,28.547 33.807,28.547 18.912,0 34.299-15.386 34.299-34.299-0.002-26.254-14.279-48.874-22.789-60.021zm-45.81-3.779h-82.533v-9.383h82.533v9.383zm-82.533,15h82.533v9.384h-82.533v-9.384zm41.357-73.241c20.243,0 37.123,14.623 40.663,33.858h-81.326c3.54-19.235 20.42-33.858 40.663-33.858zm-56.357,122.04c0,10.642-8.657,19.299-19.299,19.299-10.642,0-19.301-8.657-19.301-19.299 0-31.217 24.718-59.914 38.6-69.662v37.747 31.915zm56.267,9.352c-20.193,0-37.033-14.583-40.572-33.767h81.143c-3.54,19.184-20.378,33.767-40.571,33.767zm75.565,9.947c-10.642,0-19.299-8.657-19.299-19.299v-31.915-37.747c13.881,9.748 38.598,38.445 38.598,69.662-0.001,10.642-8.658,19.299-19.299,19.299z"/>
6
+ </svg>
@@ -0,0 +1,24 @@
1
+ # Rubee system file
2
+ # WARNING: DO NOT EDIT THIS FILE UNLESS YOU FEEL STRONG DESIRE
3
+ # Unpreditable behaviour may happen. Take it as your own risk.
4
+ def color_puts(text, color: :nil, background: :nil, style: :normal)
5
+ colors = {
6
+ black: 30, red: 31, green: 32, yellow: 33,
7
+ blue: 34, magenta: 35, cyan: 36, white: 37
8
+ }
9
+
10
+ backgrounds = {
11
+ black: 40, red: 41, green: 42, yellow: 43,
12
+ blue: 44, magenta: 45, cyan: 46, white: 47
13
+ }
14
+
15
+ styles = {
16
+ normal: 0, bold: 1, underline: 4, blink: 5
17
+ }
18
+
19
+ color_code = colors[color]
20
+ bg_code = backgrounds[background]
21
+ style_code = styles[style]
22
+ options = [style_code, color_code, bg_code].compact.join(";")
23
+ puts "\e[#{options}m#{text}\e[0m"
24
+ end
@@ -0,0 +1,17 @@
1
+ unless defined?(Rubee)
2
+ require_relative '../../../../ee.rb'
3
+ Rubee::Autoload.call
4
+ end
5
+
6
+ module Rubee
7
+ module Asyncable
8
+ def perform_async(args = {})
9
+ args.merge!(_class: self.class)
10
+ adapter.new.perform_async(**args)
11
+ end
12
+
13
+ def adapter
14
+ @adapter ||= (Rubee::Configuration.get_async_adapter || ThreadAsync)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,15 @@
1
+ module Rubee
2
+ class SidekiqAsync
3
+ def perform_async(**args)
4
+ options = if args[:options].is_a? Hash
5
+ [JSON.generate(args[:options])]
6
+ elsif args[:options].is_a? Array
7
+ args[:options]
8
+ else
9
+ [args[:options]]
10
+ end
11
+
12
+ args[:_class].perform_async(*options)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,8 @@
1
+ module Rubee
2
+ class ThreadAsync
3
+ def perform_async(**args)
4
+ color_puts "WARN: ThreadAsync engine is not yet recommended for production", color: :yellow
5
+ ThreadPool.instance.enqueue(args[:_class], args[:options])
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,53 @@
1
+ require 'singleton'
2
+
3
+ module Rubee
4
+ class ThreadPool
5
+ include Singleton
6
+ THREADS_LIMIT = 4 # adjust as needed
7
+
8
+ def initialize
9
+ @queue = Queue.new
10
+ @workers = []
11
+ @running = true
12
+ @mutex = Mutex.new
13
+ spawn_workers
14
+ end
15
+
16
+ def enqueue(task, args)
17
+ @queue << { task: task, args: args }
18
+ end
19
+
20
+ def bulk_enqueue(tasks)
21
+ @queue << tasks
22
+ end
23
+
24
+ def shutdown
25
+ @running = false
26
+ THREADS_LIMIT.times { @queue << { task: :stop, args: nil } }
27
+ @workers.each(&:join)
28
+ end
29
+
30
+ private
31
+
32
+ def spawn_workers
33
+ THREADS_LIMIT.times do
34
+ @workers << Thread.new do
35
+ while @running
36
+ task_hash = @mutex.synchronize { @queue.pop }
37
+ if task_hash
38
+ task = task_hash[:task]
39
+ args = task_hash[:args]
40
+ end
41
+ break if task == :stop
42
+ begin
43
+ task.new.perform(**args)
44
+ rescue StandardError => e
45
+ puts "ThreadPool Error: #{e.message}"
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+
@@ -0,0 +1,78 @@
1
+ module Rubee
2
+ class BaseController
3
+ include Hookable
4
+
5
+ def initialize(request, route)
6
+ @request = request
7
+ @route = route
8
+ end
9
+
10
+ def image
11
+ image_path = File.join(IMAGE_DIR, @request.path.sub('/images/', ''))
12
+
13
+ if File.exist?(image_path) && File.file?(image_path)
14
+ mime_type = Rack::Mime.mime_type(File.extname(image_path))
15
+ response_with object: File.read(image_path), type: :image, mime_type: mime_type
16
+ else
17
+ response_with object: "Image not found", type: :text
18
+ end
19
+ end
20
+
21
+ def response_with type: nil, object: nil, status: 200, mime_type: nil, render_view: nil, headers: {}, to: nil, file: nil, filename: nil
22
+ case type&.to_sym
23
+ in :json
24
+ rendered_json = object.is_a?(Array) ? object&.map(&:to_h).to_json : object.to_json
25
+ return [status, headers.merge("content-type" => "application/json"), [rendered_json]]
26
+ in :image
27
+ return [status, headers.merge("content-type" => mime_type), [object]]
28
+ in :file
29
+ return [
30
+ status,
31
+ headers.merge(
32
+ "content-disposition" => "attachment; filename=#{filename}",
33
+ "content-type" => "application/octet-stream"
34
+ ),
35
+ file
36
+ ]
37
+ in :text
38
+ return [status, headers.merge("content-type" => "text/plain"), [object.to_s]]
39
+ in :unauthentificated
40
+ return [401, headers.merge("content-type" => "text/plain"), ["Unauthentificated"]]
41
+ in :redirect
42
+ return [302, headers.merge("location" => "#{to}"), ["Unauthentificated"]]
43
+ else # rendering erb view is a default behavior
44
+ view_file_name = self.class.name.split("Controller").first.downcase
45
+ erb_file = render_view ? "#{render_view}.erb" : "#{view_file_name}_#{@route[:action]}.erb"
46
+ rendered_erb = ERB.new(File.open("app/views/#{erb_file}").read).result(binding)
47
+ return [status, headers.merge("content-type" => "text/html"), [rendered_erb]]
48
+ end
49
+ end
50
+
51
+ def params
52
+ inputs = @request.env['rack.input'].read
53
+ body = JSON.parse(@request.body.read.strip) rescue body = {}
54
+ body.merge!(URI.decode_www_form(inputs).to_h.transform_keys(&:to_sym)) rescue nil
55
+ @params ||= extract_params(@request.path, @route[:path])
56
+ .merge(body)
57
+ .merge(@request.params)
58
+ .transform_keys(&:to_sym)
59
+ .select { |k,v| ![:_method].include?(k.downcase.to_sym) }
60
+ end
61
+
62
+ def headers
63
+ @request.env.select {|k,v| k.start_with? 'HTTP_'}
64
+ .collect {|key, val| [key.sub(/^HTTP_/, ''), val]}
65
+ end
66
+
67
+ def extract_params(path, pattern)
68
+ regex_pattern = pattern.gsub(/\{(\w+)\}/, '(?<\1>[^/]+)')
69
+ regex = Regexp.new("^#{regex_pattern}$")
70
+
71
+ if match = path.match(regex)
72
+ return match.named_captures&.transform_keys(&:to_sym)
73
+ end
74
+
75
+ {}
76
+ end
77
+ end
78
+ end