From 848d385019440decabb9370a2c17aa94413977e4 Mon Sep 17 00:00:00 2001
From: Ceb! <8844096+pinkfloydsito@users.noreply.github.com>
Date: Fri, 21 Feb 2025 13:56:46 +0200
Subject: [PATCH 01/23] REST - Refactor endpoint to use marshmallow schemas.
Added makefile to test the endpoint. Changed index.html call to match what is
required to use (#1)
---
Makefile | 19 +++++
frontend/src/index.html | 6 +-
orchestrator/requirements.txt | 3 +-
orchestrator/src/app.py | 85 ++++++++++++-------
orchestrator/src/controllers/__init__.py | 0
.../src/controllers/bookstore_controller.py | 75 ++++++++++++++++
orchestrator/src/error_handlers.py | 15 ++++
orchestrator/src/schema.py | 74 ++++++++++++++++
test.txt | 0
9 files changed, 244 insertions(+), 33 deletions(-)
create mode 100644 Makefile
create mode 100644 orchestrator/src/controllers/__init__.py
create mode 100644 orchestrator/src/controllers/bookstore_controller.py
create mode 100644 orchestrator/src/error_handlers.py
create mode 100644 orchestrator/src/schema.py
create mode 100644 test.txt
diff --git a/Makefile b/Makefile
new file mode 100644
index 000000000..549ecf269
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,19 @@
+test_bookstore_post:
+ curl 'http://localhost:8081/checkout' \
+ -H 'Accept: */*' \
+ -H 'Accept-Language: en-US,en;q=0.9' \
+ -H 'Cache-Control: no-cache' \
+ -H 'Connection: keep-alive' \
+ -H 'Content-Type: application/json' \
+ -H 'DNT: 1' \
+ -H 'Origin: http://localhost:8080' \
+ -H 'Pragma: no-cache' \
+ -H 'Referer: http://localhost:8080/' \
+ -H 'Sec-Fetch-Dest: empty' \
+ -H 'Sec-Fetch-Mode: cors' \
+ -H 'Sec-Fetch-Site: same-site' \
+ -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36' \
+ -H 'sec-ch-ua: "Not A(Brand";v="8", "Chromium";v="132", "Google Chrome";v="132"' \
+ -H 'sec-ch-ua-mobile: ?0' \
+ -H 'sec-ch-ua-platform: "macOS"' \
+ --data-raw '{"user":{"name":"John Doe","contact":"john.doe@example.com"},"creditCard":{"number":"4111111111111111","expirationDate":"12/25","cvv":"123"},"userComment":"Please handle with care.","items":[{"name":"Book A","quantity":1},{"name":"Book B","quantity":2}],"billingAddress":{"street":"123 Main St","city":"Springfield","state":"IL","zip":"62701","country":"USA"},"shippingMethod":"Standard","giftWrapping":true,"termsAndConditionsAccepted":true}'
diff --git a/frontend/src/index.html b/frontend/src/index.html
index 15c47351f..3f374e5f9 100644
--- a/frontend/src/index.html
+++ b/frontend/src/index.html
@@ -111,13 +111,17 @@
Items
},
shippingMethod: formData.get('shippingMethod'),
giftWrapping: formData.get('giftWrapping') === 'on',
- termsAccepted: formData.get('terms') === 'on',
+ termsAndConditionsAccepted: formData.get('terms') === 'on',
};
try {
const response = await fetch('http://localhost:8081/checkout', {
+ headers: {
+ 'Content-Type': 'application/json'
+ },
method: 'POST',
body: JSON.stringify(data),
+
});
const result = await response.json();
diff --git a/orchestrator/requirements.txt b/orchestrator/requirements.txt
index 5ba8e254b..3fee8e15f 100644
--- a/orchestrator/requirements.txt
+++ b/orchestrator/requirements.txt
@@ -9,4 +9,5 @@ MarkupSafe==2.1.3
protobuf==4.25.2
Werkzeug==3.0.1
Flask-CORS==4.0.0
-watchdog==6.0.0
\ No newline at end of file
+watchdog==6.0.0
+marshmallow==3.26.1
diff --git a/orchestrator/src/app.py b/orchestrator/src/app.py
index 62d5d0662..405a06d95 100644
--- a/orchestrator/src/app.py
+++ b/orchestrator/src/app.py
@@ -4,23 +4,27 @@
# This set of lines are needed to import the gRPC stubs.
# The path of the stubs is relative to the current file, or absolute inside the container.
# Change these lines only if strictly needed.
-FILE = __file__ if '__file__' in globals() else os.getenv("PYTHONFILE", "")
-fraud_detection_grpc_path = os.path.abspath(os.path.join(FILE, '../../../utils/pb/fraud_detection'))
+FILE = __file__ if "__file__" in globals() else os.getenv("PYTHONFILE", "")
+fraud_detection_grpc_path = os.path.abspath(
+ os.path.join(FILE, "../../../utils/pb/fraud_detection")
+)
sys.path.insert(0, fraud_detection_grpc_path)
import fraud_detection_pb2 as fraud_detection
import fraud_detection_pb2_grpc as fraud_detection_grpc
import grpc
-def greet(name='you'):
+
+def greet(name="you"):
# Establish a connection with the fraud-detection gRPC service.
- with grpc.insecure_channel('fraud_detection:50051') as channel:
+ with grpc.insecure_channel("fraud_detection:50051") as channel:
# Create a stub object.
stub = fraud_detection_grpc.HelloServiceStub(channel)
# Call the service through the stub object.
response = stub.SayHello(fraud_detection.HelloRequest(name=name))
return response.greeting
+
# Import Flask.
# Flask is a web framework for Python.
# It allows you to build a web application quickly.
@@ -29,47 +33,66 @@ def greet(name='you'):
from flask_cors import CORS
import json
-# Create a simple Flask app.
-app = Flask(__name__)
+
+def create_app():
+ app = Flask(__name__)
+
+ # Import and register blueprints/controllers
+ from controllers.bookstore_controller import bookstore_bp
+
+ app.register_blueprint(bookstore_bp)
+
+ # Register error handlers
+ from error_handlers import register_error_handlers
+
+ register_error_handlers(app)
+
+ return app
+
+
+app = create_app()
# Enable CORS for the app.
-CORS(app, resources={r'/*': {'origins': '*'}})
+CORS(app, resources={r"/*": {"origins": "*"}})
+
# Define a GET endpoint.
-@app.route('/', methods=['GET'])
+@app.route("/", methods=["GET"])
def index():
"""
Responds with 'Hello, [name]' when a GET request is made to '/' endpoint.
"""
# Test the fraud-detection gRPC service.
- response = greet(name='orchestrator')
+ username = request.args.get("name") or "other"
+ response = greet(name=username)
# Return the response.
return response
-@app.route('/checkout', methods=['POST'])
-def checkout():
- """
- Responds with a JSON object containing the order ID, status, and suggested books.
- """
- # Get request object data to json
- request_data = json.loads(request.data)
- # Print request object data
- print("Request Data:", request_data.get('items'))
-
- # Dummy response following the provided YAML specification for the bookstore
- order_status_response = {
- 'orderId': '12345',
- 'status': 'Order Approved',
- 'suggestedBooks': [
- {'bookId': '123', 'title': 'The Best Book', 'author': 'Author 1'},
- {'bookId': '456', 'title': 'The Second Best Book', 'author': 'Author 2'}
- ]
- }
- return order_status_response
+# @app.route("/checkout", methods=["POST"])
+# def checkout():
+# """
+# Responds with a JSON object containing the order ID, status, and suggested books.
+# """
+# # Get request object data to json
+# request_data = json.loads(request.data)
+# # Print request object data
+# print("Request Data:", request_data.get("items"))
+#
+# # Dummy response following the provided YAML specification for the bookstore
+# order_status_response = {
+# "orderId": "12345",
+# "status": "Order Approved",
+# "suggestedBooks": [
+# {"bookId": "123", "title": "The Best Book", "author": "Author 1"},
+# {"bookId": "456", "title": "The Second Best Book", "author": "Author 2"},
+# ],
+# }
+#
+# return order_status_response
-if __name__ == '__main__':
+if __name__ == "__main__":
# Run the app in debug mode to enable hot reloading.
# This is useful for development.
# The default port is 5000.
- app.run(host='0.0.0.0')
+ app.run(host="0.0.0.0")
diff --git a/orchestrator/src/controllers/__init__.py b/orchestrator/src/controllers/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/orchestrator/src/controllers/bookstore_controller.py b/orchestrator/src/controllers/bookstore_controller.py
new file mode 100644
index 000000000..b2bd29775
--- /dev/null
+++ b/orchestrator/src/controllers/bookstore_controller.py
@@ -0,0 +1,75 @@
+from flask import Blueprint, request, jsonify
+from marshmallow import ValidationError
+from schema import *
+import uuid
+
+# XXX: Change this: Mock database for demonstration
+suggested_books_db = [
+ {"bookId": "book1", "title": "The Great Gatsby", "author": "F. Scott Fitzgerald"},
+ {"bookId": "book2", "title": "1984", "author": "George Orwell"},
+ {"bookId": "book3", "title": "To Kill a Mockingbird", "author": "Harper Lee"},
+]
+
+bookstore_bp = Blueprint("bookstore", __name__)
+
+
+@bookstore_bp.route("/checkout", methods=["POST"])
+def checkout():
+ try:
+ # Validate request data
+ schema = CheckoutRequestSchema()
+ data = schema.load(request.json)
+
+ # Validate terms and conditions
+ if not data["termsAndConditionsAccepted"]:
+ return jsonify(
+ {
+ "error": {
+ "code": "TERMS_NOT_ACCEPTED",
+ "message": "Terms and conditions must be accepted",
+ }
+ }
+ ), 400
+
+ # Process payment
+ # payment_successful = process_payment(data['creditCard'])
+
+ # if payment_successful:
+ # # Generate order response
+ # response = {
+ # "orderId": str(uuid.uuid4()),
+ # "status": "Order Approved",
+ # "suggestedBooks": suggested_books_db[:2] # Return 2 suggested books
+ # }
+ # return jsonify(OrderStatusResponseSchema().dump(response))
+ # else:
+ # # Payment failed
+ # response = {
+ # "orderId": str(uuid.uuid4()),
+ # "status": "Order Rejected",
+ # "suggestedBooks": []
+ # }
+ # return jsonify(OrderStatusResponseSchema().dump(response))
+
+ response = {
+ "orderId": str(uuid.uuid4()),
+ "status": "Order Approved",
+ "suggestedBooks": suggested_books_db[:2], # Return 2 suggested books
+ }
+
+ return jsonify(OrderStatusResponseSchema().dump(response))
+
+ except ValidationError as err:
+ return jsonify(
+ {"error": {"code": "VALIDATION_ERROR", "message": str(err.messages)}}
+ ), 400
+ except Exception as e:
+ print(e)
+ return jsonify(
+ {
+ "error": {
+ "code": "INTERNAL_ERROR",
+ "message": "An internal error occurred",
+ }
+ }
+ ), 500
diff --git a/orchestrator/src/error_handlers.py b/orchestrator/src/error_handlers.py
new file mode 100644
index 000000000..56efef91d
--- /dev/null
+++ b/orchestrator/src/error_handlers.py
@@ -0,0 +1,15 @@
+from flask import jsonify
+
+
+def register_error_handlers(app):
+ @app.errorhandler(404)
+ def not_found(error):
+ return jsonify(
+ {"error": {"code": "NOT_FOUND", "message": "Resource not found"}}
+ ), 404
+
+ @app.errorhandler(405)
+ def method_not_allowed(error):
+ return jsonify(
+ {"error": {"code": "METHOD_NOT_ALLOWED", "message": "Method not allowed"}}
+ ), 405
diff --git a/orchestrator/src/schema.py b/orchestrator/src/schema.py
new file mode 100644
index 000000000..b480b357b
--- /dev/null
+++ b/orchestrator/src/schema.py
@@ -0,0 +1,74 @@
+from marshmallow import Schema, fields, validate
+
+
+class DeviceSchema(Schema):
+ type = fields.Str(required=True)
+ model = fields.Str(required=True)
+ os = fields.Str(required=True)
+
+
+class BrowserSchema(Schema):
+ name = fields.Str(required=True)
+ version = fields.Str(required=True)
+
+
+class BillingAddressSchema(Schema):
+ street = fields.Str(required=True)
+ city = fields.Str(required=True)
+ state = fields.Str(required=True)
+ zip = fields.Str(required=True)
+ country = fields.Str(required=True)
+
+
+class UserSchema(Schema):
+ name = fields.Str(required=True)
+ contact = fields.Str(required=True)
+
+
+class CreditCardSchema(Schema):
+ number = fields.Str(required=True, validate=validate.Length(min=15, max=16))
+ expirationDate = fields.Str(required=True)
+ cvv = fields.Str(required=True, validate=validate.Length(equal=3))
+
+
+class ItemSchema(Schema):
+ name = fields.Str(required=True)
+ quantity = fields.Int(required=True, validate=validate.Range(min=1))
+
+
+class CheckoutRequestSchema(Schema):
+ user = fields.Nested(UserSchema, required=True)
+ creditCard = fields.Nested(CreditCardSchema, required=True)
+ userComment = fields.Str()
+ items = fields.List(
+ fields.Nested(ItemSchema), required=True, validate=validate.Length(min=1)
+ )
+ discountCode = fields.Str()
+ shippingMethod = fields.Str(required=True)
+ giftMessage = fields.Str()
+ billingAddress = fields.Nested(BillingAddressSchema, required=True)
+ giftWrapping = fields.Bool()
+ termsAndConditionsAccepted = fields.Bool(required=True)
+ notificationPreferences = fields.List(fields.Str())
+ device = fields.Nested(DeviceSchema)
+ browser = fields.Nested(BrowserSchema)
+ appVersion = fields.Str()
+ screenResolution = fields.Str()
+ referrer = fields.Str()
+ deviceLanguage = fields.Str()
+
+
+class SuggestedBookSchema(Schema):
+ bookId = fields.Str(required=True)
+ title = fields.Str(required=True)
+ author = fields.Str(required=True)
+
+
+class OrderStatusResponseSchema(Schema):
+ orderId = fields.Str(required=True)
+ status = fields.Str(required=True)
+ suggestedBooks = fields.List(fields.Nested(SuggestedBookSchema))
+
+
+class ErrorResponseSchema(Schema):
+ error = fields.Dict(keys=fields.Str(), values=fields.Str())
diff --git a/test.txt b/test.txt
new file mode 100644
index 000000000..e69de29bb
From bcd57250bd8274ef1f44dc9141db4d79e460a967 Mon Sep 17 00:00:00 2001
From: Naveen Kumar <42708813+naveenkumarkk@users.noreply.github.com>
Date: Wed, 26 Feb 2025 09:35:10 +0200
Subject: [PATCH 02/23] feat:fintech api (#2)
---
orchestrator/requirements.txt | 1 +
orchestrator/src/app.py | 41 +++------
.../src/controllers/bookstore_controller.py | 57 +++++++++---
.../src/controllers/fintech_controller.py | 91 +++++++++++++++++++
orchestrator/src/schema.py | 38 ++++++++
5 files changed, 187 insertions(+), 41 deletions(-)
create mode 100644 orchestrator/src/controllers/fintech_controller.py
diff --git a/orchestrator/requirements.txt b/orchestrator/requirements.txt
index 3fee8e15f..7e724fe6f 100644
--- a/orchestrator/requirements.txt
+++ b/orchestrator/requirements.txt
@@ -11,3 +11,4 @@ Werkzeug==3.0.1
Flask-CORS==4.0.0
watchdog==6.0.0
marshmallow==3.26.1
+requests==2.31.0
\ No newline at end of file
diff --git a/orchestrator/src/app.py b/orchestrator/src/app.py
index 405a06d95..7af66c6d1 100644
--- a/orchestrator/src/app.py
+++ b/orchestrator/src/app.py
@@ -31,20 +31,24 @@ def greet(name="you"):
# For more information, see https://flask.palletsprojects.com/en/latest/
from flask import Flask, request
from flask_cors import CORS
-import json
-
def create_app():
app = Flask(__name__)
- # Import and register blueprints/controllers
- from controllers.bookstore_controller import bookstore_bp
+ # ✅ List of blueprints
+ blueprints = [
+ ("controllers.bookstore_controller", "bookstore_bp"),
+ ("controllers.fintech_controller", "fintech_bp"),
+ ]
- app.register_blueprint(bookstore_bp)
+ # Dynamically import and register blueprints
+ for module_path, bp_name in blueprints:
+ module = __import__(module_path, fromlist=[bp_name])
+ blueprint = getattr(module, bp_name)
+ app.register_blueprint(blueprint)
- # Register error handlers
+ # ✅ Register error handlers
from error_handlers import register_error_handlers
-
register_error_handlers(app)
return app
@@ -68,29 +72,6 @@ def index():
return response
-# @app.route("/checkout", methods=["POST"])
-# def checkout():
-# """
-# Responds with a JSON object containing the order ID, status, and suggested books.
-# """
-# # Get request object data to json
-# request_data = json.loads(request.data)
-# # Print request object data
-# print("Request Data:", request_data.get("items"))
-#
-# # Dummy response following the provided YAML specification for the bookstore
-# order_status_response = {
-# "orderId": "12345",
-# "status": "Order Approved",
-# "suggestedBooks": [
-# {"bookId": "123", "title": "The Best Book", "author": "Author 1"},
-# {"bookId": "456", "title": "The Second Best Book", "author": "Author 2"},
-# ],
-# }
-#
-# return order_status_response
-
-
if __name__ == "__main__":
# Run the app in debug mode to enable hot reloading.
# This is useful for development.
diff --git a/orchestrator/src/controllers/bookstore_controller.py b/orchestrator/src/controllers/bookstore_controller.py
index b2bd29775..ff57d22b5 100644
--- a/orchestrator/src/controllers/bookstore_controller.py
+++ b/orchestrator/src/controllers/bookstore_controller.py
@@ -2,8 +2,9 @@
from marshmallow import ValidationError
from schema import *
import uuid
+import requests
+
-# XXX: Change this: Mock database for demonstration
suggested_books_db = [
{"bookId": "book1", "title": "The Great Gatsby", "author": "F. Scott Fitzgerald"},
{"bookId": "book2", "title": "1984", "author": "George Orwell"},
@@ -20,7 +21,6 @@ def checkout():
schema = CheckoutRequestSchema()
data = schema.load(request.json)
- # Validate terms and conditions
if not data["termsAndConditionsAccepted"]:
return jsonify(
{
@@ -31,9 +31,8 @@ def checkout():
}
), 400
- # Process payment
- # payment_successful = process_payment(data['creditCard'])
-
+ # payment_successful = process_payment(data['user'])
+
# if payment_successful:
# # Generate order response
# response = {
@@ -50,13 +49,11 @@ def checkout():
# "suggestedBooks": []
# }
# return jsonify(OrderStatusResponseSchema().dump(response))
-
response = {
- "orderId": str(uuid.uuid4()),
- "status": "Order Approved",
- "suggestedBooks": suggested_books_db[:2], # Return 2 suggested books
- }
-
+ "orderId": str(uuid.uuid4()),
+ "status": "Order Approved",
+ "suggestedBooks": suggested_books_db[:2] # Return 2 suggested books
+ }
return jsonify(OrderStatusResponseSchema().dump(response))
except ValidationError as err:
@@ -73,3 +70,41 @@ def checkout():
}
}
), 500
+
+def process_payment(user):
+ """Handles the payment by calling the /transfer API."""
+ try:
+ transfer_payload = {
+ "sender": {
+ "name": "Bookstore Inc.",
+ "accountNumber": "111122223333"
+ },
+ "recipient": {
+ "name": user['name'],
+ "accountNumber": "444455556666"
+ },
+ "amount": 10000,
+ "currency": "USD",
+ "paymentMethod": "Credit Card",
+ "transferNote": "Book purchase",
+ "notificationPreferences": ["Email"],
+ "device": {"type": "Desktop", "model": "PC", "os": "Windows 11"},
+ "browser": {"name": "Chrome", "version": "118.0"},
+ "appVersion": "1.0.0",
+ "screenResolution": "1920x1080",
+ "referrer": "Bookstore Website",
+ "deviceLanguage": "en-US"
+ }
+
+ response = requests.post("http://localhost:8081/transfer", json=transfer_payload)
+
+ if response.status_code == 200:
+ transfer_data = response.json()
+ return transfer_data["status"] == "Transfer Approved"
+ else:
+ print(f"Transfer API failed with status code: {response.status_code}")
+ return False
+
+ except Exception as e:
+ print(f"Error processing payment: {e}")
+ return False
diff --git a/orchestrator/src/controllers/fintech_controller.py b/orchestrator/src/controllers/fintech_controller.py
new file mode 100644
index 000000000..2319a77fa
--- /dev/null
+++ b/orchestrator/src/controllers/fintech_controller.py
@@ -0,0 +1,91 @@
+from flask import Blueprint, request, jsonify
+from marshmallow import ValidationError
+from schema import *
+import uuid
+import random
+
+transfer_db = [
+ {
+ "sender": {"name": "John Doe", "accountNumber": "123456789012"},
+ "recipient": {"name": "Jane Smith", "accountNumber": "987654321098"},
+ "amount": 250.75,
+ "currency": "USD",
+ "paymentMethod": "Credit Card",
+ "transferNote": "Payment for services rendered",
+ "notificationPreferences": ["Email", "SMS"],
+ "device": {"type": "Mobile", "model": "iPhone 13", "os": "iOS 16.2"},
+ "browser": {"name": "Safari", "version": "16.2"},
+ "appVersion": "5.3.1",
+ "screenResolution": "1170x2532",
+ "referrer": "BankingApp",
+ "deviceLanguage": "en-US",
+ }
+]
+
+transfer_status_db = [
+ {
+ "transactionId": "TXN202402220001",
+ "status": "Transfer Approved",
+ "suggestedProducts": [
+ {"productId": "PROD001", "title": "Platinum Credit Card"},
+ {"productId": "PROD002", "title": "High-Yield Savings Account"},
+ {"productId": "PROD003", "title": "International Travel Insurance"},
+ ],
+ }
+]
+
+rejected_transfer = [
+ {
+ "transactionId": "TXN202402220002",
+ "status": "Transfer Rejected",
+ "suggestedProducts": [],
+ }
+]
+
+fintech_bp = Blueprint("fintech", __name__)
+
+
+@fintech_bp.route("/transfer", methods=["POST"])
+def transfer():
+ try:
+ # Validate incoming request
+ schema = TransferRequestSchema()
+ data = schema.load(request.json)
+
+ # Simulate transfer approval/rejection (for demo purposes)
+ status = random.choice(["Transfer Approved", "Transfer Rejected"])
+ transaction_id = str(uuid.uuid4())
+
+ # If approved, suggest products; otherwise, suggestProducts is empty
+ suggested_products = []
+ if status == "Transfer Approved":
+ suggested_products = [
+ {"productId": "PROD001", "title": "Premium Savings Account"},
+ {"productId": "PROD002", "title": "Travel Rewards Card"},
+ {"productId": "PROD003", "title": "Personal Loan Offer"}
+ ]
+
+ response = {
+ "transactionId": transaction_id,
+ "status": status,
+ "suggestedProducts": suggested_products
+ }
+
+ return jsonify(TransferStatusResponseSchema().dump(response)), 200
+
+ except ValidationError as err:
+ return jsonify({
+ "error": {
+ "code": "VALIDATION_ERROR",
+ "message": err.messages
+ }
+ }), 400
+
+ except Exception as e:
+ print(f"Server Error: {e}")
+ return jsonify({
+ "error": {
+ "code": "SERVER_ERROR",
+ "message": "An internal server error occurred."
+ }
+ }), 500
\ No newline at end of file
diff --git a/orchestrator/src/schema.py b/orchestrator/src/schema.py
index b480b357b..d832d4fb2 100644
--- a/orchestrator/src/schema.py
+++ b/orchestrator/src/schema.py
@@ -72,3 +72,41 @@ class OrderStatusResponseSchema(Schema):
class ErrorResponseSchema(Schema):
error = fields.Dict(keys=fields.Str(), values=fields.Str())
+
+
+class SenderRecipientSchema(Schema):
+ name = fields.String(required=True)
+ accountNumber = fields.String(required=True)
+
+
+class BrowserSchema(Schema):
+ name = fields.String(required=True)
+ version = fields.String(required=True)
+
+
+class TransferRequestSchema(Schema):
+ sender = fields.Nested(SenderRecipientSchema, required=True)
+ recipient = fields.Nested(SenderRecipientSchema, required=True)
+ amount = fields.Float(required=True)
+ currency = fields.String(required=True)
+ paymentMethod = fields.String(required=True)
+ transferNote = fields.String(required=False)
+ notificationPreferences = fields.List(fields.String(), required=False)
+ device = fields.Nested(DeviceSchema, required=True)
+ browser = fields.Nested(BrowserSchema, required=True)
+ appVersion = fields.String(required=True)
+ screenResolution = fields.String(required=False)
+ referrer = fields.String(required=False)
+ deviceLanguage = fields.String(required=False)
+
+
+class SuggestedProductSchema(Schema):
+ productId = fields.String()
+ title = fields.String()
+
+class TransferStatusResponseSchema(Schema):
+ transactionId = fields.String()
+ status = fields.String()
+ suggestedProducts = fields.List(fields.Nested(SuggestedProductSchema))
+
+
\ No newline at end of file
From cb77d440ca3da3ffe5601f4412652d8ef648887f Mon Sep 17 00:00:00 2001
From: Ceb! <8844096+pinkfloydsito@users.noreply.github.com>
Date: Sat, 1 Mar 2025 08:22:59 +0200
Subject: [PATCH 03/23] =?UTF-8?q?=F0=9F=92=AA=20Suggestions=20Microservice?=
=?UTF-8?q?=20(#4)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* build(dependencies-of-grpc-bumped-to-1.7.0): grpcio 1.6 was being used, we require the version to be bumped to the current stable one
* feat(GRPC-Factory): grpc channels are now created in factory
grpc channel creation is orchestrated in a factory rather than in the main file
* feat(Suggestions-Microservice): Suggestions microservice template is defined, added sample endpoint and bumped dependencies for grpc
* Updated main file adding suggestions service, bumped fraud_detection dependencies
* Books are not fetched from postgres. Vector Embeddings working with cosine similarity score, updated requirements to use the necessary dependencies
* removed unused code in main app file
---
.env.example | 9 +
.gitignore | 5 +-
data/books.sample.csv | 2 +
db/Dockerfile | 14 ++
db/init-extensions.sql | 1 +
docker-compose.yaml | 37 +++-
fraud_detection/requirements.txt | 6 +-
fraud_detection/src/app.py | 16 +-
frontend/src/index.html | 62 +++++-
orchestrator/requirements.txt | 12 +-
orchestrator/src/app.py | 60 +++---
.../src/controllers/bookstore_controller.py | 176 ++++++++++++++----
orchestrator/src/grpc_client_factory.py | 56 ++++++
orchestrator/src/schema.py | 3 +-
suggestions/Dockerfile | 15 ++
suggestions/requirements.txt | 9 +
suggestions/src/app.py | 95 ++++++++++
test.txt => utils/__init__.py | 0
utils/data_loader/book_data_loader_service.py | 83 +++++++++
utils/models/book.py | 32 ++++
utils/other/hotreload.py | 17 +-
utils/pb/suggestions/__init__.py | 0
utils/pb/suggestions/suggestions.proto | 35 ++++
utils/pb/suggestions/suggestions_pb2.py | 46 +++++
utils/pb/suggestions/suggestions_pb2.pyi | 50 +++++
utils/pb/suggestions/suggestions_pb2_grpc.py | 97 ++++++++++
26 files changed, 848 insertions(+), 90 deletions(-)
create mode 100644 .env.example
create mode 100644 data/books.sample.csv
create mode 100644 db/Dockerfile
create mode 100644 db/init-extensions.sql
create mode 100644 orchestrator/src/grpc_client_factory.py
create mode 100644 suggestions/Dockerfile
create mode 100644 suggestions/requirements.txt
create mode 100644 suggestions/src/app.py
rename test.txt => utils/__init__.py (100%)
create mode 100644 utils/data_loader/book_data_loader_service.py
create mode 100644 utils/models/book.py
create mode 100644 utils/pb/suggestions/__init__.py
create mode 100644 utils/pb/suggestions/suggestions.proto
create mode 100644 utils/pb/suggestions/suggestions_pb2.py
create mode 100644 utils/pb/suggestions/suggestions_pb2.pyi
create mode 100644 utils/pb/suggestions/suggestions_pb2_grpc.py
diff --git a/.env.example b/.env.example
new file mode 100644
index 000000000..63e131362
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,9 @@
+# PostgreSQL
+# ------------------------------------------------------------------------------
+POSTGRES_HOST=postgres
+POSTGRES_PORT=5432
+POSTGRES_DB=bookstore
+POSTGRES_USER=debug
+POSTGRES_PASSWORD=debug
+# DATABASE_URL="postgresql+psycopg2://${POSTGRES_USER}:${POSTGRES_PASSWORD}@localhost:${POSTGRES_PORT}/${POSTGRES_DB}"
+MODEL_NAME="all-MiniLM-L6-v2"
diff --git a/.gitignore b/.gitignore
index ed8ebf583..3e9201519 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,4 @@
-__pycache__
\ No newline at end of file
+__pycache__
+.env
+books.csv
+postgres_data/
diff --git a/data/books.sample.csv b/data/books.sample.csv
new file mode 100644
index 000000000..5970177cc
--- /dev/null
+++ b/data/books.sample.csv
@@ -0,0 +1,2 @@
+Title,Author,Genre,SubGenre,Height,Publisher
+Fundamentals of Wavelets,"Goswami, Jaideva",tech,signal_processing,228,Wiley
diff --git a/db/Dockerfile b/db/Dockerfile
new file mode 100644
index 000000000..4eb83b46a
--- /dev/null
+++ b/db/Dockerfile
@@ -0,0 +1,14 @@
+FROM docker.io/postgres:16
+
+RUN apt-get update && apt-get install -y \
+ git \
+ build-essential \
+ postgresql-server-dev-$PG_MAJOR \
+ && rm -rf /var/lib/apt/lists/*
+
+RUN git clone --branch v0.7.0 https://github.com/pgvector/pgvector.git \
+ && cd pgvector \
+ && make \
+ && make install
+
+COPY ./db/init-extensions.sql /docker-entrypoint-initdb.d/
diff --git a/db/init-extensions.sql b/db/init-extensions.sql
new file mode 100644
index 000000000..0aa0fc225
--- /dev/null
+++ b/db/init-extensions.sql
@@ -0,0 +1 @@
+CREATE EXTENSION IF NOT EXISTS vector;
diff --git a/docker-compose.yaml b/docker-compose.yaml
index b4a60a537..71374e0e1 100644
--- a/docker-compose.yaml
+++ b/docker-compose.yaml
@@ -14,6 +14,8 @@ services:
# Mount the frontend directory
- ./frontend/src:/usr/share/nginx/html
orchestrator:
+ env_file:
+ - .env
build:
# Use the current directory as the build context
# This allows us to access the files in the current directory inside the Dockerfile
@@ -36,6 +38,8 @@ services:
# Mount the orchestrator/src directory in the current directory to the /app/orchestrator/src directory in the container
- ./orchestrator/src:/app/orchestrator/src
fraud_detection:
+ env_file:
+ - .env
build:
# Use the current directory as the build context
# This allows us to access the files in the current directory inside the Dockerfile
@@ -56,4 +60,35 @@ services:
# Mount the utils directory in the current directory to the /app/utils directory in the container
- ./utils:/app/utils
# Mount the fraud_detection/src directory in the current directory to the /app/fraud_detection/src directory in the container
- - ./fraud_detection/src:/app/fraud_detection/src
\ No newline at end of file
+ - ./fraud_detection/src:/app/fraud_detection/src
+ suggestions:
+ build:
+ context: ./
+ dockerfile: ./suggestions/Dockerfile
+ env_file:
+ - .env
+ ports:
+ - 50053:50053
+ environment:
+ # Pass the environment variables to the container
+ # The PYTHONUNBUFFERED environment variable ensures that the output from the application is logged to the console
+ - PYTHONUNBUFFERED=TRUE
+ # The PYTHONFILE environment variable specifies the absolute entry point of the application
+ - PYTHONFILE=/app/suggestions/src/app.py
+ volumes:
+ # Mount the utils directory in the current directory to the /app/utils directory in the container
+ - ./utils:/app/utils
+ - ./suggestions/src:/app/suggestions/src
+
+ postgres:
+ build:
+ context: .
+ dockerfile: ./db/Dockerfile
+ image: bookstore_postgres
+ container_name: bookstore_postgres
+ volumes:
+ - ./postgres_data:/var/lib/postgresql/data
+ env_file:
+ - .env
+ ports:
+ - "5432:5432"
diff --git a/fraud_detection/requirements.txt b/fraud_detection/requirements.txt
index a80eedef7..44d63261e 100644
--- a/fraud_detection/requirements.txt
+++ b/fraud_detection/requirements.txt
@@ -1,4 +1,4 @@
-grpcio==1.60.0
-grpcio-tools==1.60.0
-protobuf==4.25.2
+grpcio==1.70.0
+grpcio-tools==1.70.0
+protobuf==5.29.3
watchdog==6.0.0
diff --git a/fraud_detection/src/app.py b/fraud_detection/src/app.py
index b2f1d2fce..fbda956ab 100644
--- a/fraud_detection/src/app.py
+++ b/fraud_detection/src/app.py
@@ -1,11 +1,14 @@
+import logging
import sys
import os
# This set of lines are needed to import the gRPC stubs.
# The path of the stubs is relative to the current file, or absolute inside the container.
# Change these lines only if strictly needed.
-FILE = __file__ if '__file__' in globals() else os.getenv("PYTHONFILE", "")
-fraud_detection_grpc_path = os.path.abspath(os.path.join(FILE, '../../../utils/pb/fraud_detection'))
+FILE = __file__ if "__file__" in globals() else os.getenv("PYTHONFILE", "")
+fraud_detection_grpc_path = os.path.abspath(
+ os.path.join(FILE, "../../../utils/pb/fraud_detection")
+)
sys.path.insert(0, fraud_detection_grpc_path)
import fraud_detection_pb2 as fraud_detection
import fraud_detection_pb2_grpc as fraud_detection_grpc
@@ -13,6 +16,7 @@
import grpc
from concurrent import futures
+
# Create a class to define the server functions, derived from
# fraud_detection_pb2_grpc.HelloServiceServicer
class HelloService(fraud_detection_grpc.HelloServiceServicer):
@@ -24,9 +28,11 @@ def SayHello(self, request, context):
response.greeting = "Hello, " + request.name
# Print the greeting message
print(response.greeting)
+ logging.info("response: ", response.greeting)
# Return the response object
return response
+
def serve():
# Create a gRPC server
server = grpc.server(futures.ThreadPoolExecutor())
@@ -41,5 +47,7 @@ def serve():
# Keep thread alive
server.wait_for_termination()
-if __name__ == '__main__':
- serve()
\ No newline at end of file
+
+if __name__ == "__main__":
+ serve()
+
diff --git a/frontend/src/index.html b/frontend/src/index.html
index 3f374e5f9..3309222d8 100644
--- a/frontend/src/index.html
+++ b/frontend/src/index.html
@@ -13,6 +13,10 @@ Checkout Page
Items
+
@@ -73,22 +77,60 @@
Items