initial commit

This commit is contained in:
lennyaiko
2025-03-27 11:29:36 +01:00
commit 8e2d371218
35 changed files with 548 additions and 0 deletions
View File
+1
View File
@@ -0,0 +1 @@
./vscode
+24
View File
@@ -0,0 +1,24 @@
{
"editor.lineNumbers": "off",
"editor.padding.top": 3,
"editor.padding.bottom": 3,
"editor.formatOnSave": true,
"editor.formatOnPaste": true,
"editor.fontSize": 14,
"editor.lineHeight": 4.5,
"editor.suggestFontSize": 15,
// "editor.suggestLineHeight": 4,
"breadcrumbs.enabled": false,
"workbench.tips.enabled": false,
"workbench.statusBar.visible": false,
// "workbench.editor.showTabs": "single",
"git.enableSmartCommit": true,
"workbench.editor.editorActionsLocation": "hidden",
// "workbench.activityBar.location": "hidden",
"workbench.editor.enablePreviewFromQuickOpen": false,
"editor.lightbulb.enabled": "off",
"editor.selectionHighlight": false,
"editor.overviewRulerBorder": false,
"editor.renderLineHighlight": "none",
"editor.occurrencesHighlight": "off"
}
+21
View File
@@ -0,0 +1,21 @@
# Use Python base image
FROM python:3.10
# Set working directory
WORKDIR /app
# Copy files to container
COPY . /app
# Install dependencies
RUN pip install --no-cache-dir -r requirements.txt
# Expose port 5000
EXPOSE 5000
# Set environment variables
ENV FLASK_APP=app.py
ENV FLASK_RUN_HOST=0.0.0.0
# Run the application
CMD ["gunicorn", "-w", "4", "-b", "0.0.0.0:5000", "wsgi:wsgi_app"]
View File
Binary file not shown.
Binary file not shown.
+6
View File
@@ -0,0 +1,6 @@
from app import create_app
app = create_app()
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000, debug=True)
+27
View File
@@ -0,0 +1,27 @@
from flask import Flask
from flask_cors import CORS
from app.config import Config
from app.routes import auth_bp, eligibility_bp
from app.errors import method_not_allowed, unsupported_media_type
def create_app():
"""Factory function to create a Flask app instance"""
app = Flask(__name__)
# Load configuration
app.config.from_object(Config)
CORS(app)
# Register blueprints
app.register_blueprint(auth_bp)
# app.register_blueprint(loan_bp)
app.register_blueprint(eligibility_bp, url_prefix="/eligibility")
# app.register_blueprint(repayment_bp)
# Error Handlers
app.register_error_handler(405, method_not_allowed)
app.register_error_handler(415, unsupported_media_type)
return app
Binary file not shown.
Binary file not shown.
+9
View File
@@ -0,0 +1,9 @@
import os
class Config:
"""Base configuration for Flask app"""
SECRET_KEY = os.getenv("SECRET_KEY", "supersecretkey")
API_BASE_URL = "https://coreapi.dev.simbrellang.net/v1/api/salary"
JWT_SECRET_KEY = os.getenv("JWT_SECRET_KEY", "your_jwt_secret")
DEBUG = True
+1
View File
@@ -0,0 +1 @@
from .handlers import method_not_allowed, unsupported_media_type
Binary file not shown.
Binary file not shown.
+18
View File
@@ -0,0 +1,18 @@
from flask import jsonify
from app.helpers.response_helper import ResponseHelper
def method_not_allowed(error):
return ResponseHelper.method_not_allowed(message="Method Not Allowed")
def not_found(error):
return ResponseHelper.not_found(message="URL Not Found")
def bad_request(error):
return ResponseHelper.bad_request(message="Bad Request")
def unsupported_media_type(error):
return ResponseHelper.error(message="Unsupported Media Type", status_code=415)
+251
View File
@@ -0,0 +1,251 @@
from flask import jsonify
from typing import List, Dict, Union, Optional, Any
class ResponseHelper:
"""
A helper class for building standardized JSON responses in Flask.
"""
@staticmethod
def build_response(
status: bool,
message: str,
data: Optional[Union[Dict, List, str]] = None,
status_code: int = 200,
error: Optional[Union[Dict, str]] = None,
) -> Dict[str, Any]:
"""
Build a standardized JSON response.
Args:
status (bool): Indicates whether the request was successful.
message (str): A message describing the result of the request.
data (Optional[Union[Dict, List, str]]): The data to return in the response.
status_code (int): The HTTP status code for the response.
error (Optional[Union[Dict, str]]): Any error details to include in the response.
Returns:
Dict[str, Any]: A dictionary representing the JSON response.
"""
response = {
"status": status,
"statusCode": status_code,
"message": message,
"data": data if data is not None else {},
"error": error if error is not None else {},
}
return jsonify(response), status_code
@staticmethod
def success(
data: Optional[Union[Dict, List, str]] = None,
message: str = "Successful",
status_code: int = 200,
error: Optional[Union[Dict, str]] = None,
) -> Dict[str, Any]:
"""
Return a success response.
Args:
data (Optional[Union[Dict, List, str]]): The data to return in the response.
message (str): A message describing the result of the request.
status_code (int): The HTTP status code for the response.
error (Optional[Union[Dict, str]]): Any error details to include in the response.
Returns:
Dict[str, Any]: A dictionary representing the JSON response.
"""
return ResponseHelper.build_response(True, message, data, status_code, error)
@staticmethod
def error(
message: str = "An error occurred",
status_code: int = 400,
data: Optional[Union[Dict, List, str]] = None,
error: Optional[Union[Dict, str]] = None,
) -> Dict[str, Any]:
"""
Return an error response.
Args:
message (str): A message describing the error.
status_code (int): The HTTP status code for the response.
data (Optional[Union[Dict, List, str]]): The data to return in the response.
error (Optional[Union[Dict, str]]): Any error details to include in the response.
Returns:
Dict[str, Any]: A dictionary representing the JSON response.
"""
return ResponseHelper.build_response(False, message, data, status_code, error)
@staticmethod
def created(
data: Optional[Union[Dict, List, str]] = None,
message: str = "Resource created successfully",
error: Optional[Union[Dict, str]] = None,
) -> Dict[str, Any]:
"""
Return a response for a created resource.
Args:
data (Optional[Union[Dict, List, str]]): The data to return in the response.
message (str): A message describing the result of the request.
error (Optional[Union[Dict, str]]): Any error details to include in the response.
Returns:
Dict[str, Any]: A dictionary representing the JSON response.
"""
return ResponseHelper.build_response(True, message, data, 201, error)
@staticmethod
def updated(
data: Optional[Union[Dict, List, str]] = None,
message: str = "Resource updated successfully",
error: Optional[Union[Dict, str]] = None,
) -> Dict[str, Any]:
"""
Return a response for an updated resource.
Args:
data (Optional[Union[Dict, List, str]]): The data to return in the response.
message (str): A message describing the result of the request.
error (Optional[Union[Dict, str]]): Any error details to include in the response.
Returns:
Dict[str, Any]: A dictionary representing the JSON response.
"""
return ResponseHelper.build_response(True, message, data, 200, error)
@staticmethod
def internal_server_error(
message: str = "Internal Server Error",
data: Optional[Union[Dict, List, str]] = None,
error: Optional[Union[Dict, str]] = None,
) -> Dict[str, Any]:
"""
Return a response for an internal server error.
Args:
message (str): A message describing the error.
data (Optional[Union[Dict, List, str]]): The data to return in the response.
error (Optional[Union[Dict, str]]): Any error details to include in the response.
Returns:
Dict[str, Any]: A dictionary representing the JSON response.
"""
return ResponseHelper.build_response(False, message, data, 500, error)
@staticmethod
def unauthorized(
message: str = "Unauthorized",
data: Optional[Union[Dict, List, str]] = None,
error: Optional[Union[Dict, str]] = None,
) -> Dict[str, Any]:
"""
Return a response for an unauthorized request.
Args:
message (str): A message describing the error.
data (Optional[Union[Dict, List, str]]): The data to return in the response.
error (Optional[Union[Dict, str]]): Any error details to include in the response.
Returns:
Dict[str, Any]: A dictionary representing the JSON response.
"""
return ResponseHelper.build_response(False, message, data, 401, error)
@staticmethod
def forbidden(
message: str = "Forbidden",
data: Optional[Union[Dict, List, str]] = None,
error: Optional[Union[Dict, str]] = None,
) -> Dict[str, Any]:
"""
Return a response for a forbidden request.
Args:
message (str): A message describing the error.
data (Optional[Union[Dict, List, str]]): The data to return in the response.
error (Optional[Union[Dict, str]]): Any error details to include in the response.
Returns:
Dict[str, Any]: A dictionary representing the JSON response.
"""
return ResponseHelper.build_response(False, message, data, 403, error)
@staticmethod
def not_found(
message: str = "Resource not found",
data: Optional[Union[Dict, List, str]] = None,
error: Optional[Union[Dict, str]] = None,
) -> Dict[str, Any]:
"""
Return a response for a not found resource.
Args:
message (str): A message describing the error.
data (Optional[Union[Dict, List, str]]): The data to return in the response.
error (Optional[Union[Dict, str]]): Any error details to include in the response.
Returns:
Dict[str, Any]: A dictionary representing the JSON response.
"""
return ResponseHelper.build_response(False, message, data, 404, error)
@staticmethod
def unprocessable_entity(
message: str = "Unprocessable entity",
data: Optional[Union[Dict, List, str]] = None,
error: Optional[Union[Dict, str]] = None,
) -> Dict[str, Any]:
"""
Return a response for an unprocessable entity.
Args:
message (str): A message describing the error.
data (Optional[Union[Dict, List, str]]): The data to return in the response.
error (Optional[Union[Dict, str]]): Any error details to include in the response.
Returns:
Dict[str, Any]: A dictionary representing the JSON response.
"""
return ResponseHelper.build_response(False, message, data, 422, error)
@staticmethod
def method_not_allowed(
message: str = "Method Not Allowed",
data: Optional[Union[Dict, List, str]] = None,
error: Optional[Union[Dict, str]] = None,
) -> Dict[str, Any]:
"""
Return a response for a method not allowed error.
Args:
message (str): A message describing the error.
data (Optional[Union[Dict, List, str]]): The data to return in the response.
error (Optional[Union[Dict, str]]): Any error details to include in the response.
Returns:
Dict[str, Any]: A dictionary representing the JSON response.
"""
return ResponseHelper.build_response(False, message, data, 405, error)
@staticmethod
def bad_request(
message: str = "Bad Request",
data: Optional[Union[Dict, List, str]] = None,
error: Optional[Union[Dict, str]] = None,
) -> Dict[str, Any]:
"""
Return a response for a bad request error.
Args:
message (str): A message describing the error.
data (Optional[Union[Dict, List, str]]): The data to return in the response.
error (Optional[Union[Dict, str]]): Any error details to include in the response.
Returns:
Dict[str, Any]: A dictionary representing the JSON response.
"""
return ResponseHelper.build_response(False, message, data, 400, error)
+4
View File
@@ -0,0 +1,4 @@
from .authentication import auth_bp
from .eligibility import eligibility_bp
# from .loan import loan_bp
# from .repayment import repayment_bp
Binary file not shown.
Binary file not shown.
+19
View File
@@ -0,0 +1,19 @@
from flask import Blueprint, request, jsonify
import requests
from app.utils.auth import get_headers
auth_bp = Blueprint("auth", __name__)
@auth_bp.route("/health", methods=["GET"])
def health():
return jsonify({"status": "Up"})
@auth_bp.route("/login", methods=["POST"])
def login():
data = request.json
api_url = "https://coreapi.dev.simbrellang.net/api/auth/login"
response = requests.post(api_url, json=data)
if response.status_code == 200:
return jsonify(response.json()), 200
return jsonify({"error": "Invalid credentials"}), response.status_code
+42
View File
@@ -0,0 +1,42 @@
from flask import Blueprint, request, jsonify, current_app
import requests
from app.utils.auth import get_headers
eligibility_bp = Blueprint("eligibility", __name__)
@eligibility_bp.route("/check", methods=["POST"])
def eligibility_check():
data = request.json
api_url = f"{current_app.config['API_BASE_URL']}/EligibilityCheck"
print(api_url)
# response = requests.post(api_url, json=data, headers=get_headers())
# return jsonify(response.json()), response.status_code
response = {
"customerId": "CN621868",
"transactionId": "Tr201712RK9232P115",
"countryCode": "NGR",
"msisdn": "2348012345678",
"eligibleOffers": [
{
"offerId": 101,
"minAmount": 5000,
"maxAmount": 20000,
"productId": 2030,
"tenor": 30,
},
{
"offerId": 102,
"minAmount": 20000,
"maxAmount": 50000,
"productId": 2090,
"tenor": 90,
},
],
"resultCode": "00",
"resultDescription": "Successful",
}
return jsonify(response), 200
View File
View File
View File
Binary file not shown.
Binary file not shown.
+8
View File
@@ -0,0 +1,8 @@
import requests
from flask import current_app
def get_headers():
return {
"Authorization": f"Bearer {current_app.config['JWT_SECRET_KEY']}",
"Content-Type": "application/json"
}
+13
View File
@@ -0,0 +1,13 @@
import logging
# Configure logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s",
handlers=[
# logging.StreamHandler(), # Log to console
logging.FileHandler("app.log", mode='a') # Log to file
]
)
logger = logging.getLogger("DetectionService")
+30
View File
@@ -0,0 +1,30 @@
version: "3.8"
services:
flask:
build: .
ports:
- "5000:5000"
environment:
- FLASK_APP=app.py
- FLASK_RUN_HOST=0.0.0.0
volumes:
- .:/app
restart: always
networks:
- digital
swagger:
image: swaggerapi/swagger-ui
ports:
- "9000:8080"
volumes:
- ./openapi.yml:/usr/local/openapi.yml
environment:
- SWAGGER_JSON=/usr/local/openapi.yml
restart: always
networks:
- digital
networks:
digital:
driver: bridge
+60
View File
@@ -0,0 +1,60 @@
openapi: 3.0.3
info:
title: Sample Flask API
description: A simple Flask API with Swagger documentation running in Docker
version: 1.0.0
contact:
name: API Support
email: support@example.com
license:
name: MIT
url: https://opensource.org/licenses/MIT
servers:
- url: http://localhost:5000
description: Local development server
paths:
/health:
get:
summary: Returns a health message
responses:
200:
description: A successful response
/eligibility/check:
post:
summary: Performs eligibility check on a user
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
transactionId:
type: string
description: The transaction ID
example: Tr201712RK9232P115
customerId:
type: string
description: The customer ID
example: CN621868
countryCode:
type: string
description: The country code
example: NGR
accountId:
type: string
description: The account ID
example: ACN8263457
msisdn:
type: string
description: The MSISDN
example: 8012345678
channel:
type: string
description: The channel
example: 100
responses:
200:
description: A successful response
+7
View File
@@ -0,0 +1,7 @@
# Flask and Extensions
Flask==2.3.3
Flask-Marshmallow==0.15.0
marshmallow==3.19.0
Flask-Cors==3.0.10
gunicorn
requests
+7
View File
@@ -0,0 +1,7 @@
from app import create_app
app = create_app()
if __name__ != "__main__":
# Expose WSGI app instance for Gunicorn
wsgi_app = app