Skip to content

Commit 1c567fb

Browse files
committed
Swagger Documentation for Backend APIs
1 parent 08dac2d commit 1c567fb

5 files changed

Lines changed: 330 additions & 0 deletions

File tree

app.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333
import backend.sockets.alert_socket # Register centralized alert socket events
3434
import backend.sockets.crisis_events # Register crisis monitoring events
3535
from backend.utils.i18n import t
36+
from backend.auth import auth_bp as legacy_auth_bp
37+
from backend.docs.swagger import swagger_bp
3638

3739
from routes.irrigation_routes import irrigation_bp
3840

@@ -86,6 +88,8 @@
8688
app.register_blueprint(model_versioning_bp)
8789
app.register_blueprint(irrigation_bp)
8890
app.register_blueprint(rotation_bp)
91+
app.register_blueprint(legacy_auth_bp)
92+
app.register_blueprint(swagger_bp)
8993

9094
# Register API v1 (including loan, weather, schemes, etc.)
9195
register_api(app)

backend/api/v1/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from flask import Blueprint
22
from .loan import loan_bp
3+
from .auth import auth_bp
34
from .config import config_bp
45
from .tasks import tasks_bp
56
from .notifications import notifications_bp
@@ -63,6 +64,7 @@
6364

6465
# Register sub-blueprints
6566
api_v1.register_blueprint(loan_bp)
67+
api_v1.register_blueprint(auth_bp, url_prefix="/auth")
6668
api_v1.register_blueprint(config_bp)
6769
api_v1.register_blueprint(tasks_bp)
6870
api_v1.register_blueprint(notifications_bp)

backend/api/v1/auth.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,37 @@
33
from backend.services.audit_service import AuditService
44
from backend.models import User
55
from backend.extensions import db, limiter
6+
from backend.docs.swagger import swagger_operation
67

78
auth_bp = Blueprint('auth', __name__)
89

910
@auth_bp.route('/register', methods=['POST'])
11+
@swagger_operation(
12+
'/api/v1/auth/register',
13+
'post',
14+
'Register a user and send a verification email',
15+
'Create a new user account, then send a verification email.',
16+
request_body={
17+
'required': True,
18+
'content': {
19+
'application/json': {
20+
'schema': {
21+
'type': 'object',
22+
'required': ['username', 'email', 'password'],
23+
'properties': {
24+
'username': {'type': 'string', 'example': 'Farmer Name'},
25+
'email': {'type': 'string', 'format': 'email', 'example': 'farmer@gmail.com'},
26+
'password': {'type': 'string', 'example': 'SecurePass123'},
27+
},
28+
},
29+
},
30+
},
31+
},
32+
responses={
33+
'201': {'description': 'User registered successfully'},
34+
'400': {'description': 'Validation error'},
35+
},
36+
)
1037
@limiter.limit("5 per hour")
1138
def register():
1239
"""Register a new user and send verification email."""
@@ -50,6 +77,16 @@ def register():
5077
}), 201
5178

5279
@auth_bp.route('/verify-email/<token>', methods=['GET'])
80+
@swagger_operation(
81+
'/api/v1/auth/verify-email/{token}',
82+
'get',
83+
'Verify email address',
84+
'Verify a user email address using the emailed token.',
85+
responses={
86+
'200': {'description': 'Email verified successfully'},
87+
'400': {'description': 'Verification token invalid or expired'},
88+
},
89+
)
5390
def verify_email(token):
5491
"""Verify email endpoint."""
5592
success, message = AuthService.verify_email(token)
@@ -60,6 +97,30 @@ def verify_email(token):
6097
return jsonify({'status': 'error', 'message': message}), 400
6198

6299
@auth_bp.route('/forgot-password', methods=['POST'])
100+
@swagger_operation(
101+
'/api/v1/auth/forgot-password',
102+
'post',
103+
'Request a password reset',
104+
'Send a password reset email without revealing whether the address exists.',
105+
request_body={
106+
'required': True,
107+
'content': {
108+
'application/json': {
109+
'schema': {
110+
'type': 'object',
111+
'required': ['email'],
112+
'properties': {
113+
'email': {'type': 'string', 'format': 'email', 'example': 'farmer@gmail.com'},
114+
},
115+
},
116+
},
117+
},
118+
},
119+
responses={
120+
'200': {'description': 'Password reset email queued'},
121+
'400': {'description': 'Missing email'},
122+
},
123+
)
63124
@limiter.limit("3 per hour")
64125
def forgot_password():
65126
"""Request password reset email."""
@@ -83,6 +144,30 @@ def forgot_password():
83144
}), 200
84145

85146
@auth_bp.route('/reset-password/<token>', methods=['POST'])
147+
@swagger_operation(
148+
'/api/v1/auth/reset-password/{token}',
149+
'post',
150+
'Reset password with token',
151+
'Set a new password using the reset token.',
152+
request_body={
153+
'required': True,
154+
'content': {
155+
'application/json': {
156+
'schema': {
157+
'type': 'object',
158+
'required': ['password'],
159+
'properties': {
160+
'password': {'type': 'string', 'example': 'NewSecurePass123'},
161+
},
162+
},
163+
},
164+
},
165+
},
166+
responses={
167+
'200': {'description': 'Password reset successfully'},
168+
'400': {'description': 'Invalid token or password'},
169+
},
170+
)
86171
@limiter.limit("5 per hour")
87172
def reset_password(token):
88173
"""Reset password using token."""

backend/auth/routes.py

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from backend.models import User
77
from .jwt_utils import jwt_manager
88
from .decorators import token_required
9+
from backend.docs.swagger import swagger_operation
910
from backend.utils.validation import (
1011
sanitize_input,
1112
validate_email,
@@ -22,6 +23,37 @@
2223

2324

2425
@auth_bp.route('/register', methods=['POST'])
26+
@swagger_operation(
27+
'/api/auth/register',
28+
'post',
29+
'Register a user',
30+
'Create a new user account and return the created profile.',
31+
request_body={
32+
'required': True,
33+
'content': {
34+
'application/json': {
35+
'schema': {
36+
'type': 'object',
37+
'required': ['username', 'email', 'password', 'full_name', 'role'],
38+
'properties': {
39+
'username': {'type': 'string', 'example': 'farmer123'},
40+
'email': {'type': 'string', 'format': 'email', 'example': 'farmer@example.com'},
41+
'password': {'type': 'string', 'example': 'SecurePass123'},
42+
'full_name': {'type': 'string', 'example': 'John Doe'},
43+
'role': {'type': 'string', 'example': 'farmer'},
44+
'phone': {'type': 'string', 'example': '9876543210'},
45+
'location': {'type': 'string', 'example': 'Maharashtra'},
46+
},
47+
},
48+
},
49+
},
50+
},
51+
responses={
52+
'201': {'description': 'User created successfully'},
53+
'400': {'description': 'Validation error'},
54+
'409': {'description': 'User already exists'},
55+
},
56+
)
2557
def register():
2658
"""
2759
Register a new user.
@@ -125,6 +157,32 @@ def register():
125157

126158

127159
@auth_bp.route('/login', methods=['POST'])
160+
@swagger_operation(
161+
'/api/auth/login',
162+
'post',
163+
'Login a user',
164+
'Authenticate a user and return an access token plus the user profile.',
165+
request_body={
166+
'required': True,
167+
'content': {
168+
'application/json': {
169+
'schema': {
170+
'type': 'object',
171+
'required': ['username', 'password'],
172+
'properties': {
173+
'username': {'type': 'string', 'example': 'farmer123'},
174+
'password': {'type': 'string', 'example': 'SecurePass123'},
175+
},
176+
},
177+
},
178+
},
179+
},
180+
responses={
181+
'200': {'description': 'Login successful'},
182+
'400': {'description': 'Validation error'},
183+
'401': {'description': 'Invalid credentials'},
184+
},
185+
)
128186
def login():
129187
"""
130188
Authenticate user and return tokens.
@@ -211,6 +269,16 @@ def login():
211269

212270

213271
@auth_bp.route('/refresh', methods=['POST'])
272+
@swagger_operation(
273+
'/api/auth/refresh',
274+
'post',
275+
'Refresh access token',
276+
'Exchange a valid refresh token cookie for a new access token.',
277+
responses={
278+
'200': {'description': 'Token refreshed successfully'},
279+
'401': {'description': 'Refresh token missing or invalid'},
280+
},
281+
)
214282
def refresh_token():
215283
"""
216284
Refresh access token using refresh token from cookie.
@@ -266,6 +334,17 @@ def refresh_token():
266334

267335

268336
@auth_bp.route('/logout', methods=['POST'])
337+
@swagger_operation(
338+
'/api/auth/logout',
339+
'post',
340+
'Logout the current user',
341+
'Invalidate the current session by clearing the refresh token cookie.',
342+
security=[{'bearerAuth': []}],
343+
responses={
344+
'200': {'description': 'Logout successful'},
345+
'401': {'description': 'Authentication required'},
346+
},
347+
)
269348
@token_required
270349
def logout(current_user):
271350
"""
@@ -293,6 +372,18 @@ def logout(current_user):
293372

294373

295374
@auth_bp.route('/me', methods=['GET'])
375+
@swagger_operation(
376+
'/api/auth/me',
377+
'get',
378+
'Get current user',
379+
'Return the authenticated user profile.',
380+
security=[{'bearerAuth': []}],
381+
responses={
382+
'200': {'description': 'User information returned successfully'},
383+
'401': {'description': 'Authentication required'},
384+
'404': {'description': 'User not found'},
385+
},
386+
)
296387
@token_required
297388
def get_current_user(current_user):
298389
"""
@@ -316,6 +407,17 @@ def get_current_user(current_user):
316407

317408

318409
@auth_bp.route('/validate', methods=['GET'])
410+
@swagger_operation(
411+
'/api/auth/validate',
412+
'get',
413+
'Validate access token',
414+
'Check whether the access token is still valid.',
415+
security=[{'bearerAuth': []}],
416+
responses={
417+
'200': {'description': 'Token is valid'},
418+
'401': {'description': 'Authentication required'},
419+
},
420+
)
319421
@token_required
320422
def validate_token(current_user):
321423
"""

0 commit comments

Comments
 (0)