diff --git a/.DS_Store b/.DS_Store index e81c242..8ba90e7 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/.gitignore b/.gitignore index 1541da5..b316843 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ __pycache__/ -.env \ No newline at end of file +.env +app.log +.DS_Store \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 3e3f6f1..fa0ff11 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,4 +18,4 @@ ENV FLASK_APP=app.py ENV FLASK_RUN_HOST=0.0.0.0 # Run the application -CMD ["flask", "run"] +CMD ["gunicorn", "-w", "4", "-b", "0.0.0.0:5000", "wsgi:wsgi_app"] \ No newline at end of file diff --git a/README.md b/README.md index 35a41e4..9546231 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,26 @@ git clone https://github.com/username/repository.git cd repository ``` -### 2. Run the Application with Docker Compose + +### 2. Create a `.env` File + +Before running the application, create a `.env` file in the root directory and add the required environment variables: + +```bash +touch .env +``` + +Then, open the `.env` file and add the following: + +```ini +# Environment Variables +VALID_API_KEY=testtest-api-key-12345 +VALID_APP_ID=app1 +``` + +This ensures that the application uses secure API keys and app IDs. + +### 3. Run the Application with Docker Compose Once you have the repository cloned, you can easily set up and run the application using Docker Compose. Simply execute the following command: @@ -29,7 +48,7 @@ docker-compose up --build This command will build the Docker image and start the Flask application in a container. By default, the application will be accessible at `http://localhost:5000`. -### 3. Health Check +### 4. Health Check You can check if the Flask application is running by accessing the `/health` endpoint. To perform a health check, run the following command: @@ -45,7 +64,7 @@ If the application is running properly, you should receive a response similar to } ``` -### 4. Stop the Application +### 5. Stop the Application To stop the application, use: diff --git a/app.log b/app.log index e06d896..974071d 100644 --- a/app.log +++ b/app.log @@ -1,253 +1,4 @@ -2025-03-20 14:13:09,801 - INFO - WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. - * Running on all addresses (0.0.0.0) - * Running on http://127.0.0.1:5000 - * Running on http://192.168.43.192:5000 -2025-03-20 14:13:09,802 - INFO - Press CTRL+C to quit -2025-03-20 14:13:09,804 - INFO - * Restarting with stat -2025-03-20 14:13:10,513 - WARNING - * Debugger is active! -2025-03-20 14:13:10,534 - INFO - * Debugger PIN: 716-001-293 -2025-03-20 14:13:18,851 - INFO - 127.0.0.1 - - [20/Mar/2025 14:13:18] "GET / HTTP/1.1" 404 - -2025-03-20 14:13:19,654 - INFO - 127.0.0.1 - - [20/Mar/2025 14:13:19] "GET /favicon.ico HTTP/1.1" 404 - -2025-03-20 14:14:03,987 - INFO - 127.0.0.1 - - [20/Mar/2025 14:14:03] "GET /EligibilityCheck HTTP/1.1" 405 - -2025-03-20 14:14:14,898 - INFO - 127.0.0.1 - - [20/Mar/2025 14:14:14] "GET /EligibilityCheck HTTP/1.1" 405 - -2025-03-20 14:14:21,063 - INFO - 127.0.0.1 - - [20/Mar/2025 14:14:21] "POST /EligibilityCheck HTTP/1.1" 415 - -2025-03-20 14:14:40,328 - INFO - 127.0.0.1 - - [20/Mar/2025 14:14:40] "POST /EligibilityCheck HTTP/1.1" 415 - -2025-03-20 14:15:52,642 - INFO - EligibilityCheck request received: {'hi': 'bye'} -2025-03-20 14:15:52,643 - INFO - Processing EligibilityCheck request -2025-03-20 14:15:52,647 - ERROR - An error occurred during EligibilityCheck processing: {'income': ['Missing data for required field.'], 'user_id': ['Missing data for required field.'], 'credit_score': ['Missing data for required field.'], 'hi': ['Unknown field.']} -Traceback (most recent call last): - File "/Users/viviandagbue/Documents/livelinessCheckYOLO/app/blueprints/eligibility_check.py", line 23, in process_request - validated_data = schema.load(data) # Raises an error if invalid - File "/Users/viviandagbue/Documents/venv/lib/python3.9/site-packages/marshmallow/schema.py", line 722, in load - return self._do_load( - File "/Users/viviandagbue/Documents/venv/lib/python3.9/site-packages/marshmallow/schema.py", line 909, in _do_load - raise exc -marshmallow.exceptions.ValidationError: {'income': ['Missing data for required field.'], 'user_id': ['Missing data for required field.'], 'credit_score': ['Missing data for required field.'], 'hi': ['Unknown field.']} -2025-03-20 14:15:52,683 - INFO - 127.0.0.1 - - [20/Mar/2025 14:15:52] "POST /EligibilityCheck HTTP/1.1" 200 - -2025-03-20 14:27:54,479 - INFO - * Detected change in '/Users/viviandagbue/Documents/livelinessCheckYOLO/app/routes/main.py', reloading -2025-03-20 14:27:54,721 - INFO - * Restarting with stat -2025-03-20 14:27:56,270 - WARNING - * Debugger is active! -2025-03-20 14:27:56,301 - INFO - * Debugger PIN: 716-001-293 -2025-03-20 14:28:00,496 - INFO - * Detected change in '/Users/viviandagbue/Documents/livelinessCheckYOLO/app/routes/main.py', reloading -2025-03-20 14:28:00,704 - INFO - * Restarting with stat -2025-03-20 14:28:01,613 - WARNING - * Debugger is active! -2025-03-20 14:28:01,691 - INFO - * Debugger PIN: 716-001-293 -2025-03-20 14:28:03,809 - INFO - * Detected change in '/Users/viviandagbue/Documents/livelinessCheckYOLO/app/routes/main.py', reloading -2025-03-20 14:28:04,018 - INFO - * Restarting with stat -2025-03-20 14:28:04,870 - WARNING - * Debugger is active! -2025-03-20 14:28:04,886 - INFO - * Debugger PIN: 716-001-293 -2025-03-20 14:28:07,012 - INFO - * Detected change in '/Users/viviandagbue/Documents/livelinessCheckYOLO/app/routes/main.py', reloading -2025-03-20 14:28:07,200 - INFO - * Restarting with stat -2025-03-20 14:28:08,193 - WARNING - * Debugger is active! -2025-03-20 14:28:08,206 - INFO - * Debugger PIN: 716-001-293 -2025-03-20 14:28:10,289 - INFO - * Detected change in '/Users/viviandagbue/Documents/livelinessCheckYOLO/app/routes/main.py', reloading -2025-03-20 14:28:10,503 - INFO - * Restarting with stat -2025-03-20 14:28:11,437 - WARNING - * Debugger is active! -2025-03-20 14:28:11,456 - INFO - * Debugger PIN: 716-001-293 -2025-03-20 14:28:13,532 - INFO - * Detected change in '/Users/viviandagbue/Documents/livelinessCheckYOLO/app/routes/main.py', reloading -2025-03-20 14:28:13,750 - INFO - * Restarting with stat -2025-03-20 14:28:14,774 - WARNING - * Debugger is active! -2025-03-20 14:28:14,815 - INFO - * Debugger PIN: 716-001-293 -2025-03-20 14:28:17,101 - INFO - * Detected change in '/Users/viviandagbue/Documents/livelinessCheckYOLO/app/routes/main.py', reloading -2025-03-20 14:28:17,262 - INFO - * Restarting with stat -2025-03-20 14:28:18,410 - WARNING - * Debugger is active! -2025-03-20 14:28:18,428 - INFO - * Debugger PIN: 716-001-293 -2025-03-20 14:28:19,512 - INFO - * Detected change in '/Users/viviandagbue/Documents/livelinessCheckYOLO/app/routes/main.py', reloading -2025-03-20 14:28:19,690 - INFO - * Restarting with stat -2025-03-20 14:28:20,801 - WARNING - * Debugger is active! -2025-03-20 14:28:20,936 - INFO - * Debugger PIN: 716-001-293 -2025-03-20 14:28:41,355 - INFO - * Detected change in '/Users/viviandagbue/Documents/livelinessCheckYOLO/app/config.py', reloading -2025-03-20 14:28:41,499 - INFO - * Restarting with stat -2025-03-20 14:28:42,243 - WARNING - * Debugger is active! -2025-03-20 14:28:42,255 - INFO - * Debugger PIN: 716-001-293 -2025-03-20 14:28:45,329 - INFO - * Detected change in '/Users/viviandagbue/Documents/livelinessCheckYOLO/app/config.py', reloading -2025-03-20 14:28:45,519 - INFO - * Restarting with stat -2025-03-20 14:28:46,382 - WARNING - * Debugger is active! -2025-03-20 14:28:46,403 - INFO - * Debugger PIN: 716-001-293 -2025-03-20 14:28:51,552 - INFO - * Detected change in '/Users/viviandagbue/Documents/livelinessCheckYOLO/app/config.py', reloading -2025-03-20 14:28:51,712 - INFO - * Restarting with stat -2025-03-20 14:28:52,758 - WARNING - * Debugger is active! -2025-03-20 14:28:52,792 - INFO - * Debugger PIN: 716-001-293 -2025-03-20 14:29:13,307 - INFO - * Detected change in '/Users/viviandagbue/Documents/livelinessCheckYOLO/app/routes/main.py', reloading -2025-03-20 14:29:13,443 - INFO - * Restarting with stat -2025-03-20 14:29:15,136 - WARNING - * Debugger is active! -2025-03-20 14:29:15,155 - INFO - * Debugger PIN: 716-001-293 -2025-03-20 14:29:24,320 - INFO - * Detected change in '/Users/viviandagbue/Documents/livelinessCheckYOLO/app/routes/main.py', reloading -2025-03-20 14:29:24,462 - INFO - * Restarting with stat -2025-03-20 14:29:25,635 - WARNING - * Debugger is active! -2025-03-20 14:29:25,668 - INFO - * Debugger PIN: 716-001-293 -2025-03-20 14:29:39,423 - INFO - 127.0.0.1 - - [20/Mar/2025 14:29:39] "POST /health HTTP/1.1" 405 - -2025-03-20 14:30:17,001 - INFO - * Detected change in '/Users/viviandagbue/Documents/livelinessCheckYOLO/app/routes/__init__.py', reloading -2025-03-20 14:30:17,444 - INFO - * Restarting with stat -2025-03-20 14:30:20,724 - WARNING - * Debugger is active! -2025-03-20 14:30:20,742 - INFO - * Debugger PIN: 716-001-293 -2025-03-20 14:37:21,240 - INFO - * Detected change in '/Users/viviandagbue/Documents/livelinessCheckYOLO/app/middlewares/__init__.py', reloading -2025-03-20 14:37:21,737 - INFO - * Restarting with stat -2025-03-20 14:37:23,075 - WARNING - * Debugger is active! -2025-03-20 14:37:23,088 - INFO - * Debugger PIN: 716-001-293 -2025-03-20 14:39:23,292 - INFO - * Detected change in '/Users/viviandagbue/Documents/livelinessCheckYOLO/app/middlewares/verify_api_key.py', reloading -2025-03-20 14:39:23,563 - INFO - * Restarting with stat -2025-03-20 14:39:25,229 - WARNING - * Debugger is active! -2025-03-20 14:39:25,254 - INFO - * Debugger PIN: 716-001-293 -2025-03-20 14:39:26,355 - INFO - * Detected change in '/Users/viviandagbue/Documents/livelinessCheckYOLO/app/middlewares/verify_api_key.py', reloading -2025-03-20 14:39:26,517 - INFO - * Restarting with stat -2025-03-20 14:39:27,457 - WARNING - * Debugger is active! -2025-03-20 14:39:27,476 - INFO - * Debugger PIN: 716-001-293 -2025-03-20 14:41:20,187 - INFO - * Detected change in '/Users/viviandagbue/Documents/livelinessCheckYOLO/app/middlewares/encryption.py', reloading -2025-03-20 14:41:20,429 - INFO - * Restarting with stat -2025-03-20 14:41:21,622 - WARNING - * Debugger is active! -2025-03-20 14:41:21,634 - INFO - * Debugger PIN: 716-001-293 -2025-03-20 14:42:03,558 - INFO - * Detected change in '/Users/viviandagbue/Documents/livelinessCheckYOLO/app/middlewares/cors.py', reloading -2025-03-20 14:42:03,709 - INFO - * Restarting with stat -2025-03-20 14:42:05,112 - WARNING - * Debugger is active! -2025-03-20 14:42:05,137 - INFO - * Debugger PIN: 716-001-293 -2025-03-20 14:42:30,565 - INFO - * Detected change in '/Users/viviandagbue/Documents/livelinessCheckYOLO/app/middlewares/cors.py', reloading -2025-03-20 14:42:30,976 - INFO - * Restarting with stat -2025-03-20 14:42:32,051 - WARNING - * Debugger is active! -2025-03-20 14:42:32,065 - INFO - * Debugger PIN: 716-001-293 -2025-03-20 14:43:14,856 - INFO - * Detected change in '/Users/viviandagbue/Documents/livelinessCheckYOLO/app/middlewares/request_validator.py', reloading -2025-03-20 14:43:15,006 - INFO - * Restarting with stat -2025-03-20 14:43:16,050 - WARNING - * Debugger is active! -2025-03-20 14:43:16,062 - INFO - * Debugger PIN: 716-001-293 -2025-03-20 14:44:03,834 - INFO - * Detected change in '/Users/viviandagbue/Documents/livelinessCheckYOLO/app/middlewares/request_validator.py', reloading -2025-03-20 14:44:04,003 - INFO - * Restarting with stat -2025-03-20 14:44:05,142 - WARNING - * Debugger is active! -2025-03-20 14:44:05,182 - INFO - * Debugger PIN: 716-001-293 -2025-03-20 14:44:09,364 - INFO - * Detected change in '/Users/viviandagbue/Documents/livelinessCheckYOLO/app/middlewares/request_validator.py', reloading -2025-03-20 14:44:09,512 - INFO - * Restarting with stat -2025-03-20 14:44:10,512 - WARNING - * Debugger is active! -2025-03-20 14:44:10,548 - INFO - * Debugger PIN: 716-001-293 -2025-03-20 14:44:11,658 - INFO - * Detected change in '/Users/viviandagbue/Documents/livelinessCheckYOLO/app/middlewares/request_validator.py', reloading -2025-03-20 14:44:11,834 - INFO - * Restarting with stat -2025-03-20 14:44:12,885 - WARNING - * Debugger is active! -2025-03-20 14:44:12,904 - INFO - * Debugger PIN: 716-001-293 -2025-03-20 14:44:37,478 - INFO - * Detected change in '/Users/viviandagbue/Documents/livelinessCheckYOLO/app/middlewares/request_validator.py', reloading -2025-03-20 14:44:37,636 - INFO - * Restarting with stat -2025-03-20 14:44:38,872 - WARNING - * Debugger is active! -2025-03-20 14:44:38,889 - INFO - * Debugger PIN: 716-001-293 -2025-03-20 14:44:52,116 - INFO - * Detected change in '/Users/viviandagbue/Documents/livelinessCheckYOLO/app/middlewares/request_validator.py', reloading -2025-03-20 14:44:52,260 - INFO - * Restarting with stat -2025-03-20 14:44:53,048 - WARNING - * Debugger is active! -2025-03-20 14:44:53,061 - INFO - * Debugger PIN: 716-001-293 -2025-03-20 14:44:54,180 - INFO - * Detected change in '/Users/viviandagbue/Documents/livelinessCheckYOLO/app/middlewares/request_validator.py', reloading -2025-03-20 14:44:54,328 - INFO - * Restarting with stat -2025-03-20 14:44:55,536 - WARNING - * Debugger is active! -2025-03-20 14:44:55,549 - INFO - * Debugger PIN: 716-001-293 -2025-03-20 14:44:56,596 - INFO - * Detected change in '/Users/viviandagbue/Documents/livelinessCheckYOLO/app/middlewares/request_validator.py', reloading -2025-03-20 14:44:56,745 - INFO - * Restarting with stat -2025-03-20 14:44:57,615 - WARNING - * Debugger is active! -2025-03-20 14:44:57,627 - INFO - * Debugger PIN: 716-001-293 -2025-03-20 14:44:59,795 - INFO - * Detected change in '/Users/viviandagbue/Documents/livelinessCheckYOLO/app/middlewares/request_validator.py', reloading -2025-03-20 14:45:00,013 - INFO - * Restarting with stat -2025-03-20 14:45:00,839 - WARNING - * Debugger is active! -2025-03-20 14:45:00,854 - INFO - * Debugger PIN: 716-001-293 -2025-03-20 14:45:05,953 - INFO - * Detected change in '/Users/viviandagbue/Documents/livelinessCheckYOLO/app/middlewares/request_validator.py', reloading -2025-03-20 14:45:06,143 - INFO - * Restarting with stat -2025-03-20 14:45:06,976 - WARNING - * Debugger is active! -2025-03-20 14:45:06,992 - INFO - * Debugger PIN: 716-001-293 -2025-03-20 14:45:09,077 - INFO - * Detected change in '/Users/viviandagbue/Documents/livelinessCheckYOLO/app/middlewares/request_validator.py', reloading -2025-03-20 14:45:09,366 - INFO - * Restarting with stat -2025-03-20 14:45:10,260 - WARNING - * Debugger is active! -2025-03-20 14:45:10,491 - INFO - * Debugger PIN: 716-001-293 -2025-03-20 14:45:11,571 - INFO - * Detected change in '/Users/viviandagbue/Documents/livelinessCheckYOLO/app/middlewares/request_validator.py', reloading -2025-03-20 14:45:11,748 - INFO - * Restarting with stat -2025-03-20 14:45:12,615 - WARNING - * Debugger is active! -2025-03-20 14:45:12,631 - INFO - * Debugger PIN: 716-001-293 -2025-03-20 14:45:14,690 - INFO - * Detected change in '/Users/viviandagbue/Documents/livelinessCheckYOLO/app/middlewares/request_validator.py', reloading -2025-03-20 14:45:14,821 - INFO - * Restarting with stat -2025-03-20 14:45:15,576 - WARNING - * Debugger is active! -2025-03-20 14:45:15,597 - INFO - * Debugger PIN: 716-001-293 -2025-03-20 14:47:55,793 - INFO - * Detected change in '/Users/viviandagbue/Documents/livelinessCheckYOLO/app/__init__.py', reloading -2025-03-20 14:47:56,010 - INFO - * Restarting with stat -2025-03-20 14:48:23,203 - INFO - WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. - * Running on all addresses (0.0.0.0) - * Running on http://127.0.0.1:5000 - * Running on http://192.168.43.192:5000 -2025-03-20 14:48:23,204 - INFO - Press CTRL+C to quit -2025-03-20 14:48:23,208 - INFO - * Restarting with stat -2025-03-20 14:48:25,258 - WARNING - * Debugger is active! -2025-03-20 14:48:25,331 - INFO - * Debugger PIN: 716-001-293 -2025-03-20 14:48:30,183 - INFO - 127.0.0.1 - - [20/Mar/2025 14:48:30] "POST /api/health HTTP/1.1" 405 - -2025-03-20 14:48:35,969 - INFO - 127.0.0.1 - - [20/Mar/2025 14:48:35] "GET /api/health HTTP/1.1" 200 - -2025-03-20 14:53:13,504 - INFO - * Detected change in '/Users/viviandagbue/Documents/livelinessCheckYOLO/app/schemas/eligibility_check.py', reloading -2025-03-20 14:53:14,721 - INFO - * Restarting with stat -2025-03-20 14:53:20,279 - WARNING - * Debugger is active! -2025-03-20 14:53:20,384 - INFO - * Debugger PIN: 716-001-293 -2025-03-20 14:54:16,579 - INFO - * Detected change in '/Users/viviandagbue/Documents/livelinessCheckYOLO/app/blueprints/eligibility_check.py', reloading -2025-03-20 14:54:17,245 - INFO - * Restarting with stat -2025-03-20 14:54:21,211 - WARNING - * Debugger is active! -2025-03-20 14:54:21,298 - INFO - * Debugger PIN: 716-001-293 -2025-03-20 14:54:32,913 - INFO - * Detected change in '/Users/viviandagbue/Documents/livelinessCheckYOLO/app/blueprints/eligibility_check.py', reloading -2025-03-20 14:54:33,587 - INFO - * Restarting with stat -2025-03-20 14:54:36,684 - WARNING - * Debugger is active! -2025-03-20 14:54:36,850 - INFO - * Debugger PIN: 716-001-293 -2025-03-20 14:54:38,191 - INFO - * Detected change in '/Users/viviandagbue/Documents/livelinessCheckYOLO/app/blueprints/eligibility_check.py', reloading -2025-03-20 14:54:38,906 - INFO - * Restarting with stat -2025-03-20 14:54:44,502 - WARNING - * Debugger is active! -2025-03-20 14:54:44,539 - INFO - * Debugger PIN: 716-001-293 -2025-03-20 14:55:04,187 - INFO - 127.0.0.1 - - [20/Mar/2025 14:55:04] "GET /api/EligibilityCheck HTTP/1.1" 405 - -2025-03-20 14:55:10,064 - INFO - Processing EligibilityCheck request -2025-03-20 14:55:10,073 - ERROR - An error occurred during EligibilityCheck processing: {'lien_amount': ['Missing data for required field.'], 'lienAmount': ['Unknown field.']} -Traceback (most recent call last): - File "/Users/viviandagbue/Documents/livelinessCheckYOLO/app/blueprints/eligibility_check.py", line 23, in process_request - validated_data = schema.load(data) # Raises an error if invalid - File "/Users/viviandagbue/Documents/venv/lib/python3.9/site-packages/marshmallow/schema.py", line 722, in load - return self._do_load( - File "/Users/viviandagbue/Documents/venv/lib/python3.9/site-packages/marshmallow/schema.py", line 909, in _do_load - raise exc -marshmallow.exceptions.ValidationError: {'lien_amount': ['Missing data for required field.'], 'lienAmount': ['Unknown field.']} -2025-03-20 14:55:10,103 - INFO - 127.0.0.1 - - [20/Mar/2025 14:55:10] "POST /api/EligibilityCheck HTTP/1.1" 200 - -2025-03-20 14:55:37,613 - INFO - * Detected change in '/Users/viviandagbue/Documents/livelinessCheckYOLO/app/schemas/eligibility_check.py', reloading -2025-03-20 14:55:38,428 - INFO - * Restarting with stat -2025-03-20 14:55:47,297 - WARNING - * Debugger is active! -2025-03-20 14:55:47,413 - INFO - * Debugger PIN: 716-001-293 -2025-03-20 14:55:51,943 - INFO - * Detected change in '/Users/viviandagbue/Documents/livelinessCheckYOLO/app/schemas/eligibility_check.py', reloading -2025-03-20 14:55:52,455 - INFO - * Restarting with stat -2025-03-20 14:55:56,940 - WARNING - * Debugger is active! -2025-03-20 14:55:56,968 - INFO - * Debugger PIN: 716-001-293 -2025-03-20 14:56:01,718 - INFO - Processing EligibilityCheck request -2025-03-20 14:56:01,737 - INFO - 127.0.0.1 - - [20/Mar/2025 14:56:01] "POST /api/EligibilityCheck HTTP/1.1" 200 - -2025-03-20 14:59:24,543 - INFO - * Detected change in '/Users/viviandagbue/Documents/livelinessCheckYOLO/app/schemas/select_offer.py', reloading -2025-03-20 14:59:25,271 - INFO - * Restarting with stat -2025-03-20 14:59:31,688 - WARNING - * Debugger is active! -2025-03-20 14:59:31,738 - INFO - * Debugger PIN: 716-001-293 -2025-03-20 15:00:44,032 - INFO - * Detected change in '/Users/viviandagbue/Documents/livelinessCheckYOLO/app/blueprints/select_offer.py', reloading -2025-03-20 15:00:44,863 - INFO - * Restarting with stat -2025-03-20 15:00:52,010 - WARNING - * Debugger is active! -2025-03-20 15:00:52,134 - INFO - * Debugger PIN: 716-001-293 -2025-03-20 15:00:53,368 - INFO - * Detected change in '/Users/viviandagbue/Documents/livelinessCheckYOLO/app/blueprints/select_offer.py', reloading -2025-03-20 15:00:53,663 - INFO - * Restarting with stat -2025-03-20 17:05:48,612 - INFO - WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. - * Running on all addresses (0.0.0.0) - * Running on http://127.0.0.1:5000 - * Running on http://192.168.43.192:5000 -2025-03-20 17:05:48,613 - INFO - Press CTRL+C to quit -2025-03-20 17:05:48,614 - INFO - * Restarting with stat -2025-03-20 17:05:49,355 - WARNING - * Debugger is active! -2025-03-20 17:05:49,373 - INFO - * Debugger PIN: 716-001-293 -2025-03-20 17:05:52,329 - INFO - Processing EligibilityCheck request -2025-03-20 17:05:52,333 - INFO - 127.0.0.1 - - [20/Mar/2025 17:05:52] "POST /api/EligibilityCheck HTTP/1.1" 200 - -2025-03-20 17:16:19,388 - INFO - WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. - * Running on http://127.0.0.1:5000 -2025-03-20 17:16:19,388 - INFO - Press CTRL+C to quit -2025-03-20 17:17:44,903 - INFO - Processing SelectOffer request -2025-03-20 17:17:44,908 - ERROR - Validation Error: {'productId': ['Missing data for required field.'], 'requestId': ['Missing data for required field.'], 'requestedAmount': ['Missing data for required field.'], 'countryCode': ['Unknown field.'], 'lienAmount': ['Unknown field.'], '$type': ['Unknown field.']} -2025-03-20 17:17:44,916 - INFO - 127.0.0.1 - - [20/Mar/2025 17:17:44] "POST /api/SelectOffer HTTP/1.1" 200 - -2025-03-20 17:45:41,511 - INFO - WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. - * Running on http://127.0.0.1:5000 -2025-03-20 17:45:41,513 - INFO - Press CTRL+C to quit -2025-03-20 17:51:07,000 - INFO - WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. - * Running on all addresses (0.0.0.0) - * Running on http://127.0.0.1:5000 - * Running on http://172.20.0.2:5000 -2025-03-20 17:51:07,001 - INFO - Press CTRL+C to quit -2025-03-20 17:51:28,286 - INFO - 172.20.0.1 - - [20/Mar/2025 17:51:28] "GET / HTTP/1.1" 404 - -2025-03-20 17:51:28,603 - INFO - 172.20.0.1 - - [20/Mar/2025 17:51:28] "GET /favicon.ico HTTP/1.1" 404 - -2025-03-20 17:51:38,063 - INFO - 172.20.0.1 - - [20/Mar/2025 17:51:38] "GET /api/health HTTP/1.1" 200 - -2025-03-20 17:52:29,740 - INFO - 172.20.0.1 - - [20/Mar/2025 17:52:29] "GET /health HTTP/1.1" 404 - -2025-03-20 17:53:14,937 - INFO - WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. - * Running on all addresses (0.0.0.0) - * Running on http://127.0.0.1:5000 - * Running on http://172.20.0.2:5000 -2025-03-20 17:53:14,938 - INFO - Press CTRL+C to quit -2025-03-20 17:53:49,208 - INFO - 172.20.0.1 - - [20/Mar/2025 17:53:49] "GET /health HTTP/1.1" 200 - +2025-03-21 15:22:45,402 - INFO - Processing Disbursement request +2025-03-21 15:23:08,061 - INFO - Processing Disbursement request +2025-03-21 15:23:11,884 - ERROR - Unauthorized access: Missing App-ID. +2025-03-21 15:23:14,667 - INFO - Processing Disbursement request diff --git a/app/__init__.py b/app/__init__.py index 179b2a7..be3109d 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -2,6 +2,7 @@ from flask import Flask from flask_cors import CORS from app.config import Config from app.routes import api +from app.errors import method_not_allowed, unsupported_media_type def create_app(): """ Factory function to create a Flask app instance """ @@ -16,4 +17,8 @@ def create_app(): # Register blueprints app.register_blueprint(api) + # Error Handlers + app.register_error_handler(405, method_not_allowed) + app.register_error_handler(415, unsupported_media_type) + return app diff --git a/app/blueprints/__init__.py b/app/blueprints/__init__.py deleted file mode 100644 index fcdb467..0000000 --- a/app/blueprints/__init__.py +++ /dev/null @@ -1,18 +0,0 @@ -from app.blueprints.eligibility_check import EligibilityCheckService -from app.blueprints.select_offer import SelectOfferService -from app.blueprints.provide_loan import ProvideLoanService -from app.blueprints.loan_information import LoanInformationService -from app.blueprints.repayment import RepaymentService -from app.blueprints.customer_consent import CustomerConsentService -from app.blueprints.notification_callback import NotificationCallbackService -from app.blueprints.rac_check import RACCheckService -from app.blueprints.disbursement import DisbursementService -from app.blueprints.collect_loan import CollectLoanService -from app.blueprints.transaction_verify import TransactionVerifyService -from app.blueprints.penal_charge import PenalChargeService -from app.blueprints.revoke_enable_consent import RevokeEnableConsentService -from app.blueprints.token_validation import TokenValidationService -from app.blueprints.lien_check import LienCheckService -from app.blueprints.new_transaction_check import NewTransactionCheckService -from app.blueprints.sms import SMSService -from app.blueprints.bulk_sms import BulkSMSService diff --git a/app/blueprints/bulk_sms.py b/app/blueprints/bulk_sms.py deleted file mode 100644 index b06063b..0000000 --- a/app/blueprints/bulk_sms.py +++ /dev/null @@ -1,55 +0,0 @@ -from marshmallow import ValidationError -from app.utils.logger import logger -from app.helpers.response_helper import ResponseHelper -from app.schemas.bulk_sms import BulkSMSSchema - -class BulkSMSService: - @staticmethod - def process_request(data): - """ - Process the Bulk SMS request. - - Args: - data (dict): The request data. - - Returns: - dict: A standardized response. - """ - try: - logger.info("Processing BulkSMS request") - - # Validate input data using BulkSMSSchema - schema = BulkSMSSchema() - validated_data = schema.load(data) # Raises ValidationError if invalid - - # Simulated Bulk SMS sending logic - response_data = { - "data": "", - "statusCode": 200, - "isSuccessful": True, - "errorMessage": None - } - - - # return ResponseHelper.success( - # data=response_data, - # message="Bulk SMS sent successfully" - # ) - - 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 - ) - - 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) - ) diff --git a/app/blueprints/sms.py b/app/blueprints/sms.py deleted file mode 100644 index 08ce9a3..0000000 --- a/app/blueprints/sms.py +++ /dev/null @@ -1,56 +0,0 @@ -from marshmallow import ValidationError -from app.utils.logger import logger -from app.helpers.response_helper import ResponseHelper -from app.schemas.sms import SMSSchema - - -class SMSService: - @staticmethod - def process_request(data): - """ - Process the SMS request. - - Args: - data (dict): The request data. - - Returns: - dict: A standardized response. - """ - try: - logger.info("Processing SMS request") - - # Validate input data using SMSSchema - schema = SMSSchema() - validated_data = schema.load(data) # Raises ValidationError if invalid - - # Simulated SMS sending logic - response_data = { - "data": "", - "statusCode": 200, - "isSuccessful": True, - "errorMessage": None - } - - - # return ResponseHelper.success( - # data=response_data, - # message="SMS sent successfully" - # ) - - 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 - ) - - 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) - ) diff --git a/app/errors/__init__.py b/app/errors/__init__.py new file mode 100644 index 0000000..15c380c --- /dev/null +++ b/app/errors/__init__.py @@ -0,0 +1 @@ +from .handlers import method_not_allowed, unsupported_media_type \ No newline at end of file diff --git a/app/errors/handlers.py b/app/errors/handlers.py new file mode 100644 index 0000000..0b4cbdf --- /dev/null +++ b/app/errors/handlers.py @@ -0,0 +1,14 @@ +from flask import jsonify +from app.helpers.response_helper import ResponseHelper + +def method_not_allowed(error): + return jsonify({"message": "Method Not Allowed"}), 405 + +def not_found(error): + return jsonify({"message": "Resource not found"}), 404 + +def bad_request(error): + return jsonify({"message": "Bad Request"}), 400 + +def unsupported_media_type(error): + return jsonify({"message": "Unsupported Media Type"}), 415 diff --git a/app/helpers/response_helper.py b/app/helpers/response_helper.py index 47faeaf..adcc178 100644 --- a/app/helpers/response_helper.py +++ b/app/helpers/response_helper.py @@ -35,7 +35,7 @@ class ResponseHelper: "data": data if data is not None else {}, "error": error if error is not None else {}, } - return response + return jsonify(response), status_code @staticmethod def success( @@ -210,4 +210,42 @@ class ResponseHelper: Returns: Dict[str, Any]: A dictionary representing the JSON response. """ - return ResponseHelper.build_response(False, message, data, 422, error) \ No newline at end of file + 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) \ 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