whoosh 1.5.0 → 1.7.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.
@@ -0,0 +1,292 @@
1
+ # lib/whoosh/client_gen/fallback_backend.rb
2
+ # frozen_string_literal: true
3
+
4
+ require "fileutils"
5
+
6
+ module Whoosh
7
+ module ClientGen
8
+ class FallbackBackend
9
+ def self.generate(root: Dir.pwd, oauth: false)
10
+ new(root: root, oauth: oauth).generate
11
+ end
12
+
13
+ def initialize(root:, oauth:)
14
+ @root = root
15
+ @oauth = oauth
16
+ end
17
+
18
+ def generate
19
+ generate_schemas
20
+ generate_auth_endpoint
21
+ generate_tasks_endpoint
22
+ generate_migrations
23
+ end
24
+
25
+ private
26
+
27
+ def write_file(relative_path, content)
28
+ path = File.join(@root, relative_path)
29
+ FileUtils.mkdir_p(File.dirname(path))
30
+ File.write(path, content)
31
+ end
32
+
33
+ def generate_schemas
34
+ write_file("schemas/auth_schemas.rb", auth_schemas_content)
35
+ write_file("schemas/task_schemas.rb", task_schemas_content)
36
+ end
37
+
38
+ def generate_auth_endpoint
39
+ write_file("endpoints/auth_endpoint.rb", auth_endpoint_content)
40
+ end
41
+
42
+ def generate_tasks_endpoint
43
+ write_file("endpoints/tasks_endpoint.rb", tasks_endpoint_content)
44
+ end
45
+
46
+ def generate_migrations
47
+ timestamp = Time.now.strftime("%Y%m%d%H%M%S")
48
+ write_file("db/migrations/#{timestamp}_create_users.rb", create_users_migration)
49
+ write_file("db/migrations/#{timestamp.to_i + 1}_create_tasks.rb", create_tasks_migration)
50
+ end
51
+
52
+ def auth_schemas_content
53
+ <<~RUBY
54
+ # frozen_string_literal: true
55
+
56
+ class LoginRequest < Whoosh::Schema
57
+ field :email, String, required: true
58
+ field :password, String, required: true
59
+ end
60
+
61
+ class RegisterRequest < Whoosh::Schema
62
+ field :name, String, required: true
63
+ field :email, String, required: true
64
+ field :password, String, required: true, min_length: 8
65
+ end
66
+
67
+ class TokenResponse < Whoosh::Schema
68
+ field :token, String, required: true
69
+ field :refresh_token, String, required: true
70
+ end
71
+
72
+ class UserResponse < Whoosh::Schema
73
+ field :id, Integer, required: true
74
+ field :name, String, required: true
75
+ field :email, String, required: true
76
+ end
77
+ RUBY
78
+ end
79
+
80
+ def task_schemas_content
81
+ <<~RUBY
82
+ # frozen_string_literal: true
83
+
84
+ class CreateTaskRequest < Whoosh::Schema
85
+ field :title, String, required: true
86
+ field :description, String
87
+ field :status, String, enum: %w[pending in_progress completed]
88
+ field :due_date, String
89
+ end
90
+
91
+ class UpdateTaskRequest < Whoosh::Schema
92
+ field :title, String
93
+ field :description, String
94
+ field :status, String, enum: %w[pending in_progress completed]
95
+ field :due_date, String
96
+ end
97
+
98
+ class TaskResponse < Whoosh::Schema
99
+ field :id, Integer, required: true
100
+ field :user_id, Integer, required: true
101
+ field :title, String, required: true
102
+ field :description, String
103
+ field :status, String, required: true
104
+ field :due_date, String
105
+ field :created_at, String, required: true
106
+ field :updated_at, String, required: true
107
+ end
108
+ RUBY
109
+ end
110
+
111
+ def auth_endpoint_content
112
+ oauth_routes = if @oauth
113
+ <<~'RUBY'
114
+
115
+ App.get "/auth/:provider", auth: false do |req, db:|
116
+ provider = req.params[:provider]
117
+ redirect_url = App.oauth.authorize_url(provider: provider)
118
+ { redirect_url: redirect_url }
119
+ end
120
+
121
+ App.get "/auth/:provider/callback", auth: false do |req, db:|
122
+ provider = req.params[:provider]
123
+ user_info = App.oauth.handle_callback(provider: provider, params: req.params)
124
+ user = db[:users].where(email: user_info[:email]).first
125
+ unless user
126
+ user = { name: user_info[:name], email: user_info[:email], password_hash: nil }
127
+ user[:id] = db[:users].insert(user.merge(created_at: Time.now, updated_at: Time.now))
128
+ end
129
+ token = App.jwt.generate(sub: user[:id], email: user[:email])
130
+ refresh = App.jwt.generate(sub: user[:id], type: :refresh)
131
+ { token: token, refresh_token: refresh }
132
+ end
133
+ RUBY
134
+ else
135
+ ""
136
+ end
137
+
138
+ <<~RUBY
139
+ # frozen_string_literal: true
140
+
141
+ App.post "/auth/register", auth: false do |req, db:|
142
+ data = RegisterRequest.validate!(req.body)
143
+ existing = db[:users].where(email: data[:email]).first
144
+ raise Whoosh::Errors::ValidationError, "Email already registered" if existing
145
+
146
+ password_hash = BCrypt::Password.create(data[:password])
147
+ user_id = db[:users].insert(
148
+ name: data[:name],
149
+ email: data[:email],
150
+ password_hash: password_hash,
151
+ created_at: Time.now,
152
+ updated_at: Time.now
153
+ )
154
+ token = App.jwt.generate(sub: user_id, email: data[:email])
155
+ refresh = App.jwt.generate(sub: user_id, type: :refresh)
156
+ { token: token, refresh_token: refresh }
157
+ end
158
+
159
+ App.post "/auth/login", auth: false do |req, db:|
160
+ data = LoginRequest.validate!(req.body)
161
+ user = db[:users].where(email: data[:email]).first
162
+ raise Whoosh::Errors::UnauthorizedError, "Invalid credentials" unless user
163
+
164
+ stored = BCrypt::Password.new(user[:password_hash])
165
+ raise Whoosh::Errors::UnauthorizedError, "Invalid credentials" unless stored == data[:password]
166
+
167
+ token = App.jwt.generate(sub: user[:id], email: user[:email])
168
+ refresh = App.jwt.generate(sub: user[:id], type: :refresh)
169
+ { token: token, refresh_token: refresh }
170
+ end
171
+
172
+ App.post "/auth/refresh", auth: :jwt do |req, db:|
173
+ user = db[:users].where(id: req.current_user[:sub]).first
174
+ raise Whoosh::Errors::UnauthorizedError, "User not found" unless user
175
+
176
+ token = App.jwt.generate(sub: user[:id], email: user[:email])
177
+ refresh = App.jwt.generate(sub: user[:id], type: :refresh)
178
+ { token: token, refresh_token: refresh }
179
+ end
180
+
181
+ App.delete "/auth/logout", auth: :jwt do |req, db:|
182
+ App.jwt.revoke(req.token) if App.jwt.respond_to?(:revoke)
183
+ { message: "Logged out successfully" }
184
+ end
185
+
186
+ App.get "/auth/me", auth: :jwt do |req, db:|
187
+ user = db[:users].where(id: req.current_user[:sub]).first
188
+ raise Whoosh::Errors::NotFoundError, "User not found" unless user
189
+
190
+ { id: user[:id], name: user[:name], email: user[:email] }
191
+ end
192
+ #{oauth_routes}
193
+ RUBY
194
+ end
195
+
196
+ def tasks_endpoint_content
197
+ <<~RUBY
198
+ # frozen_string_literal: true
199
+
200
+ App.get "/tasks", auth: :jwt do |req, db:|
201
+ dataset = db[:tasks].where(user_id: req.current_user[:sub])
202
+ paginate_cursor(dataset, cursor: req.params[:cursor], limit: req.params.fetch(:limit, 20).to_i)
203
+ end
204
+
205
+ App.get "/tasks/:id", auth: :jwt do |req, db:|
206
+ task = db[:tasks].where(id: req.params[:id], user_id: req.current_user[:sub]).first
207
+ raise Whoosh::Errors::NotFoundError, "Task not found" unless task
208
+
209
+ task
210
+ end
211
+
212
+ App.post "/tasks", auth: :jwt do |req, db:|
213
+ data = CreateTaskRequest.validate!(req.body)
214
+ task_id = db[:tasks].insert(
215
+ user_id: req.current_user[:sub],
216
+ title: data[:title],
217
+ description: data[:description],
218
+ status: data.fetch(:status, "pending"),
219
+ due_date: data[:due_date],
220
+ created_at: Time.now,
221
+ updated_at: Time.now
222
+ )
223
+ db[:tasks].where(id: task_id).first
224
+ end
225
+
226
+ App.put "/tasks/:id", auth: :jwt do |req, db:|
227
+ task = db[:tasks].where(id: req.params[:id], user_id: req.current_user[:sub]).first
228
+ raise Whoosh::Errors::NotFoundError, "Task not found" unless task
229
+
230
+ data = UpdateTaskRequest.validate!(req.body)
231
+ updates = data.compact.merge(updated_at: Time.now)
232
+ db[:tasks].where(id: req.params[:id]).update(updates)
233
+ db[:tasks].where(id: req.params[:id]).first
234
+ end
235
+
236
+ App.delete "/tasks/:id", auth: :jwt do |req, db:|
237
+ task = db[:tasks].where(id: req.params[:id], user_id: req.current_user[:sub]).first
238
+ raise Whoosh::Errors::NotFoundError, "Task not found" unless task
239
+
240
+ db[:tasks].where(id: req.params[:id]).delete
241
+ { message: "Task deleted" }
242
+ end
243
+ RUBY
244
+ end
245
+
246
+ def create_users_migration
247
+ <<~RUBY
248
+ # frozen_string_literal: true
249
+
250
+ Sequel.migration do
251
+ change do
252
+ create_table(:users) do
253
+ primary_key :id
254
+ String :name, null: false
255
+ String :email, null: false
256
+ String :password_hash
257
+ DateTime :created_at, null: false
258
+ DateTime :updated_at, null: false
259
+
260
+ index :email, unique: true
261
+ end
262
+ end
263
+ end
264
+ RUBY
265
+ end
266
+
267
+ def create_tasks_migration
268
+ <<~RUBY
269
+ # frozen_string_literal: true
270
+
271
+ Sequel.migration do
272
+ change do
273
+ create_table(:tasks) do
274
+ primary_key :id
275
+ foreign_key :user_id, :users, null: false
276
+ String :title, null: false
277
+ Text :description
278
+ String :status, default: "pending"
279
+ Date :due_date
280
+ DateTime :created_at, null: false
281
+ DateTime :updated_at, null: false
282
+
283
+ index :user_id
284
+ index :status
285
+ end
286
+ end
287
+ end
288
+ RUBY
289
+ end
290
+ end
291
+ end
292
+ end