Merge branch 'testing' of DigiFi/FirstCore into master
This commit is contained in:
@@ -1,7 +1,9 @@
|
|||||||
from flask import Blueprint, request, jsonify, send_from_directory
|
from flask import Blueprint, request, jsonify, send_from_directory
|
||||||
from app.api.services import (
|
from app.api.services import (
|
||||||
AuthorizationService,
|
AuthorizationService,
|
||||||
TransactionService, LoanService,
|
TransactionService,
|
||||||
|
LoanService,
|
||||||
|
AuthService
|
||||||
)
|
)
|
||||||
from app.utils.logger import logger
|
from app.utils.logger import logger
|
||||||
from app.api.middlewares import enforce_json, require_auth
|
from app.api.middlewares import enforce_json, require_auth
|
||||||
@@ -36,9 +38,17 @@ def serve_paths(filename):
|
|||||||
return send_from_directory(swagger_dir, filename)
|
return send_from_directory(swagger_dir, filename)
|
||||||
|
|
||||||
|
|
||||||
|
# Login endpoint
|
||||||
|
@api.route("/login", methods=["POST"])
|
||||||
|
def login():
|
||||||
|
data = request.get_json()
|
||||||
|
response = AuthService.login(data)
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
# Get All Transactions Endpoint
|
# Get All Transactions Endpoint
|
||||||
@api.route("/transactions", methods=["GET"])
|
@api.route("/transactions", methods=["GET"])
|
||||||
# @jwt_required()
|
@jwt_required()
|
||||||
def get_transactions():
|
def get_transactions():
|
||||||
# Extract query parameters for filtering
|
# Extract query parameters for filtering
|
||||||
filters = {
|
filters = {
|
||||||
@@ -56,7 +66,7 @@ def get_transactions():
|
|||||||
|
|
||||||
# Get All Loans Endpoint
|
# Get All Loans Endpoint
|
||||||
@api.route("/loans", methods=["GET"])
|
@api.route("/loans", methods=["GET"])
|
||||||
# @jwt_required()
|
@jwt_required()
|
||||||
def get_loans():
|
def get_loans():
|
||||||
# Extract query parameters for filtering
|
# Extract query parameters for filtering
|
||||||
filters = {
|
filters = {
|
||||||
|
|||||||
@@ -3,3 +3,4 @@ from app.api.services.customer_consent import CustomerConsentService
|
|||||||
from app.api.services.authorization import AuthorizationService
|
from app.api.services.authorization import AuthorizationService
|
||||||
from app.api.services.transaction import TransactionService
|
from app.api.services.transaction import TransactionService
|
||||||
from app.api.services.loan import LoanService
|
from app.api.services.loan import LoanService
|
||||||
|
from app.api.services.auth_service import AuthService
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
from flask import jsonify
|
||||||
|
from app.utils.logger import logger
|
||||||
|
from app.api.services.base_service import BaseService
|
||||||
|
from app.models.user import User
|
||||||
|
from flask_jwt_extended import create_access_token
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
|
||||||
|
class AuthService(BaseService):
|
||||||
|
@staticmethod
|
||||||
|
def login(data):
|
||||||
|
"""
|
||||||
|
Process the login request.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
data (dict): Login credentials including username and password.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: A standardized response with JWT token and user information.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Extract credentials
|
||||||
|
username = data.get('username')
|
||||||
|
password = data.get('password')
|
||||||
|
|
||||||
|
# Validate input
|
||||||
|
if not username or not password:
|
||||||
|
return jsonify({
|
||||||
|
"message": "Username and password are required"
|
||||||
|
}), 400
|
||||||
|
|
||||||
|
# Get user by username
|
||||||
|
user = User.get_user_by_username(username)
|
||||||
|
|
||||||
|
# Check if user exists and password is correct
|
||||||
|
if not user or not user.check_password(password):
|
||||||
|
return jsonify({
|
||||||
|
"message": "Invalid username or password"
|
||||||
|
}), 401
|
||||||
|
|
||||||
|
# Create JWT token with 15 minute expiration
|
||||||
|
access_token = create_access_token(
|
||||||
|
identity=user.username,
|
||||||
|
expires_delta=timedelta(minutes=15),
|
||||||
|
additional_claims={"name": user.name}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Return token and user information
|
||||||
|
return {
|
||||||
|
"jwt_token": access_token,
|
||||||
|
"name": user.name
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"An error occurred during login: {str(e)}", exc_info=True)
|
||||||
|
return jsonify({
|
||||||
|
"message": "Internal Server Error"
|
||||||
|
}), 500
|
||||||
@@ -30,6 +30,9 @@ class Config:
|
|||||||
JWT_REFRESH_TOKEN_EXPIRES = os.getenv(
|
JWT_REFRESH_TOKEN_EXPIRES = os.getenv(
|
||||||
"JWT_REFRESH_TOKEN_EXPIRES", timedelta(days=30)
|
"JWT_REFRESH_TOKEN_EXPIRES", timedelta(days=30)
|
||||||
)
|
)
|
||||||
|
JWT_TOKEN_LOCATION = ["headers"]
|
||||||
|
JWT_HEADER_NAME = "Authorization"
|
||||||
|
JWT_HEADER_TYPE = "Bearer"
|
||||||
|
|
||||||
KAFKA_BROKER = 'dev-events.simbrellang.net:9085'
|
KAFKA_BROKER = 'dev-events.simbrellang.net:9085'
|
||||||
KAFKA_PAYMENT_TOPIC = 'PROCESS_PAYMENT'
|
KAFKA_PAYMENT_TOPIC = 'PROCESS_PAYMENT'
|
||||||
|
|||||||
@@ -2,5 +2,6 @@ from .customer import Customer
|
|||||||
from .account import Account
|
from .account import Account
|
||||||
from .loan import Loan
|
from .loan import Loan
|
||||||
from .transaction import Transaction
|
from .transaction import Transaction
|
||||||
|
from .user import User
|
||||||
|
|
||||||
__all__ = ['Customer', 'Account', 'Loan', 'Transaction']
|
__all__ = ['Customer', 'Account', 'Loan', 'Transaction', User]
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
from datetime import datetime, timezone
|
||||||
|
from app.extensions import db
|
||||||
|
from werkzeug.security import generate_password_hash, check_password_hash
|
||||||
|
|
||||||
|
|
||||||
|
class User(db.Model):
|
||||||
|
__tablename__ = 'users'
|
||||||
|
|
||||||
|
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||||
|
username = db.Column(db.String(50), unique=True, nullable=False)
|
||||||
|
password_hash = db.Column(db.String(255), nullable=False)
|
||||||
|
name = db.Column(db.String(100), nullable=False)
|
||||||
|
created_at = db.Column(db.DateTime, default=datetime.now(timezone.utc))
|
||||||
|
updated_at = db.Column(db.DateTime, default=datetime.now(timezone.utc), onupdate=datetime.now(timezone.utc))
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f'<User {self.username}>'
|
||||||
|
|
||||||
|
def set_password(self, password):
|
||||||
|
self.password_hash = generate_password_hash(password)
|
||||||
|
|
||||||
|
def check_password(self, password):
|
||||||
|
return check_password_hash(self.password_hash, password)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create_user(cls, username, password, name):
|
||||||
|
# Check if user already exists
|
||||||
|
if cls.query.filter_by(username=username).first():
|
||||||
|
raise ValueError("Username already exists")
|
||||||
|
|
||||||
|
user = cls(
|
||||||
|
username=username,
|
||||||
|
name=name
|
||||||
|
)
|
||||||
|
user.set_password(password)
|
||||||
|
|
||||||
|
try:
|
||||||
|
db.session.add(user)
|
||||||
|
db.session.commit()
|
||||||
|
except Exception as e:
|
||||||
|
db.session.rollback()
|
||||||
|
raise ValueError(f"Error creating user: {str(e)}")
|
||||||
|
|
||||||
|
return user
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_user_by_username(cls, username):
|
||||||
|
return cls.query.filter_by(username=username).first()
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
return {
|
||||||
|
'id': self.id,
|
||||||
|
'username': self.username,
|
||||||
|
'name': self.name,
|
||||||
|
'created_at': self.created_at.isoformat() if self.created_at else None,
|
||||||
|
'updated_at': self.updated_at.isoformat() if self.updated_at else None
|
||||||
|
}
|
||||||
@@ -58,6 +58,9 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"paths": {
|
"paths": {
|
||||||
|
"/login": {
|
||||||
|
"$ref": "../swagger/paths/Login.json"
|
||||||
|
},
|
||||||
"/Authorize": {
|
"/Authorize": {
|
||||||
"$ref": "../swagger/paths/Authorize.json"
|
"$ref": "../swagger/paths/Authorize.json"
|
||||||
},
|
},
|
||||||
@@ -88,6 +91,12 @@
|
|||||||
"AuthorizeRefreshRequest": {
|
"AuthorizeRefreshRequest": {
|
||||||
"$ref": "../swagger/schemas/AuthorizeRefreshRequest.json"
|
"$ref": "../swagger/schemas/AuthorizeRefreshRequest.json"
|
||||||
},
|
},
|
||||||
|
"LoginRequest": {
|
||||||
|
"$ref": "../swagger/schemas/LoginRequest.json"
|
||||||
|
},
|
||||||
|
"LoginResponse": {
|
||||||
|
"$ref": "../swagger/schemas/LoginResponse.json"
|
||||||
|
},
|
||||||
"LoansResponse": {
|
"LoansResponse": {
|
||||||
"$ref": "../swagger/schemas/LoansResponse.json"
|
"$ref": "../swagger/schemas/LoansResponse.json"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
{
|
||||||
|
"post": {
|
||||||
|
"tags": [
|
||||||
|
"Authentication"
|
||||||
|
],
|
||||||
|
"summary": "User login",
|
||||||
|
"description": "Authenticate a user and return a JWT token",
|
||||||
|
"operationId": "login",
|
||||||
|
"requestBody": {
|
||||||
|
"description": "User credentials",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "../schemas/LoginRequest.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Successful operation",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "../schemas/LoginResponse.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Invalid request"
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Invalid username or password"
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal server error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"username": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "digifiuser"
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "digifipass"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["username", "password"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"jwt_token": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkaWdpZml1c2VyIiwibmFtZSI6ImJhY2tvZmZpY2UgdXNlciIsImlhdCI6MTUxNjIzOTAyMn0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "backoffice user"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
<stringProp name="HTTPSampler.implementation"></stringProp>
|
<stringProp name="HTTPSampler.implementation"></stringProp>
|
||||||
</ConfigTestElement>
|
</ConfigTestElement>
|
||||||
<hashTree/>
|
<hashTree/>
|
||||||
<Arguments guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
|
<Arguments guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables">
|
||||||
<collectionProp name="Arguments.arguments">
|
<collectionProp name="Arguments.arguments">
|
||||||
<elementProp name="customer_id" elementType="Argument">
|
<elementProp name="customer_id" elementType="Argument">
|
||||||
<stringProp name="Argument.name">customer_id</stringProp>
|
<stringProp name="Argument.name">customer_id</stringProp>
|
||||||
@@ -60,18 +60,18 @@
|
|||||||
</collectionProp>
|
</collectionProp>
|
||||||
</Arguments>
|
</Arguments>
|
||||||
<hashTree/>
|
<hashTree/>
|
||||||
<ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Loan Endpoint Test" enabled="true">
|
<ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Loan Endpoint Test">
|
||||||
<intProp name="ThreadGroup.num_threads">10</intProp>
|
<intProp name="ThreadGroup.num_threads">10</intProp>
|
||||||
<intProp name="ThreadGroup.ramp_time">5</intProp>
|
<intProp name="ThreadGroup.ramp_time">1</intProp>
|
||||||
<boolProp name="ThreadGroup.same_user_on_next_iteration">true</boolProp>
|
<boolProp name="ThreadGroup.same_user_on_next_iteration">true</boolProp>
|
||||||
<stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
|
<stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
|
||||||
<elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller">
|
<elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller">
|
||||||
<stringProp name="LoopController.loops">10</stringProp>
|
<stringProp name="LoopController.loops">1</stringProp>
|
||||||
<boolProp name="LoopController.continue_forever">false</boolProp>
|
<boolProp name="LoopController.continue_forever">false</boolProp>
|
||||||
</elementProp>
|
</elementProp>
|
||||||
</ThreadGroup>
|
</ThreadGroup>
|
||||||
<hashTree>
|
<hashTree>
|
||||||
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get All Loans">
|
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get All Loans" enabled="true">
|
||||||
<stringProp name="HTTPSampler.path">/loans</stringProp>
|
<stringProp name="HTTPSampler.path">/loans</stringProp>
|
||||||
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
|
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
|
||||||
<stringProp name="HTTPSampler.method">GET</stringProp>
|
<stringProp name="HTTPSampler.method">GET</stringProp>
|
||||||
@@ -102,7 +102,7 @@
|
|||||||
</JSONPathAssertion>
|
</JSONPathAssertion>
|
||||||
<hashTree/>
|
<hashTree/>
|
||||||
</hashTree>
|
</hashTree>
|
||||||
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get Loans with Filters">
|
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get Loans with Filters" enabled="true">
|
||||||
<stringProp name="HTTPSampler.path">/loans</stringProp>
|
<stringProp name="HTTPSampler.path">/loans</stringProp>
|
||||||
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
|
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
|
||||||
<stringProp name="HTTPSampler.method">GET</stringProp>
|
<stringProp name="HTTPSampler.method">GET</stringProp>
|
||||||
@@ -128,7 +128,7 @@
|
|||||||
</elementProp>
|
</elementProp>
|
||||||
</HTTPSamplerProxy>
|
</HTTPSamplerProxy>
|
||||||
<hashTree>
|
<hashTree>
|
||||||
<ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Code Assertion">
|
<ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Code Assertion" enabled="true">
|
||||||
<collectionProp name="Asserion.test_strings">
|
<collectionProp name="Asserion.test_strings">
|
||||||
<stringProp name="49586">200</stringProp>
|
<stringProp name="49586">200</stringProp>
|
||||||
</collectionProp>
|
</collectionProp>
|
||||||
@@ -138,7 +138,7 @@
|
|||||||
<intProp name="Assertion.test_type">8</intProp>
|
<intProp name="Assertion.test_type">8</intProp>
|
||||||
</ResponseAssertion>
|
</ResponseAssertion>
|
||||||
<hashTree/>
|
<hashTree/>
|
||||||
<JSONPathAssertion guiclass="JSONPathAssertionGui" testclass="JSONPathAssertion" testname="JSON Path Assertion">
|
<JSONPathAssertion guiclass="JSONPathAssertionGui" testclass="JSONPathAssertion" testname="JSON Path Assertion" enabled="true">
|
||||||
<stringProp name="JSON_PATH">$.loans</stringProp>
|
<stringProp name="JSON_PATH">$.loans</stringProp>
|
||||||
<stringProp name="EXPECTED_VALUE"></stringProp>
|
<stringProp name="EXPECTED_VALUE"></stringProp>
|
||||||
<boolProp name="JSONVALIDATION">false</boolProp>
|
<boolProp name="JSONVALIDATION">false</boolProp>
|
||||||
@@ -148,7 +148,7 @@
|
|||||||
</JSONPathAssertion>
|
</JSONPathAssertion>
|
||||||
<hashTree/>
|
<hashTree/>
|
||||||
</hashTree>
|
</hashTree>
|
||||||
<ResultCollector guiclass="ViewResultsFullVisualizer" testclass="ResultCollector" testname="View Results Tree">
|
<ResultCollector guiclass="ViewResultsFullVisualizer" testclass="ResultCollector" testname="View Results Tree" enabled="true">
|
||||||
<boolProp name="ResultCollector.error_logging">false</boolProp>
|
<boolProp name="ResultCollector.error_logging">false</boolProp>
|
||||||
<objProp>
|
<objProp>
|
||||||
<name>saveConfig</name>
|
<name>saveConfig</name>
|
||||||
@@ -185,7 +185,7 @@
|
|||||||
<stringProp name="filename"></stringProp>
|
<stringProp name="filename"></stringProp>
|
||||||
</ResultCollector>
|
</ResultCollector>
|
||||||
<hashTree/>
|
<hashTree/>
|
||||||
<ResultCollector guiclass="SummaryReport" testclass="ResultCollector" testname="Summary Report">
|
<ResultCollector guiclass="SummaryReport" testclass="ResultCollector" testname="Summary Report" enabled="true">
|
||||||
<boolProp name="ResultCollector.error_logging">false</boolProp>
|
<boolProp name="ResultCollector.error_logging">false</boolProp>
|
||||||
<objProp>
|
<objProp>
|
||||||
<name>saveConfig</name>
|
<name>saveConfig</name>
|
||||||
@@ -298,7 +298,7 @@
|
|||||||
</elementProp>
|
</elementProp>
|
||||||
</HTTPSamplerProxy>
|
</HTTPSamplerProxy>
|
||||||
<hashTree>
|
<hashTree>
|
||||||
<ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Code Assertion" enabled="true">
|
<ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Code Assertion">
|
||||||
<collectionProp name="Asserion.test_strings">
|
<collectionProp name="Asserion.test_strings">
|
||||||
<stringProp name="49586">200</stringProp>
|
<stringProp name="49586">200</stringProp>
|
||||||
</collectionProp>
|
</collectionProp>
|
||||||
@@ -308,7 +308,7 @@
|
|||||||
<intProp name="Assertion.test_type">8</intProp>
|
<intProp name="Assertion.test_type">8</intProp>
|
||||||
</ResponseAssertion>
|
</ResponseAssertion>
|
||||||
<hashTree/>
|
<hashTree/>
|
||||||
<JSONPathAssertion guiclass="JSONPathAssertionGui" testclass="JSONPathAssertion" testname="JSON Path Assertion" enabled="true">
|
<JSONPathAssertion guiclass="JSONPathAssertionGui" testclass="JSONPathAssertion" testname="JSON Path Assertion">
|
||||||
<stringProp name="JSON_PATH">$.transactions</stringProp>
|
<stringProp name="JSON_PATH">$.transactions</stringProp>
|
||||||
<stringProp name="EXPECTED_VALUE"></stringProp>
|
<stringProp name="EXPECTED_VALUE"></stringProp>
|
||||||
<boolProp name="JSONVALIDATION">false</boolProp>
|
<boolProp name="JSONVALIDATION">false</boolProp>
|
||||||
@@ -355,7 +355,7 @@
|
|||||||
<stringProp name="filename"></stringProp>
|
<stringProp name="filename"></stringProp>
|
||||||
</ResultCollector>
|
</ResultCollector>
|
||||||
<hashTree/>
|
<hashTree/>
|
||||||
<ResultCollector guiclass="SummaryReport" testclass="ResultCollector" testname="Summary Report" enabled="true">
|
<ResultCollector guiclass="SummaryReport" testclass="ResultCollector" testname="Summary Report">
|
||||||
<boolProp name="ResultCollector.error_logging">false</boolProp>
|
<boolProp name="ResultCollector.error_logging">false</boolProp>
|
||||||
<objProp>
|
<objProp>
|
||||||
<name>saveConfig</name>
|
<name>saveConfig</name>
|
||||||
|
|||||||
Reference in New Issue
Block a user