rapitapir 0.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/.rspec +3 -0
- data/.rubocop.yml +57 -0
- data/CHANGELOG.md +94 -0
- data/CLEANUP_SUMMARY.md +155 -0
- data/CONTRIBUTING.md +280 -0
- data/LICENSE +21 -0
- data/README.md +485 -0
- data/debug_hash.rb +20 -0
- data/docs/EXTENSION_COMPARISON.md +388 -0
- data/docs/SINATRA_EXTENSION.md +467 -0
- data/docs/archive/PHASE_1_2_COMPLETE.md +77 -0
- data/docs/archive/PHASE_1_3_COMPLETE.md +152 -0
- data/docs/archive/PHASE_2_1_OBSERVABILITY_COMPLETED.md +203 -0
- data/docs/archive/PHASE_2_SUMMARY.md +209 -0
- data/docs/archive/REFACTORING_SUMMARY.md +184 -0
- data/docs/archive/phase_1_3_plan.md +136 -0
- data/docs/archive/sinatra_extension_summary.md +188 -0
- data/docs/archive/sinatra_working_solution.md +113 -0
- data/docs/archive/typescript-client-generator-summary.md +259 -0
- data/docs/auto-derivation.md +146 -0
- data/docs/blueprint.md +1091 -0
- data/docs/endpoint-definition.md +211 -0
- data/docs/github_pages_fix.md +52 -0
- data/docs/github_pages_setup.md +49 -0
- data/docs/implementation-status.md +357 -0
- data/docs/observability.md +647 -0
- data/docs/phase3-plan.md +108 -0
- data/docs/sinatra_rapitapir.md +87 -0
- data/docs/type_shortcuts.md +146 -0
- data/examples/README_ENTERPRISE.md +202 -0
- data/examples/authentication_example.rb +192 -0
- data/examples/auto_derivation_ruby_friendly.rb +163 -0
- data/examples/cli/user_api_endpoints.rb +56 -0
- data/examples/client/typescript_client_example.rb +102 -0
- data/examples/client/user-api-client.ts +193 -0
- data/examples/demo_api.rb +41 -0
- data/examples/docs/documentation_example.rb +112 -0
- data/examples/docs/user-api-docs.html +789 -0
- data/examples/docs/user-api-docs.md +403 -0
- data/examples/enhanced_auto_derivation_test.rb +83 -0
- data/examples/enterprise_extension_demo.rb +417 -0
- data/examples/enterprise_rapitapir_api.rb +662 -0
- data/examples/getting_started_extension.rb +218 -0
- data/examples/hello_world.rb +74 -0
- data/examples/oauth2/.env.example +19 -0
- data/examples/oauth2/README.md +205 -0
- data/examples/oauth2/generic_oauth2_api.rb +226 -0
- data/examples/oauth2/get_token.rb +72 -0
- data/examples/oauth2/songs_api_with_auth0.rb +320 -0
- data/examples/oauth2/test_api.sh +16 -0
- data/examples/oauth2/test_songs_api.sh +110 -0
- data/examples/observability/.env.example +35 -0
- data/examples/observability/README.md +230 -0
- data/examples/observability/README_HONEYCOMB.md +332 -0
- data/examples/observability/advanced_setup.rb +384 -0
- data/examples/observability/basic_setup.rb +192 -0
- data/examples/observability/complete_test.rb +121 -0
- data/examples/observability/honeycomb_example.rb +523 -0
- data/examples/observability/honeycomb_rapitapir_clean.rb +488 -0
- data/examples/observability/honeycomb_rapitapir_example.rb +523 -0
- data/examples/observability/honeycomb_working_example.rb +489 -0
- data/examples/observability/quick_test.rb +78 -0
- data/examples/observability/simple_test.rb +14 -0
- data/examples/observability/test_honeycomb_demo.rb +354 -0
- data/examples/observability/test_live_honeycomb.rb +111 -0
- data/examples/observability/test_validation.rb +78 -0
- data/examples/observability/test_working_validation.rb +66 -0
- data/examples/openapi/user_api_schema.rb +132 -0
- data/examples/production_ready_example.rb +105 -0
- data/examples/rails/users_controller.rb +146 -0
- data/examples/readme/basic_sinatra_example.rb +128 -0
- data/examples/server/user_api.rb +179 -0
- data/examples/simple_auto_derivation_demo.rb +44 -0
- data/examples/simple_demo_api.rb +18 -0
- data/examples/sinatra/user_app.rb +127 -0
- data/examples/t_shortcut_demo.rb +59 -0
- data/examples/user_api.rb +190 -0
- data/examples/working_getting_started.rb +184 -0
- data/examples/working_simple_example.rb +195 -0
- data/lib/rapitapir/auth/configuration.rb +129 -0
- data/lib/rapitapir/auth/context.rb +122 -0
- data/lib/rapitapir/auth/errors.rb +104 -0
- data/lib/rapitapir/auth/middleware.rb +324 -0
- data/lib/rapitapir/auth/oauth2.rb +350 -0
- data/lib/rapitapir/auth/schemes.rb +420 -0
- data/lib/rapitapir/auth.rb +113 -0
- data/lib/rapitapir/cli/command.rb +535 -0
- data/lib/rapitapir/cli/server.rb +243 -0
- data/lib/rapitapir/cli/validator.rb +373 -0
- data/lib/rapitapir/client/generator_base.rb +272 -0
- data/lib/rapitapir/client/typescript_generator.rb +350 -0
- data/lib/rapitapir/core/endpoint.rb +158 -0
- data/lib/rapitapir/core/enhanced_endpoint.rb +235 -0
- data/lib/rapitapir/core/input.rb +182 -0
- data/lib/rapitapir/core/output.rb +164 -0
- data/lib/rapitapir/core/request.rb +19 -0
- data/lib/rapitapir/core/response.rb +17 -0
- data/lib/rapitapir/docs/html_generator.rb +780 -0
- data/lib/rapitapir/docs/markdown_generator.rb +464 -0
- data/lib/rapitapir/dsl/endpoint_dsl.rb +116 -0
- data/lib/rapitapir/dsl/enhanced_endpoint_dsl.rb +62 -0
- data/lib/rapitapir/dsl/enhanced_input.rb +73 -0
- data/lib/rapitapir/dsl/enhanced_output.rb +63 -0
- data/lib/rapitapir/dsl/enhanced_structures.rb +393 -0
- data/lib/rapitapir/dsl/fluent_dsl.rb +72 -0
- data/lib/rapitapir/dsl/fluent_endpoint_builder.rb +316 -0
- data/lib/rapitapir/dsl/http_verbs.rb +77 -0
- data/lib/rapitapir/dsl/input_methods.rb +47 -0
- data/lib/rapitapir/dsl/observability_methods.rb +81 -0
- data/lib/rapitapir/dsl/output_methods.rb +43 -0
- data/lib/rapitapir/dsl/type_resolution.rb +43 -0
- data/lib/rapitapir/observability/configuration.rb +108 -0
- data/lib/rapitapir/observability/health_check.rb +236 -0
- data/lib/rapitapir/observability/logging.rb +270 -0
- data/lib/rapitapir/observability/metrics.rb +203 -0
- data/lib/rapitapir/observability/middleware.rb +243 -0
- data/lib/rapitapir/observability/tracing.rb +143 -0
- data/lib/rapitapir/observability.rb +28 -0
- data/lib/rapitapir/openapi/schema_generator.rb +403 -0
- data/lib/rapitapir/schema.rb +136 -0
- data/lib/rapitapir/server/enhanced_rack_adapter.rb +379 -0
- data/lib/rapitapir/server/middleware.rb +120 -0
- data/lib/rapitapir/server/path_matcher.rb +45 -0
- data/lib/rapitapir/server/rack_adapter.rb +215 -0
- data/lib/rapitapir/server/rails_adapter.rb +17 -0
- data/lib/rapitapir/server/rails_adapter_class.rb +53 -0
- data/lib/rapitapir/server/rails_controller.rb +72 -0
- data/lib/rapitapir/server/rails_input_processor.rb +73 -0
- data/lib/rapitapir/server/rails_response_handler.rb +29 -0
- data/lib/rapitapir/server/sinatra_adapter.rb +200 -0
- data/lib/rapitapir/server/sinatra_integration.rb +93 -0
- data/lib/rapitapir/sinatra/configuration.rb +91 -0
- data/lib/rapitapir/sinatra/extension.rb +214 -0
- data/lib/rapitapir/sinatra/oauth2_helpers.rb +236 -0
- data/lib/rapitapir/sinatra/resource_builder.rb +152 -0
- data/lib/rapitapir/sinatra/swagger_ui_generator.rb +166 -0
- data/lib/rapitapir/sinatra_rapitapir.rb +40 -0
- data/lib/rapitapir/types/array.rb +163 -0
- data/lib/rapitapir/types/auto_derivation.rb +265 -0
- data/lib/rapitapir/types/base.rb +146 -0
- data/lib/rapitapir/types/boolean.rb +46 -0
- data/lib/rapitapir/types/date.rb +92 -0
- data/lib/rapitapir/types/datetime.rb +98 -0
- data/lib/rapitapir/types/email.rb +32 -0
- data/lib/rapitapir/types/float.rb +134 -0
- data/lib/rapitapir/types/hash.rb +161 -0
- data/lib/rapitapir/types/integer.rb +143 -0
- data/lib/rapitapir/types/object.rb +156 -0
- data/lib/rapitapir/types/optional.rb +65 -0
- data/lib/rapitapir/types/string.rb +185 -0
- data/lib/rapitapir/types/uuid.rb +32 -0
- data/lib/rapitapir/types.rb +155 -0
- data/lib/rapitapir/version.rb +5 -0
- data/lib/rapitapir.rb +173 -0
- data/rapitapir.gemspec +66 -0
- metadata +387 -0
@@ -0,0 +1,417 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Enterprise Task Management API - RapiTapir Sinatra Extension Demo
|
4
|
+
#
|
5
|
+
# This example demonstrates the new ergonomic Sinatra extension that eliminates
|
6
|
+
# boilerplate and provides a seamless developer experience for building
|
7
|
+
# enterprise-grade APIs with RapiTapir.
|
8
|
+
|
9
|
+
# Check for Sinatra availability
|
10
|
+
begin
|
11
|
+
require 'sinatra/base'
|
12
|
+
SINATRA_AVAILABLE = true
|
13
|
+
rescue LoadError
|
14
|
+
SINATRA_AVAILABLE = false
|
15
|
+
puts 'ā ļø Sinatra not available. Install with: gem install sinatra'
|
16
|
+
puts 'š Running in demo mode instead...'
|
17
|
+
end
|
18
|
+
|
19
|
+
require 'json'
|
20
|
+
require_relative '../lib/rapitapir'
|
21
|
+
|
22
|
+
# Only require extension if Sinatra is available
|
23
|
+
require_relative '../lib/rapitapir/sinatra/extension' if SINATRA_AVAILABLE
|
24
|
+
|
25
|
+
# Sample databases (same as before)
|
26
|
+
class UserDatabase
|
27
|
+
USERS = {
|
28
|
+
'user-token-123' => {
|
29
|
+
id: 1,
|
30
|
+
name: 'John Doe',
|
31
|
+
email: 'john.doe@example.com',
|
32
|
+
role: 'user',
|
33
|
+
scopes: %w[read write]
|
34
|
+
},
|
35
|
+
'admin-token-456' => {
|
36
|
+
id: 2,
|
37
|
+
name: 'Jane Admin',
|
38
|
+
email: 'jane.admin@example.com',
|
39
|
+
role: 'admin',
|
40
|
+
scopes: %w[read write admin delete]
|
41
|
+
}
|
42
|
+
}.freeze
|
43
|
+
|
44
|
+
def self.find_by_token(token)
|
45
|
+
USERS[token]
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.all_users
|
49
|
+
USERS.values
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.find_by_id(id)
|
53
|
+
USERS.values.find { |user| user[:id] == id.to_i }
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class TaskDatabase
|
58
|
+
@@tasks = [
|
59
|
+
{ id: 1, title: 'Setup CI/CD Pipeline', description: 'Configure automated testing and deployment',
|
60
|
+
status: 'in_progress', assignee_id: 1, created_at: Time.now - 86_400 },
|
61
|
+
{ id: 2, title: 'Review Security Audit', description: 'Complete quarterly security review', status: 'pending',
|
62
|
+
assignee_id: 2, created_at: Time.now - 3600 }
|
63
|
+
]
|
64
|
+
@@next_id = 3
|
65
|
+
|
66
|
+
def self.all
|
67
|
+
@@tasks
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.find(id)
|
71
|
+
@@tasks.find { |task| task[:id] == id.to_i }
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.create(attrs)
|
75
|
+
task = attrs.merge(id: @@next_id, created_at: Time.now)
|
76
|
+
@@next_id += 1
|
77
|
+
@@tasks << task
|
78
|
+
task
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.update(id, attrs)
|
82
|
+
task = find(id)
|
83
|
+
return nil unless task
|
84
|
+
|
85
|
+
attrs.each { |key, value| task[key] = value }
|
86
|
+
task[:updated_at] = Time.now
|
87
|
+
task
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.delete(id)
|
91
|
+
@@tasks.reject! { |task| task[:id] == id.to_i }
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# Define schemas using RapiTapir types
|
96
|
+
TASK_SCHEMA = RapiTapir::Types.hash({
|
97
|
+
'id' => RapiTapir::Types.integer,
|
98
|
+
'title' => RapiTapir::Types.string,
|
99
|
+
'description' => RapiTapir::Types.string,
|
100
|
+
'status' => RapiTapir::Types.string,
|
101
|
+
'assignee_id' => RapiTapir::Types.integer,
|
102
|
+
'created_at' => RapiTapir::Types.string,
|
103
|
+
'updated_at' => RapiTapir::Types.optional(RapiTapir::Types.string)
|
104
|
+
})
|
105
|
+
|
106
|
+
USER_SCHEMA = RapiTapir::Types.hash({
|
107
|
+
'id' => RapiTapir::Types.integer,
|
108
|
+
'name' => RapiTapir::Types.string,
|
109
|
+
'email' => RapiTapir::Types.string,
|
110
|
+
'role' => RapiTapir::Types.string,
|
111
|
+
'scopes' => RapiTapir::Types.array(RapiTapir::Types.string)
|
112
|
+
})
|
113
|
+
|
114
|
+
# Main Application using RapiTapir Sinatra Extension
|
115
|
+
if SINATRA_AVAILABLE
|
116
|
+
class EnterpriseTaskAPI < Sinatra::Base
|
117
|
+
# Register the RapiTapir extension
|
118
|
+
register RapiTapir::Sinatra::Extension
|
119
|
+
|
120
|
+
# Configure the application in one place
|
121
|
+
rapitapir do
|
122
|
+
# API information
|
123
|
+
info(
|
124
|
+
title: 'Enterprise Task Management API',
|
125
|
+
description: 'A production-ready task management API built with RapiTapir Sinatra Extension',
|
126
|
+
version: '2.0.0',
|
127
|
+
contact: {
|
128
|
+
name: 'API Support',
|
129
|
+
email: 'api-support@example.com'
|
130
|
+
}
|
131
|
+
)
|
132
|
+
|
133
|
+
# Server configuration
|
134
|
+
server(url: 'http://localhost:4567', description: 'Development server')
|
135
|
+
server(url: 'https://api.example.com', description: 'Production server')
|
136
|
+
|
137
|
+
# Authentication configuration
|
138
|
+
bearer_auth(:bearer, {
|
139
|
+
realm: 'Enterprise Task Management API',
|
140
|
+
token_validator: proc do |token|
|
141
|
+
user = UserDatabase.find_by_token(token)
|
142
|
+
next nil unless user
|
143
|
+
|
144
|
+
{
|
145
|
+
user: user,
|
146
|
+
scopes: user[:scopes]
|
147
|
+
}
|
148
|
+
end
|
149
|
+
})
|
150
|
+
|
151
|
+
# Middleware configuration based on environment
|
152
|
+
if development?
|
153
|
+
development_defaults!
|
154
|
+
else
|
155
|
+
production_defaults!
|
156
|
+
end
|
157
|
+
|
158
|
+
# Enable documentation
|
159
|
+
enable_docs(path: '/docs', openapi_path: '/openapi.json')
|
160
|
+
end
|
161
|
+
|
162
|
+
# Health check endpoint (public)
|
163
|
+
endpoint(
|
164
|
+
RapiTapir.get('/health')
|
165
|
+
.summary('Health check')
|
166
|
+
.description('Returns the health status of the API')
|
167
|
+
.ok(RapiTapir::Types.hash({
|
168
|
+
'status' => RapiTapir::Types.string,
|
169
|
+
'timestamp' => RapiTapir::Types.string,
|
170
|
+
'version' => RapiTapir::Types.string,
|
171
|
+
'features' => RapiTapir::Types.array(RapiTapir::Types.string)
|
172
|
+
}))
|
173
|
+
.build
|
174
|
+
) do |_inputs|
|
175
|
+
{
|
176
|
+
status: 'healthy',
|
177
|
+
timestamp: Time.now.iso8601,
|
178
|
+
version: '2.0.0',
|
179
|
+
features: ['RapiTapir Sinatra Extension', 'Auto-generated OpenAPI', 'Zero Boilerplate']
|
180
|
+
}
|
181
|
+
end
|
182
|
+
|
183
|
+
# Tasks resource - using the RESTful resource builder
|
184
|
+
api_resource '/api/v1/tasks', schema: TASK_SCHEMA do
|
185
|
+
# Enable full CRUD operations with custom handlers
|
186
|
+
crud do
|
187
|
+
# List tasks with filtering
|
188
|
+
index do |inputs|
|
189
|
+
tasks = TaskDatabase.all
|
190
|
+
|
191
|
+
# Apply filters if provided
|
192
|
+
tasks = tasks.select { |task| task[:status] == inputs[:status] } if inputs[:status]
|
193
|
+
|
194
|
+
# Apply pagination
|
195
|
+
limit = inputs[:limit] || 50
|
196
|
+
offset = inputs[:offset] || 0
|
197
|
+
tasks = tasks.drop(offset).take(limit)
|
198
|
+
|
199
|
+
# Format timestamps
|
200
|
+
tasks.map do |task|
|
201
|
+
task_copy = task.dup
|
202
|
+
task_copy[:created_at] = task_copy[:created_at].iso8601 if task_copy[:created_at]
|
203
|
+
task_copy[:updated_at] = task_copy[:updated_at].iso8601 if task_copy[:updated_at]
|
204
|
+
task_copy
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
# Get specific task
|
209
|
+
show do |inputs|
|
210
|
+
task = TaskDatabase.find(inputs[:id])
|
211
|
+
halt 404, { error: 'Task not found' }.to_json unless task
|
212
|
+
|
213
|
+
# Enrich with assignee details
|
214
|
+
assignee = UserDatabase.find_by_id(task[:assignee_id])
|
215
|
+
task_with_assignee = task.dup
|
216
|
+
task_with_assignee[:created_at] = task_with_assignee[:created_at].iso8601 if task_with_assignee[:created_at]
|
217
|
+
task_with_assignee[:assignee] = if assignee
|
218
|
+
{
|
219
|
+
id: assignee[:id],
|
220
|
+
name: assignee[:name],
|
221
|
+
email: assignee[:email]
|
222
|
+
}
|
223
|
+
end
|
224
|
+
|
225
|
+
task_with_assignee
|
226
|
+
end
|
227
|
+
|
228
|
+
# Create new task
|
229
|
+
create do |inputs|
|
230
|
+
body = inputs[:body]
|
231
|
+
|
232
|
+
# Validate assignee exists
|
233
|
+
assignee = UserDatabase.find_by_id(body['assignee_id'])
|
234
|
+
halt 400, { error: 'Invalid assignee ID' }.to_json unless assignee
|
235
|
+
|
236
|
+
# Create task
|
237
|
+
task_data = {
|
238
|
+
title: body['title'],
|
239
|
+
description: body['description'],
|
240
|
+
status: body['status'] || 'pending',
|
241
|
+
assignee_id: body['assignee_id']
|
242
|
+
}
|
243
|
+
|
244
|
+
task = TaskDatabase.create(task_data)
|
245
|
+
task[:created_at] = task[:created_at].iso8601
|
246
|
+
task
|
247
|
+
end
|
248
|
+
|
249
|
+
# Update task
|
250
|
+
update do |inputs|
|
251
|
+
task = TaskDatabase.find(inputs[:id])
|
252
|
+
halt 404, { error: 'Task not found' }.to_json unless task
|
253
|
+
|
254
|
+
body = inputs[:body]
|
255
|
+
update_data = {}
|
256
|
+
|
257
|
+
# Prepare update data
|
258
|
+
update_data[:title] = body['title'] if body['title']
|
259
|
+
update_data[:description] = body['description'] if body['description']
|
260
|
+
update_data[:status] = body['status'] if body['status']
|
261
|
+
|
262
|
+
if body['assignee_id']
|
263
|
+
assignee = UserDatabase.find_by_id(body['assignee_id'])
|
264
|
+
halt 400, { error: 'Invalid assignee ID' }.to_json unless assignee
|
265
|
+
update_data[:assignee_id] = body['assignee_id']
|
266
|
+
end
|
267
|
+
|
268
|
+
# Update task
|
269
|
+
updated_task = TaskDatabase.update(inputs[:id], update_data)
|
270
|
+
updated_task[:created_at] = updated_task[:created_at].iso8601 if updated_task[:created_at]
|
271
|
+
updated_task[:updated_at] = updated_task[:updated_at].iso8601 if updated_task[:updated_at]
|
272
|
+
updated_task
|
273
|
+
end
|
274
|
+
|
275
|
+
# Delete task (requires admin scope)
|
276
|
+
destroy(scopes: ['admin']) do |inputs|
|
277
|
+
task = TaskDatabase.find(inputs[:id])
|
278
|
+
halt 404, { error: 'Task not found' }.to_json unless task
|
279
|
+
|
280
|
+
TaskDatabase.delete(inputs[:id])
|
281
|
+
status 204
|
282
|
+
nil
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
# Custom endpoint: Get tasks by status
|
287
|
+
custom(:get, 'by-status/:status',
|
288
|
+
summary: 'Get tasks by status',
|
289
|
+
configure: lambda { |endpoint|
|
290
|
+
endpoint
|
291
|
+
.path_param(:status, RapiTapir::Types.string, description: 'Task status')
|
292
|
+
.ok(RapiTapir::Types.array(TASK_SCHEMA))
|
293
|
+
}) do |inputs|
|
294
|
+
tasks = TaskDatabase.all.select { |task| task[:status] == inputs[:status] }
|
295
|
+
tasks.map do |task|
|
296
|
+
task_copy = task.dup
|
297
|
+
task_copy[:created_at] = task_copy[:created_at].iso8601 if task_copy[:created_at]
|
298
|
+
task_copy
|
299
|
+
end
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
# User profile endpoint
|
304
|
+
endpoint(
|
305
|
+
RapiTapir.get('/api/v1/profile')
|
306
|
+
.summary('Get current user profile')
|
307
|
+
.description('Retrieve the profile of the authenticated user')
|
308
|
+
.ok(RapiTapir::Types.hash({
|
309
|
+
'id' => RapiTapir::Types.integer,
|
310
|
+
'name' => RapiTapir::Types.string,
|
311
|
+
'email' => RapiTapir::Types.string,
|
312
|
+
'role' => RapiTapir::Types.string,
|
313
|
+
'scopes' => RapiTapir::Types.array(RapiTapir::Types.string)
|
314
|
+
}))
|
315
|
+
.build
|
316
|
+
) do |_inputs|
|
317
|
+
require_authentication!
|
318
|
+
current_user
|
319
|
+
end
|
320
|
+
|
321
|
+
# Admin endpoint - list all users
|
322
|
+
endpoint(
|
323
|
+
RapiTapir.get('/api/v1/admin/users')
|
324
|
+
.summary('List all users (admin only)')
|
325
|
+
.description('Retrieve a list of all users in the system. Requires admin permission.')
|
326
|
+
.ok(RapiTapir::Types.array(USER_SCHEMA))
|
327
|
+
.build
|
328
|
+
) do |_inputs|
|
329
|
+
require_scope!('admin')
|
330
|
+
UserDatabase.all_users
|
331
|
+
end
|
332
|
+
|
333
|
+
# Development info
|
334
|
+
configure :development do
|
335
|
+
puts "\n#{'=' * 70}"
|
336
|
+
puts 'š ENTERPRISE TASK MANAGEMENT API v2.0 - RapiTapir Extension'
|
337
|
+
puts '=' * 70
|
338
|
+
puts 'š API Documentation: http://localhost:4567/docs'
|
339
|
+
puts 'š OpenAPI Spec: http://localhost:4567/openapi.json'
|
340
|
+
puts 'ā¤ļø Health Check: http://localhost:4567/health'
|
341
|
+
puts "\nš Bearer Tokens:"
|
342
|
+
puts ' User: user-token-123 (scopes: read, write)'
|
343
|
+
puts ' Admin: admin-token-456 (scopes: read, write, admin, delete)'
|
344
|
+
puts "\n⨠NEW FEATURES with RapiTapir Extension:"
|
345
|
+
puts ' šÆ Zero boilerplate configuration'
|
346
|
+
puts ' š§ Automatic middleware setup'
|
347
|
+
puts ' š¦ RESTful resource builder'
|
348
|
+
puts ' š”ļø Built-in authentication helpers'
|
349
|
+
puts ' š Auto-generated beautiful documentation'
|
350
|
+
puts ' šļø SOLID principles architecture'
|
351
|
+
puts ''
|
352
|
+
end
|
353
|
+
end
|
354
|
+
|
355
|
+
# Start the server
|
356
|
+
EnterpriseTaskAPI.run! if __FILE__ == $PROGRAM_NAME
|
357
|
+
else
|
358
|
+
# Demo mode when Sinatra is not available
|
359
|
+
puts "\n#{'=' * 70}"
|
360
|
+
puts 'š ENTERPRISE TASK MANAGEMENT API v2.0 - Demo Mode'
|
361
|
+
puts '=' * 70
|
362
|
+
|
363
|
+
puts "\nā
Successfully loaded:"
|
364
|
+
puts ' ⢠RapiTapir core and type system'
|
365
|
+
puts ' ⢠Task and User schemas'
|
366
|
+
puts ' ⢠Database classes'
|
367
|
+
|
368
|
+
puts "\nšÆ This enterprise API would provide:"
|
369
|
+
puts ' GET /health - Health check (public)'
|
370
|
+
puts ' GET /api/v1/tasks - List tasks with filtering'
|
371
|
+
puts ' GET /api/v1/tasks/:id - Get specific task'
|
372
|
+
puts ' POST /api/v1/tasks - Create new task'
|
373
|
+
puts ' PUT /api/v1/tasks/:id - Update task'
|
374
|
+
puts ' DELETE /api/v1/tasks/:id - Delete task (admin only)'
|
375
|
+
puts ' GET /api/v1/tasks/by-status/:status - Tasks by status'
|
376
|
+
puts ' GET /api/v1/profile - Current user profile'
|
377
|
+
puts ' GET /api/v1/admin/users - List users (admin only)'
|
378
|
+
puts ' GET /docs - Swagger UI documentation'
|
379
|
+
puts ' GET /openapi.json - OpenAPI 3.0 specification'
|
380
|
+
|
381
|
+
puts "\nš”ļø Security features:"
|
382
|
+
puts ' ⢠Bearer token authentication'
|
383
|
+
puts ' ⢠Scope-based authorization (read, write, admin, delete)'
|
384
|
+
puts ' ⢠CORS protection'
|
385
|
+
puts ' ⢠Rate limiting (configurable per environment)'
|
386
|
+
puts ' ⢠Security headers'
|
387
|
+
|
388
|
+
puts "\nšÆ Extension advantages demonstrated:"
|
389
|
+
puts ' ⢠90% less boilerplate code compared to manual implementation'
|
390
|
+
puts ' ⢠One-line configuration: development_defaults!()'
|
391
|
+
puts ' ⢠RESTful resource builder: api_resource + crud block'
|
392
|
+
puts ' ⢠Built-in auth helpers: require_authentication!, require_scope!'
|
393
|
+
puts ' ⢠Auto-generated OpenAPI 3.0 documentation'
|
394
|
+
puts ' ⢠Production-ready middleware stack'
|
395
|
+
|
396
|
+
puts "\nš” Authentication tokens that would work:"
|
397
|
+
puts ' User token: user-token-123 (scopes: read, write)'
|
398
|
+
puts ' Admin token: admin-token-456 (scopes: read, write, admin, delete)'
|
399
|
+
|
400
|
+
puts "\nš Sample API calls that would work:"
|
401
|
+
puts " curl -H 'Authorization: Bearer user-token-123' http://localhost:4567/api/v1/tasks"
|
402
|
+
puts " curl -H 'Authorization: Bearer admin-token-456' http://localhost:4567/api/v1/admin/users"
|
403
|
+
puts " curl -X POST -H 'Authorization: Bearer user-token-123' \\"
|
404
|
+
puts " -H 'Content-Type: application/json' \\"
|
405
|
+
puts " -d '{\"title\":\"New Task\",\"description\":\"Test\",\"assignee_id\":1}' \\"
|
406
|
+
puts ' http://localhost:4567/api/v1/tasks'
|
407
|
+
|
408
|
+
puts "\nš” To run the actual server:"
|
409
|
+
puts ' gem install sinatra'
|
410
|
+
puts " ruby #{__FILE__}"
|
411
|
+
|
412
|
+
puts "\nšļø Architecture highlights:"
|
413
|
+
puts ' ⢠SOLID principles implementation'
|
414
|
+
puts ' ⢠Single Responsibility: Each class has one purpose'
|
415
|
+
puts ' ⢠Open/Closed: Extensible without modification'
|
416
|
+
puts ' ⢠Dependency Inversion: Auth logic injected via procs'
|
417
|
+
end
|