From 43dc5bd835552e08fbbd0b7eabc8027b052a9619 Mon Sep 17 00:00:00 2001 From: VivianDee <115420678+VivianDee@users.noreply.github.com> Date: Fri, 21 Mar 2025 16:26:58 +0100 Subject: [PATCH] [update]: .gitignore file --- .DS_Store | Bin 6148 -> 6148 bytes .gitignore | 4 +- Dockerfile | 2 +- README.md | 25 +- app.log | 257 +----------------- app/__init__.py | 5 + app/blueprints/__init__.py | 18 -- app/blueprints/bulk_sms.py | 55 ---- app/blueprints/sms.py | 56 ---- app/errors/__init__.py | 1 + app/errors/handlers.py | 14 + app/helpers/response_helper.py | 42 ++- app/middlewares/__init__.py | 3 + app/middlewares/app_id_checker.py | 26 ++ app/middlewares/cors.py | 14 +- app/middlewares/encryption.py | 14 - app/middlewares/request_validator.py | 11 - app/middlewares/verify_api_key.py | 29 +- app/routes/routes.py | 106 +++++--- app/schemas/disbursement.py | 27 +- app/schemas/eligibility_check.py | 16 +- app/schemas/lien_check.py | 8 +- app/schemas/penal_charge.py | 18 +- app/schemas/revoke_enable_consent.py | 16 +- app/schemas/select_offer.py | 16 +- app/services/__init__.py | 16 ++ app/{blueprints => services}/collect_loan.py | 20 +- .../customer_consent.py | 19 +- app/{blueprints => services}/disbursement.py | 18 +- .../eligibility_check.py | 20 +- app/{blueprints => services}/lien_check.py | 17 +- .../loan_information.py | 18 +- .../new_transaction_check.py | 17 +- .../notification_callback.py | 18 +- app/{blueprints => services}/penal_charge.py | 18 +- app/{blueprints => services}/provide_loan.py | 18 +- app/{blueprints => services}/rac_check.py | 18 +- app/{blueprints => services}/repayment.py | 18 +- .../revoke_enable_consent.py | 18 +- app/{blueprints => services}/select_offer.py | 38 ++- .../token_validation.py | 18 +- .../transaction_verify.py | 18 +- app/utils/logger.py | 2 +- docker-compose.yml | 4 +- requirements.txt | 5 +- wsgi.py | 7 + 46 files changed, 423 insertions(+), 705 deletions(-) delete mode 100644 app/blueprints/__init__.py delete mode 100644 app/blueprints/bulk_sms.py delete mode 100644 app/blueprints/sms.py create mode 100644 app/errors/__init__.py create mode 100644 app/errors/handlers.py create mode 100644 app/middlewares/app_id_checker.py delete mode 100644 app/middlewares/encryption.py delete mode 100644 app/middlewares/request_validator.py create mode 100644 app/services/__init__.py rename app/{blueprints => services}/collect_loan.py (80%) rename app/{blueprints => services}/customer_consent.py (75%) rename app/{blueprints => services}/disbursement.py (83%) rename app/{blueprints => services}/eligibility_check.py (81%) rename app/{blueprints => services}/lien_check.py (79%) rename app/{blueprints => services}/loan_information.py (83%) rename app/{blueprints => services}/new_transaction_check.py (83%) rename app/{blueprints => services}/notification_callback.py (78%) rename app/{blueprints => services}/penal_charge.py (78%) rename app/{blueprints => services}/provide_loan.py (80%) rename app/{blueprints => services}/rac_check.py (82%) rename app/{blueprints => services}/repayment.py (78%) rename app/{blueprints => services}/revoke_enable_consent.py (80%) rename app/{blueprints => services}/select_offer.py (89%) rename app/{blueprints => services}/token_validation.py (80%) rename app/{blueprints => services}/transaction_verify.py (81%) create mode 100644 wsgi.py diff --git a/.DS_Store b/.DS_Store index e81c242592628868a41ba832ff3693730f757c78..8ba90e75a820f2e15be6ceaa4668cb286cb9c3ee 100644 GIT binary patch literal 6148 zcmeHKK~BRk5FDo!v~a07aY4!lh{O-lsy%Sv0v~`jp+cm%&{m?i-1!4v;C-B6*0!p~ zg=;0yuCyL|y|Zz~QXB&?gH>?>^Z<0|f=QRn4<_^CJC=lqOd6?SC*#VFIg)Z}e?bA+ zyB6*+$NaKt>|dWV*_Si-A|b0-OscW96*nA*o`?(;ET&O`kEp=GCVP%`GuNC&xhE)C zae_ybc)>Y)7Beo=V!=p;HAhuc<1MOjgyB|=dpvNJWyJZ6I9*(0gg%*9#EefVnv7%` zF?w8IiD#@>UwSk86P8?O#`YDx3tpa6>WS>S0os4wHM-RuxbMnhLc1uqWsL==b}7lcXb6Ko$5`3YhlrW;oz0 zg|l_%<>ahQ>9=$-$*VnXDD1eSn7MKkpVFN%o=S(92dq6ZLen1sCxbSsz+M&j23JvW AyZ`_I delta 110 zcmZoMXfc=|#>AjHu~68Uk%57MnIVy(fT19zI5{UNKR*X30s%}=8l(n@1sEXmn 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) \ No newline at end of file diff --git a/app/middlewares/__init__.py b/app/middlewares/__init__.py index e69de29..c439164 100644 --- a/app/middlewares/__init__.py +++ b/app/middlewares/__init__.py @@ -0,0 +1,3 @@ +from .verify_api_key import require_api_key +from .app_id_checker import require_app_id +from .cors import enforce_json \ No newline at end of file diff --git a/app/middlewares/app_id_checker.py b/app/middlewares/app_id_checker.py new file mode 100644 index 0000000..18797b0 --- /dev/null +++ b/app/middlewares/app_id_checker.py @@ -0,0 +1,26 @@ +from functools import wraps +from flask import request, jsonify +from app.utils.logger import logger +import os + +# Load valid App-IDs from environment variables (comma-separated list) +VALID_APP_ID = os.getenv("VALID_APP_ID", "app1,app2,app3").split(",") + +def require_app_id(f): + """Decorator to enforce App-ID validation.""" + @wraps(f) + def decorated_function(*args, **kwargs): + app_id = request.headers.get("App-ID") + + if not app_id: + logger.error("Unauthorized access: Missing App-ID.") + return jsonify({"message": "Invalid request parameters"}), 400 + + + if app_id not in VALID_APP_ID: + logger.error(f"Unauthorized access: Invalid App-ID {app_id}.") + return jsonify({"message": "Invalid request parameters"}), 400 + + return f(*args, **kwargs) + + return decorated_function diff --git a/app/middlewares/cors.py b/app/middlewares/cors.py index e655fac..7df0844 100644 --- a/app/middlewares/cors.py +++ b/app/middlewares/cors.py @@ -1,9 +1,7 @@ -# app/middlewares/cors.py -from flask import request +from flask import request, jsonify -def cors_headers(response): - """Allow cross-origin requests""" - response.headers["Access-Control-Allow-Origin"] = "*" - response.headers["Access-Control-Allow-Methods"] = "GET, POST, PUT, PATCH, DELETE" - response.headers["Access-Control-Allow-Headers"] = "Authorization, Content-Type" - return response + +def enforce_json(): + """Middleware to enforce JSON Content-Type for incoming requests""" + if request.method in ["POST", "PUT", "PATCH"] and request.content_type != "application/json": + return jsonify({"message": "Invalid request parameters"}), 400 diff --git a/app/middlewares/encryption.py b/app/middlewares/encryption.py deleted file mode 100644 index ccb3372..0000000 --- a/app/middlewares/encryption.py +++ /dev/null @@ -1,14 +0,0 @@ -# app/middlewares/encryption.py -from cryptography.fernet import Fernet -import os - -ENCRYPTION_KEY = os.getenv("ENCRYPTION_KEY", Fernet.generate_key()) -cipher = Fernet(ENCRYPTION_KEY) - -def encrypt_data(data): - """Encrypt sensitive data""" - return cipher.encrypt(data.encode()).decode() - -def decrypt_data(data): - """Decrypt sensitive data""" - return cipher.decrypt(data.encode()).decode() diff --git a/app/middlewares/request_validator.py b/app/middlewares/request_validator.py deleted file mode 100644 index 903e450..0000000 --- a/app/middlewares/request_validator.py +++ /dev/null @@ -1,11 +0,0 @@ -# app/middlewares/request_validator.py -from flask import request -from app.helpers.response_helper import ResponseHelper - -def validate_json(): - """Ensure request has valid JSON""" - if not request.is_json: - return ResponseHelper.error( - message="Request must be JSON", - status_code=415 - ) diff --git a/app/middlewares/verify_api_key.py b/app/middlewares/verify_api_key.py index 22831df..81644b1 100644 --- a/app/middlewares/verify_api_key.py +++ b/app/middlewares/verify_api_key.py @@ -1,8 +1,25 @@ -# app/middlewares/auth.py +from functools import wraps from flask import request, jsonify +from app.utils.logger import logger +import os -def require_api_key(): - """Middleware to check if API key is present""" - api_key = request.headers.get("X-API-KEY") - if not api_key: - return jsonify({"error": "Missing API key"}), 403 +# Load valid API key from environment variables (fallback for testing) +VALID_API_KEY = os.getenv("VALID_API_KEY", "test-api-key-12345") + +def require_api_key(f): + """Decorator to enforce API key authentication.""" + @wraps(f) + def decorated_function(*args, **kwargs): + api_key = request.headers.get("X-API-KEY") + + if not api_key: + logger.error("Unauthorized access: Missing API key.") + return jsonify({"message": "Invalid request parameters"}), 400 + + if api_key != VALID_API_KEY: + logger.error("Unauthorized access: Invalid API key.") + return jsonify({"message": "Invalid request parameters"}), 400 + + return f(*args, **kwargs) + + return decorated_function diff --git a/app/routes/routes.py b/app/routes/routes.py index 1033b4c..2c75412 100644 --- a/app/routes/routes.py +++ b/app/routes/routes.py @@ -1,5 +1,5 @@ from flask import Blueprint, request, jsonify -from app.blueprints import ( +from app.services import ( EligibilityCheckService, SelectOfferService, ProvideLoanService, @@ -15,159 +15,195 @@ from app.blueprints import ( RevokeEnableConsentService, TokenValidationService, LienCheckService, - NewTransactionCheckService, - SMSService, - BulkSMSService + NewTransactionCheckService ) from app.utils.logger import logger +from app.middlewares import require_api_key, require_app_id, enforce_json api = Blueprint("api", __name__) +@api.before_request +def cors_middleware(): + """Middleware applied globally to all API routes in this blueprint""" + return enforce_json() + + # EligibilityCheck Endpoint @api.route('/EligibilityCheck', methods=['POST']) +@require_api_key +@require_app_id def eligibility_check(): data = request.get_json() # logger.info(f"EligibilityCheck request received: {data}") response = EligibilityCheckService.process_request(data) - return jsonify(response) + return response # SelectOffer Endpoint @api.route('/SelectOffer', methods=['POST']) +@require_api_key +@require_app_id def select_offer(): data = request.get_json() # logger.info(f"SelectOffer request received: {data}") response = SelectOfferService.process_request(data) - return jsonify(response) + return response + # ProvideLoan Endpoint @api.route('/ProvideLoan', methods=['POST']) +@require_api_key +@require_app_id def provide_loan(): data = request.get_json() # logger.info(f"ProvideLoan request received: {data}") response = ProvideLoanService.process_request(data) - return jsonify(response) + return response + # LoanInformation Endpoint @api.route('/LoanInformation', methods=['GET']) +@require_api_key +@require_app_id def loan_information(): data = request.args.to_dict() # logger.info(f"LoanInformation request received: {data}") response = LoanInformationService.process_request(data) - return jsonify(response) + return response + # Repayment Endpoint @api.route('/Repayment', methods=['POST']) +@require_api_key +@require_app_id def repayment(): data = request.get_json() # logger.info(f"Repayment request received: {data}") response = RepaymentService.process_request(data) - return jsonify(response) + return response + # CustomerConsent Endpoint @api.route('/CustomerConsent', methods=['POST']) +@require_api_key +@require_app_id def customer_consent(): data = request.get_json() # logger.info(f"CustomerConsent request received: {data}") response = CustomerConsentService.process_request(data) - return jsonify(response) + return response + # NotificationCallback Endpoint @api.route('/NotificationCallback', methods=['POST']) +@require_api_key +@require_app_id def notification_callback(): data = request.get_json() # logger.info(f"NotificationCallback request received: {data}") response = NotificationCallbackService.process_request(data) - return jsonify(response) + return response + # RACCheck Endpoint @api.route('/RACCheck', methods=['POST']) +@require_api_key +@require_app_id def rac_check(): data = request.get_json() # logger.info(f"RACCheck request received: {data}") response = RACCheckService.process_request(data) - return jsonify(response) + return response + # Disbursement Endpoint @api.route('/Disbursement', methods=['POST']) +@require_api_key +@require_app_id def disbursement(): data = request.get_json() # logger.info(f"Disbursement request received: {data}") response = DisbursementService.process_request(data) - return jsonify(response) + return response + # CollectLoan Endpoint @api.route('/CollectLoan', methods=['POST']) +@require_api_key +@require_app_id def collect_loan(): data = request.get_json() # logger.info(f"CollectLoan request received: {data}") response = CollectLoanService.process_request(data) - return jsonify(response) + return response + # TransactionVerify Endpoint @api.route('/TransactionVerify', methods=['POST']) +@require_api_key +@require_app_id def transaction_verify(): data = request.get_json() # logger.info(f"TransactionVerify request received: {data}") response = TransactionVerifyService.process_request(data) - return jsonify(response) + return response + # PenalCharge Endpoint @api.route('/PenalCharge', methods=['POST']) +@require_api_key +@require_app_id def penal_charge(): data = request.get_json() # logger.info(f"PenalCharge request received: {data}") response = PenalChargeService.process_request(data) - return jsonify(response) + return response + # RevokeEnableConsent Endpoint @api.route('/RevokeEnableConsent', methods=['POST']) +@require_api_key +@require_app_id def revoke_enable_consent(): data = request.get_json() # logger.info(f"RevokeEnableConsent request received: {data}") response = RevokeEnableConsentService.process_request(data) - return jsonify(response) + return response + # TokenValidation Endpoint @api.route('/TokenValidation', methods=['POST']) +@require_api_key +@require_app_id def token_validation(): data = request.get_json() # logger.info(f"TokenValidation request received: {data}") response = TokenValidationService.process_request(data) - return jsonify(response) + return response + # LienCheck Endpoint @api.route('/LienCheck', methods=['POST']) +@require_api_key +@require_app_id def lien_check(): data = request.get_json() # logger.info(f"LienCheck request received: {data}") response = LienCheckService.process_request(data) - return jsonify(response) + return response + # NewTransactionCheck Endpoint @api.route('/NewTransactionCheck', methods=['POST']) +@require_api_key +@require_app_id def new_transaction_check(): data = request.get_json() # logger.info(f"NewTransactionCheck request received: {data}") response = NewTransactionCheckService.process_request(data) - return jsonify(response) + return response -# SMS Endpoint -@api.route('/SMS', methods=['POST']) -def sms(): - data = request.get_json() - # logger.info(f"SMS request received: {data}") - response = SMSService.process_request(data) - return jsonify(response) - -# BulkSMS Endpoint -@api.route('/BulkSMS', methods=['POST']) -def bulk_sms(): - data = request.get_json() - # logger.info(f"BulkSMS request received: {data}") - response = BulkSMSService.process_request(data) - return jsonify(response) # Health Check Endpoint @api.route('/health', methods=['GET']) diff --git a/app/schemas/disbursement.py b/app/schemas/disbursement.py index b9ffe39..5f4bead 100644 --- a/app/schemas/disbursement.py +++ b/app/schemas/disbursement.py @@ -2,16 +2,17 @@ from marshmallow import Schema, fields # Disbursement Schema class DisbursementSchema(Schema): - requestId = fields.Str(required=True, data_key="requestId") - debtId = fields.Str(required=True, data_key="debtId") - transactionId = fields.Str(required=True, data_key="transactionId") - customerId = fields.Str(required=True, data_key="customerId") - accountId = fields.Str(required=True, data_key="accountId") - productId = fields.Str(required=True, data_key="productId") - provideAmount = fields.Float(required=True, data_key="provideAmount") - collectAmountInterest = fields.Float(required=False, data_key="collectAmountInterest") # Optional - collectAmountMgtFee = fields.Float(required=True, data_key="collectAmountMgtFee") - collectAmountInsurance = fields.Float(required=True, data_key="collectAmountInsurance") - collectAmountVAT = fields.Float(required=True, data_key="collectAmountVAT") - countryId = fields.Str(required=True, data_key="countryId") - comment = fields.Str(required=False, data_key="comment") # Optional \ No newline at end of file + requestId = fields.Str(required=True) + debtId = fields.Str(required=True) + transactionId = fields.Str(required=True) + customerId = fields.Str(required=True) + accountId = fields.Str(required=True) + productId = fields.Str(required=True) + provideAmount = fields.Float(required=True) + collectAmountInterest = fields.Float(required=False) # Optional + collectAmountMgtFee = fields.Float(required=True) + collectAmountInsurance = fields.Float(required=True) + collectAmountVAT = fields.Float(required=True) + countryId = fields.Str(required=True) + comment = fields.Str(required=False) # Optional + \ No newline at end of file diff --git a/app/schemas/eligibility_check.py b/app/schemas/eligibility_check.py index 41e3a3e..016168c 100644 --- a/app/schemas/eligibility_check.py +++ b/app/schemas/eligibility_check.py @@ -1,11 +1,11 @@ from marshmallow import Schema, fields class EligibilityCheckSchema(Schema): - type = fields.Str(required=True, description="Request type") - transactionId = fields.Str(data_key="transactionId", required=True, description="Transaction ID") - countryCode = fields.Str(data_key="countryCode", required=True, description="Country code (ISO)") - customerId = fields.Str(data_key="customerId", required=True, description="Customer ID") - accountId = fields.Str(data_key="accountId", required=True, description="Account ID") - msisdn = fields.Str(required=True, description="Mobile number") - lienAmount = fields.Float(required=True, description="Amount for lien") - channel = fields.Str(required=True, description="Transaction channel (USSD, Mobile, Web)") + type = fields.Str(required=True) + transactionId = fields.Str(required=True) + countryCode = fields.Str(required=True) + customerId = fields.Str(required=True) + accountId = fields.Str(required=True) + msisdn = fields.Str(required=True) + lienAmount = fields.Float(required=True) + channel = fields.Str(required=True) diff --git a/app/schemas/lien_check.py b/app/schemas/lien_check.py index 79c509e..7a0912a 100644 --- a/app/schemas/lien_check.py +++ b/app/schemas/lien_check.py @@ -2,7 +2,7 @@ from marshmallow import Schema, fields # Lien Check Schema class LienCheckSchema(Schema): - transactionId = fields.Str(required=True, metadata={"description": "Unique Identifier in Simbrella system"}) - customerId = fields.Str(required=True, metadata={"description": "Unique identifier of customer"}) - accountId = fields.Str(required=True, metadata={"description": "Unique identifier of account"}) - countryId = fields.Str(required=True, metadata={"description": 'Set to static value "01"'}) \ No newline at end of file + transactionId = fields.Str(required=True) + customerId = fields.Str(required=True) + accountId = fields.Str(required=True) + countryId = fields.Str(required=True) \ No newline at end of file diff --git a/app/schemas/penal_charge.py b/app/schemas/penal_charge.py index cb04e4e..40d6d0a 100644 --- a/app/schemas/penal_charge.py +++ b/app/schemas/penal_charge.py @@ -3,12 +3,12 @@ from marshmallow import Schema, fields # Penal Charge Schema class PenalChargeSchema(Schema): - transactionId = fields.Str(required=True, metadata={"description": "Unique identifier of transaction in Simbrella system"}) - fbnTransactionId = fields.Str(required=True, metadata={"description": "Unique id of the transaction received from FBN in Eligibility or Provision requests"}) - debtId = fields.Str(required=True, metadata={"description": "Unique identifier of providing loan in Simbrella system"}) - customerId = fields.Str(required=True, metadata={"description": "Unique identifier of a user"}) - accountId = fields.Str(required=True, metadata={"description": "Specific identifier of a user’s account"}) - penalCharge = fields.Decimal(required=True, metadata={"description": "Penalty amount that needs to be collected from user’s account"}) - lienAmount = fields.Decimal(required=True, metadata={"description": "Aggregated (summed up) lien amount"}) - countryId = fields.Str(required=True, metadata={"description": 'Set to static value "01"'}) - comment = fields.Str(required=False, metadata={"description": "Any additional comment for provided loan operation"}) + transactionId = fields.Str(required=True) + fbnTransactionId = fields.Str(required=True) + debtId = fields.Str(required=True) + customerId = fields.Str(required=True) + accountId = fields.Str(required=True) + penalCharge = fields.Decimal(required=True) + lienAmount = fields.Decimal(required=True) + countryId = fields.Str(required=True) + comment = fields.Str(required=False) diff --git a/app/schemas/revoke_enable_consent.py b/app/schemas/revoke_enable_consent.py index fd617eb..ce1a5f5 100644 --- a/app/schemas/revoke_enable_consent.py +++ b/app/schemas/revoke_enable_consent.py @@ -3,11 +3,11 @@ from marshmallow import Schema, fields # Revoke Enable Consent Schema class RevokeEnableConsentSchema(Schema): - transactionId = fields.Str(required=True, metadata={"description": "Unique identifier of transaction in Simbrella system"}) - fbnTransactionId = fields.Str(required=True, metadata={"description": "Unique id of the transaction received from FBN in CustomerConsentRequest"}) - customerId = fields.Str(required=True, metadata={"description": "Unique identifier of a user"}) - accountId = fields.Str(required=True, metadata={"description": "Specific identifier of a user’s account"}) - processTime = fields.DateTime(required=True, metadata={"description": "Date and time when consent request was processed"}) - consentType = fields.Str(required=True, metadata={"description": '“Enable” or “Revoke”'}) - countryId = fields.Str(required=True, metadata={"description": 'Set to static value "01"'}) - comment = fields.Str(required=False, metadata={"description": "Any additional comment for consent operation"}) + transactionId = fields.Str(required=True) + fbnTransactionId = fields.Str(required=True) + customerId = fields.Str(required=True) + accountId = fields.Str(required=True) + processTime = fields.DateTime(required=True) + consentType = fields.Str(required=True) + countryId = fields.Str(required=True) + comment = fields.Str(required=False) diff --git a/app/schemas/select_offer.py b/app/schemas/select_offer.py index 99398e1..f3544aa 100644 --- a/app/schemas/select_offer.py +++ b/app/schemas/select_offer.py @@ -2,12 +2,12 @@ from marshmallow import Schema, fields # Select Offer Schema class SelectOfferSchema(Schema): - requestId = fields.Str(required=True, description="Unique request identifier") - transactionId = fields.Str(required=True, description="Transaction ID") - customerId = fields.Str(required=True, description="Customer ID") - accountId = fields.Str(required=True, description="Account ID") - msisdn = fields.Str(required=True, description="Mobile number") - requestedAmount = fields.Float(required=True, description="Amount requested") - productId = fields.Str(required=True, description="Product ID") - channel = fields.Str(required=True, description="Transaction channel (e.g., USSD)") + requestId = fields.Str(required=True) + transactionId = fields.Str(required=True) + customerId = fields.Str(required=True) + accountId = fields.Str(required=True) + msisdn = fields.Str(required=True) + requestedAmount = fields.Float(required=True) + productId = fields.Str(required=True) + channel = fields.Str(required=True) diff --git a/app/services/__init__.py b/app/services/__init__.py new file mode 100644 index 0000000..9a1af9f --- /dev/null +++ b/app/services/__init__.py @@ -0,0 +1,16 @@ +from app.services.eligibility_check import EligibilityCheckService +from app.services.select_offer import SelectOfferService +from app.services.provide_loan import ProvideLoanService +from app.services.loan_information import LoanInformationService +from app.services.repayment import RepaymentService +from app.services.customer_consent import CustomerConsentService +from app.services.notification_callback import NotificationCallbackService +from app.services.rac_check import RACCheckService +from app.services.disbursement import DisbursementService +from app.services.collect_loan import CollectLoanService +from app.services.transaction_verify import TransactionVerifyService +from app.services.penal_charge import PenalChargeService +from app.services.revoke_enable_consent import RevokeEnableConsentService +from app.services.token_validation import TokenValidationService +from app.services.lien_check import LienCheckService +from app.services.new_transaction_check import NewTransactionCheckService \ No newline at end of file diff --git a/app/blueprints/collect_loan.py b/app/services/collect_loan.py similarity index 80% rename from app/blueprints/collect_loan.py rename to app/services/collect_loan.py index 3912d57..ec65b5a 100644 --- a/app/blueprints/collect_loan.py +++ b/app/services/collect_loan.py @@ -1,4 +1,4 @@ -from flask import request +from flask import request, jsonify from marshmallow import ValidationError from app.utils.logger import logger from app.helpers.response_helper import ResponseHelper @@ -44,20 +44,16 @@ class CollectLoanService: # data=response_data, # message="Loan collection completed successfully" # ) - return response_data + return jsonify(response_data), 200 except ValidationError as err: logger.error(f"Validation Error: {err.messages}") - return ResponseHelper.error( - message="Invalid input data", - status_code=400, - error=err.messages - ) + return jsonify({ + "message": "Validation exception" + }) , 422 except Exception as e: logger.error(f"An error occurred: {str(e)}", exc_info=True) - return ResponseHelper.error( - message="An internal error occurred", - status_code=500, - error=str(e) - ) + return jsonify({ + "message": "Internal Server Error" + }) , 500 diff --git a/app/blueprints/customer_consent.py b/app/services/customer_consent.py similarity index 75% rename from app/blueprints/customer_consent.py rename to app/services/customer_consent.py index 6f33f47..f37aa47 100644 --- a/app/blueprints/customer_consent.py +++ b/app/services/customer_consent.py @@ -1,7 +1,6 @@ -from flask import request +from flask import request, jsonify from marshmallow import ValidationError from app.utils.logger import logger -from app.helpers.response_helper import ResponseHelper from app.schemas.customer_consent import CustomerConsentSchema @@ -40,16 +39,12 @@ class CustomerConsentService: except ValidationError as err: logger.error(f"Validation Error: {err.messages}") - return ResponseHelper.error( - message="Invalid input data", - status_code=400, - error=err.messages - ) + return jsonify({ + "message": "Validation exception" + }) , 422 except Exception as e: logger.error(f"An error occurred: {str(e)}", exc_info=True) - return ResponseHelper.error( - message="An internal error occurred", - status_code=500, - error=str(e) - ) + return jsonify({ + "message": "Internal Server Error" + }) , 500 diff --git a/app/blueprints/disbursement.py b/app/services/disbursement.py similarity index 83% rename from app/blueprints/disbursement.py rename to app/services/disbursement.py index b2ad0db..4005077 100644 --- a/app/blueprints/disbursement.py +++ b/app/services/disbursement.py @@ -1,4 +1,4 @@ -from flask import request +from flask import request, jsonify from marshmallow import ValidationError from app.utils.logger import logger from app.helpers.response_helper import ResponseHelper @@ -51,16 +51,12 @@ class DisbursementService: except ValidationError as err: logger.error(f"Validation Error: {err.messages}") - return ResponseHelper.error( - message="Invalid input data", - status_code=400, - error=err.messages - ) + return jsonify({ + "message": "Validation exception" + }) , 422 except Exception as e: logger.error(f"An error occurred: {str(e)}", exc_info=True) - return ResponseHelper.error( - message="An internal error occurred", - status_code=500, - error=str(e) - ) + return jsonify({ + "message": "Internal Server Error" + }) , 500 diff --git a/app/blueprints/eligibility_check.py b/app/services/eligibility_check.py similarity index 81% rename from app/blueprints/eligibility_check.py rename to app/services/eligibility_check.py index 3b78d74..a450607 100644 --- a/app/blueprints/eligibility_check.py +++ b/app/services/eligibility_check.py @@ -1,4 +1,4 @@ -from flask import session +from flask import session, jsonify from app.utils.logger import logger from app.helpers.response_helper import ResponseHelper from app.schemas.eligibility_check import EligibilityCheckSchema @@ -62,16 +62,12 @@ class EligibilityCheckService: return response_data except ValidationError as err: logger.error(f"Validation Error: {err.messages}") - return ResponseHelper.error( - message="Invalid input data", - status_code=400, - error=err.messages - ) + return jsonify({ + "message": "Validation exception" + }) , 422 except Exception as e: - logger.error(f"An error occurred during EligibilityCheck processing: {str(e)}", exc_info=True) - return ResponseHelper.error( - message="An internal error occurred", - status_code=500, - error=str(e) - ) \ No newline at end of file + logger.error(f"An error occurred: {str(e)}", exc_info=True) + return jsonify({ + "message": "Internal Server Error" + }) , 500 \ No newline at end of file diff --git a/app/blueprints/lien_check.py b/app/services/lien_check.py similarity index 79% rename from app/blueprints/lien_check.py rename to app/services/lien_check.py index b3f2b3e..0fbcd5d 100644 --- a/app/blueprints/lien_check.py +++ b/app/services/lien_check.py @@ -1,3 +1,4 @@ +from flask import request, jsonify from marshmallow import ValidationError from app.utils.logger import logger from app.helpers.response_helper import ResponseHelper @@ -39,16 +40,12 @@ class LienCheckService: except ValidationError as err: logger.error(f"Validation Error: {err.messages}") - return ResponseHelper.error( - message="Invalid input data", - status_code=400, - error=err.messages - ) + return jsonify({ + "message": "Validation exception" + }) , 422 except Exception as e: logger.error(f"An error occurred: {str(e)}", exc_info=True) - return ResponseHelper.error( - message="An internal error occurred", - status_code=500, - error=str(e) - ) + return jsonify({ + "message": "Internal Server Error" + }) , 500 diff --git a/app/blueprints/loan_information.py b/app/services/loan_information.py similarity index 83% rename from app/blueprints/loan_information.py rename to app/services/loan_information.py index d9197f9..6136cd1 100644 --- a/app/blueprints/loan_information.py +++ b/app/services/loan_information.py @@ -1,4 +1,4 @@ -from flask import request +from flask import request, jsonify from marshmallow import ValidationError from app.utils.logger import logger from app.helpers.response_helper import ResponseHelper @@ -52,16 +52,12 @@ class LoanInformationService: except ValidationError as err: logger.error(f"Validation Error: {err.messages}") - return ResponseHelper.error( - message="Invalid input data", - status_code=400, - error=err.messages - ) + return jsonify({ + "message": "Validation exception" + }) , 422 except Exception as e: logger.error(f"An error occurred: {str(e)}", exc_info=True) - return ResponseHelper.error( - message="An internal error occurred", - status_code=500, - error=str(e) - ) + return jsonify({ + "message": "Internal Server Error" + }) , 500 diff --git a/app/blueprints/new_transaction_check.py b/app/services/new_transaction_check.py similarity index 83% rename from app/blueprints/new_transaction_check.py rename to app/services/new_transaction_check.py index 60461f9..0eeac65 100644 --- a/app/blueprints/new_transaction_check.py +++ b/app/services/new_transaction_check.py @@ -1,3 +1,4 @@ +from flask import request, jsonify from marshmallow import ValidationError from app.utils.logger import logger from app.helpers.response_helper import ResponseHelper @@ -46,16 +47,12 @@ class NewTransactionCheckService: except ValidationError as err: logger.error(f"Validation Error: {err.messages}") - return ResponseHelper.error( - message="Invalid input data", - status_code=400, - error=err.messages - ) + return jsonify({ + "message": "Validation exception" + }) , 422 except Exception as e: logger.error(f"An error occurred: {str(e)}", exc_info=True) - return ResponseHelper.error( - message="An internal error occurred", - status_code=500, - error=str(e) - ) + return jsonify({ + "message": "Internal Server Error" + }) , 500 diff --git a/app/blueprints/notification_callback.py b/app/services/notification_callback.py similarity index 78% rename from app/blueprints/notification_callback.py rename to app/services/notification_callback.py index 72522fa..c587a9c 100644 --- a/app/blueprints/notification_callback.py +++ b/app/services/notification_callback.py @@ -1,4 +1,4 @@ -from flask import request +from flask import request, jsonify from marshmallow import ValidationError from app.utils.logger import logger from app.helpers.response_helper import ResponseHelper @@ -39,16 +39,12 @@ class NotificationCallbackService: except ValidationError as err: logger.error(f"Validation Error: {err.messages}") - return ResponseHelper.error( - message="Invalid input data", - status_code=400, - error=err.messages - ) + return jsonify({ + "message": "Validation exception" + }) , 422 except Exception as e: logger.error(f"An error occurred: {str(e)}", exc_info=True) - return ResponseHelper.error( - message="An internal error occurred", - status_code=500, - error=str(e) - ) + return jsonify({ + "message": "Internal Server Error" + }) , 500 diff --git a/app/blueprints/penal_charge.py b/app/services/penal_charge.py similarity index 78% rename from app/blueprints/penal_charge.py rename to app/services/penal_charge.py index 5cc9df4..7b1d1ad 100644 --- a/app/blueprints/penal_charge.py +++ b/app/services/penal_charge.py @@ -1,4 +1,4 @@ -from flask import request +from flask import request, jsonify from marshmallow import ValidationError from app.utils.logger import logger from app.helpers.response_helper import ResponseHelper @@ -40,16 +40,12 @@ class PenalChargeService: except ValidationError as err: logger.error(f"Validation Error: {err.messages}") - return ResponseHelper.error( - message="Invalid input data", - status_code=400, - error=err.messages - ) + return jsonify({ + "message": "Validation exception" + }) , 422 except Exception as e: logger.error(f"An error occurred: {str(e)}", exc_info=True) - return ResponseHelper.error( - message="An internal error occurred", - status_code=500, - error=str(e) - ) + return jsonify({ + "message": "Internal Server Error" + }) , 500 diff --git a/app/blueprints/provide_loan.py b/app/services/provide_loan.py similarity index 80% rename from app/blueprints/provide_loan.py rename to app/services/provide_loan.py index 3c84d45..b5b5396 100644 --- a/app/blueprints/provide_loan.py +++ b/app/services/provide_loan.py @@ -1,4 +1,4 @@ -from flask import request +from flask import request, jsonify from marshmallow import ValidationError from app.utils.logger import logger from app.helpers.response_helper import ResponseHelper @@ -44,16 +44,12 @@ class ProvideLoanService: except ValidationError as err: logger.error(f"Validation Error: {err.messages}") - return ResponseHelper.error( - message="Invalid input data", - status_code=400, - error=err.messages - ) + return jsonify({ + "message": "Validation exception" + }) , 422 except Exception as e: logger.error(f"An error occurred: {str(e)}", exc_info=True) - return ResponseHelper.error( - message="An internal error occurred", - status_code=500, - error=str(e) - ) + return jsonify({ + "message": "Internal Server Error" + }) , 500 diff --git a/app/blueprints/rac_check.py b/app/services/rac_check.py similarity index 82% rename from app/blueprints/rac_check.py rename to app/services/rac_check.py index 7a1397e..ea7eaa3 100644 --- a/app/blueprints/rac_check.py +++ b/app/services/rac_check.py @@ -1,4 +1,4 @@ -from flask import request +from flask import request, jsonify from marshmallow import ValidationError from app.utils.logger import logger from app.helpers.response_helper import ResponseHelper @@ -52,16 +52,12 @@ class RACCheckService: except ValidationError as err: logger.error(f"Validation Error: {err.messages}") - return ResponseHelper.error( - message="Invalid input data", - status_code=400, - error=err.messages - ) + return jsonify({ + "message": "Validation exception" + }) , 422 except Exception as e: logger.error(f"An error occurred: {str(e)}", exc_info=True) - return ResponseHelper.error( - message="An internal error occurred", - status_code=500, - error=str(e) - ) + return jsonify({ + "message": "Internal Server Error" + }) , 500 diff --git a/app/blueprints/repayment.py b/app/services/repayment.py similarity index 78% rename from app/blueprints/repayment.py rename to app/services/repayment.py index fbedf68..472890d 100644 --- a/app/blueprints/repayment.py +++ b/app/services/repayment.py @@ -1,4 +1,4 @@ -from flask import request +from flask import request, jsonify from marshmallow import ValidationError from app.utils.logger import logger from app.helpers.response_helper import ResponseHelper @@ -39,16 +39,12 @@ class RepaymentService: except ValidationError as err: logger.error(f"Validation Error: {err.messages}") - return ResponseHelper.error( - message="Invalid input data", - status_code=400, - error=err.messages - ) + return jsonify({ + "message": "Validation exception" + }) , 422 except Exception as e: logger.error(f"An error occurred: {str(e)}", exc_info=True) - return ResponseHelper.error( - message="An internal error occurred", - status_code=500, - error=str(e) - ) + return jsonify({ + "message": "Internal Server Error" + }) , 500 diff --git a/app/blueprints/revoke_enable_consent.py b/app/services/revoke_enable_consent.py similarity index 80% rename from app/blueprints/revoke_enable_consent.py rename to app/services/revoke_enable_consent.py index 4379d7a..fffb5af 100644 --- a/app/blueprints/revoke_enable_consent.py +++ b/app/services/revoke_enable_consent.py @@ -1,4 +1,4 @@ -from flask import request +from flask import request, jsonify from marshmallow import ValidationError from app.utils.logger import logger from app.helpers.response_helper import ResponseHelper @@ -43,16 +43,12 @@ class RevokeEnableConsentService: except ValidationError as err: logger.error(f"Validation Error: {err.messages}") - return ResponseHelper.error( - message="Invalid input data", - status_code=400, - error=err.messages - ) + return jsonify({ + "message": "Validation exception" + }) , 422 except Exception as e: logger.error(f"An error occurred: {str(e)}", exc_info=True) - return ResponseHelper.error( - message="An internal error occurred", - status_code=500, - error=str(e) - ) + return jsonify({ + "message": "Internal Server Error" + }) , 500 diff --git a/app/blueprints/select_offer.py b/app/services/select_offer.py similarity index 89% rename from app/blueprints/select_offer.py rename to app/services/select_offer.py index 5a21f16..c0e12e0 100644 --- a/app/blueprints/select_offer.py +++ b/app/services/select_offer.py @@ -1,4 +1,4 @@ -from flask import request +from flask import request, jsonify from marshmallow import ValidationError from app.utils.logger import logger from app.helpers.response_helper import ResponseHelper @@ -23,14 +23,7 @@ class SelectOfferService: schema = SelectOfferSchema() validated_data = schema.load(data) # Raises ValidationError if invalid - # Business logic - selecting an offer - response_data = { - "outstandingDebtAmount": 0, - "requestId": "202111170001371256908", - "transactionId": "1231231321232", - "customerId": "1256907", - "accountId": "5948306019", - "offers": [ + offers = [ { "offerId": "14451", "productId": "2030", @@ -79,7 +72,16 @@ class SelectOfferService: "installmentAmount": 4021.15, "totalRepaymentAmount": 12063.45 } - ], + ] + + # Business logic - selecting an offer + response_data = { + "outstandingDebtAmount": 0, + "requestId": "202111170001371256908", + "transactionId": "1231231321232", + "customerId": "1256907", + "accountId": "5948306019", + "offers": offers, "resultCode": "00", "resultDescription": "Successful" } @@ -94,16 +96,12 @@ class SelectOfferService: except ValidationError as err: logger.error(f"Validation Error: {err.messages}") - return ResponseHelper.error( - message="Invalid input data", - status_code=400, - error=err.messages - ) + return jsonify({ + "message": "Validation exception" + }) , 422 except Exception as e: logger.error(f"An error occurred: {str(e)}", exc_info=True) - return ResponseHelper.error( - message="An internal error occurred", - status_code=500, - error=str(e) - ) + return jsonify({ + "message": "Internal Server Error" + }) , 500 diff --git a/app/blueprints/token_validation.py b/app/services/token_validation.py similarity index 80% rename from app/blueprints/token_validation.py rename to app/services/token_validation.py index 88fc77d..3226f23 100644 --- a/app/blueprints/token_validation.py +++ b/app/services/token_validation.py @@ -1,4 +1,4 @@ -from flask import request +from flask import request, jsonify from marshmallow import ValidationError from app.utils.logger import logger from app.helpers.response_helper import ResponseHelper @@ -43,16 +43,12 @@ class TokenValidationService: except ValidationError as err: logger.error(f"Validation Error: {err.messages}") - return ResponseHelper.error( - message="Invalid input data", - status_code=400, - error=err.messages - ) + return jsonify({ + "message": "Validation exception" + }) , 422 except Exception as e: logger.error(f"An error occurred: {str(e)}", exc_info=True) - return ResponseHelper.error( - message="An internal error occurred", - status_code=500, - error=str(e) - ) + return jsonify({ + "message": "Internal Server Error" + }) , 500 diff --git a/app/blueprints/transaction_verify.py b/app/services/transaction_verify.py similarity index 81% rename from app/blueprints/transaction_verify.py rename to app/services/transaction_verify.py index 7aea3a0..2232835 100644 --- a/app/blueprints/transaction_verify.py +++ b/app/services/transaction_verify.py @@ -1,4 +1,4 @@ -from flask import request +from flask import request, jsonify from marshmallow import ValidationError from app.utils.logger import logger from app.helpers.response_helper import ResponseHelper @@ -46,16 +46,12 @@ class TransactionVerifyService: except ValidationError as err: logger.error(f"Validation Error: {err.messages}") - return ResponseHelper.error( - message="Invalid input data", - status_code=400, - error=err.messages - ) + return jsonify({ + "message": "Validation exception" + }) , 422 except Exception as e: logger.error(f"An error occurred: {str(e)}", exc_info=True) - return ResponseHelper.error( - message="An internal error occurred", - status_code=500, - error=str(e) - ) + return jsonify({ + "message": "Internal Server Error" + }) , 500 diff --git a/app/utils/logger.py b/app/utils/logger.py index 9c0d571..4ee0e98 100644 --- a/app/utils/logger.py +++ b/app/utils/logger.py @@ -5,7 +5,7 @@ logging.basicConfig( level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s", handlers=[ - logging.StreamHandler(), # Log to console + # logging.StreamHandler(), logging.FileHandler("app.log", mode='a') # Log to file ] ) diff --git a/docker-compose.yml b/docker-compose.yml index 9da065a..b4d292a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,8 +1,8 @@ services: - web: + digifi-flaska002: build: . ports: - - "5000:5000" + - "7200:5000" environment: - FLASK_APP=app.py - FLASK_RUN_HOST=0.0.0.0 diff --git a/requirements.txt b/requirements.txt index 232eb1c..737f663 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,11 +1,10 @@ # Flask and Extensions Flask==2.3.3 -# Flask-SQLAlchemy==3.0.5 Flask-Marshmallow==0.15.0 marshmallow==3.19.0 Flask-Cors==3.0.10 -# marshmallow-sqlalchemy==0.29.0 -# mysqlclient==2.2.0 +gunicorn + diff --git a/wsgi.py b/wsgi.py new file mode 100644 index 0000000..49f70e1 --- /dev/null +++ b/wsgi.py @@ -0,0 +1,7 @@ +from app import create_app + +app = create_app() + +if __name__ != "__main__": + # Expose WSGI app instance for Gunicorn + wsgi_app = app