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