stipa 0.2.3 → 0.2.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 629ad04a4c8bcd7193a81038be6845f90d9e01b229bfbc0b2d1cb7931359ac06
4
- data.tar.gz: 7d57a8f7839381d2abfe434221cc58de4a8bddf945d7f55ced2c7c9f272c6bef
3
+ metadata.gz: c13ca91809cf6a4b578b9336b9588f4f544055dab765021a54c822f55281291d
4
+ data.tar.gz: ac20150e5e8528532aebe21cf8f1b6354101cf6a92deeb1fe760cd813ef28841
5
5
  SHA512:
6
- metadata.gz: d96ce9f31e87e046095910f784526cc9a7eb7ca98e5af626b07dd668a12c0eb46e7bab0bd60950788ca924ea75aea08b97b981e38b4ba6615edb2dfbaf72661c
7
- data.tar.gz: 74e83a3380253c27ef9eddbd4cdba82b3a7658b9261a4dc5ad2b2a58a75cd5820799f9a6ba0cd2512f485ec38c5cb7b354dd07368769c34dc2ab214bee89fc7a
6
+ metadata.gz: 84e7a0fd600288d87b0ae3c69510028d39d89f4ce2da788624682b39a608b10b2c43fb071cfbbc59f95199c35376cb9acbf32e13d1cf12651fb40d2c39a0cbe0
7
+ data.tar.gz: 2097c4e81b7fc64049a4bb073c7f0ddb704c2e0916d2f0254214cfffbe904ad16b10b40ddaa1ecc0d16ed5a02b9cf8576b6fc6da511e96894c2ef2cc87b02b74
@@ -35,6 +35,7 @@ module Stipa
35
35
  'controllers/application_controller.rb' => t_application_controller,
36
36
  'controllers/health_controller.rb' => t_health_controller,
37
37
  'models/application_model.rb' => t_application_model,
38
+ 'models/post.rb' => t_sample_model,
38
39
  'db/migrate/001_create_posts.rb' => t_migration_create_posts,
39
40
  }
40
41
  end
@@ -92,20 +93,68 @@ module Stipa
92
93
 
93
94
  private
94
95
 
96
+ # ── Success helpers ──────────────────────────────
97
+
95
98
  def json(data, status: 200)
96
99
  res.status = status
97
100
  res.json(data)
98
101
  end
99
102
 
100
- def not_found!(message = 'Not found')
101
- res.status = 404
102
- res.json(error: message)
103
- throw :halt
103
+ def created!(data = nil)
104
+ res.status = 201
105
+ data ? res.json(data) : res.tap { _1.body = '' }
106
+ end
107
+
108
+ def no_content!
109
+ res.status = 204
110
+ res.body = ''
111
+ end
112
+
113
+ # ── Redirection ──────────────────────────────────
114
+
115
+ def redirect_to(path, status: 302)
116
+ res.status = status
117
+ res['Location'] = path
118
+ res.body = ''
104
119
  end
105
120
 
106
- def unprocessable!(errors)
107
- res.status = 422
108
- res.json(errors: errors)
121
+ # ── Error helpers ────────────────────────────────
122
+
123
+ def bad_request!(message = 'Bad Request')
124
+ error(400, message)
125
+ end
126
+
127
+ def unauthorized!(message = 'Unauthorized')
128
+ error(401, message)
129
+ end
130
+
131
+ def forbidden!(message = 'Forbidden')
132
+ error(403, message)
133
+ end
134
+
135
+ def not_found!(message = 'Not Found')
136
+ error(404, message)
137
+ end
138
+
139
+ def conflict!(message = 'Conflict')
140
+ error(409, message)
141
+ end
142
+
143
+ def unprocessable_entity!(message = 'Unprocessable Entity')
144
+ error(422, message)
145
+ end
146
+
147
+ def too_many_requests!(message = 'Too Many Requests')
148
+ error(429, message)
149
+ end
150
+
151
+ def internal_server_error!(message = 'Internal Server Error')
152
+ error(500, message)
153
+ end
154
+
155
+ def error(status, message)
156
+ res.status = status
157
+ res.json(error: { message: message })
109
158
  throw :halt
110
159
  end
111
160
 
@@ -5,7 +5,7 @@ require 'pathname'
5
5
  module Stipa
6
6
  module Generators
7
7
  class Base
8
- GEM_JS = File.expand_path('../../js', __dir__)
8
+ GEM_JS = File.expand_path('../../js', __dir__)
9
9
  GEM_MEDIA = File.expand_path('../../../media', __dir__)
10
10
  GEM_ROOT = File.expand_path('../..', GEM_JS)
11
11
 
@@ -51,7 +51,7 @@ module Stipa
51
51
  system('git', 'commit', '-q', '-m', 'Initial commit')
52
52
  end
53
53
  say ' git initialized repository with initial commit'
54
- rescue => e
54
+ rescue StandardError => e
55
55
  say " warn git init failed: #{e.message}"
56
56
  end
57
57
 
@@ -150,7 +150,7 @@ module Stipa
150
150
  %w[get post put patch delete].each do |verb|
151
151
  define_method(verb) do |pattern, to:|
152
152
  klass, action = resolve(to)
153
- @app.public_send(verb, pattern) { |req, res| klass.new(req, res).public_send(action) }
153
+ @app.public_send(verb, pattern) { |req, res| catch(:halt) { klass.new(req, res).public_send(action) } }
154
154
  end
155
155
  end
156
156
  end
@@ -188,19 +188,45 @@ module Stipa
188
188
  <<~RUBY
189
189
  # frozen_string_literal: true
190
190
 
191
+ Sequel::Model.require_valid_table = false
192
+
191
193
  require 'stipa/model'
192
194
 
193
- # Include this in any Sequel::Model subclass to get
194
- # timestamps, UUID, soft delete, and serialization.
195
+ # Base model class providing timestamps, UUID, soft delete,
196
+ # serialization, dirty tracking, and validation helpers.
195
197
  #
196
198
  # Usage:
197
- # class Post < Sequel::Model
198
- # include ApplicationModel
199
+ # class Car < ApplicationModel
199
200
  # end
200
201
 
201
- module ApplicationModel
202
- def self.included(base)
203
- base.include Stipa::Model
202
+ class ApplicationModel < Sequel::Model
203
+ include Stipa::Model
204
+
205
+ # Ensure every subclass maps to its own table (e.g. Car → cars),
206
+ # not to the parent's abstract +application_models+ table.
207
+ def self.inherited(subclass)
208
+ super
209
+ subclass.set_dataset(subclass.implicit_table_name) unless subclass.name.to_s.empty?
210
+ end
211
+ end
212
+
213
+ Sequel::Model.require_valid_table = true
214
+ RUBY
215
+ end
216
+
217
+ def t_sample_model
218
+ <<~RUBY
219
+ # frozen_string_literal: true
220
+
221
+ class Post < ApplicationModel
222
+ def validate
223
+ super
224
+ errors.add(:title, 'is required') if title.to_s.strip.empty?
225
+ errors.add(:slug, 'is required') if slug.to_s.strip.empty?
226
+ end
227
+
228
+ def to_param
229
+ slug
204
230
  end
205
231
  end
206
232
  RUBY
@@ -21,7 +21,7 @@ module Stipa
21
21
  end
22
22
 
23
23
  def post_generate
24
- copy_asset 'stipa-vue.js', from: GEM_JS
24
+ copy_asset 'stipa-vue.js', from: GEM_JS
25
25
  copy_asset 'logo.png', from: GEM_MEDIA
26
26
  copy_asset 'favicon.ico', from: GEM_MEDIA
27
27
  end
@@ -54,30 +54,31 @@ module Stipa
54
54
 
55
55
  def files
56
56
  {
57
- '.gitignore' => t_gitignore,
58
- 'Gemfile' => t_gemfile,
59
- 'Rakefile' => t_rakefile(config_path: 'app/config'),
60
- 'package.json' => t_package_json,
61
- 'rollup.config.js' => t_rollup_config,
62
- 'tsconfig.json' => t_tsconfig,
63
- 'server.rb' => t_server,
64
- 'app/config/database.rb' => t_database_config,
65
- 'app/config/routes.rb' => t_routes(
57
+ '.gitignore' => t_gitignore,
58
+ 'Gemfile' => t_gemfile,
59
+ 'Rakefile' => t_rakefile(config_path: 'app/config'),
60
+ 'package.json' => t_package_json,
61
+ 'rollup.config.js' => t_rollup_config,
62
+ 'tsconfig.json' => t_tsconfig,
63
+ 'server.rb' => t_server,
64
+ 'app/config/database.rb' => t_database_config,
65
+ 'app/config/routes.rb' => t_routes(
66
66
  extra_requires: ['../controllers/home_controller', '../controllers/health_controller'],
67
- extra_routes: ["get '/', to: 'home#index'", "get '/api/health', to: 'health#show'"],
68
- method_override: true,
67
+ extra_routes: ["get '/', to: 'home#index'", "get '/api/health', to: 'health#show'"],
68
+ method_override: true
69
69
  ),
70
- 'app/controllers/application_controller.rb' => t_application_controller,
71
- 'app/controllers/home_controller.rb' => t_home_controller,
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,
75
- 'app/views/layouts/application.html.erb' => t_layout,
76
- 'app/views/home/index.html.erb' => t_home_index,
77
- 'public/app.css' => t_app_css,
78
- 'app/components/RequestCard.vue' => t_request_card_vue,
79
- 'app/main.ts' => t_main_ts,
80
- 'app/shims-vue.d.ts' => t_shims_vue,
70
+ 'app/controllers/application_controller.rb' => t_application_controller,
71
+ 'app/controllers/home_controller.rb' => t_home_controller,
72
+ 'app/controllers/health_controller.rb' => t_health_controller,
73
+ 'app/models/application_model.rb' => t_application_model,
74
+ 'app/models/post.rb' => t_sample_model,
75
+ 'db/migrate/001_create_posts.rb' => t_migration_create_posts,
76
+ 'app/views/layouts/application.html.erb' => t_layout,
77
+ 'app/views/home/index.html.erb' => t_home_index,
78
+ 'public/app.css' => t_app_css,
79
+ 'app/components/RequestCard.vue' => t_request_card_vue,
80
+ 'app/main.ts' => t_main_ts,
81
+ 'app/shims-vue.d.ts' => t_shims_vue
81
82
  }
82
83
  end
83
84
 
@@ -101,22 +102,22 @@ module Stipa
101
102
  private: true,
102
103
  type: 'module',
103
104
  scripts: {
104
- 'copy:vue' => 'cp node_modules/vue/dist/vue.esm-browser.prod.js public/vendor/vue.esm-browser.prod.js',
105
- build: 'npm run copy:vue && rollup -c',
106
- watch: 'rollup -c --watch',
107
- dev: 'npm run copy:vue && concurrently "STIPA_RELOAD=1 bundle exec ruby server.rb" "rollup -c --watch"',
108
- typecheck: 'vue-tsc --noEmit',
105
+ 'copy:vue' => 'cp node_modules/vue/dist/vue.esm-browser.prod.js public/vendor/vue.esm-browser.prod.js',
106
+ build: 'npm run copy:vue && rollup -c',
107
+ watch: 'rollup -c --watch',
108
+ dev: 'npm run copy:vue && concurrently "STIPA_RELOAD=1 bundle exec ruby server.rb" "rollup -c --watch"',
109
+ typecheck: 'vue-tsc --noEmit'
109
110
  },
110
111
  devDependencies: {
111
- 'concurrently' => '^8.0.0',
112
- 'rollup' => '^4.0.0',
113
- 'rollup-plugin-vue' => '^6.0.0',
112
+ 'concurrently' => '^8.0.0',
113
+ 'rollup' => '^4.0.0',
114
+ 'rollup-plugin-vue' => '^6.0.0',
114
115
  '@rollup/plugin-typescript' => '^11.0.0',
115
- '@vue/compiler-sfc' => '^3.4.0',
116
- 'typescript' => '^5.0.0',
117
- 'vue' => '^3.4.0',
118
- 'vue-tsc' => '^2.0.0',
119
- },
116
+ '@vue/compiler-sfc' => '^3.4.0',
117
+ 'typescript' => '^5.0.0',
118
+ 'vue' => '^3.4.0',
119
+ 'vue-tsc' => '^2.0.0'
120
+ }
120
121
  ) + "\n"
121
122
  end
122
123
 
@@ -142,14 +143,14 @@ module Stipa
142
143
  def t_tsconfig
143
144
  JSON.pretty_generate(
144
145
  compilerOptions: {
145
- target: 'ESNext',
146
- module: 'ESNext',
146
+ target: 'ESNext',
147
+ module: 'ESNext',
147
148
  moduleResolution: 'bundler',
148
- strict: true,
149
- skipLibCheck: true,
150
- allowJs: true,
149
+ strict: true,
150
+ skipLibCheck: true,
151
+ allowJs: true
151
152
  },
152
- include: ['app/**/*'],
153
+ include: ['app/**/*']
153
154
  ) + "\n"
154
155
  end
155
156
 
@@ -209,15 +210,68 @@ module Stipa
209
210
  res.render(template, locals: locals, layout: layout)
210
211
  end
211
212
 
213
+ # ── Success helpers ──────────────────────────────
214
+
215
+ def json(data, status: 200)
216
+ res.status = status
217
+ res.json(data)
218
+ end
219
+
220
+ def created!(data = nil)
221
+ res.status = 201
222
+ data ? res.json(data) : res.tap { _1.body = '' }
223
+ end
224
+
225
+ def no_content!
226
+ res.status = 204
227
+ res.body = ''
228
+ end
229
+
230
+ # ── Redirection ──────────────────────────────────
231
+
212
232
  def redirect_to(path, status: 302)
213
233
  res.status = status
214
234
  res['Location'] = path
215
235
  res.body = ''
216
236
  end
217
237
 
238
+ # ── Error helpers ────────────────────────────────
239
+
240
+ def bad_request!(message = 'Bad Request')
241
+ error(400, message)
242
+ end
243
+
244
+ def unauthorized!(message = 'Unauthorized')
245
+ error(401, message)
246
+ end
247
+
248
+ def forbidden!(message = 'Forbidden')
249
+ error(403, message)
250
+ end
251
+
218
252
  def not_found!(message = 'Not Found')
219
- res.status = 404
220
- res.body = message
253
+ error(404, message)
254
+ end
255
+
256
+ def conflict!(message = 'Conflict')
257
+ error(409, message)
258
+ end
259
+
260
+ def unprocessable_entity!(message = 'Unprocessable Entity')
261
+ error(422, message)
262
+ end
263
+
264
+ def too_many_requests!(message = 'Too Many Requests')
265
+ error(429, message)
266
+ end
267
+
268
+ def internal_server_error!(message = 'Internal Server Error')
269
+ error(500, message)
270
+ end
271
+
272
+ def error(status, message)
273
+ res.status = status
274
+ res.json(error: { message: message })
221
275
  throw :halt
222
276
  end
223
277
 
@@ -237,6 +291,8 @@ module Stipa
237
291
  k, v = pair.split('=', 2)
238
292
  p[k.to_sym] = URI.decode_www_form_component(v.to_s)
239
293
  end
294
+ elsif req['content-type']&.include?('application/json')
295
+ JSON.parse(req.body).each { |k, v| p[k.to_sym] = v }
240
296
  end
241
297
 
242
298
  p
@@ -423,8 +479,6 @@ module Stipa
423
479
  CSS
424
480
  end
425
481
 
426
-
427
-
428
482
  def t_shims_vue
429
483
  <<~TS
430
484
  declare module '*.vue' {
@@ -4,8 +4,11 @@ module Stipa
4
4
  module Model
5
5
  module UUID
6
6
  def self.included(base)
7
- base.instance_eval do
8
- before_create { self.id ||= SecureRandom.uuid }
7
+ base.class_eval do
8
+ def before_create
9
+ self.id ||= SecureRandom.uuid
10
+ super
11
+ end
9
12
  end
10
13
  end
11
14
 
data/lib/stipa/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Stipa
2
- VERSION = '0.2.3'
2
+ VERSION = '0.2.5'
3
3
  end
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.2.3
4
+ version: 0.2.5
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-07-26 00:00:00.000000000 Z
11
+ date: 2026-07-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest