From cffc69bee9e2f3717b0ae1822585078d21ed6135 Mon Sep 17 00:00:00 2001 From: oskarasd123 Date: Fri, 20 Feb 2026 13:36:34 +0200 Subject: [PATCH 01/23] fraud detection grpc is working --- .gitignore | 3 +- fraud_detection/requirements.txt | 6 +- fraud_detection/src/app.py | 16 ++-- orchestrator/requirements.txt | 8 +- orchestrator/src/app.py | 21 +++--- .../pb/fraud_detection/fraud_detection.proto | 15 ++-- .../pb/fraud_detection/fraud_detection_pb2.py | 30 +++++--- .../fraud_detection/fraud_detection_pb2.pyi | 22 +++--- .../fraud_detection_pb2_grpc.py | 73 +++++++++++++------ 9 files changed, 121 insertions(+), 73 deletions(-) diff --git a/.gitignore b/.gitignore index ed8ebf583..01d7f95b0 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -__pycache__ \ No newline at end of file +__pycache__ +venv \ No newline at end of file diff --git a/fraud_detection/requirements.txt b/fraud_detection/requirements.txt index a80eedef7..3fe94e780 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.6 watchdog==6.0.0 diff --git a/fraud_detection/src/app.py b/fraud_detection/src/app.py index b2f1d2fce..165605bb1 100644 --- a/fraud_detection/src/app.py +++ b/fraud_detection/src/app.py @@ -15,23 +15,23 @@ # Create a class to define the server functions, derived from # fraud_detection_pb2_grpc.HelloServiceServicer -class HelloService(fraud_detection_grpc.HelloServiceServicer): +class FraudDetectionService(fraud_detection_grpc.FraudDetectionService): # Create an RPC function to say hello - def SayHello(self, request, context): + def checkFraud(self, request, context): # Create a HelloResponse object - response = fraud_detection.HelloResponse() + response = fraud_detection.FraudResponse() # Set the greeting field of the response object - response.greeting = "Hello, " + request.name - # Print the greeting message - print(response.greeting) - # Return the response object + is_fraud = False + if "999" in request.card_nr or request.order_ammount > 1000: + is_fraud = True + response.is_fraud = is_fraud return response def serve(): # Create a gRPC server server = grpc.server(futures.ThreadPoolExecutor()) # Add HelloService - fraud_detection_grpc.add_HelloServiceServicer_to_server(HelloService(), server) + fraud_detection_grpc.add_FraudDetectionServiceServicer_to_server(FraudDetectionService(), server) # Listen on port 50051 port = "50051" server.add_insecure_port("[::]:" + port) diff --git a/orchestrator/requirements.txt b/orchestrator/requirements.txt index 5ba8e254b..23d986436 100644 --- a/orchestrator/requirements.txt +++ b/orchestrator/requirements.txt @@ -1,12 +1,12 @@ blinker==1.7.0 click==8.1.7 Flask==3.0.0 -grpcio==1.60.0 -grpcio-tools==1.60.0 itsdangerous==2.1.2 Jinja2==3.1.3 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 +grpcio==1.70.0 +grpcio-tools==1.70.0 +protobuf==5.29.6 +watchdog==6.0.0 diff --git a/orchestrator/src/app.py b/orchestrator/src/app.py index 62d5d0662..0a86260f8 100644 --- a/orchestrator/src/app.py +++ b/orchestrator/src/app.py @@ -12,14 +12,14 @@ import grpc -def greet(name='you'): +def detect_fraud(card_nr, order_ammount): # Establish a connection with the fraud-detection gRPC service. with grpc.insecure_channel('fraud_detection:50051') as channel: # Create a stub object. - stub = fraud_detection_grpc.HelloServiceStub(channel) + stub = fraud_detection_grpc.FraudDetectionServiceStub(channel) # Call the service through the stub object. - response = stub.SayHello(fraud_detection.HelloRequest(name=name)) - return response.greeting + response = stub.checkFraud(fraud_detection.FraudRequest(card_nr=card_nr, order_ammount=order_ammount)) + return response.is_fraud # Import Flask. # Flask is a web framework for Python. @@ -40,10 +40,8 @@ def index(): """ Responds with 'Hello, [name]' when a GET request is made to '/' endpoint. """ - # Test the fraud-detection gRPC service. - response = greet(name='orchestrator') # Return the response. - return response + return "hello orchestrator" @app.route('/checkout', methods=['POST']) def checkout(): @@ -53,12 +51,17 @@ def checkout(): # Get request object data to json request_data = json.loads(request.data) # Print request object data - print("Request Data:", request_data.get('items')) + print("Request Data:", request_data) + + is_fraud = detect_fraud(request_data["creditCard"]["number"], sum([item["quantity"] for item in request_data["items"]])) + + + # Dummy response following the provided YAML specification for the bookstore order_status_response = { 'orderId': '12345', - 'status': 'Order Approved', + 'status': ('odred declined' if is_fraud else 'Order Approved'), 'suggestedBooks': [ {'bookId': '123', 'title': 'The Best Book', 'author': 'Author 1'}, {'bookId': '456', 'title': 'The Second Best Book', 'author': 'Author 2'} diff --git a/utils/pb/fraud_detection/fraud_detection.proto b/utils/pb/fraud_detection/fraud_detection.proto index db20211d7..e7465c65e 100644 --- a/utils/pb/fraud_detection/fraud_detection.proto +++ b/utils/pb/fraud_detection/fraud_detection.proto @@ -1,15 +1,16 @@ syntax = "proto3"; -package hello; +package fraud_detection; -service HelloService { - rpc SayHello (HelloRequest) returns (HelloResponse); +service FraudDetectionService { + rpc checkFraud (FraudRequest) returns (FraudResponse); } -message HelloRequest { - string name = 1; +message FraudRequest { + string card_nr = 1; + float order_ammount = 2; } -message HelloResponse { - string greeting = 1; +message FraudResponse { + bool is_fraud = 1; } diff --git a/utils/pb/fraud_detection/fraud_detection_pb2.py b/utils/pb/fraud_detection/fraud_detection_pb2.py index cdd0bcae8..53c4c2195 100644 --- a/utils/pb/fraud_detection/fraud_detection_pb2.py +++ b/utils/pb/fraud_detection/fraud_detection_pb2.py @@ -1,12 +1,22 @@ # -*- coding: utf-8 -*- # Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE # source: fraud_detection.proto -# Protobuf Python Version: 4.25.0 +# Protobuf Python Version: 5.29.0 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 5, + 29, + 0, + '', + 'fraud_detection.proto' +) # @@protoc_insertion_point(imports) _sym_db = _symbol_database.Default() @@ -14,17 +24,17 @@ -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x15\x66raud_detection.proto\x12\x05hello\"\x1c\n\x0cHelloRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\"!\n\rHelloResponse\x12\x10\n\x08greeting\x18\x01 \x01(\t2E\n\x0cHelloService\x12\x35\n\x08SayHello\x12\x13.hello.HelloRequest\x1a\x14.hello.HelloResponseb\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x15\x66raud_detection.proto\x12\x05hello\"6\n\x0c\x46raudRequest\x12\x0f\n\x07\x63\x61rd_nr\x18\x01 \x01(\t\x12\x15\n\rorder_ammount\x18\x02 \x01(\x02\"!\n\rFraudResponse\x12\x10\n\x08is_fraud\x18\x01 \x01(\x08\x32P\n\x15\x46raudDetectionService\x12\x37\n\ncheckFraud\x12\x13.hello.FraudRequest\x1a\x14.hello.FraudResponseb\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'fraud_detection_pb2', _globals) -if _descriptor._USE_C_DESCRIPTORS == False: - DESCRIPTOR._options = None - _globals['_HELLOREQUEST']._serialized_start=32 - _globals['_HELLOREQUEST']._serialized_end=60 - _globals['_HELLORESPONSE']._serialized_start=62 - _globals['_HELLORESPONSE']._serialized_end=95 - _globals['_HELLOSERVICE']._serialized_start=97 - _globals['_HELLOSERVICE']._serialized_end=166 +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_FRAUDREQUEST']._serialized_start=32 + _globals['_FRAUDREQUEST']._serialized_end=86 + _globals['_FRAUDRESPONSE']._serialized_start=88 + _globals['_FRAUDRESPONSE']._serialized_end=121 + _globals['_FRAUDDETECTIONSERVICE']._serialized_start=123 + _globals['_FRAUDDETECTIONSERVICE']._serialized_end=203 # @@protoc_insertion_point(module_scope) diff --git a/utils/pb/fraud_detection/fraud_detection_pb2.pyi b/utils/pb/fraud_detection/fraud_detection_pb2.pyi index 30a263856..bd74d0e02 100644 --- a/utils/pb/fraud_detection/fraud_detection_pb2.pyi +++ b/utils/pb/fraud_detection/fraud_detection_pb2.pyi @@ -4,14 +4,16 @@ from typing import ClassVar as _ClassVar, Optional as _Optional DESCRIPTOR: _descriptor.FileDescriptor -class HelloRequest(_message.Message): - __slots__ = ("name",) - NAME_FIELD_NUMBER: _ClassVar[int] - name: str - def __init__(self, name: _Optional[str] = ...) -> None: ... +class FraudRequest(_message.Message): + __slots__ = ("card_nr", "order_ammount") + CARD_NR_FIELD_NUMBER: _ClassVar[int] + ORDER_AMMOUNT_FIELD_NUMBER: _ClassVar[int] + card_nr: str + order_ammount: float + def __init__(self, card_nr: _Optional[str] = ..., order_ammount: _Optional[float] = ...) -> None: ... -class HelloResponse(_message.Message): - __slots__ = ("greeting",) - GREETING_FIELD_NUMBER: _ClassVar[int] - greeting: str - def __init__(self, greeting: _Optional[str] = ...) -> None: ... +class FraudResponse(_message.Message): + __slots__ = ("is_fraud",) + IS_FRAUD_FIELD_NUMBER: _ClassVar[int] + is_fraud: bool + def __init__(self, is_fraud: bool = ...) -> None: ... diff --git a/utils/pb/fraud_detection/fraud_detection_pb2_grpc.py b/utils/pb/fraud_detection/fraud_detection_pb2_grpc.py index 4e7a27975..13e994f0a 100644 --- a/utils/pb/fraud_detection/fraud_detection_pb2_grpc.py +++ b/utils/pb/fraud_detection/fraud_detection_pb2_grpc.py @@ -1,11 +1,31 @@ # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! """Client and server classes corresponding to protobuf-defined services.""" import grpc +import warnings import fraud_detection_pb2 as fraud__detection__pb2 +GRPC_GENERATED_VERSION = '1.70.0' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False -class HelloServiceStub(object): +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True + +if _version_not_supported: + raise RuntimeError( + f'The grpc package installed is at version {GRPC_VERSION},' + + f' but the generated code in fraud_detection_pb2_grpc.py depends on' + + f' grpcio>={GRPC_GENERATED_VERSION}.' + + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' + ) + + +class FraudDetectionServiceStub(object): """Missing associated documentation comment in .proto file.""" def __init__(self, channel): @@ -14,42 +34,43 @@ def __init__(self, channel): Args: channel: A grpc.Channel. """ - self.SayHello = channel.unary_unary( - '/hello.HelloService/SayHello', - request_serializer=fraud__detection__pb2.HelloRequest.SerializeToString, - response_deserializer=fraud__detection__pb2.HelloResponse.FromString, - ) + self.checkFraud = channel.unary_unary( + '/hello.FraudDetectionService/checkFraud', + request_serializer=fraud__detection__pb2.FraudRequest.SerializeToString, + response_deserializer=fraud__detection__pb2.FraudResponse.FromString, + _registered_method=True) -class HelloServiceServicer(object): +class FraudDetectionServiceServicer(object): """Missing associated documentation comment in .proto file.""" - def SayHello(self, request, context): + def checkFraud(self, request, context): """Missing associated documentation comment in .proto file.""" context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') -def add_HelloServiceServicer_to_server(servicer, server): +def add_FraudDetectionServiceServicer_to_server(servicer, server): rpc_method_handlers = { - 'SayHello': grpc.unary_unary_rpc_method_handler( - servicer.SayHello, - request_deserializer=fraud__detection__pb2.HelloRequest.FromString, - response_serializer=fraud__detection__pb2.HelloResponse.SerializeToString, + 'checkFraud': grpc.unary_unary_rpc_method_handler( + servicer.checkFraud, + request_deserializer=fraud__detection__pb2.FraudRequest.FromString, + response_serializer=fraud__detection__pb2.FraudResponse.SerializeToString, ), } generic_handler = grpc.method_handlers_generic_handler( - 'hello.HelloService', rpc_method_handlers) + 'hello.FraudDetectionService', rpc_method_handlers) server.add_generic_rpc_handlers((generic_handler,)) + server.add_registered_method_handlers('hello.FraudDetectionService', rpc_method_handlers) # This class is part of an EXPERIMENTAL API. -class HelloService(object): +class FraudDetectionService(object): """Missing associated documentation comment in .proto file.""" @staticmethod - def SayHello(request, + def checkFraud(request, target, options=(), channel_credentials=None, @@ -59,8 +80,18 @@ def SayHello(request, wait_for_ready=None, timeout=None, metadata=None): - return grpc.experimental.unary_unary(request, target, '/hello.HelloService/SayHello', - fraud__detection__pb2.HelloRequest.SerializeToString, - fraud__detection__pb2.HelloResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + return grpc.experimental.unary_unary( + request, + target, + '/hello.FraudDetectionService/checkFraud', + fraud__detection__pb2.FraudRequest.SerializeToString, + fraud__detection__pb2.FraudResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) From c6a222a2b807bee9122fecf96dc06d2b7c30fd27 Mon Sep 17 00:00:00 2001 From: gasper42 Date: Fri, 20 Feb 2026 13:57:23 +0200 Subject: [PATCH 02/23] second practical lession my own files - doesn't work because i haven't added the call to fraud detection in orchestration yet # Conflicts: # fraud_detection/src/app.py # orchestrator/requirements.txt # orchestrator/src/app.py # utils/pb/fraud_detection/fraud_detection.proto # utils/pb/fraud_detection/fraud_detection_pb2.py # utils/pb/fraud_detection/fraud_detection_pb2.pyi # utils/pb/fraud_detection/fraud_detection_pb2_grpc.py --- fraud_detection/requirements.txt | 1 + orchestrator/src/app.py | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/fraud_detection/requirements.txt b/fraud_detection/requirements.txt index 3fe94e780..22a808ed9 100644 --- a/fraud_detection/requirements.txt +++ b/fraud_detection/requirements.txt @@ -2,3 +2,4 @@ grpcio==1.70.0 grpcio-tools==1.70.0 protobuf==5.29.6 watchdog==6.0.0 + diff --git a/orchestrator/src/app.py b/orchestrator/src/app.py index 0a86260f8..1780b7470 100644 --- a/orchestrator/src/app.py +++ b/orchestrator/src/app.py @@ -40,8 +40,10 @@ def index(): """ Responds with 'Hello, [name]' when a GET request is made to '/' endpoint. """ + # Test the fraud-detection gRPC service. + response = greet(name='orchestrator') # Return the response. - return "hello orchestrator" + return response @app.route('/checkout', methods=['POST']) def checkout(): From 88bd897af6ed69e57d4e9d52a4a63384a93ad729 Mon Sep 17 00:00:00 2001 From: oskarasd123 Date: Fri, 27 Feb 2026 14:20:40 +0200 Subject: [PATCH 03/23] added basic transaction verification --- docker-compose.yaml | 21 +++- orchestrator/src/app.py | 21 +++- transaction_verification/Dockerfile | 15 +++ transaction_verification/requirements.txt | 4 + transaction_verification/src/app.py | 45 +++++++++ .../transaction_verification.proto | 18 ++++ .../transaction_verification_pb2.py | 40 ++++++++ .../transaction_verification_pb2.pyi | 23 +++++ .../transaction_verification_pb2_grpc.py | 97 +++++++++++++++++++ 9 files changed, 282 insertions(+), 2 deletions(-) create mode 100644 transaction_verification/Dockerfile create mode 100644 transaction_verification/requirements.txt create mode 100644 transaction_verification/src/app.py create mode 100644 utils/pb/transaction_verification/transaction_verification.proto create mode 100644 utils/pb/transaction_verification/transaction_verification_pb2.py create mode 100644 utils/pb/transaction_verification/transaction_verification_pb2.pyi create mode 100644 utils/pb/transaction_verification/transaction_verification_pb2_grpc.py diff --git a/docker-compose.yaml b/docker-compose.yaml index b4a60a537..347d4ccea 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -56,4 +56,23 @@ 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 + transaction_verification: + build: + # Use the current directory as the build context + # This allows us to access the files in the current directory inside the Dockerfile + context: ./ + # Use the Dockerfile in the fraud_detection directorys + dockerfile: ./transaction_verification/Dockerfile + ports: + - 50052:50052 + 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 + # Check app.py in the fraud_detection directory to see how this is used + - PYTHONFILE=/app/transaction_verification/src/app.py + volumes: + - ./utils:/app/utils + - ./transaction_verification/src:/app/transaction_verification/src \ No newline at end of file diff --git a/orchestrator/src/app.py b/orchestrator/src/app.py index 0a86260f8..abd285b1d 100644 --- a/orchestrator/src/app.py +++ b/orchestrator/src/app.py @@ -6,9 +6,13 @@ # 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')) +transaction_verification_grpc_path = os.path.abspath(os.path.join(FILE, '../../../utils/pb/transaction_verification')) sys.path.insert(0, fraud_detection_grpc_path) import fraud_detection_pb2 as fraud_detection import fraud_detection_pb2_grpc as fraud_detection_grpc +sys.path.insert(0, transaction_verification_grpc_path) +import transaction_verification_pb2 as transaction_verification +import transaction_verification_pb2_grpc as transaction_verification_grpc import grpc @@ -21,6 +25,17 @@ def detect_fraud(card_nr, order_ammount): response = stub.checkFraud(fraud_detection.FraudRequest(card_nr=card_nr, order_ammount=order_ammount)) return response.is_fraud +def verify_transaction(card_nr, order_id, money): + with grpc.insecure_channel('transaction_verification:50052') as channel: + # Create a stub object. + stub = transaction_verification_grpc.transactionServiceStub(channel) + # Call the service through the stub object. + if isinstance(order_id, bytes): + order_id = int.from_bytes(order_id) + response = stub.verifyTransaction(transaction_verification.PayRequest(card_nr=str(card_nr), order_id=order_id, money=money)) + if response.order_id != order_id: return False + return response.verified + # Import Flask. # Flask is a web framework for Python. # It allows you to build a web application quickly. @@ -53,8 +68,12 @@ def checkout(): # Print request object data print("Request Data:", request_data) - is_fraud = detect_fraud(request_data["creditCard"]["number"], sum([item["quantity"] for item in request_data["items"]])) + quantity = sum([item["quantity"] for item in request_data["items"]]) + + is_fraud = detect_fraud(request_data["creditCard"]["number"], quantity) + if not verify_transaction(request_data["creditCard"]["number"], os.urandom(4), quantity): + is_fraud = True diff --git a/transaction_verification/Dockerfile b/transaction_verification/Dockerfile new file mode 100644 index 000000000..2adbd4e7d --- /dev/null +++ b/transaction_verification/Dockerfile @@ -0,0 +1,15 @@ +# Use an official Python runtime as the base image +FROM python:3.11 + +# Set the working directory in the container +# Both the utils and src folders will be mounted as volumes, please see docker-compose.yaml +WORKDIR /app + +# Copy the requirements file to the working directory +COPY ./transaction_verification/requirements.txt . + +# Install the Python dependencies +RUN pip install --no-cache-dir -r requirements.txt + +# Set the command to run the application +CMD python utils/other/hotreload.py "transaction_verification/src/app.py" \ No newline at end of file diff --git a/transaction_verification/requirements.txt b/transaction_verification/requirements.txt new file mode 100644 index 000000000..3fe94e780 --- /dev/null +++ b/transaction_verification/requirements.txt @@ -0,0 +1,4 @@ +grpcio==1.70.0 +grpcio-tools==1.70.0 +protobuf==5.29.6 +watchdog==6.0.0 diff --git a/transaction_verification/src/app.py b/transaction_verification/src/app.py new file mode 100644 index 000000000..ec5058b95 --- /dev/null +++ b/transaction_verification/src/app.py @@ -0,0 +1,45 @@ +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", "") +transaction_verification_grpc_path = os.path.abspath(os.path.join(FILE, '../../../utils/pb/transaction_verification')) +sys.path.insert(0, transaction_verification_grpc_path) +import transaction_verification_pb2 as transaction_verification +import transaction_verification_pb2_grpc as transaction_verification_grpc + +import grpc +from concurrent import futures + +# Create a class to define the server functions, derived from +# fraud_detection_pb2_grpc.HelloServiceServicer +class TransactionVerificationService(transaction_verification_grpc.transactionServiceServicer): + def verifyTransaction(self, request, context): + print(request) + response = transaction_verification.PayResponse() + + response.order_id = request.order_id + verified = True + if request.money > int(request.card_nr)*0.001: # card doesn't have enough money + verified = False + response.verified = verified + print(response) + return response + +def serve(): + # Create a gRPC server + server = grpc.server(futures.ThreadPoolExecutor()) + transaction_verification_grpc.add_transactionServiceServicer_to_server(TransactionVerificationService(), server) + # Listen on port 50052 + port = "50052" + server.add_insecure_port("[::]:" + port) + # Start the server + server.start() + print("Server started. Listening on port 50052.") + # Keep thread alive + server.wait_for_termination() + +if __name__ == '__main__': + serve() \ No newline at end of file diff --git a/utils/pb/transaction_verification/transaction_verification.proto b/utils/pb/transaction_verification/transaction_verification.proto new file mode 100644 index 000000000..89224c8fa --- /dev/null +++ b/utils/pb/transaction_verification/transaction_verification.proto @@ -0,0 +1,18 @@ +syntax = "proto3"; + +package transaction_verification; + +service transactionService { + rpc verifyTransaction (PayRequest) returns (PayResponse); +} + +message PayRequest { + string card_nr = 1; + int64 order_id = 2; + float money = 3; +} + +message PayResponse { + bool verified = 1; + int64 order_id = 2; +} diff --git a/utils/pb/transaction_verification/transaction_verification_pb2.py b/utils/pb/transaction_verification/transaction_verification_pb2.py new file mode 100644 index 000000000..d99c3eca3 --- /dev/null +++ b/utils/pb/transaction_verification/transaction_verification_pb2.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: transaction_verification.proto +# Protobuf Python Version: 5.29.0 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 5, + 29, + 0, + '', + 'transaction_verification.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1etransaction_verification.proto\x12\x18transaction_verification\">\n\nPayRequest\x12\x0f\n\x07\x63\x61rd_nr\x18\x01 \x01(\t\x12\x10\n\x08order_id\x18\x02 \x01(\x03\x12\r\n\x05money\x18\x03 \x01(\x02\"1\n\x0bPayResponse\x12\x10\n\x08verified\x18\x01 \x01(\x08\x12\x10\n\x08order_id\x18\x02 \x01(\x03\x32v\n\x12transactionService\x12`\n\x11verifyTransaction\x12$.transaction_verification.PayRequest\x1a%.transaction_verification.PayResponseb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'transaction_verification_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_PAYREQUEST']._serialized_start=60 + _globals['_PAYREQUEST']._serialized_end=122 + _globals['_PAYRESPONSE']._serialized_start=124 + _globals['_PAYRESPONSE']._serialized_end=173 + _globals['_TRANSACTIONSERVICE']._serialized_start=175 + _globals['_TRANSACTIONSERVICE']._serialized_end=293 +# @@protoc_insertion_point(module_scope) diff --git a/utils/pb/transaction_verification/transaction_verification_pb2.pyi b/utils/pb/transaction_verification/transaction_verification_pb2.pyi new file mode 100644 index 000000000..141f7ad81 --- /dev/null +++ b/utils/pb/transaction_verification/transaction_verification_pb2.pyi @@ -0,0 +1,23 @@ +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from typing import ClassVar as _ClassVar, Optional as _Optional + +DESCRIPTOR: _descriptor.FileDescriptor + +class PayRequest(_message.Message): + __slots__ = ("card_nr", "order_id", "money") + CARD_NR_FIELD_NUMBER: _ClassVar[int] + ORDER_ID_FIELD_NUMBER: _ClassVar[int] + MONEY_FIELD_NUMBER: _ClassVar[int] + card_nr: str + order_id: int + money: float + def __init__(self, card_nr: _Optional[str] = ..., order_id: _Optional[int] = ..., money: _Optional[float] = ...) -> None: ... + +class PayResponse(_message.Message): + __slots__ = ("verified", "order_id") + VERIFIED_FIELD_NUMBER: _ClassVar[int] + ORDER_ID_FIELD_NUMBER: _ClassVar[int] + verified: bool + order_id: int + def __init__(self, verified: bool = ..., order_id: _Optional[int] = ...) -> None: ... diff --git a/utils/pb/transaction_verification/transaction_verification_pb2_grpc.py b/utils/pb/transaction_verification/transaction_verification_pb2_grpc.py new file mode 100644 index 000000000..351c984f2 --- /dev/null +++ b/utils/pb/transaction_verification/transaction_verification_pb2_grpc.py @@ -0,0 +1,97 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc +import warnings + +import transaction_verification_pb2 as transaction__verification__pb2 + +GRPC_GENERATED_VERSION = '1.70.0' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False + +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True + +if _version_not_supported: + raise RuntimeError( + f'The grpc package installed is at version {GRPC_VERSION},' + + f' but the generated code in transaction_verification_pb2_grpc.py depends on' + + f' grpcio>={GRPC_GENERATED_VERSION}.' + + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' + ) + + +class transactionServiceStub(object): + """Missing associated documentation comment in .proto file.""" + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.verifyTransaction = channel.unary_unary( + '/transaction_verification.transactionService/verifyTransaction', + request_serializer=transaction__verification__pb2.PayRequest.SerializeToString, + response_deserializer=transaction__verification__pb2.PayResponse.FromString, + _registered_method=True) + + +class transactionServiceServicer(object): + """Missing associated documentation comment in .proto file.""" + + def verifyTransaction(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_transactionServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'verifyTransaction': grpc.unary_unary_rpc_method_handler( + servicer.verifyTransaction, + request_deserializer=transaction__verification__pb2.PayRequest.FromString, + response_serializer=transaction__verification__pb2.PayResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'transaction_verification.transactionService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + server.add_registered_method_handlers('transaction_verification.transactionService', rpc_method_handlers) + + + # This class is part of an EXPERIMENTAL API. +class transactionService(object): + """Missing associated documentation comment in .proto file.""" + + @staticmethod + def verifyTransaction(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/transaction_verification.transactionService/verifyTransaction', + transaction__verification__pb2.PayRequest.SerializeToString, + transaction__verification__pb2.PayResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) From bf0afd0daf1914bd01f1ad5958e62cc3e76faa0b Mon Sep 17 00:00:00 2001 From: gasper42 Date: Sun, 1 Mar 2026 17:59:48 +0200 Subject: [PATCH 04/23] feat(suggestion service): env file with api key, moved environment stuff from docker compose, added .idea to gitignore, added init.py files to utils to act as python modules, added app.py for suggestion service --- .gitignore | 3 +- docker-compose.yaml | 17 +++- orchestrator/src/app.py | 22 ++++- suggestions/.env | 4 + suggestions/Dockerfile | 10 ++ suggestions/requirements.txt | 5 + suggestions/src/app.py | 54 +++++++++++ utils/__init__.py | 0 utils/pb/__init__.py | 0 utils/pb/suggestions/__init__.py | 0 utils/pb/suggestions/suggestions.proto | 15 +++ utils/pb/suggestions/suggestions_pb2.py | 40 ++++++++ utils/pb/suggestions/suggestions_pb2.pyi | 18 ++++ utils/pb/suggestions/suggestions_pb2_grpc.py | 97 ++++++++++++++++++++ 14 files changed, 281 insertions(+), 4 deletions(-) create mode 100644 suggestions/.env create mode 100644 suggestions/Dockerfile create mode 100644 suggestions/requirements.txt create mode 100644 suggestions/src/app.py create mode 100644 utils/__init__.py create mode 100644 utils/pb/__init__.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/.gitignore b/.gitignore index 01d7f95b0..3cbe8d298 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ __pycache__ -venv \ No newline at end of file +venv +.idea/ \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml index b4a60a537..cdc86f4e9 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,4 +1,3 @@ -version: '3' services: frontend: build: @@ -56,4 +55,18 @@ 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 + ports: + - 50053:50053 + env_file: + - suggestions/.env + volumes: + # 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 + - ./suggestions/src:/app/suggestions/src diff --git a/orchestrator/src/app.py b/orchestrator/src/app.py index 1780b7470..af250a03d 100644 --- a/orchestrator/src/app.py +++ b/orchestrator/src/app.py @@ -10,6 +10,15 @@ import fraud_detection_pb2 as fraud_detection import fraud_detection_pb2_grpc as fraud_detection_grpc +# 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", "") +suggestions_grpc_path = os.path.abspath(os.path.join(FILE, '../../../utils/pb/suggestions')) +sys.path.insert(0, suggestions_grpc_path) +import suggestions_pb2 as suggestions +import suggestions_pb2_grpc as suggestions_grpc + import grpc def detect_fraud(card_nr, order_ammount): @@ -21,6 +30,13 @@ def detect_fraud(card_nr, order_ammount): response = stub.checkFraud(fraud_detection.FraudRequest(card_nr=card_nr, order_ammount=order_ammount)) return response.is_fraud +def get_suggested_books(ordered_books): + with grpc.insecure_channel("suggestions:50053") as channel: + stub = suggestions_grpc.SuggestionsServiceStub(channel) + + response = stub.suggest(suggestions.SuggestRequest(ordered_books=ordered_books)) + return response.suggested_books + # Import Flask. # Flask is a web framework for Python. # It allows you to build a web application quickly. @@ -56,7 +72,10 @@ def checkout(): print("Request Data:", request_data) is_fraud = detect_fraud(request_data["creditCard"]["number"], sum([item["quantity"] for item in request_data["items"]])) - + # suggested_books = get_suggested_books([i["name"] for i in request_data["items"]]) + suggested_books = get_suggested_books(["Book A"]) + print([i["name"] for i in request_data["items"]]) + # print(suggested_books) @@ -64,6 +83,7 @@ def checkout(): order_status_response = { 'orderId': '12345', 'status': ('odred declined' if is_fraud else 'Order Approved'), + # 'suggestedBooks': suggested_books, 'suggestedBooks': [ {'bookId': '123', 'title': 'The Best Book', 'author': 'Author 1'}, {'bookId': '456', 'title': 'The Second Best Book', 'author': 'Author 2'} diff --git a/suggestions/.env b/suggestions/.env new file mode 100644 index 000000000..561d4976b --- /dev/null +++ b/suggestions/.env @@ -0,0 +1,4 @@ +api_key=c59ecdfb8fa34bab97dac34162e9a098 + +PYTHONUNBUFFERED=TRUE +PYTHONFILE=/app/suggestions/src/app.py \ No newline at end of file diff --git a/suggestions/Dockerfile b/suggestions/Dockerfile new file mode 100644 index 000000000..d68d58e88 --- /dev/null +++ b/suggestions/Dockerfile @@ -0,0 +1,10 @@ +FROM python:3.11 + +WORKDIR /app + +COPY ./suggestions/requirements.txt . + +RUN pip install --no-cache-dir -r requirements.txt + +# hot reload applications +CMD python utils/other/hotreload.py "suggestions/src/app.py" \ No newline at end of file diff --git a/suggestions/requirements.txt b/suggestions/requirements.txt new file mode 100644 index 000000000..22a808ed9 --- /dev/null +++ b/suggestions/requirements.txt @@ -0,0 +1,5 @@ +grpcio==1.70.0 +grpcio-tools==1.70.0 +protobuf==5.29.6 +watchdog==6.0.0 + diff --git a/suggestions/src/app.py b/suggestions/src/app.py new file mode 100644 index 000000000..37dc80b43 --- /dev/null +++ b/suggestions/src/app.py @@ -0,0 +1,54 @@ +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", "") +suggestions_grpc_path = os.path.abspath(os.path.join(FILE, '../../../utils/pb/suggestions')) +sys.path.insert(0, suggestions_grpc_path) +import suggestions_pb2 as suggestions +import suggestions_pb2_grpc as suggestions_grpc + +import grpc +from concurrent import futures + +# Create a class to define the server functions, derived from +# fraud_detection_pb2_grpc.HelloServiceServicer +class SuggestionsService(suggestions_grpc.SuggestionsService): + # Create an RPC function to say hello + def getSuggestions(self, request, context): + print(f"USER: ordered {request}") + + # Create a HelloResponse object + response = suggestions.SuggestResponse() + + response.suggested_books = ["book3", "book4", "book5"] + + # response.suggested_books = [ + # {'bookId': '123', 'title': 'The Best Book', 'author': 'Author 1'}, + # {'bookId': '456', 'title': 'The Second Best Book', 'author': 'Author 2'} + # ] + + return response + +def serve(): + # Create a gRPC server + server = grpc.server(futures.ThreadPoolExecutor()) + + # Add HelloService + suggestions_grpc.add_SuggestionsServiceServicer_to_server(SuggestionsService(), server) + + # Listen on port 50053 + port = "50053" + server.add_insecure_port("[::]:" + port) + + # Start the server + server.start() + print(f"Server started. Listening on port {port}.") + + # Keep thread alive + server.wait_for_termination() + +if __name__ == '__main__': + serve() \ No newline at end of file diff --git a/utils/__init__.py b/utils/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/utils/pb/__init__.py b/utils/pb/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/utils/pb/suggestions/__init__.py b/utils/pb/suggestions/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/utils/pb/suggestions/suggestions.proto b/utils/pb/suggestions/suggestions.proto new file mode 100644 index 000000000..842102e01 --- /dev/null +++ b/utils/pb/suggestions/suggestions.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; + +package suggestions; + +service SuggestionsService { + rpc suggest (SuggestRequest) returns (SuggestResponse); +} + +message SuggestRequest { + repeated string ordered_books = 1; +} + +message SuggestResponse { + repeated string suggested_books = 1; +} \ No newline at end of file diff --git a/utils/pb/suggestions/suggestions_pb2.py b/utils/pb/suggestions/suggestions_pb2.py new file mode 100644 index 000000000..03ca358b2 --- /dev/null +++ b/utils/pb/suggestions/suggestions_pb2.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: suggestions.proto +# Protobuf Python Version: 5.29.0 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 5, + 29, + 0, + '', + 'suggestions.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x11suggestions.proto\x12\x0bsuggestions\"\'\n\x0eSuggestRequest\x12\x15\n\rordered_books\x18\x01 \x03(\t\"*\n\x0fSuggestResponse\x12\x17\n\x0fsuggested_books\x18\x01 \x03(\t2Z\n\x12SuggestionsService\x12\x44\n\x07suggest\x12\x1b.suggestions.SuggestRequest\x1a\x1c.suggestions.SuggestResponseb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'suggestions_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_SUGGESTREQUEST']._serialized_start=34 + _globals['_SUGGESTREQUEST']._serialized_end=73 + _globals['_SUGGESTRESPONSE']._serialized_start=75 + _globals['_SUGGESTRESPONSE']._serialized_end=117 + _globals['_SUGGESTIONSSERVICE']._serialized_start=119 + _globals['_SUGGESTIONSSERVICE']._serialized_end=209 +# @@protoc_insertion_point(module_scope) diff --git a/utils/pb/suggestions/suggestions_pb2.pyi b/utils/pb/suggestions/suggestions_pb2.pyi new file mode 100644 index 000000000..80c47a23b --- /dev/null +++ b/utils/pb/suggestions/suggestions_pb2.pyi @@ -0,0 +1,18 @@ +from google.protobuf.internal import containers as _containers +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from typing import ClassVar as _ClassVar, Iterable as _Iterable, Optional as _Optional + +DESCRIPTOR: _descriptor.FileDescriptor + +class SuggestRequest(_message.Message): + __slots__ = ("ordered_books",) + ORDERED_BOOKS_FIELD_NUMBER: _ClassVar[int] + ordered_books: _containers.RepeatedScalarFieldContainer[str] + def __init__(self, ordered_books: _Optional[_Iterable[str]] = ...) -> None: ... + +class SuggestResponse(_message.Message): + __slots__ = ("suggested_books",) + SUGGESTED_BOOKS_FIELD_NUMBER: _ClassVar[int] + suggested_books: _containers.RepeatedScalarFieldContainer[str] + def __init__(self, suggested_books: _Optional[_Iterable[str]] = ...) -> None: ... diff --git a/utils/pb/suggestions/suggestions_pb2_grpc.py b/utils/pb/suggestions/suggestions_pb2_grpc.py new file mode 100644 index 000000000..b866e1b7e --- /dev/null +++ b/utils/pb/suggestions/suggestions_pb2_grpc.py @@ -0,0 +1,97 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc +import warnings + +import suggestions_pb2 as suggestions__pb2 + +GRPC_GENERATED_VERSION = '1.70.0' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False + +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True + +if _version_not_supported: + raise RuntimeError( + f'The grpc package installed is at version {GRPC_VERSION},' + + f' but the generated code in suggestions_pb2_grpc.py depends on' + + f' grpcio>={GRPC_GENERATED_VERSION}.' + + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' + ) + + +class SuggestionsServiceStub(object): + """Missing associated documentation comment in .proto file.""" + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.suggest = channel.unary_unary( + '/suggestions.SuggestionsService/suggest', + request_serializer=suggestions__pb2.SuggestRequest.SerializeToString, + response_deserializer=suggestions__pb2.SuggestResponse.FromString, + _registered_method=True) + + +class SuggestionsServiceServicer(object): + """Missing associated documentation comment in .proto file.""" + + def suggest(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_SuggestionsServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'suggest': grpc.unary_unary_rpc_method_handler( + servicer.suggest, + request_deserializer=suggestions__pb2.SuggestRequest.FromString, + response_serializer=suggestions__pb2.SuggestResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'suggestions.SuggestionsService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + server.add_registered_method_handlers('suggestions.SuggestionsService', rpc_method_handlers) + + + # This class is part of an EXPERIMENTAL API. +class SuggestionsService(object): + """Missing associated documentation comment in .proto file.""" + + @staticmethod + def suggest(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/suggestions.SuggestionsService/suggest', + suggestions__pb2.SuggestRequest.SerializeToString, + suggestions__pb2.SuggestResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) From 638792c69f3d9cbc5e239274b576496755c5fb37 Mon Sep 17 00:00:00 2001 From: gasper42 Date: Mon, 2 Mar 2026 15:10:36 +0200 Subject: [PATCH 05/23] feat(suggestion service): finished suggestion service with API call to exterenal esrvice added logging --- frontend/src/index.html | 8 ++- orchestrator/src/app.py | 31 ++++++---- suggestions/requirements.txt | 6 +- suggestions/src/BigBookAPI/__init__.py | 0 suggestions/src/BigBookAPI/book_script.py | 71 +++++++++++++++++++++++ suggestions/src/app.py | 33 +++++++++-- utils/pb/suggestions/suggestions.proto | 8 ++- utils/pb/suggestions/suggestions_pb2.py | 16 ++--- utils/pb/suggestions/suggestions_pb2.pyi | 16 ++++- 9 files changed, 159 insertions(+), 30 deletions(-) create mode 100644 suggestions/src/BigBookAPI/__init__.py create mode 100644 suggestions/src/BigBookAPI/book_script.py diff --git a/frontend/src/index.html b/frontend/src/index.html index 15c47351f..d729c1d59 100644 --- a/frontend/src/index.html +++ b/frontend/src/index.html @@ -66,6 +66,10 @@

Items

+ @@ -74,8 +78,8 @@

Items