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 +7 -0
- data/LICENSE +21 -0
- data/bin/rubee +259 -0
- data/lib/Dockerfile +33 -0
- data/lib/app/controllers/welcome_controller.rb +5 -0
- data/lib/app/models/user.rb +6 -0
- data/lib/app/views/welcome_show.erb +52 -0
- data/lib/config/base_configuration.rb +11 -0
- data/lib/config/routes.rb +3 -0
- data/lib/config.ru +3 -0
- data/lib/db/create_users.rb +16 -0
- data/lib/db/structure.rb +34 -0
- data/lib/db/test.db +0 -0
- data/lib/images/rubee.svg +6 -0
- data/lib/inits/print_colors.rb +24 -0
- data/lib/rubee/async/asyncable.rb +17 -0
- data/lib/rubee/async/sidekiq_async.rb +15 -0
- data/lib/rubee/async/thread_async.rb +8 -0
- data/lib/rubee/async/thread_pool.rb +53 -0
- data/lib/rubee/controllers/base_controller.rb +78 -0
- data/lib/rubee/controllers/extensions/auth_tokenable.rb +81 -0
- data/lib/rubee/controllers/extensions/middlewarable.rb +31 -0
- data/lib/rubee/controllers/middlewares/auth_token_middleware.rb +42 -0
- data/lib/rubee/extensions/hookable.rb +85 -0
- data/lib/rubee/extensions/serializable.rb +28 -0
- data/lib/rubee/models/database_object.rb +50 -0
- data/lib/rubee/models/sequel_object.rb +86 -0
- data/lib/rubee/tests/auth_tokenable_test.rb +29 -0
- data/lib/rubee/tests/rubeeapp_test.rb +24 -0
- data/lib/rubee/tests/test_helper.rb +11 -0
- data/lib/rubee/tests/user_model_test.rb +51 -0
- data/lib/rubee.rb +259 -0
- data/lib/version.rb +1 -0
- data/readme.md +353 -0
- metadata +95 -0
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,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
|
data/lib/config.ru
ADDED
@@ -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
|
data/lib/db/structure.rb
ADDED
@@ -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,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
|