Skip to content

Commit 72720f8

Browse files
committed
docs: add Swagger/OpenAPI documentation with rswag
- Configure swagger_helper with API schemas - Add OpenAPI 3.0.1 specification - Define schemas for Player, Match, Error, Pagination - Add integration specs for Players API - Configure rswag-ui and rswag-api - Mount Swagger UI at /api-docs
1 parent 7d56866 commit 72720f8

4 files changed

Lines changed: 380 additions & 0 deletions

File tree

config/initializers/rswag_api.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Rswag::Api.configure do |c|
2+
# Specify a root folder where Swagger JSON files are located
3+
c.swagger_root = Rails.root.join('swagger').to_s
4+
5+
# Inject a lambda function to alter the returned Swagger prior to serialization
6+
# c.swagger_filter = lambda { |swagger, env| swagger['host'] = env['HTTP_HOST'] }
7+
end

config/initializers/rswag_ui.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
Rswag::Ui.configure do |c|
2+
# List the Swagger endpoints that you want to be documented through the
3+
# swagger-ui. The first parameter is the path (absolute or relative to the UI
4+
# host) to the corresponding endpoint and the second is a title that will be
5+
# displayed in the document selector.
6+
c.swagger_endpoint '/api-docs/v1/swagger.yaml', 'ProStaff API V1 Docs'
7+
8+
# Add Basic Auth in case your API is private
9+
# c.basic_auth_enabled = true
10+
# c.basic_auth_credentials 'username', 'password'
11+
end

spec/integration/players_spec.rb

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
require 'swagger_helper'
2+
3+
RSpec.describe 'Players API', type: :request do
4+
let(:organization) { create(:organization) }
5+
let(:user) { create(:user, :admin, organization: organization) }
6+
let(:Authorization) { "Bearer #{Authentication::Services::JwtService.encode(user_id: user.id)}" }
7+
8+
path '/api/v1/players' do
9+
get 'List all players' do
10+
tags 'Players'
11+
produces 'application/json'
12+
security [bearerAuth: []]
13+
14+
parameter name: :page, in: :query, type: :integer, required: false, description: 'Page number'
15+
parameter name: :per_page, in: :query, type: :integer, required: false, description: 'Items per page'
16+
parameter name: :role, in: :query, type: :string, required: false, description: 'Filter by role'
17+
parameter name: :status, in: :query, type: :string, required: false, description: 'Filter by status'
18+
parameter name: :search, in: :query, type: :string, required: false, description: 'Search by summoner name or real name'
19+
20+
response '200', 'players found' do
21+
schema type: :object,
22+
properties: {
23+
data: {
24+
type: :object,
25+
properties: {
26+
players: {
27+
type: :array,
28+
items: { '$ref' => '#/components/schemas/Player' }
29+
},
30+
pagination: { '$ref' => '#/components/schemas/Pagination' }
31+
}
32+
}
33+
}
34+
35+
run_test!
36+
end
37+
38+
response '401', 'unauthorized' do
39+
let(:Authorization) { nil }
40+
schema '$ref' => '#/components/schemas/Error'
41+
run_test!
42+
end
43+
end
44+
45+
post 'Create a player' do
46+
tags 'Players'
47+
consumes 'application/json'
48+
produces 'application/json'
49+
security [bearerAuth: []]
50+
51+
parameter name: :player, in: :body, schema: {
52+
type: :object,
53+
properties: {
54+
player: {
55+
type: :object,
56+
properties: {
57+
summoner_name: { type: :string },
58+
real_name: { type: :string },
59+
role: { type: :string, enum: %w[top jungle mid adc support] },
60+
status: { type: :string, enum: %w[active inactive benched trial] },
61+
jersey_number: { type: :integer },
62+
birth_date: { type: :string, format: :date },
63+
country: { type: :string }
64+
},
65+
required: %w[summoner_name role]
66+
}
67+
}
68+
}
69+
70+
response '201', 'player created' do
71+
let(:player) do
72+
{
73+
player: {
74+
summoner_name: 'TestPlayer',
75+
real_name: 'Test User',
76+
role: 'mid',
77+
status: 'active'
78+
}
79+
}
80+
end
81+
82+
schema type: :object,
83+
properties: {
84+
message: { type: :string },
85+
data: {
86+
type: :object,
87+
properties: {
88+
player: { '$ref' => '#/components/schemas/Player' }
89+
}
90+
}
91+
}
92+
93+
run_test!
94+
end
95+
96+
response '422', 'invalid request' do
97+
let(:player) { { player: { summoner_name: '' } } }
98+
schema '$ref' => '#/components/schemas/Error'
99+
run_test!
100+
end
101+
end
102+
end
103+
104+
path '/api/v1/players/{id}' do
105+
parameter name: :id, in: :path, type: :string, description: 'Player ID'
106+
107+
get 'Show player details' do
108+
tags 'Players'
109+
produces 'application/json'
110+
security [bearerAuth: []]
111+
112+
response '200', 'player found' do
113+
let(:id) { create(:player, organization: organization).id }
114+
115+
schema type: :object,
116+
properties: {
117+
data: {
118+
type: :object,
119+
properties: {
120+
player: { '$ref' => '#/components/schemas/Player' }
121+
}
122+
}
123+
}
124+
125+
run_test!
126+
end
127+
128+
response '404', 'player not found' do
129+
let(:id) { '99999' }
130+
schema '$ref' => '#/components/schemas/Error'
131+
run_test!
132+
end
133+
end
134+
135+
patch 'Update a player' do
136+
tags 'Players'
137+
consumes 'application/json'
138+
produces 'application/json'
139+
security [bearerAuth: []]
140+
141+
parameter name: :player, in: :body, schema: {
142+
type: :object,
143+
properties: {
144+
player: {
145+
type: :object,
146+
properties: {
147+
summoner_name: { type: :string },
148+
real_name: { type: :string },
149+
status: { type: :string }
150+
}
151+
}
152+
}
153+
}
154+
155+
response '200', 'player updated' do
156+
let(:id) { create(:player, organization: organization).id }
157+
let(:player) { { player: { summoner_name: 'UpdatedName' } } }
158+
159+
schema type: :object,
160+
properties: {
161+
message: { type: :string },
162+
data: {
163+
type: :object,
164+
properties: {
165+
player: { '$ref' => '#/components/schemas/Player' }
166+
}
167+
}
168+
}
169+
170+
run_test!
171+
end
172+
end
173+
174+
delete 'Delete a player' do
175+
tags 'Players'
176+
produces 'application/json'
177+
security [bearerAuth: []]
178+
179+
response '200', 'player deleted' do
180+
let(:user) { create(:user, :owner, organization: organization) }
181+
let(:id) { create(:player, organization: organization).id }
182+
183+
schema type: :object,
184+
properties: {
185+
message: { type: :string }
186+
}
187+
188+
run_test!
189+
end
190+
end
191+
end
192+
193+
path '/api/v1/players/{id}/stats' do
194+
parameter name: :id, in: :path, type: :string, description: 'Player ID'
195+
196+
get 'Get player statistics' do
197+
tags 'Players'
198+
produces 'application/json'
199+
security [bearerAuth: []]
200+
201+
response '200', 'statistics retrieved' do
202+
let(:id) { create(:player, organization: organization).id }
203+
204+
schema type: :object,
205+
properties: {
206+
data: {
207+
type: :object,
208+
properties: {
209+
player: { '$ref' => '#/components/schemas/Player' },
210+
overall: { type: :object },
211+
recent_form: { type: :object },
212+
champion_pool: { type: :array },
213+
performance_by_role: { type: :array }
214+
}
215+
}
216+
}
217+
218+
run_test!
219+
end
220+
end
221+
end
222+
end

spec/swagger_helper.rb

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
require 'rails_helper'
2+
3+
RSpec.configure do |config|
4+
# Specify a root folder where Swagger JSON files are generated
5+
config.swagger_root = Rails.root.join('swagger').to_s
6+
7+
# Define one or more Swagger documents
8+
config.swagger_docs = {
9+
'v1/swagger.yaml' => {
10+
openapi: '3.0.1',
11+
info: {
12+
title: 'ProStaff API V1',
13+
version: 'v1',
14+
description: 'API documentation for ProStaff - Esports Team Management Platform',
15+
contact: {
16+
name: 'ProStaff Support',
17+
email: 'support@prostaff.gg'
18+
}
19+
},
20+
servers: [
21+
{
22+
url: 'http://localhost:3333',
23+
description: 'Development server'
24+
},
25+
{
26+
url: 'https://api.prostaff.gg',
27+
description: 'Production server'
28+
}
29+
],
30+
paths: {},
31+
components: {
32+
securitySchemes: {
33+
bearerAuth: {
34+
type: :http,
35+
scheme: :bearer,
36+
bearerFormat: 'JWT',
37+
description: 'JWT authorization token'
38+
}
39+
},
40+
schemas: {
41+
Error: {
42+
type: :object,
43+
properties: {
44+
error: {
45+
type: :object,
46+
properties: {
47+
code: { type: :string },
48+
message: { type: :string },
49+
details: { type: :object }
50+
},
51+
required: %w[code message]
52+
}
53+
},
54+
required: ['error']
55+
},
56+
User: {
57+
type: :object,
58+
properties: {
59+
id: { type: :string, format: :uuid },
60+
email: { type: :string, format: :email },
61+
full_name: { type: :string },
62+
role: { type: :string, enum: %w[owner admin coach analyst viewer] },
63+
timezone: { type: :string },
64+
language: { type: :string },
65+
created_at: { type: :string, format: 'date-time' },
66+
updated_at: { type: :string, format: 'date-time' }
67+
},
68+
required: %w[id email full_name role]
69+
},
70+
Organization: {
71+
type: :object,
72+
properties: {
73+
id: { type: :string, format: :uuid },
74+
name: { type: :string },
75+
region: { type: :string },
76+
tier: { type: :string, enum: %w[amateur semi_pro professional] },
77+
created_at: { type: :string, format: 'date-time' },
78+
updated_at: { type: :string, format: 'date-time' }
79+
},
80+
required: %w[id name region tier]
81+
},
82+
Player: {
83+
type: :object,
84+
properties: {
85+
id: { type: :string, format: :uuid },
86+
summoner_name: { type: :string },
87+
real_name: { type: :string, nullable: true },
88+
role: { type: :string, enum: %w[top jungle mid adc support] },
89+
status: { type: :string, enum: %w[active inactive benched trial] },
90+
jersey_number: { type: :integer, nullable: true },
91+
country: { type: :string, nullable: true },
92+
solo_queue_tier: { type: :string, nullable: true },
93+
solo_queue_rank: { type: :string, nullable: true },
94+
solo_queue_lp: { type: :integer, nullable: true },
95+
current_rank: { type: :string },
96+
win_rate: { type: :number, format: :float },
97+
created_at: { type: :string, format: 'date-time' },
98+
updated_at: { type: :string, format: 'date-time' }
99+
},
100+
required: %w[id summoner_name role status]
101+
},
102+
Match: {
103+
type: :object,
104+
properties: {
105+
id: { type: :string, format: :uuid },
106+
match_type: { type: :string, enum: %w[official scrim tournament] },
107+
game_start: { type: :string, format: 'date-time' },
108+
game_duration: { type: :integer },
109+
victory: { type: :boolean },
110+
opponent_name: { type: :string, nullable: true },
111+
our_score: { type: :integer, nullable: true },
112+
opponent_score: { type: :integer, nullable: true },
113+
result: { type: :string },
114+
created_at: { type: :string, format: 'date-time' },
115+
updated_at: { type: :string, format: 'date-time' }
116+
},
117+
required: %w[id match_type]
118+
},
119+
Pagination: {
120+
type: :object,
121+
properties: {
122+
current_page: { type: :integer },
123+
per_page: { type: :integer },
124+
total_pages: { type: :integer },
125+
total_count: { type: :integer },
126+
has_next_page: { type: :boolean },
127+
has_prev_page: { type: :boolean }
128+
}
129+
}
130+
}
131+
},
132+
security: [
133+
{ bearerAuth: [] }
134+
]
135+
}
136+
}
137+
138+
# Specify the format of the output Swagger file
139+
config.swagger_format = :yaml
140+
end

0 commit comments

Comments
 (0)