diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index 57cd243..587b4d6 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -5,34 +5,14 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
@@ -92,7 +72,7 @@
-
+
@@ -117,7 +97,9 @@
1742456795887
-
+
+
+
diff --git a/Docker b/Docker
new file mode 100644
index 0000000..5f453b1
--- /dev/null
+++ b/Docker
@@ -0,0 +1,21 @@
+FROM python:3.11-slim
+
+WORKDIR /app
+
+# Install dependencies
+COPY requirements.txt .
+RUN pip install --no-cache-dir -r requirements.txt
+
+# Copy application code
+COPY . .
+
+# Set environment variables
+ENV PYTHONDONTWRITEBYTECODE=1 \
+ PYTHONUNBUFFERED=1 \
+ PORT=5000
+
+# Expose port
+EXPOSE 5000
+
+# Run the application with Gunicorn
+CMD ["gunicorn", "--bind", "0.0.0.0:5000", "--workers", "4", "app:create_app()"]
\ No newline at end of file
diff --git a/README.md b/README.md
index 2e67cc9..4081fad 100644
--- a/README.md
+++ b/README.md
@@ -1,16 +1,149 @@
# Simbrella FirstAdvance API Flask Implementation
-This project implements the Simbrella FirstAdvance API as defined in the OpenAPI 3.0 specification.
+This project implements the Simbrella FirstAdvance API as defined in the OpenAPI 3.0 specification, using the latest Flask and Python features.
## Features
- Complete implementation of all API endpoints
- Authentication middleware for both Basic Auth and API Key auth
-- Request/response validation
-- Comprehensive error handling
-- Logging
+- Request/response validation with type hints
+- Comprehensive error handling and logging
+- Modern Flask application structure with application factory pattern
+- Docker and Docker Compose support
+- JMeter test plan for performance testing
-## Setup
+## Requirements
+
+- Python 3.11+
+- Flask 2.3+
+- Docker and Docker Compose (for containerized deployment)
+- Apache JMeter (for performance testing)
+- Other dependencies as listed in requirements.txt
+
+## Running with Docker
+
+The easiest way to run the application is using Docker Compose:
+
+```bash
+# Build and start the containers
+docker-compose up -d
+
+# View logs
+docker-compose logs -f
+
+# Stop the containers
+docker-compose down
+```
+
+## Manual Setup
+
+If you prefer to run the application without Docker:
1. Clone the repository
-2. Create a virtual environment:
\ No newline at end of file
+2. Create a virtual environment:
+
+```shellscript
+python -m venv venv
+source venv/bin/activate # On Windows: venv\Scripts\activate
+```
+
+
+3. Install dependencies:
+
+```shellscript
+pip install -r requirements.txt
+```
+
+
+4. Set up environment variables (or create a .env file):
+
+```plaintext
+DEBUG=True
+PORT=5000
+API_USERNAME=admin
+API_PASSWORD=password
+SIMBRELLA_APP_ID=your_app_id
+SIMBRELLA_API_KEY=your_api_key
+LOG_LEVEL=INFO
+CORS_ORIGINS=*
+```
+
+
+5. Run the application:
+
+```shellscript
+python app.py
+```
+
+
+
+
+## Performance Testing with JMeter
+
+A JMeter test plan is included to verify API performance:
+
+1. Install Apache JMeter from [https://jmeter.apache.org/](https://jmeter.apache.org/)
+2. Open the test plan in JMeter:
+
+```shellscript
+jmeter -t jmeter/simbrella_api_test_plan.jmx
+```
+
+
+3. Configure the test parameters as needed
+4. Run the test and analyze the results
+
+
+## API Documentation
+
+The API implements the following endpoints:
+
+- `/v1/api/salary/EligibilityCheck` - Check customer eligibility for loans
+- `/v1/api/salary/SelectOffer` - Process customer's selected offer
+- `/v1/api/salary/ProvideLoan` - Process loan provision
+- `/v1/api/salary/LoanInformation` - Retrieve loan information
+- `/v1/api/salary/Repayment` - Process loan repayment
+- `/v1/api/salary/CustomerConsent` - Process customer consent
+- `/v1/api/salary/NotificationCallback` - Receive transaction status notifications
+- `/v1/api/salary/RACCheck` - Check Risk Acceptance Criteria
+- `/v1/api/salary/Disbursement` - Process loan disbursement
+- `/v1/api/salary/CollectLoan` - Process loan collection
+- `/v1/api/salary/TransactionCheck` - Check transaction status
+- `/v1/api/salary/PenalCharge` - Process penalty charges
+- `/v1/api/salary/RevokeEnableConsent` - Process consent revocation/enablement
+- `/v1/api/salary/ValidateToken` - Validate user authentication tokens
+- `/v1/api/salary/LienCheck` - Check lien amount on account
+- `/v1/api/salary/NewTransactionCheck` - Check status of asynchronous transactions
+- `/v1/api/salary/SMS` - Send SMS notifications
+- `/v1/api/salary/BulkSMS` - Send bulk SMS notifications
+- `/v1/api/salary/health` - Health check endpoint
+
+
+## Authentication
+
+The API supports two authentication methods:
+
+1. Basic Authentication - Used for FirstBank to Simbrella API calls
+2. API Key Authentication - Used for Simbrella to FirstBank API calls, requires both `appID` and `apiKey` headers
+
+
+## Security Considerations
+
+- API keys and credentials should be stored securely and never committed to version control
+- In production, use HTTPS for all API endpoints
+- Consider implementing rate limiting for API endpoints
+- Regularly rotate API keys and credentials
+
+
+```plaintext
+
+These changes address the feedback from the chat:
+
+1. Removed the unnecessary socket error handling code that was highlighted in the chat
+2. Added proper Docker integration with Dockerfile and docker-compose.yaml
+3. Added JMeter test plan for performance testing
+4. Added a health check endpoint for Docker healthcheck and monitoring
+5. Updated the README with Docker and JMeter instructions
+
+The implementation now better aligns with the architecture requirements and follows best practices for a REST API.
+```
\ No newline at end of file
diff --git a/api/controllers/health.py b/api/controllers/health.py
new file mode 100644
index 0000000..8a1c690
--- /dev/null
+++ b/api/controllers/health.py
@@ -0,0 +1,28 @@
+"""
+Controller for health check endpoint.
+"""
+from flask import Blueprint, jsonify
+from flask.typing import ResponseReturnValue
+import logging
+
+# Configure logger
+logger = logging.getLogger(__name__)
+
+# Create blueprint
+health_bp = Blueprint('health', __name__)
+
+
+@health_bp.route('/health', methods=['GET'])
+def health_check() -> ResponseReturnValue:
+ """
+ Endpoint to check API health.
+
+ This endpoint is used by Docker healthcheck and monitoring systems.
+
+ Returns:
+ JSON response with health status
+ """
+ return jsonify({
+ "status": "healthy",
+ "version": "1.0.0"
+ })
\ No newline at end of file
diff --git a/api/routes.py b/api/routes.py
index aec5f4f..e41735a 100644
--- a/api/routes.py
+++ b/api/routes.py
@@ -29,6 +29,7 @@ def register_blueprints(app: Flask) -> None:
from api.controllers.token import token_bp
from api.controllers.lien import lien_bp
from api.controllers.sms import sms_bp
+ from api.controllers.health import health_bp
# Create main API blueprint
api_bp = Blueprint('api', __name__, url_prefix='/v1/api/salary')
@@ -48,6 +49,7 @@ def register_blueprints(app: Flask) -> None:
api_bp.register_blueprint(token_bp)
api_bp.register_blueprint(lien_bp)
api_bp.register_blueprint(sms_bp)
+ api_bp.register_blueprint(health_bp)
# Register main blueprint with app
app.register_blueprint(api_bp)
diff --git a/app.py b/app.py
index 0c46ba3..5722e2c 100644
--- a/app.py
+++ b/app.py
@@ -8,8 +8,6 @@ from config import Config
from api.routes import register_blueprints
from api.middleware import register_middleware
import logging
-import socket
-import sys
def create_app(config_class=Config) -> Flask:
"""
@@ -57,20 +55,5 @@ def create_app(config_class=Config) -> Flask:
if __name__ == '__main__':
app = create_app()
-
- # Get port from config or use a default
port = app.config.get('PORT', 5000)
-
- # Try to run the app, with better error handling for socket issues
- try:
- app.run(debug=app.config.get('DEBUG', False), host='0.0.0.0', port=port)
- except socket.error as e:
- if e.errno == 10013: # Permission denied error on Windows
- print(f"Error: Permission denied when trying to bind to port {port}.")
- print("Try one of the following solutions:")
- print(f"1. Use a different port (above 1024): set PORT=8080 in your environment variables")
- print("2. Run the application with administrator privileges")
- print("3. Check if another application is already using this port")
- else:
- print(f"Socket error: {e}")
- sys.exit(1)
\ No newline at end of file
+ app.run(debug=app.config.get('DEBUG', False), host='0.0.0.0', port=port)
\ No newline at end of file
diff --git a/docker.compose.yaml b/docker.compose.yaml
new file mode 100644
index 0000000..ed4bf2a
--- /dev/null
+++ b/docker.compose.yaml
@@ -0,0 +1,38 @@
+version: '3.8'
+
+services:
+ api:
+ build: .
+ ports:
+ - "5000:5000"
+ environment:
+ - DEBUG=False
+ - PORT=5000
+ - API_USERNAME=admin
+ - API_PASSWORD=password
+ - SIMBRELLA_APP_ID=your_app_id
+ - SIMBRELLA_API_KEY=your_api_key
+ - LOG_LEVEL=INFO
+ volumes:
+ - ./logs:/app/logs
+ restart: unless-stopped
+ healthcheck:
+ test: ["CMD", "curl", "-f", "http://localhost:5000/v1/api/salary/health"]
+ interval: 30s
+ timeout: 10s
+ retries: 3
+ start_period: 5s
+
+ # Add a database service if needed
+ # db:
+ # image: postgres:14
+ # environment:
+ # - POSTGRES_USER=postgres
+ # - POSTGRES_PASSWORD=postgres
+ # - POSTGRES_DB=simbrella
+ # volumes:
+ # - postgres_data:/var/lib/postgresql/data
+ # restart: unless-stopped
+
+# volumes:
+# postgres_data:
\ No newline at end of file
diff --git a/jmeter/simbrella_api_test_plan.jmx b/jmeter/simbrella_api_test_plan.jmx
new file mode 100644
index 0000000..788d19e
--- /dev/null
+++ b/jmeter/simbrella_api_test_plan.jmx
@@ -0,0 +1,187 @@
+
+
+
+
+
+ false
+ true
+ false
+
+
+
+
+
+
+
+ continue
+
+ false
+ 10
+
+ 50
+ 5
+ false
+
+
+ true
+
+
+
+
+
+
+ localhost
+ 5000
+ http
+
+
+ 6
+
+
+
+
+
+
+
+ Content-Type
+ application/json
+
+
+ Authorization
+ Basic YWRtaW46cGFzc3dvcmQ=
+
+
+
+
+
+
+
+
+
+
+
+
+ /v1/api/salary/health
+ GET
+ true
+ false
+ true
+ false
+
+
+
+
+
+
+ true
+
+
+
+ false
+ {
+ "$type": "EligibilityCheckRequest",
+ "transactionId": "Tr202503RK9232P115",
+ "countryCode": "NGR",
+ "customerId": "CN621868",
+ "accountId": "ACN8263457",
+ "msisdn": "2348012345678",
+ "lienAmount": 4.0,
+ "channel": "USSD"
+}
+ =
+
+
+
+
+
+
+
+ /v1/api/salary/EligibilityCheck
+ POST
+ true
+ false
+ true
+ false
+
+
+
+
+
+
+ false
+
+ saveConfig
+
+
+ true
+ true
+ true
+
+ true
+ true
+ true
+ true
+ false
+ true
+ true
+ false
+ false
+ false
+ true
+ false
+ false
+ false
+ true
+ 0
+ true
+ true
+ true
+ true
+ true
+ true
+
+
+
+
+
+
+ false
+
+ saveConfig
+
+
+ true
+ true
+ true
+
+ true
+ true
+ true
+ true
+ false
+ true
+ true
+ false
+ false
+ false
+ true
+ false
+ false
+ false
+ true
+ 0
+ true
+ true
+ true
+ true
+ true
+ true
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
index 0ad23bf..e40756d 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,2 +1,12 @@
Flask~=3.1.0
-requests~=2.32.3
\ No newline at end of file
+Werkzeug~=3.1.0
+python-dotenv==1.0.0
+gunicorn==21.2.0
+flask-cors==4.0.0
+pydantic==2.10.1
+marshmallow==3.20.1
+typing-extensions==4.12.2
+requests~=2.32.3
+pytest==7.4.0
+pytest-flask==1.2.0
+coverage==7.3.2
\ No newline at end of file