stipa 0.1.7 → 0.2.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 +4 -4
- data/README.md +119 -2
- data/lib/stipa/app.rb +4 -4
- data/lib/stipa/database.rb +132 -0
- data/lib/stipa/generators/api.rb +21 -1
- data/lib/stipa/generators/base.rb +120 -0
- data/lib/stipa/generators/vue.rb +17 -0
- data/lib/stipa/{connection.rb → http/connection.rb} +2 -0
- data/lib/stipa/model/pagination.rb +28 -0
- data/lib/stipa/model/serialization.rb +25 -0
- data/lib/stipa/model/soft_delete.rb +35 -0
- data/lib/stipa/model/timestamps.rb +14 -0
- data/lib/stipa/model/uuid.rb +17 -0
- data/lib/stipa/model.rb +19 -0
- data/lib/stipa/{server.rb → server/server.rb} +4 -4
- data/lib/stipa/version.rb +1 -1
- data/lib/stipa.rb +21 -20
- metadata +18 -11
- /data/lib/stipa/{request.rb → http/request.rb} +0 -0
- /data/lib/stipa/{response.rb → http/response.rb} +0 -0
- /data/lib/stipa/{middleware.rb → middleware/stack.rb} +0 -0
- /data/lib/stipa/{static.rb → middleware/static.rb} +0 -0
- /data/lib/stipa/{reloader.rb → server/reloader.rb} +0 -0
- /data/lib/stipa/{thread_pool.rb → server/thread_pool.rb} +0 -0
- /data/lib/stipa/{template.rb → template/template.rb} +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f091e5c4ee4aa36a0ebeb498b1ad781be0bb0d0f3423c5c1b242ff1dc11754f0
|
|
4
|
+
data.tar.gz: 2b97f2bc8eaa3a8cfe1ccacab9a030209b88ba44dbd76f84cbc99ba56530ca52
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cac5a86b535550c92811bbff7db3715109e736e32d1eab7e809b2da14d448e768c4a9ee2a5e1b9a15f9ad07dc4949dd369314dc644ddb071aebc4e84bd7926fc
|
|
7
|
+
data.tar.gz: 83dc705b0d41a4616609bb2a8469511a8cb6ef46e12c8acc4372eb6875bf031fc49a9b62707ca6ce698dfc8b7f7660e37970df631df93b2131dba4ed3cace838
|
data/README.md
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
- **Middleware stack** compiled once at startup — zero per-request overhead
|
|
16
16
|
- **ERB template engine** with layouts, partials, and Vue 3 island helpers
|
|
17
17
|
- **CLI generator** — `stipa new myapp` scaffolds a full MVC app with Vue + TypeScript
|
|
18
|
+
- **Database layer** — optional Sequel integration with migrations, models, and connection management
|
|
18
19
|
|
|
19
20
|
---
|
|
20
21
|
|
|
@@ -73,15 +74,21 @@ Generated structure (`--vue`):
|
|
|
73
74
|
myapp/
|
|
74
75
|
├── server.rb # entry point
|
|
75
76
|
├── Gemfile
|
|
77
|
+
├── Rakefile # db:migrate, db:rollback, db:version
|
|
76
78
|
├── package.json # rollup + vue + typescript
|
|
77
79
|
├── rollup.config.js
|
|
78
80
|
├── tsconfig.json
|
|
79
|
-
├──
|
|
80
|
-
│ ├── config/
|
|
81
|
+
├── app/
|
|
82
|
+
│ ├── config/
|
|
83
|
+
│ │ ├── database.rb # DATABASE_URL, Sequel settings
|
|
84
|
+
│ │ └── routes.rb
|
|
81
85
|
│ ├── controllers/
|
|
82
86
|
│ ├── models/
|
|
87
|
+
│ │ └── application_model.rb # base model with Stipa::Model
|
|
83
88
|
│ ├── views/
|
|
84
89
|
│ └── components/ # Vue SFC source (.vue, .ts)
|
|
90
|
+
├── db/
|
|
91
|
+
│ └── migrate/ # Sequel migrations
|
|
85
92
|
└── public/
|
|
86
93
|
├── stipa-vue.js
|
|
87
94
|
├── app.css
|
|
@@ -324,6 +331,116 @@ Handles `SIGTERM` / `SIGINT` with graceful drain.
|
|
|
324
331
|
|
|
325
332
|
---
|
|
326
333
|
|
|
334
|
+
## Database
|
|
335
|
+
|
|
336
|
+
Optional Sequel integration. Add to your Gemfile:
|
|
337
|
+
|
|
338
|
+
```ruby
|
|
339
|
+
gem 'sequel'
|
|
340
|
+
gem 'pg' # or mysql2, sqlite3
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
Configure via `DATABASE_URL`:
|
|
344
|
+
|
|
345
|
+
```bash
|
|
346
|
+
# .env (development / test)
|
|
347
|
+
DATABASE_URL=postgres://user:password@localhost:5432/myapp
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
```ruby
|
|
351
|
+
require 'stipa/database'
|
|
352
|
+
|
|
353
|
+
Stipa::Database.connect!
|
|
354
|
+
DB = Stipa::Database.connection
|
|
355
|
+
|
|
356
|
+
# health check
|
|
357
|
+
Stipa::Database.healthy? # => true
|
|
358
|
+
|
|
359
|
+
# transactions
|
|
360
|
+
Stipa::Database.transaction { DB[:posts].insert(title: 'Hello') }
|
|
361
|
+
|
|
362
|
+
# shutdown
|
|
363
|
+
Stipa::Database.disconnect!
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
### Migrations
|
|
367
|
+
|
|
368
|
+
```bash
|
|
369
|
+
rake db:migrate # run pending migrations
|
|
370
|
+
rake db:rollback # rollback last migration
|
|
371
|
+
rake db:version # show current version
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
Create a migration:
|
|
375
|
+
|
|
376
|
+
```ruby
|
|
377
|
+
# db/migrate/002_create_comments.rb
|
|
378
|
+
Sequel.migration do
|
|
379
|
+
change do
|
|
380
|
+
create_table :comments do
|
|
381
|
+
primary_key :id
|
|
382
|
+
foreign_key :post_id, null: false
|
|
383
|
+
String :body, text: true
|
|
384
|
+
DateTime :created_at, null: false
|
|
385
|
+
DateTime :updated_at, null: false
|
|
386
|
+
end
|
|
387
|
+
end
|
|
388
|
+
end
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
---
|
|
392
|
+
|
|
393
|
+
## Models
|
|
394
|
+
|
|
395
|
+
Models use `Stipa::Model` — a set of Sequel plugins for common patterns:
|
|
396
|
+
|
|
397
|
+
```ruby
|
|
398
|
+
require 'stipa/model'
|
|
399
|
+
|
|
400
|
+
class Post < ApplicationModel
|
|
401
|
+
# Included by default:
|
|
402
|
+
# - Timestamps (created_at / updated_at)
|
|
403
|
+
# - UUID (auto-generate UUID primary key)
|
|
404
|
+
# - SoftDelete (deleted_at scoping)
|
|
405
|
+
# - Serialization (to_hash, to_json, from_json)
|
|
406
|
+
|
|
407
|
+
plugin :validation_helpers
|
|
408
|
+
|
|
409
|
+
def validate
|
|
410
|
+
super
|
|
411
|
+
validates_presence [:title]
|
|
412
|
+
validates_unique [:slug]
|
|
413
|
+
end
|
|
414
|
+
end
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
### Standalone modules
|
|
418
|
+
|
|
419
|
+
Use individual modules without the full `Stipa::Model` bundle:
|
|
420
|
+
|
|
421
|
+
```ruby
|
|
422
|
+
class User < Sequel::Model
|
|
423
|
+
include Stipa::Model::UUID
|
|
424
|
+
include Stipa::Model::Pagination
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
# Pagination
|
|
428
|
+
result = User.paginate(page: 2, per_page: 10)
|
|
429
|
+
result[:records] # => [#<User>, ...]
|
|
430
|
+
result[:total] # => 150
|
|
431
|
+
result[:total_pages] # => 15
|
|
432
|
+
|
|
433
|
+
# Soft delete
|
|
434
|
+
user.soft_delete
|
|
435
|
+
user.deleted? # => true
|
|
436
|
+
User.all # => excludes soft-deleted
|
|
437
|
+
User.with_deleted # => includes all
|
|
438
|
+
User.only_deleted # => soft-deleted only
|
|
439
|
+
user.restore
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
---
|
|
443
|
+
|
|
327
444
|
## License
|
|
328
445
|
|
|
329
446
|
MIT
|
data/lib/stipa/app.rb
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
require_relative 'version'
|
|
2
2
|
require_relative 'logger'
|
|
3
|
-
require_relative 'server'
|
|
4
|
-
require_relative 'middleware'
|
|
5
|
-
require_relative 'request'
|
|
6
|
-
require_relative 'response'
|
|
3
|
+
require_relative 'server/server'
|
|
4
|
+
require_relative 'middleware/stack'
|
|
5
|
+
require_relative 'http/request'
|
|
6
|
+
require_relative 'http/response'
|
|
7
7
|
|
|
8
8
|
module Stipa
|
|
9
9
|
# User-facing DSL for defining routes and middleware.
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'sequel'
|
|
4
|
+
|
|
5
|
+
module Stipa
|
|
6
|
+
# Database connection manager.
|
|
7
|
+
#
|
|
8
|
+
# Usage:
|
|
9
|
+
# require 'stipa/database'
|
|
10
|
+
#
|
|
11
|
+
# Stipa::Database.connect! # reads DATABASE_URL from environment
|
|
12
|
+
# DB = Stipa::Database.connection
|
|
13
|
+
#
|
|
14
|
+
# Environment variables:
|
|
15
|
+
# DATABASE_URL — connection string (required)
|
|
16
|
+
# DATABASE_POOL — max connections (default: 5)
|
|
17
|
+
#
|
|
18
|
+
# Connection string examples:
|
|
19
|
+
# postgres://user:pass@localhost:5432/myapp
|
|
20
|
+
# mysql2://user:pass@localhost:3306/myapp
|
|
21
|
+
# sqlite://db/development.db
|
|
22
|
+
#
|
|
23
|
+
module Database
|
|
24
|
+
module_function
|
|
25
|
+
|
|
26
|
+
def connect!
|
|
27
|
+
return @connection if @connection
|
|
28
|
+
|
|
29
|
+
@connection = Sequel.connect(
|
|
30
|
+
ENV.fetch('DATABASE_URL'),
|
|
31
|
+
max_connections: Integer(ENV.fetch('DATABASE_POOL', '5')),
|
|
32
|
+
connect_timeout: 5,
|
|
33
|
+
test: true,
|
|
34
|
+
keep_reference: false,
|
|
35
|
+
after_connect: lambda do |connection|
|
|
36
|
+
connection.exec("SET TIME ZONE 'UTC'") if connection.respond_to?(:exec)
|
|
37
|
+
end
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
configure_connection!
|
|
41
|
+
Sequel::Model.db = @connection
|
|
42
|
+
@connection
|
|
43
|
+
rescue KeyError => e
|
|
44
|
+
raise "Missing database configuration: #{e.message}"
|
|
45
|
+
rescue ArgumentError => e
|
|
46
|
+
raise "Invalid database configuration: #{e.message}"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def connection
|
|
50
|
+
@connection || raise('Database.connect! must be called first')
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def connected?
|
|
54
|
+
!@connection.nil?
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def healthy?
|
|
58
|
+
connection.get(Sequel.lit('SELECT 1')) == 1
|
|
59
|
+
rescue Sequel::Error
|
|
60
|
+
false
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def disconnect!
|
|
64
|
+
@connection&.disconnect
|
|
65
|
+
@connection = nil
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def transaction(&block)
|
|
69
|
+
connection.transaction(&block)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# -------------------------------------------------------------------
|
|
73
|
+
# Database-specific extensions
|
|
74
|
+
# -------------------------------------------------------------------
|
|
75
|
+
#
|
|
76
|
+
# The defaults below are tuned for PostgreSQL. If you're using a
|
|
77
|
+
# different adapter, comment out or remove the lines that don't apply.
|
|
78
|
+
#
|
|
79
|
+
# ── PostgreSQL ──────────────────────────────────────────────────────
|
|
80
|
+
# No changes needed — the lines below work out of the box.
|
|
81
|
+
#
|
|
82
|
+
# ── MySQL / MariaDB ─────────────────────────────────────────────────
|
|
83
|
+
# Replace the body of this method with:
|
|
84
|
+
#
|
|
85
|
+
# def configure_connection!
|
|
86
|
+
# return unless @connection.adapter_scheme == :mysql
|
|
87
|
+
#
|
|
88
|
+
# @connection.extension :pg_json # not available — remove
|
|
89
|
+
# @connection.extension :pg_array # not available — remove
|
|
90
|
+
#
|
|
91
|
+
# Sequel.extension :pg_json_ops # not available — remove
|
|
92
|
+
# Sequel.extension :pg_array_ops # not available — remove
|
|
93
|
+
# end
|
|
94
|
+
#
|
|
95
|
+
# ── SQLite ──────────────────────────────────────────────────────────
|
|
96
|
+
# Replace the body of this method with:
|
|
97
|
+
#
|
|
98
|
+
# def configure_connection!
|
|
99
|
+
# return unless @connection.adapter_scheme == :sqlite
|
|
100
|
+
#
|
|
101
|
+
# @connection.extension :pagination # optional: simple pagination
|
|
102
|
+
# end
|
|
103
|
+
#
|
|
104
|
+
# ── Generic / adapter-agnostic ──────────────────────────────────────
|
|
105
|
+
# If you want a safe default that works with any adapter:
|
|
106
|
+
#
|
|
107
|
+
# def configure_connection!
|
|
108
|
+
# # No adapter-specific extensions.
|
|
109
|
+
# # Add extensions conditionally based on adapter_scheme:
|
|
110
|
+
# #
|
|
111
|
+
# # case @connection.adapter_scheme
|
|
112
|
+
# # when :postgres
|
|
113
|
+
# # @connection.extension :pg_json
|
|
114
|
+
# # @connection.extension :pg_array
|
|
115
|
+
# # when :sqlite
|
|
116
|
+
# # @connection.extension :pagination
|
|
117
|
+
# # end
|
|
118
|
+
# end
|
|
119
|
+
#
|
|
120
|
+
def configure_connection!
|
|
121
|
+
return unless @connection.adapter_scheme == :postgres
|
|
122
|
+
|
|
123
|
+
@connection.extension :pg_json
|
|
124
|
+
@connection.extension :pg_array
|
|
125
|
+
|
|
126
|
+
Sequel.extension :pg_json_ops
|
|
127
|
+
Sequel.extension :pg_array_ops
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
private_class_method :configure_connection!
|
|
131
|
+
end
|
|
132
|
+
end
|
data/lib/stipa/generators/api.rb
CHANGED
|
@@ -8,7 +8,7 @@ module Stipa
|
|
|
8
8
|
def template_name = 'api'
|
|
9
9
|
|
|
10
10
|
def dirs
|
|
11
|
-
%w[config controllers]
|
|
11
|
+
%w[config controllers models db/migrate]
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
def done_message
|
|
@@ -25,13 +25,17 @@ module Stipa
|
|
|
25
25
|
{
|
|
26
26
|
'.gitignore' => t_gitignore,
|
|
27
27
|
'Gemfile' => t_gemfile,
|
|
28
|
+
'Rakefile' => t_rakefile,
|
|
28
29
|
'server.rb' => t_server,
|
|
30
|
+
'config/database.rb' => t_database_config,
|
|
29
31
|
'config/routes.rb' => t_routes(
|
|
30
32
|
extra_requires: ['../controllers/health_controller'],
|
|
31
33
|
extra_routes: ["get '/health', to: 'health#show'"],
|
|
32
34
|
),
|
|
33
35
|
'controllers/application_controller.rb' => t_application_controller,
|
|
34
36
|
'controllers/health_controller.rb' => t_health_controller,
|
|
37
|
+
'models/application_model.rb' => t_application_model,
|
|
38
|
+
'db/migrate/001_create_posts.rb' => t_migration_create_posts,
|
|
35
39
|
}
|
|
36
40
|
end
|
|
37
41
|
|
|
@@ -42,8 +46,16 @@ module Stipa
|
|
|
42
46
|
def t_server
|
|
43
47
|
<<~RUBY
|
|
44
48
|
require 'stipa'
|
|
49
|
+
require 'stipa/database'
|
|
50
|
+
|
|
51
|
+
require_relative 'config/database'
|
|
45
52
|
require_relative 'config/routes'
|
|
46
53
|
|
|
54
|
+
Stipa::Database.connect!
|
|
55
|
+
|
|
56
|
+
# Models must be loaded after the database connection is established.
|
|
57
|
+
require_relative 'models/application_model'
|
|
58
|
+
|
|
47
59
|
app = Stipa::App.new
|
|
48
60
|
|
|
49
61
|
app.use Stipa::Middleware::RequestId
|
|
@@ -52,6 +64,14 @@ module Stipa
|
|
|
52
64
|
|
|
53
65
|
Routes.draw(app)
|
|
54
66
|
|
|
67
|
+
app.get '/api/health' do |_req, res|
|
|
68
|
+
res.json({ status: 'ok', framework: 'Stipa', version: Stipa::VERSION, ts: Time.now.utc.iso8601 })
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
at_exit do
|
|
72
|
+
Stipa::Database.disconnect!
|
|
73
|
+
end
|
|
74
|
+
|
|
55
75
|
app.start(host: '127.0.0.1', port: 3710)
|
|
56
76
|
RUBY
|
|
57
77
|
end
|
|
@@ -87,6 +87,14 @@ module Stipa
|
|
|
87
87
|
source 'https://rubygems.org'
|
|
88
88
|
|
|
89
89
|
gem 'stipa'
|
|
90
|
+
gem 'rake'
|
|
91
|
+
gem 'sequel'
|
|
92
|
+
gem 'dotenv'
|
|
93
|
+
|
|
94
|
+
# Database adapter — uncomment the one you need:
|
|
95
|
+
# gem 'pg' # PostgreSQL
|
|
96
|
+
# gem 'mysql2' # MySQL / MariaDB
|
|
97
|
+
# gem 'sqlite3' # SQLite
|
|
90
98
|
RUBY
|
|
91
99
|
end
|
|
92
100
|
|
|
@@ -148,6 +156,118 @@ module Stipa
|
|
|
148
156
|
end
|
|
149
157
|
RUBY
|
|
150
158
|
end
|
|
159
|
+
|
|
160
|
+
def t_database_config
|
|
161
|
+
<<~RUBY
|
|
162
|
+
# frozen_string_literal: true
|
|
163
|
+
|
|
164
|
+
environment = ENV.fetch('APP_ENV', 'development')
|
|
165
|
+
|
|
166
|
+
case environment
|
|
167
|
+
when 'development', 'test'
|
|
168
|
+
require 'dotenv/load'
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Set DATABASE_URL in your .env file (development/test) or environment (production).
|
|
172
|
+
#
|
|
173
|
+
# PostgreSQL:
|
|
174
|
+
# DATABASE_URL=postgres://user:password@localhost:5432/myapp
|
|
175
|
+
#
|
|
176
|
+
# MySQL:
|
|
177
|
+
# DATABASE_URL=mysql2://user:password@localhost:3306/myapp
|
|
178
|
+
#
|
|
179
|
+
# SQLite:
|
|
180
|
+
# DATABASE_URL=sqlite://db/development.db
|
|
181
|
+
|
|
182
|
+
Sequel.database_timezone = :utc
|
|
183
|
+
Sequel.application_timezone = :local
|
|
184
|
+
RUBY
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def t_application_model
|
|
188
|
+
<<~RUBY
|
|
189
|
+
# frozen_string_literal: true
|
|
190
|
+
|
|
191
|
+
require 'stipa/model'
|
|
192
|
+
|
|
193
|
+
class ApplicationModel < Sequel::Model
|
|
194
|
+
include Stipa::Model
|
|
195
|
+
|
|
196
|
+
def self.dataset
|
|
197
|
+
super
|
|
198
|
+
rescue Sequel::Error => e
|
|
199
|
+
raise "Database not ready: \#{e.message}"
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
RUBY
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def t_migration_create_posts
|
|
206
|
+
<<~RUBY
|
|
207
|
+
# frozen_string_literal: true
|
|
208
|
+
|
|
209
|
+
Sequel.migration do
|
|
210
|
+
change do
|
|
211
|
+
create_table :posts do
|
|
212
|
+
primary_key :id
|
|
213
|
+
String :title, null: false
|
|
214
|
+
String :body, text: true
|
|
215
|
+
String :slug, null: false
|
|
216
|
+
DateTime :created_at, null: false
|
|
217
|
+
DateTime :updated_at, null: false
|
|
218
|
+
|
|
219
|
+
index :slug, unique: true
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
RUBY
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def t_rakefile
|
|
227
|
+
<<~RUBY
|
|
228
|
+
# frozen_string_literal: true
|
|
229
|
+
|
|
230
|
+
require 'rake'
|
|
231
|
+
require 'sequel'
|
|
232
|
+
require 'sequel/extensions/migration'
|
|
233
|
+
require 'stipa/database'
|
|
234
|
+
|
|
235
|
+
namespace :db do
|
|
236
|
+
desc 'Run pending migrations'
|
|
237
|
+
task :migrate do
|
|
238
|
+
load_config
|
|
239
|
+
Sequel::Migrator.run(Stipa::Database.connection, 'db/migrate')
|
|
240
|
+
puts "Migrated to \#{Stipa::Database.connection[:schema_info].first[:version]}"
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
desc 'Rollback the last migration'
|
|
244
|
+
task :rollback do
|
|
245
|
+
load_config
|
|
246
|
+
Sequel::Migrator.run(Stipa::Database.connection, 'db/migrate', target: current_version - 1)
|
|
247
|
+
puts "Rolled back to \#{current_version}"
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
desc 'Show current migration version'
|
|
251
|
+
task :version do
|
|
252
|
+
load_config
|
|
253
|
+
puts "Current version: \#{current_version}"
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
task default: 'db:migrate'
|
|
258
|
+
|
|
259
|
+
def load_config
|
|
260
|
+
require_relative 'config/database'
|
|
261
|
+
Stipa::Database.connect!
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
def current_version
|
|
265
|
+
Stipa::Database.connection[:schema_info].first[:version]
|
|
266
|
+
rescue
|
|
267
|
+
0
|
|
268
|
+
end
|
|
269
|
+
RUBY
|
|
270
|
+
end
|
|
151
271
|
end
|
|
152
272
|
end
|
|
153
273
|
end
|
data/lib/stipa/generators/vue.rb
CHANGED
|
@@ -15,6 +15,7 @@ module Stipa
|
|
|
15
15
|
app/views/layouts
|
|
16
16
|
app/views/home
|
|
17
17
|
app/components
|
|
18
|
+
db/migrate
|
|
18
19
|
public/vendor
|
|
19
20
|
]
|
|
20
21
|
end
|
|
@@ -55,10 +56,12 @@ module Stipa
|
|
|
55
56
|
{
|
|
56
57
|
'.gitignore' => t_gitignore,
|
|
57
58
|
'Gemfile' => t_gemfile,
|
|
59
|
+
'Rakefile' => t_rakefile,
|
|
58
60
|
'package.json' => t_package_json,
|
|
59
61
|
'rollup.config.js' => t_rollup_config,
|
|
60
62
|
'tsconfig.json' => t_tsconfig,
|
|
61
63
|
'server.rb' => t_server,
|
|
64
|
+
'app/config/database.rb' => t_database_config,
|
|
62
65
|
'app/config/routes.rb' => t_routes(
|
|
63
66
|
extra_requires: ['../controllers/home_controller', '../controllers/health_controller'],
|
|
64
67
|
extra_routes: ["get '/', to: 'home#index'", "get '/api/health', to: 'health#show'"],
|
|
@@ -67,6 +70,8 @@ module Stipa
|
|
|
67
70
|
'app/controllers/application_controller.rb' => t_application_controller,
|
|
68
71
|
'app/controllers/home_controller.rb' => t_home_controller,
|
|
69
72
|
'app/controllers/health_controller.rb' => t_health_controller,
|
|
73
|
+
'app/models/application_model.rb' => t_application_model,
|
|
74
|
+
'db/migrate/001_create_posts.rb' => t_migration_create_posts,
|
|
70
75
|
'app/views/layouts/application.html.erb' => t_layout,
|
|
71
76
|
'app/views/home/index.html.erb' => t_home_index,
|
|
72
77
|
'public/app.css' => t_app_css,
|
|
@@ -151,8 +156,16 @@ module Stipa
|
|
|
151
156
|
def t_server
|
|
152
157
|
<<~RUBY
|
|
153
158
|
require 'stipa'
|
|
159
|
+
require 'stipa/database'
|
|
160
|
+
|
|
161
|
+
require_relative 'app/config/database'
|
|
154
162
|
require_relative 'app/config/routes'
|
|
155
163
|
|
|
164
|
+
Stipa::Database.connect!
|
|
165
|
+
|
|
166
|
+
# Models must be loaded after the database connection is established.
|
|
167
|
+
require_relative 'app/models/application_model'
|
|
168
|
+
|
|
156
169
|
APP_DIR = __dir__
|
|
157
170
|
|
|
158
171
|
app = Stipa::App.new(
|
|
@@ -169,6 +182,10 @@ module Stipa
|
|
|
169
182
|
res.json({ status: 'ok', framework: 'Stipa', version: Stipa::VERSION, ts: Time.now.utc.iso8601 })
|
|
170
183
|
end
|
|
171
184
|
|
|
185
|
+
at_exit do
|
|
186
|
+
Stipa::Database.disconnect!
|
|
187
|
+
end
|
|
188
|
+
|
|
172
189
|
app.start(host: '127.0.0.1', port: 3710)
|
|
173
190
|
RUBY
|
|
174
191
|
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module Stipa
|
|
2
|
+
module Model
|
|
3
|
+
module Pagination
|
|
4
|
+
def self.included(base)
|
|
5
|
+
base.extend(ClassMethods)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
module ClassMethods
|
|
9
|
+
def paginate(page: 1, per_page: 20)
|
|
10
|
+
page = [page.to_i, 1].max
|
|
11
|
+
per_page = [per_page.to_i, 1].max
|
|
12
|
+
|
|
13
|
+
total = count
|
|
14
|
+
records = limit(per_page).offset((page - 1) * per_page).all
|
|
15
|
+
total_pages = (total.to_f / per_page).ceil
|
|
16
|
+
|
|
17
|
+
{
|
|
18
|
+
records: records,
|
|
19
|
+
page: page,
|
|
20
|
+
per_page: per_page,
|
|
21
|
+
total: total,
|
|
22
|
+
total_pages: total_pages,
|
|
23
|
+
}
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
|
|
3
|
+
module Stipa
|
|
4
|
+
module Model
|
|
5
|
+
module Serialization
|
|
6
|
+
def to_hash
|
|
7
|
+
columns.each_with_object({}) do |col, h|
|
|
8
|
+
h[col] = send(col)
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def to_json(*args)
|
|
13
|
+
to_hash.to_json(*args)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.included(base)
|
|
17
|
+
base.instance_eval do
|
|
18
|
+
def from_json(json)
|
|
19
|
+
new(JSON.parse(json))
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
module Stipa
|
|
2
|
+
module Model
|
|
3
|
+
module SoftDelete
|
|
4
|
+
def self.included(base)
|
|
5
|
+
base.instance_eval do
|
|
6
|
+
dataset_module do
|
|
7
|
+
def deleted
|
|
8
|
+
where(deleted_at: !nil)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def not_deleted
|
|
12
|
+
where(deleted_at: nil)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.dataset
|
|
17
|
+
super.not_deleted
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def soft_delete
|
|
23
|
+
update(deleted_at: Time.now.utc)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def restore
|
|
27
|
+
update(deleted_at: nil)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def deleted?
|
|
31
|
+
!deleted_at.nil?
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
data/lib/stipa/model.rb
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
require_relative 'model/uuid'
|
|
2
|
+
require_relative 'model/soft_delete'
|
|
3
|
+
require_relative 'model/serialization'
|
|
4
|
+
require_relative 'model/timestamps'
|
|
5
|
+
require_relative 'model/pagination'
|
|
6
|
+
|
|
7
|
+
module Stipa
|
|
8
|
+
module Model
|
|
9
|
+
def self.included(base)
|
|
10
|
+
base.plugin :validation_helpers
|
|
11
|
+
base.plugin :dirty
|
|
12
|
+
|
|
13
|
+
base.include Timestamps
|
|
14
|
+
base.include UUID
|
|
15
|
+
base.include SoftDelete
|
|
16
|
+
base.include Serialization
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
require 'socket'
|
|
2
2
|
require_relative 'thread_pool'
|
|
3
|
-
require_relative 'connection'
|
|
4
|
-
require_relative 'request'
|
|
5
|
-
require_relative 'response'
|
|
6
|
-
require_relative 'logger'
|
|
3
|
+
require_relative '../http/connection'
|
|
4
|
+
require_relative '../http/request'
|
|
5
|
+
require_relative '../http/response'
|
|
6
|
+
require_relative '../logger'
|
|
7
7
|
|
|
8
8
|
module Stipa
|
|
9
9
|
# TCP accept loop and connection lifecycle manager.
|
data/lib/stipa/version.rb
CHANGED
data/lib/stipa.rb
CHANGED
|
@@ -5,27 +5,28 @@
|
|
|
5
5
|
# loads the entire framework.
|
|
6
6
|
#
|
|
7
7
|
# Dependency order:
|
|
8
|
-
# version
|
|
9
|
-
# logger
|
|
10
|
-
# thread_pool
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
8
|
+
# version — no deps
|
|
9
|
+
# logger — no deps
|
|
10
|
+
# server/thread_pool — no deps
|
|
11
|
+
# server/reloader — no deps
|
|
12
|
+
# middleware/stack — no deps
|
|
13
|
+
# middleware/static — depends on middleware/stack
|
|
14
|
+
# template/template — no deps (stdlib only: erb, json)
|
|
15
|
+
# http/request — no deps
|
|
16
|
+
# http/response — depends on template (via render helper)
|
|
17
|
+
# http/connection — depends on http/request, http/response
|
|
18
|
+
# server/server — depends on server/thread_pool, http/connection
|
|
19
|
+
# app — depends on server/server, middleware, template, http/request, http/response
|
|
19
20
|
|
|
20
21
|
require_relative 'stipa/version'
|
|
21
22
|
require_relative 'stipa/logger'
|
|
22
|
-
require_relative 'stipa/thread_pool'
|
|
23
|
-
require_relative 'stipa/reloader'
|
|
24
|
-
require_relative 'stipa/middleware'
|
|
25
|
-
require_relative 'stipa/static'
|
|
26
|
-
require_relative 'stipa/template'
|
|
27
|
-
require_relative 'stipa/request'
|
|
28
|
-
require_relative 'stipa/response'
|
|
29
|
-
require_relative 'stipa/connection'
|
|
30
|
-
require_relative 'stipa/server'
|
|
23
|
+
require_relative 'stipa/server/thread_pool'
|
|
24
|
+
require_relative 'stipa/server/reloader'
|
|
25
|
+
require_relative 'stipa/middleware/stack'
|
|
26
|
+
require_relative 'stipa/middleware/static'
|
|
27
|
+
require_relative 'stipa/template/template'
|
|
28
|
+
require_relative 'stipa/http/request'
|
|
29
|
+
require_relative 'stipa/http/response'
|
|
30
|
+
require_relative 'stipa/http/connection'
|
|
31
|
+
require_relative 'stipa/server/server'
|
|
31
32
|
require_relative 'stipa/app'
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: stipa
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1
|
|
4
|
+
version: 0.2.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jānis Harbs
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-07-26 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: minitest
|
|
@@ -93,20 +93,27 @@ files:
|
|
|
93
93
|
- lib/stipa.rb
|
|
94
94
|
- lib/stipa/app.rb
|
|
95
95
|
- lib/stipa/cli.rb
|
|
96
|
-
- lib/stipa/
|
|
96
|
+
- lib/stipa/database.rb
|
|
97
97
|
- lib/stipa/generator.rb
|
|
98
98
|
- lib/stipa/generators/api.rb
|
|
99
99
|
- lib/stipa/generators/base.rb
|
|
100
100
|
- lib/stipa/generators/vue.rb
|
|
101
|
+
- lib/stipa/http/connection.rb
|
|
102
|
+
- lib/stipa/http/request.rb
|
|
103
|
+
- lib/stipa/http/response.rb
|
|
101
104
|
- lib/stipa/logger.rb
|
|
102
|
-
- lib/stipa/middleware.rb
|
|
103
|
-
- lib/stipa/
|
|
104
|
-
- lib/stipa/
|
|
105
|
-
- lib/stipa/
|
|
106
|
-
- lib/stipa/
|
|
107
|
-
- lib/stipa/
|
|
108
|
-
- lib/stipa/
|
|
109
|
-
- lib/stipa/
|
|
105
|
+
- lib/stipa/middleware/stack.rb
|
|
106
|
+
- lib/stipa/middleware/static.rb
|
|
107
|
+
- lib/stipa/model.rb
|
|
108
|
+
- lib/stipa/model/pagination.rb
|
|
109
|
+
- lib/stipa/model/serialization.rb
|
|
110
|
+
- lib/stipa/model/soft_delete.rb
|
|
111
|
+
- lib/stipa/model/timestamps.rb
|
|
112
|
+
- lib/stipa/model/uuid.rb
|
|
113
|
+
- lib/stipa/server/reloader.rb
|
|
114
|
+
- lib/stipa/server/server.rb
|
|
115
|
+
- lib/stipa/server/thread_pool.rb
|
|
116
|
+
- lib/stipa/template/template.rb
|
|
110
117
|
- lib/stipa/version.rb
|
|
111
118
|
- media/favicon.ico
|
|
112
119
|
- media/logo.png
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|