rsodx 0.0.1
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.txt +21 -0
- data/README.md +180 -0
- data/bin/console +12 -0
- data/bin/rsodx +9 -0
- data/bin/setup +8 -0
- data/lib/rsodx/base.rb +6 -0
- data/lib/rsodx/boot.rb +11 -0
- data/lib/rsodx/cli/generate.rb +17 -0
- data/lib/rsodx/cli/generate_interactor.rb +34 -0
- data/lib/rsodx/cli/generate_migration.rb +43 -0
- data/lib/rsodx/cli/handler.rb +26 -0
- data/lib/rsodx/cli/scaffold.rb +133 -0
- data/lib/rsodx/cli/server.rb +12 -0
- data/lib/rsodx/connect.rb +13 -0
- data/lib/rsodx/contract.rb +5 -0
- data/lib/rsodx/environment.rb +12 -0
- data/lib/rsodx/error.rb +6 -0
- data/lib/rsodx/interactor.rb +39 -0
- data/lib/rsodx/interactors/healthcheck.rb +7 -0
- data/lib/rsodx/router.rb +20 -0
- data/lib/rsodx/router_dsl.rb +61 -0
- data/lib/rsodx/tasks.rb +20 -0
- data/lib/rsodx/version.rb +5 -0
- data/lib/rsodx.rb +7 -0
- data/spec/rsodx_spec.rb +11 -0
- data/spec/spec_helper.rb +15 -0
- metadata +240 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 9530a9b38e69fdae2f3eee74634ab298aabf3fa11df94d5e28dffd46e7fbffed
|
4
|
+
data.tar.gz: 45db9f15c1a2fbee0d2047373c2958ac913fa262e1833ba9c07b925d76f2577e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2074732db6099bd5681845ed895d5679e9b33b5aea3fe5a8a83d767ac47dbc670d86511d330599b79e940ae51797a6c9d77861dff8472082ec8806d4c9f07c9b
|
7
|
+
data.tar.gz: eb6590b94d33281d0d179a9c6cb62b1c70fef8115c8b8486e230f21154672cdf618ed37738b1fe1d6e918fa60282d5e98c27cec1b5a41dfe56226002661b4d2b
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2025 Первушин
|
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
|
13
|
+
all 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
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,180 @@
|
|
1
|
+
# Rsodx
|
2
|
+
|
3
|
+
> Add Rsodx and just code ✨
|
4
|
+
|
5
|
+
Rsodx is a lightweight, modular microservice framework for Ruby — designed to be fast, clean, and scalable. It provides a minimal architecture inspired by Rails, Sinatra, and Sequel, allowing you to focus on writing business logic without boilerplate.
|
6
|
+
|
7
|
+
---
|
8
|
+
|
9
|
+
## 🧠 Philosophy
|
10
|
+
|
11
|
+
- No monoliths — build small services
|
12
|
+
- No magic — just plain Ruby
|
13
|
+
- No opinionated ORM or router — just simple tools
|
14
|
+
- Easily extendable and production-ready
|
15
|
+
|
16
|
+
---
|
17
|
+
|
18
|
+
Вот аккуратно оформленный раздел `Installation` для твоего README:
|
19
|
+
|
20
|
+
---
|
21
|
+
|
22
|
+
## 📦 Installation
|
23
|
+
|
24
|
+
```text
|
25
|
+
gem 'rsodx', github: 'eugene-ruby/rsodx'
|
26
|
+
```
|
27
|
+
|
28
|
+
You can install the gem directly from [RubyGems.org](https://rubygems.org/gems/rsodx) after release:
|
29
|
+
|
30
|
+
### With Bundler
|
31
|
+
|
32
|
+
```bash
|
33
|
+
bundle add rsodx
|
34
|
+
```
|
35
|
+
|
36
|
+
### Without Bundler
|
37
|
+
|
38
|
+
```bash
|
39
|
+
gem install rsodx
|
40
|
+
```
|
41
|
+
|
42
|
+
> ✅ `rsodx` is designed for microservice architecture and includes routing, interactors, validation, and more — all in one lightweight package.
|
43
|
+
|
44
|
+
---
|
45
|
+
|
46
|
+
|
47
|
+
## 📦 Project Structure
|
48
|
+
|
49
|
+
```text
|
50
|
+
my_service/
|
51
|
+
├── app/
|
52
|
+
│ ├── api/
|
53
|
+
│ ├── interactors/
|
54
|
+
│ ├── models/
|
55
|
+
│ ├── presenters/
|
56
|
+
│ ├── serializers/
|
57
|
+
│ ├── app.rb
|
58
|
+
│ └── router.rb
|
59
|
+
├── config/
|
60
|
+
│ ├── environment.rb
|
61
|
+
│ ├── environments/
|
62
|
+
│ └── initializers/
|
63
|
+
├── db/
|
64
|
+
│ └── migrations/
|
65
|
+
├── bin/
|
66
|
+
│ └── console
|
67
|
+
├── .env
|
68
|
+
├── config.ru
|
69
|
+
├── Gemfile
|
70
|
+
└── Rakefile
|
71
|
+
```
|
72
|
+
|
73
|
+
---
|
74
|
+
|
75
|
+
## 🧰 CLI Commands
|
76
|
+
|
77
|
+
### Create new service
|
78
|
+
|
79
|
+
```bash
|
80
|
+
rsodx new my_service
|
81
|
+
```
|
82
|
+
|
83
|
+
---
|
84
|
+
|
85
|
+
### Generate interactor
|
86
|
+
|
87
|
+
```bash
|
88
|
+
bin/rsodx generate interactor CreateUser
|
89
|
+
```
|
90
|
+
Creates `app/interactors/create_user.rb`:
|
91
|
+
|
92
|
+
```ruby
|
93
|
+
class CreateUser < Rsodx::Interactor
|
94
|
+
def call
|
95
|
+
# business logic here
|
96
|
+
end
|
97
|
+
end
|
98
|
+
```
|
99
|
+
|
100
|
+
---
|
101
|
+
|
102
|
+
### Generate migration
|
103
|
+
|
104
|
+
```bash
|
105
|
+
bin/rsodx generate migration CreateUsers
|
106
|
+
```
|
107
|
+
Creates `db/migrations/20240413_create_users.rb`:
|
108
|
+
|
109
|
+
```ruby
|
110
|
+
Sequel.migration do
|
111
|
+
change do
|
112
|
+
# create_table :users do
|
113
|
+
# primary_key :id
|
114
|
+
# String :email
|
115
|
+
# DateTime :created_at
|
116
|
+
# end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
```
|
120
|
+
|
121
|
+
---
|
122
|
+
|
123
|
+
## 📂 DSL Router Example
|
124
|
+
|
125
|
+
```ruby
|
126
|
+
class Router < Rsodx::Router
|
127
|
+
namespace "/v1" do
|
128
|
+
post "/users", CreateUsers
|
129
|
+
end
|
130
|
+
end
|
131
|
+
```
|
132
|
+
|
133
|
+
---
|
134
|
+
|
135
|
+
## ⚙️ Rake Tasks
|
136
|
+
|
137
|
+
You can define your own `DB.connect` logic and use built-in tasks:
|
138
|
+
|
139
|
+
```ruby
|
140
|
+
# config/environment.rb
|
141
|
+
require "rsodx"
|
142
|
+
require "rsodx/db"
|
143
|
+
Rsodx::DB.connect
|
144
|
+
```
|
145
|
+
|
146
|
+
### Run migration
|
147
|
+
```bash
|
148
|
+
rake db:migrate
|
149
|
+
```
|
150
|
+
|
151
|
+
### Rollback migration
|
152
|
+
```bash
|
153
|
+
rake db:rollback
|
154
|
+
```
|
155
|
+
|
156
|
+
---
|
157
|
+
|
158
|
+
## 💻 Interactive Console
|
159
|
+
|
160
|
+
```bash
|
161
|
+
bin/console
|
162
|
+
```
|
163
|
+
Inside IRB:
|
164
|
+
```ruby
|
165
|
+
reload! # reload environment
|
166
|
+
CreateUser.call(params: {...})
|
167
|
+
```
|
168
|
+
|
169
|
+
---
|
170
|
+
|
171
|
+
## 🔜 Roadmap
|
172
|
+
- Generator for models, serializers, presenters
|
173
|
+
- HTTP + JSON helpers
|
174
|
+
- Authentication middleware
|
175
|
+
- Better documentation site
|
176
|
+
|
177
|
+
---
|
178
|
+
|
179
|
+
## 🧬 License
|
180
|
+
MIT — created by Eugene Pervushin
|
data/bin/console
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "bundler/setup"
|
5
|
+
require "rsodx"
|
6
|
+
require_relative '../lib/config/environment'
|
7
|
+
|
8
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
9
|
+
# with your gem easier. You can also use a different console, if you like.
|
10
|
+
|
11
|
+
require "irb"
|
12
|
+
IRB.start(__FILE__)
|
data/bin/rsodx
ADDED
data/bin/setup
ADDED
data/lib/rsodx/base.rb
ADDED
data/lib/rsodx/boot.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
module Rsodx
|
2
|
+
module Boot
|
3
|
+
def self.load_app_structure(app_root)
|
4
|
+
%w[models interactors presenters serializers api].each do |folder|
|
5
|
+
path = File.join(app_root, "app", folder)
|
6
|
+
$LOAD_PATH.unshift(path) if Dir.exist?(path)
|
7
|
+
Dir["#{path}/**/*.rb"].sort.each { |file| require file }
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Rsodx::Cli
|
2
|
+
class Generate
|
3
|
+
def self.run(args)
|
4
|
+
type = args.shift
|
5
|
+
name = args.shift
|
6
|
+
case type
|
7
|
+
when "interactor", "in"
|
8
|
+
GenerateInteractor.new(name).create
|
9
|
+
when "migration"
|
10
|
+
GenerateMigration.new(name).create
|
11
|
+
else
|
12
|
+
puts "Unknown generate type: #{type}"
|
13
|
+
exit(1)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Rsodx
|
2
|
+
module Cli
|
3
|
+
class GenerateInteractor
|
4
|
+
def initialize(name)
|
5
|
+
@name = name
|
6
|
+
@file_name = File.join("app/interactors", snake_case(name) + ".rb")
|
7
|
+
end
|
8
|
+
|
9
|
+
def create
|
10
|
+
puts "📦 Creating interactor: #{@file_name}"
|
11
|
+
FileUtils.mkdir_p("app/interactors")
|
12
|
+
File.write(@file_name, content)
|
13
|
+
end
|
14
|
+
|
15
|
+
def content
|
16
|
+
<<~RUBY
|
17
|
+
class #{@name} < AppInteractor
|
18
|
+
def call
|
19
|
+
# implement logic here
|
20
|
+
end
|
21
|
+
end
|
22
|
+
RUBY
|
23
|
+
end
|
24
|
+
|
25
|
+
def snake_case(str)
|
26
|
+
str.gsub(/::/, '/').
|
27
|
+
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
28
|
+
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
29
|
+
tr("-", "_").
|
30
|
+
downcase
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require "fileutils"
|
2
|
+
require "time"
|
3
|
+
|
4
|
+
module Rsodx::Cli
|
5
|
+
class GenerateMigration
|
6
|
+
def initialize(name)
|
7
|
+
@name = name
|
8
|
+
@timestamp = Time.now.utc.strftime("%Y%m%d%H%M%S")
|
9
|
+
@file_name = File.join("db/migrations", "#{@timestamp}_#{snake_case(name)}.rb")
|
10
|
+
end
|
11
|
+
|
12
|
+
def create
|
13
|
+
puts "📦 Creating migration: #{@file_name}"
|
14
|
+
FileUtils.mkdir_p("db/migrations")
|
15
|
+
File.write(@file_name, content)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def content
|
21
|
+
<<~RUBY
|
22
|
+
Sequel.migration do
|
23
|
+
change do
|
24
|
+
# create_table(:example) do
|
25
|
+
# primary_key :id
|
26
|
+
# String :name
|
27
|
+
# DateTime :created_at
|
28
|
+
# DateTime :updated_at
|
29
|
+
# end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
RUBY
|
33
|
+
end
|
34
|
+
|
35
|
+
def snake_case(str)
|
36
|
+
str.gsub(/::/, '/')
|
37
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/,'\\1_\\2')
|
38
|
+
.gsub(/([a-z\\d])([A-Z])/,'\\1_\\2')
|
39
|
+
.tr("-", "_")
|
40
|
+
.downcase
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require_relative "scaffold"
|
2
|
+
|
3
|
+
module Rsodx::Cli
|
4
|
+
class Handler
|
5
|
+
def self.run
|
6
|
+
command = ARGV.shift
|
7
|
+
case command
|
8
|
+
when "s", "server"
|
9
|
+
Rsodx::Cli::Server.run
|
10
|
+
when "generate", "g"
|
11
|
+
Rsodx::Cli::Generate.run(ARGV)
|
12
|
+
when "new"
|
13
|
+
name = ARGV.shift
|
14
|
+
if name.nil? || name.empty?
|
15
|
+
puts "Usage: rsodx new <project_name>"
|
16
|
+
exit(1)
|
17
|
+
end
|
18
|
+
Scaffold.new(name).create
|
19
|
+
else
|
20
|
+
puts "Unknown command: #{command}"
|
21
|
+
puts "Usage: rsodx new <project_name>"
|
22
|
+
exit(1)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
module Rsodx::Cli
|
2
|
+
class Scaffold
|
3
|
+
attr_reader :name
|
4
|
+
|
5
|
+
RUBY_VERSION = "3.4.2".freeze
|
6
|
+
|
7
|
+
# path: "../rsodx"
|
8
|
+
GEMFILE = <<~GEMFILE.freeze
|
9
|
+
source "https://rubygems.org"
|
10
|
+
|
11
|
+
gem "rsodx"
|
12
|
+
gem "pg"
|
13
|
+
GEMFILE
|
14
|
+
|
15
|
+
BINRSODX = <<~BINRSODX.freeze
|
16
|
+
#!/usr/bin/env ruby
|
17
|
+
|
18
|
+
require "fileutils"
|
19
|
+
require "optparse"
|
20
|
+
require "rsodx"
|
21
|
+
|
22
|
+
if __FILE__ == $0
|
23
|
+
Rsodx::Cli::Handler.run
|
24
|
+
end
|
25
|
+
BINRSODX
|
26
|
+
|
27
|
+
CONSOLE = <<~CONSOLE.freeze
|
28
|
+
#!/usr/bin/env ruby
|
29
|
+
|
30
|
+
require "irb"
|
31
|
+
require "irb/completion"
|
32
|
+
|
33
|
+
def reload!
|
34
|
+
puts "🔄 Reloading..."
|
35
|
+
load File.expand_path("../config/environment.rb", __dir__)
|
36
|
+
end
|
37
|
+
|
38
|
+
require_relative "../config/environment"
|
39
|
+
|
40
|
+
puts "🔬 Welcome to Rsodx console (#{ENV['RACK_ENV'] || 'development'})"
|
41
|
+
puts "Tip: access Rsodx modules, models, interactors, etc."
|
42
|
+
|
43
|
+
IRB.start
|
44
|
+
CONSOLE
|
45
|
+
|
46
|
+
CONFIGRU = <<~RACK.freeze
|
47
|
+
require_relative "./app/app"
|
48
|
+
run App
|
49
|
+
RACK
|
50
|
+
|
51
|
+
ROUTE = <<~ROUTE.freeze
|
52
|
+
class Router < Rsodx::Router
|
53
|
+
get "/healthcheck", Rsodx::Interactors::Healthcheck
|
54
|
+
end
|
55
|
+
ROUTE
|
56
|
+
|
57
|
+
APP = <<~APP.freeze
|
58
|
+
require "rsodx"
|
59
|
+
require_relative "router"
|
60
|
+
|
61
|
+
class App < Rsodx::Base
|
62
|
+
use Router
|
63
|
+
end
|
64
|
+
APP
|
65
|
+
|
66
|
+
APP_INTERACTOR = <<~APP_INTERACTOR.freeze
|
67
|
+
class AppInteractor < Rsodx::Interactor
|
68
|
+
end
|
69
|
+
APP_INTERACTOR
|
70
|
+
|
71
|
+
RAKEFILE = <<~RAKEFILE.freeze
|
72
|
+
require_relative "config/environment"
|
73
|
+
require "rsodx/tasks"
|
74
|
+
RAKEFILE
|
75
|
+
|
76
|
+
ENVFILE = <<~ENVFILE.freeze
|
77
|
+
DATABASE_URL=postgres://user:password@localhost:5432/base_development
|
78
|
+
ENVFILE
|
79
|
+
|
80
|
+
FOLDERS = %w[app/api app/interactors app/models app/presenters
|
81
|
+
app/serializers config/initializers
|
82
|
+
config/environments db/migrations spec bin].freeze
|
83
|
+
|
84
|
+
def initialize(name)
|
85
|
+
@name = name
|
86
|
+
@app_path = File.join(Dir.pwd, name)
|
87
|
+
end
|
88
|
+
|
89
|
+
def create
|
90
|
+
puts "Creating project: #{name}"
|
91
|
+
create_folders
|
92
|
+
create_files
|
93
|
+
puts "✅ Done!"
|
94
|
+
end
|
95
|
+
|
96
|
+
def create_folders
|
97
|
+
FOLDERS.each do |path|
|
98
|
+
FileUtils.mkdir_p(File.join(@app_path, path))
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def create_files
|
103
|
+
write("Gemfile", GEMFILE)
|
104
|
+
write(".ruby-version", RUBY_VERSION)
|
105
|
+
write(".env", ENVFILE)
|
106
|
+
write("config/environment.rb", env_loader)
|
107
|
+
write("Rakefile", RAKEFILE)
|
108
|
+
write("app/interactors/app_interactor.rb", APP_INTERACTOR)
|
109
|
+
write("app/app.rb", APP)
|
110
|
+
write("app/router.rb", ROUTE)
|
111
|
+
write("config/environments/development.rb", "")
|
112
|
+
write("config.ru", CONFIGRU)
|
113
|
+
write("bin/console", CONSOLE)
|
114
|
+
write("bin/rsodx", BINRSODX)
|
115
|
+
FileUtils.chmod("+x", File.join(@app_path, "bin/console"))
|
116
|
+
end
|
117
|
+
|
118
|
+
def write(relative_path, content)
|
119
|
+
full_path = File.join(@app_path, relative_path)
|
120
|
+
File.write(full_path, content)
|
121
|
+
end
|
122
|
+
|
123
|
+
def env_loader
|
124
|
+
<<~RB
|
125
|
+
require "rsodx"
|
126
|
+
Rsodx::Environment.load_dotenv(ENV["RACK_ENV"] || "development")
|
127
|
+
Rsodx::Connect.connect ENV["DATABASE_URL"]
|
128
|
+
Rsodx::Environment.load_initializers(File.expand_path("../..", __FILE__))
|
129
|
+
Rsodx::Boot.load_app_structure(File.expand_path("../..", __FILE__))
|
130
|
+
RB
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require "rack/handler/puma"
|
2
|
+
module Rsodx::Cli
|
3
|
+
class Server
|
4
|
+
def self.run
|
5
|
+
pid = spawn("bundle exec rake server")
|
6
|
+
Process.wait(pid)
|
7
|
+
puts "🚀 Starting Rsodx server PID: #{pid} at http://localhost:9292"
|
8
|
+
rescue LoadError => e
|
9
|
+
abort "❌ Error #{e.message}"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Rsodx
|
2
|
+
module Environment
|
3
|
+
def self.load_dotenv(env = nil)
|
4
|
+
require "dotenv"
|
5
|
+
Dotenv.load('.env', ".env.#{env}") if env
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.load_initializers(config_path)
|
9
|
+
Dir[File.join(config_path, "initializers", "*.rb")].sort.each { |file| require file }
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
data/lib/rsodx/error.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
require "interactor"
|
2
|
+
require "json"
|
3
|
+
module Rsodx
|
4
|
+
class Interactor
|
5
|
+
include ::Interactor
|
6
|
+
|
7
|
+
class << self
|
8
|
+
attr_accessor :contract_class
|
9
|
+
|
10
|
+
def contract(&block)
|
11
|
+
before do
|
12
|
+
self.class.contract_class ||= Class.new(Rsodx::Contract, &block)
|
13
|
+
contract = self.class.contract_class.new.call(context.params.to_h)
|
14
|
+
|
15
|
+
if contract.failure?
|
16
|
+
halt(400, contract.errors.to_h)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def halt(code, message)
|
23
|
+
log_error(code, message)
|
24
|
+
context.fail!(error_code: code, error: Rsodx::Error::ContractError.new(message))
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def log_error(code, message)
|
30
|
+
puts JSON.pretty_generate({
|
31
|
+
level: "error",
|
32
|
+
code: code,
|
33
|
+
message: message,
|
34
|
+
context: context.to_h,
|
35
|
+
backtrace: caller
|
36
|
+
})
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/rsodx/router.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'json'
|
2
|
+
require_relative "router_dsl"
|
3
|
+
|
4
|
+
module Rsodx
|
5
|
+
class Router < Base
|
6
|
+
include RouterDSL
|
7
|
+
set :show_exceptions, false
|
8
|
+
set :raise_errors, true
|
9
|
+
|
10
|
+
before do
|
11
|
+
content_type :json
|
12
|
+
end
|
13
|
+
|
14
|
+
error do
|
15
|
+
status 500
|
16
|
+
{ error: env['sinatra.error'].message }.to_json
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Rsodx
|
4
|
+
module RouterDSL
|
5
|
+
def self.included(base)
|
6
|
+
base.extend(ClassMethods)
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
def route(verb, path, interactor, opts = {})
|
11
|
+
define_route(verb, path) do
|
12
|
+
payload = begin
|
13
|
+
request.body.rewind
|
14
|
+
JSON.parse(request.body.read, symbolize_names: true)
|
15
|
+
rescue JSON::ParserError
|
16
|
+
{}
|
17
|
+
end
|
18
|
+
|
19
|
+
ctx = {
|
20
|
+
params: params.to_h,
|
21
|
+
json: payload
|
22
|
+
}.merge(opts)
|
23
|
+
|
24
|
+
result = interactor.call(ctx)
|
25
|
+
|
26
|
+
status(result.error_code || 200)
|
27
|
+
|
28
|
+
if result.success?
|
29
|
+
{ success: true, data: result.data }.to_json
|
30
|
+
else
|
31
|
+
{ success: false, error: result.error }.to_json
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
%i[get post put patch delete].each do |verb|
|
37
|
+
define_method(verb) do |path, interactor = nil, **opts, &block|
|
38
|
+
if interactor
|
39
|
+
route(verb, path, interactor, opts)
|
40
|
+
else
|
41
|
+
super(path, &block)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def namespace(prefix, &block)
|
47
|
+
raise ArgumentError, "Block required for namespace" unless block_given?
|
48
|
+
previous = @namespace_prefix || ""
|
49
|
+
@namespace_prefix = previous + prefix
|
50
|
+
class_eval(&block)
|
51
|
+
@namespace_prefix = previous
|
52
|
+
end
|
53
|
+
|
54
|
+
def define_route(method, path, *args, &block)
|
55
|
+
route_block = block || args.pop
|
56
|
+
full_path = (@namespace_prefix || "") + path
|
57
|
+
Sinatra::Base.send(method.downcase, full_path, *args, &route_block)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
data/lib/rsodx/tasks.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
return unless defined?(Rake::DSL) rescue false
|
2
|
+
include Rake::DSL if defined?(Rake::DSL)
|
3
|
+
|
4
|
+
require "sequel/extensions/migration"
|
5
|
+
|
6
|
+
desc "Запуск Puma-сервера"
|
7
|
+
task :server do
|
8
|
+
sh "bundle exec puma -p 9292 config.ru"
|
9
|
+
end
|
10
|
+
|
11
|
+
namespace :db do
|
12
|
+
task :migrate do
|
13
|
+
Sequel::Migrator.run(DB.connect, "db/migrations")
|
14
|
+
end
|
15
|
+
|
16
|
+
task :rollback do
|
17
|
+
target = Sequel::Migrator.apply(DB.connect, "db/migrations", :down) - 1
|
18
|
+
Sequel::Migrator.run(DB.connect, "db/migrations", target: target)
|
19
|
+
end
|
20
|
+
end
|
data/lib/rsodx.rb
ADDED
data/spec/rsodx_spec.rb
ADDED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rsodx"
|
4
|
+
|
5
|
+
RSpec.configure do |config|
|
6
|
+
# Enable flags like --only-failures and --next-failure
|
7
|
+
config.example_status_persistence_file_path = ".rspec_status"
|
8
|
+
|
9
|
+
# Disable RSpec exposing methods globally on `Module` and `main`
|
10
|
+
config.disable_monkey_patching!
|
11
|
+
|
12
|
+
config.expect_with :rspec do |c|
|
13
|
+
c.syntax = :expect
|
14
|
+
end
|
15
|
+
end
|
metadata
ADDED
@@ -0,0 +1,240 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rsodx
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Eugene Pervushin
|
8
|
+
bindir: bin
|
9
|
+
cert_chain: []
|
10
|
+
date: 2025-04-14 00:00:00.000000000 Z
|
11
|
+
dependencies:
|
12
|
+
- !ruby/object:Gem::Dependency
|
13
|
+
name: rake
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - "~>"
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: '13.1'
|
19
|
+
type: :runtime
|
20
|
+
prerelease: false
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
requirements:
|
23
|
+
- - "~>"
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: '13.1'
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: sinatra
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
29
|
+
requirements:
|
30
|
+
- - "~>"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '4.1'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '4.1'
|
40
|
+
- !ruby/object:Gem::Dependency
|
41
|
+
name: sequel
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '5.91'
|
47
|
+
type: :runtime
|
48
|
+
prerelease: false
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '5.91'
|
54
|
+
- !ruby/object:Gem::Dependency
|
55
|
+
name: pg
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - "~>"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '1.5'
|
61
|
+
type: :runtime
|
62
|
+
prerelease: false
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - "~>"
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '1.5'
|
68
|
+
- !ruby/object:Gem::Dependency
|
69
|
+
name: dry-validation
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - "~>"
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '1.11'
|
75
|
+
type: :runtime
|
76
|
+
prerelease: false
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - "~>"
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '1.11'
|
82
|
+
- !ruby/object:Gem::Dependency
|
83
|
+
name: interactor
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - "~>"
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '3.1'
|
89
|
+
type: :runtime
|
90
|
+
prerelease: false
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - "~>"
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '3.1'
|
96
|
+
- !ruby/object:Gem::Dependency
|
97
|
+
name: dotenv
|
98
|
+
requirement: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - "~>"
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '3.1'
|
103
|
+
type: :runtime
|
104
|
+
prerelease: false
|
105
|
+
version_requirements: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - "~>"
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '3.1'
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: puma
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
requirements:
|
114
|
+
- - "~>"
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '6.4'
|
117
|
+
type: :runtime
|
118
|
+
prerelease: false
|
119
|
+
version_requirements: !ruby/object:Gem::Requirement
|
120
|
+
requirements:
|
121
|
+
- - "~>"
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: '6.4'
|
124
|
+
- !ruby/object:Gem::Dependency
|
125
|
+
name: json
|
126
|
+
requirement: !ruby/object:Gem::Requirement
|
127
|
+
requirements:
|
128
|
+
- - "~>"
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: '2.7'
|
131
|
+
type: :runtime
|
132
|
+
prerelease: false
|
133
|
+
version_requirements: !ruby/object:Gem::Requirement
|
134
|
+
requirements:
|
135
|
+
- - "~>"
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
version: '2.7'
|
138
|
+
- !ruby/object:Gem::Dependency
|
139
|
+
name: ostruct
|
140
|
+
requirement: !ruby/object:Gem::Requirement
|
141
|
+
requirements:
|
142
|
+
- - "~>"
|
143
|
+
- !ruby/object:Gem::Version
|
144
|
+
version: '0.5'
|
145
|
+
type: :runtime
|
146
|
+
prerelease: false
|
147
|
+
version_requirements: !ruby/object:Gem::Requirement
|
148
|
+
requirements:
|
149
|
+
- - "~>"
|
150
|
+
- !ruby/object:Gem::Version
|
151
|
+
version: '0.5'
|
152
|
+
- !ruby/object:Gem::Dependency
|
153
|
+
name: rspec
|
154
|
+
requirement: !ruby/object:Gem::Requirement
|
155
|
+
requirements:
|
156
|
+
- - "~>"
|
157
|
+
- !ruby/object:Gem::Version
|
158
|
+
version: '3.0'
|
159
|
+
type: :development
|
160
|
+
prerelease: false
|
161
|
+
version_requirements: !ruby/object:Gem::Requirement
|
162
|
+
requirements:
|
163
|
+
- - "~>"
|
164
|
+
- !ruby/object:Gem::Version
|
165
|
+
version: '3.0'
|
166
|
+
- !ruby/object:Gem::Dependency
|
167
|
+
name: rubocop
|
168
|
+
requirement: !ruby/object:Gem::Requirement
|
169
|
+
requirements:
|
170
|
+
- - "~>"
|
171
|
+
- !ruby/object:Gem::Version
|
172
|
+
version: '1.75'
|
173
|
+
type: :development
|
174
|
+
prerelease: false
|
175
|
+
version_requirements: !ruby/object:Gem::Requirement
|
176
|
+
requirements:
|
177
|
+
- - "~>"
|
178
|
+
- !ruby/object:Gem::Version
|
179
|
+
version: '1.75'
|
180
|
+
description: Rsodx is a lightweight Ruby microframework designed for modular, service-oriented
|
181
|
+
applications. Built with simplicity and performance in mind, it's perfect for small
|
182
|
+
web apps, microservices, and CLI tools.
|
183
|
+
email:
|
184
|
+
- sodium229897@gmail.com
|
185
|
+
executables:
|
186
|
+
- rsodx
|
187
|
+
extensions: []
|
188
|
+
extra_rdoc_files: []
|
189
|
+
files:
|
190
|
+
- LICENSE.txt
|
191
|
+
- README.md
|
192
|
+
- bin/console
|
193
|
+
- bin/rsodx
|
194
|
+
- bin/setup
|
195
|
+
- lib/rsodx.rb
|
196
|
+
- lib/rsodx/base.rb
|
197
|
+
- lib/rsodx/boot.rb
|
198
|
+
- lib/rsodx/cli/generate.rb
|
199
|
+
- lib/rsodx/cli/generate_interactor.rb
|
200
|
+
- lib/rsodx/cli/generate_migration.rb
|
201
|
+
- lib/rsodx/cli/handler.rb
|
202
|
+
- lib/rsodx/cli/scaffold.rb
|
203
|
+
- lib/rsodx/cli/server.rb
|
204
|
+
- lib/rsodx/connect.rb
|
205
|
+
- lib/rsodx/contract.rb
|
206
|
+
- lib/rsodx/environment.rb
|
207
|
+
- lib/rsodx/error.rb
|
208
|
+
- lib/rsodx/interactor.rb
|
209
|
+
- lib/rsodx/interactors/healthcheck.rb
|
210
|
+
- lib/rsodx/router.rb
|
211
|
+
- lib/rsodx/router_dsl.rb
|
212
|
+
- lib/rsodx/tasks.rb
|
213
|
+
- lib/rsodx/version.rb
|
214
|
+
- spec/rsodx_spec.rb
|
215
|
+
- spec/spec_helper.rb
|
216
|
+
homepage: https://github.com/eugene-ruby/rsodx
|
217
|
+
licenses:
|
218
|
+
- MIT
|
219
|
+
metadata:
|
220
|
+
allowed_push_host: https://rubygems.org
|
221
|
+
source_code_uri: https://github.com/eugene-ruby/rsodx
|
222
|
+
changelog_uri: https://github.com/eugene-ruby/rsodx/blob/main/CHANGELOG.md
|
223
|
+
rdoc_options: []
|
224
|
+
require_paths:
|
225
|
+
- lib
|
226
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
227
|
+
requirements:
|
228
|
+
- - ">="
|
229
|
+
- !ruby/object:Gem::Version
|
230
|
+
version: 3.4.0
|
231
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
232
|
+
requirements:
|
233
|
+
- - ">="
|
234
|
+
- !ruby/object:Gem::Version
|
235
|
+
version: '0'
|
236
|
+
requirements: []
|
237
|
+
rubygems_version: 3.6.2
|
238
|
+
specification_version: 4
|
239
|
+
summary: Minimal Ruby toolkit for clean, modular service-based apps
|
240
|
+
test_files: []
|