commit fcc1fc7b0bbff258569d79e0e43cbd1285dc1568 Author: mukeshs Date: Tue Oct 14 16:50:42 2025 +0530 first commit diff --git a/Machine-Backend b/Machine-Backend new file mode 160000 index 0000000..07b92e3 --- /dev/null +++ b/Machine-Backend @@ -0,0 +1 @@ +Subproject commit 07b92e34c63c2e3317a53870aa543c63cff413bf diff --git a/Project/Dockerfile b/Project/Dockerfile new file mode 100644 index 0000000..e69de29 diff --git a/Project/microservice.py b/Project/microservice.py new file mode 100644 index 0000000..eaba3bc --- /dev/null +++ b/Project/microservice.py @@ -0,0 +1,533 @@ +from flask import Flask, request, jsonify, Response +import requests +import os +from flask_cors import CORS + +# Initialize Flask app +app = Flask(__name__) + +# Enhanced CORS configuration +CORS(app, + resources={r"/*": {"origins": "*"}}, + supports_credentials=True, + allow_headers=["Content-Type", "Authorization", "Access-Control-Allow-Credentials"], + methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"] +) + +# Configuration - point to your main backend +MAIN_BACKEND_URL = "http://127.0.0.1:5000" # Change this to your main backend URL + +# Add OPTIONS handler for preflight requests +@app.before_request +def handle_preflight(): + if request.method == "OPTIONS": + response = Response() + response.headers.add("Access-Control-Allow-Origin", "*") + response.headers.add('Access-Control-Allow-Headers', "*") + response.headers.add('Access-Control-Allow-Methods', "*") + return response + +# Routes +@app.route('/login', methods=['POST']) +def login(): + """Handle machine login""" + try: + data = request.json + print(f"Login request received: {data}") + + # Forward the login request to the main backend + response = requests.post(f"{MAIN_BACKEND_URL}/login", json=data) + print(f"Login response status: {response.status_code}") + + # Pass through the response + return Response( + response.content, + status=response.status_code, + content_type=response.headers.get('Content-Type', 'application/json') + ) + except Exception as e: + print(f"Login error: {e}") + return jsonify({"error": "Login service unavailable"}), 500 + +@app.route('/uploads/', methods=['GET']) +def get_image(filename): + """Forward image requests to the main backend""" + try: + response = requests.get(f"{MAIN_BACKEND_URL}/uploads/{filename}", stream=True) + + return Response( + response.content, + status=response.status_code, + content_type=response.headers.get('Content-Type', 'image/jpeg') + ) + except Exception as e: + print(f"Image fetch error: {e}") + return jsonify({"error": "Image not found"}), 404 + +@app.route('/machine-slots/', methods=['GET']) +def get_machine_slots(machine_id): + """Get slots for a specific machine""" + try: + print(f"Fetching slots for machine: {machine_id}") + # Simply forward the request to the main backend + response = requests.get(f"{MAIN_BACKEND_URL}/machine-slots/{machine_id}") + print(f"Slots response status: {response.status_code}") + + # Pass through the response + return Response( + response.content, + status=response.status_code, + content_type=response.headers.get('Content-Type', 'application/json') + ) + except Exception as e: + print(f"Machine slots error: {e}") + return jsonify({"error": "Could not fetch machine slots"}), 500 + +@app.route('/machine-slots/update-inventory', methods=['POST']) +def update_inventory(): + """Forward inventory update request to main backend""" + try: + print("=== Inventory Update Request ===") + data = request.json + + if not data: + return jsonify({"error": "No data received"}), 400 + + machine_id = data.get('machineId') + updates = data.get('inventoryUpdates', []) + + print(f"Machine ID: {machine_id}") + print(f"Inventory updates: {len(updates)}") + + response = requests.post( + f"{MAIN_BACKEND_URL}/machine-slots/update-inventory", + json=data, + headers={'Content-Type': 'application/json'} + ) + + print(f"Inventory update response: {response.status_code}") + + return Response( + response.content, + status=response.status_code, + content_type=response.headers.get('Content-Type', 'application/json') + ) + + except Exception as e: + print(f"Inventory update error: {e}") + return jsonify({"error": "Could not update inventory"}), 500 + +@app.route('/vending-state/', methods=['GET']) +def get_vending_state(machine_id): + """Get the vending state for a machine""" + try: + # Forward the request to the main backend + response = requests.get(f"{MAIN_BACKEND_URL}/machines/{machine_id}") + + # Pass through the response + return Response( + response.content, + status=response.status_code, + content_type=response.headers.get('Content-Type', 'application/json') + ) + except Exception as e: + print(f"Vending state error: {e}") + return jsonify({"error": "Could not fetch vending state"}), 500 + +@app.route('/create-payu-order', methods=['POST', 'OPTIONS']) +def create_payu_order(): + """Create PayU payment order - forward to main backend""" + try: + print("=== PayU Order Creation Request ===") + data = request.json + print(f"Request data: {data}") + + if not data: + print("No data received in request") + return jsonify({"error": "No data received"}), 400 + + # Forward to main backend + print(f"Forwarding to: {MAIN_BACKEND_URL}/create-payu-order") + response = requests.post( + f"{MAIN_BACKEND_URL}/create-payu-order", + json=data, + headers={'Content-Type': 'application/json'} + ) + + print(f"Main backend response status: {response.status_code}") + print(f"Main backend response: {response.text}") + + return Response( + response.content, + status=response.status_code, + content_type=response.headers.get('Content-Type', 'application/json') + ) + + except requests.exceptions.ConnectionError as e: + print(f"Connection error to main backend: {e}") + return jsonify({"error": "Payment service unavailable"}), 503 + except Exception as e: + print(f"PayU order creation error: {e}") + return jsonify({"error": "Could not create payment order"}), 500 + +@app.route('/vending-state/', methods=['PUT']) +def update_vending_state(machine_id): + """Update the vending state for a machine""" + try: + # Forward the request to the main backend + headers = {k: v for k, v in request.headers.items() if k not in ['Host', 'Content-Length']} + response = requests.put( + f"{MAIN_BACKEND_URL}/machines/{machine_id}/vending-state", + json=request.json, + headers=headers + ) + + # Pass through the response + return Response( + response.content, + status=response.status_code, + content_type=response.headers.get('Content-Type', 'application/json') + ) + except Exception as e: + print(f"Update vending state error: {e}") + return jsonify({"error": "Could not update vending state"}), 500 + +# NEW: Serial Communication Routes + +@app.route('/machine/connect', methods=['POST']) +def connect_machine(): + """Forward machine connection request to main backend""" + try: + print("=== Machine Connection Request ===") + data = request.json or {} + print(f"Connection data: {data}") + + response = requests.post( + f"{MAIN_BACKEND_URL}/machine/connect", + json=data, + headers={'Content-Type': 'application/json'} + ) + + print(f"Connection response: {response.status_code}") + + return Response( + response.content, + status=response.status_code, + content_type=response.headers.get('Content-Type', 'application/json') + ) + + except Exception as e: + print(f"Machine connection error: {e}") + return jsonify({"error": "Could not connect to machine"}), 500 + +@app.route('/machine/status', methods=['GET']) +def get_machine_status(): + """Forward machine status request to main backend""" + try: + print("=== Machine Status Request ===") + + response = requests.get(f"{MAIN_BACKEND_URL}/machine/status") + + print(f"Status response: {response.status_code}") + + return Response( + response.content, + status=response.status_code, + content_type=response.headers.get('Content-Type', 'application/json') + ) + + except Exception as e: + print(f"Machine status error: {e}") + return jsonify({"error": "Could not get machine status"}), 500 + +@app.route('/machine/dispense', methods=['POST']) +def dispense_product(): + """Forward single product dispensing to main backend""" + try: + print("=== Single Product Dispensing Request ===") + data = request.json + + if not data: + return jsonify({"error": "No data received"}), 400 + + print(f"Dispense data: {data}") + + response = requests.post( + f"{MAIN_BACKEND_URL}/machine/dispense", + json=data, + headers={'Content-Type': 'application/json'} + ) + + print(f"Dispense response: {response.status_code}") + print(f"Dispense result: {response.text}") + + return Response( + response.content, + status=response.status_code, + content_type=response.headers.get('Content-Type', 'application/json') + ) + + except Exception as e: + print(f"Product dispensing error: {e}") + return jsonify({"error": "Could not dispense product"}), 500 + +@app.route('/machine/dispense-multiple', methods=['POST']) +def dispense_multiple_products(): + """Forward multiple product dispensing to main backend""" + try: + print("=== Multiple Product Dispensing Request ===") + data = request.json + + if not data: + return jsonify({"error": "No data received"}), 400 + + machine_id = data.get('machineId') + items = data.get('items', []) + + print(f"Machine ID: {machine_id}") + print(f"Items to dispense: {len(items)}") + for item in items: + print(f" - Slot {item.get('slotId')}: {item.get('quantity')}x {item.get('productName')}") + + response = requests.post( + f"{MAIN_BACKEND_URL}/machine/dispense-multiple", + json=data, + headers={'Content-Type': 'application/json'} + ) + + print(f"Multiple dispense response: {response.status_code}") + + return Response( + response.content, + status=response.status_code, + content_type=response.headers.get('Content-Type', 'application/json') + ) + + except Exception as e: + print(f"Multiple product dispensing error: {e}") + return jsonify({"error": "Could not dispense multiple products"}), 500 + +@app.route('/machine/reset', methods=['POST']) +def reset_machine(): + """Forward machine reset request to main backend""" + try: + print("=== Machine Reset Request ===") + + response = requests.post(f"{MAIN_BACKEND_URL}/machine/reset") + + print(f"Reset response: {response.status_code}") + + return Response( + response.content, + status=response.status_code, + content_type=response.headers.get('Content-Type', 'application/json') + ) + + except Exception as e: + print(f"Machine reset error: {e}") + return jsonify({"error": "Could not reset machine"}), 500 + +@app.route('/machine/disconnect', methods=['POST']) +def disconnect_machine(): + """Forward machine disconnect request to main backend""" + try: + print("=== Machine Disconnect Request ===") + + response = requests.post(f"{MAIN_BACKEND_URL}/machine/disconnect") + + print(f"Disconnect response: {response.status_code}") + + return Response( + response.content, + status=response.status_code, + content_type=response.headers.get('Content-Type', 'application/json') + ) + + except Exception as e: + print(f"Machine disconnect error: {e}") + return jsonify({"error": "Could not disconnect machine"}), 500 + +@app.route('/payment-confirmed', methods=['POST']) +def payment_confirmed(): + """Forward payment confirmation to main backend for dispensing""" + try: + print("=== Payment Confirmation Request ===") + data = request.json + + if not data: + return jsonify({"error": "No data received"}), 400 + + machine_id = data.get('machineId') + cart_items = data.get('cartItems', []) + + print(f"Payment confirmed for machine: {machine_id}") + print(f"Cart items: {len(cart_items)}") + + response = requests.post( + f"{MAIN_BACKEND_URL}/payment-confirmed", + json=data, + headers={'Content-Type': 'application/json'} + ) + + print(f"Payment confirmation response: {response.status_code}") + + return Response( + response.content, + status=response.status_code, + content_type=response.headers.get('Content-Type', 'application/json') + ) + + except Exception as e: + print(f"Payment confirmation error: {e}") + return jsonify({"error": "Could not process payment confirmation"}), 500 + +@app.route('/transactions/bulk-create', methods=['POST']) +def bulk_create_transactions(): + """Forward bulk transaction creation to main backend""" + try: + print("=== Bulk Transaction Creation Request ===") + data = request.json + + if not data: + return jsonify({"error": "No data received"}), 400 + + transactions = data.get('transactions', []) + print(f"Creating {len(transactions)} transactions") + + for trans in transactions: + print(f" - {trans.get('product_name')} x {trans.get('quantity')} = ₹{trans.get('amount')}") + + response = requests.post( + f"{MAIN_BACKEND_URL}/transactions/bulk-create", + json=data, + headers={'Content-Type': 'application/json'} + ) + + print(f"Bulk transaction response: {response.status_code}") + + return Response( + response.content, + status=response.status_code, + content_type=response.headers.get('Content-Type', 'application/json') + ) + + except Exception as e: + print(f"Bulk transaction creation error: {e}") + return jsonify({"error": "Could not create transactions"}), 500 + +# LEGACY: Keep existing dispense-products route for backward compatibility +@app.route('/dispense-products', methods=['POST']) +def dispense_products(): + """Legacy route - redirect to new dispensing system""" + print("=== Legacy Dispense Products Request ===") + print("Redirecting to new dispensing system...") + + # Get dispensing instructions from the request + data = request.json + if not data: + print("No JSON data received") + return jsonify({"error": "No data received"}), 400 + + machine_id = data.get('machineId') + dispensing_instructions = data.get('dispensingInstructions', []) + + if not machine_id or not dispensing_instructions: + print("Missing machine ID or dispensing instructions") + return jsonify({"error": "Missing machine ID or dispensing instructions"}), 400 + + # Convert to new format and forward to new dispensing endpoint + try: + new_format = { + 'machineId': machine_id, + 'items': [ + { + 'slotId': instruction.get('slotId'), + 'quantity': instruction.get('quantity', 1), + 'productName': instruction.get('productName', 'Unknown') + } + for instruction in dispensing_instructions + ] + } + + return dispense_multiple_products_internal(new_format) + + except Exception as e: + print(f"Legacy conversion error: {e}") + return jsonify({"error": "Could not process legacy request"}), 500 + +def dispense_multiple_products_internal(data): + """Internal function to handle dispensing without duplicate logging""" + try: + response = requests.post( + f"{MAIN_BACKEND_URL}/machine/dispense-multiple", + json=data, + headers={'Content-Type': 'application/json'} + ) + + return Response( + response.content, + status=response.status_code, + content_type=response.headers.get('Content-Type', 'application/json') + ) + + except Exception as e: + return jsonify({"error": "Could not dispense products"}), 500 + +@app.route('/payment-success', methods=['POST', 'GET']) +def payment_success(): + data = request.form.to_dict() or request.args.to_dict() + print("✅ Payment Success:", data) + return jsonify({ + "success": True, + "message": "Payment successful", + "data": data + }), 200 + +@app.route('/payment-failure', methods=['POST', 'GET']) +def payment_failure(): + data = request.form.to_dict() or request.args.to_dict() + print("❌ Payment Failure:", data) + return jsonify({ + "success": False, + "message": "Payment failed", + "data": data + }), 200 + + + +# Add a test route to verify the server is running +@app.route('/', methods=['GET']) +def test_route(): + return jsonify({"status": "Flask server is running"}), 200 + +# Add error handlers +@app.errorhandler(404) +def not_found(error): + return jsonify({"error": "Endpoint not found"}), 404 + +@app.errorhandler(500) +def internal_error(error): + return jsonify({"error": "Internal server error"}), 500 + +if __name__ == '__main__': + # Print startup message + print("Starting Flask server on port 5001...") + print("Available routes:") + print(" - /") + print(" - /login [POST]") + print(" - /uploads/ [GET]") + print(" - /machine-slots/ [GET]") + print(" - /vending-state/ [GET/PUT]") + print(" - /create-payu-order [POST]") + print(" - /dispense-products [POST] (legacy)") + print(" - /machine/connect [POST]") + print(" - /machine/status [GET]") + print(" - /machine/dispense [POST]") + print(" - /machine/dispense-multiple [POST]") + print(" - /machine/reset [POST]") + print(" - /machine/disconnect [POST]") + print(" - /payment-confirmed [POST]") + print(f"Main backend URL: {MAIN_BACKEND_URL}") + + # Run the server + app.run(host='0.0.0.0', debug=True, port=5001) \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..dd5400d --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,43 @@ +version: "3.9" + +services: + frontend: + build: + context: ./fuse-starter-2000 + container_name: vending-frontend + ports: + - "8086:80" + depends_on: + - backend + restart: always + + backend: + build: + context: ./Machine-Backend + container_name: vending-backend + env_file: + - ./Machine-Backend/.env + ports: + - "8087:5000" + volumes: + - ./Machine-Backend:/app + depends_on: + - db + restart: always + + db: + image: mysql:8.0 + container_name: vending-db + restart: always + environment: + MYSQL_ROOT_PASSWORD: rootpass + MYSQL_DATABASE: vending + MYSQL_USER: vendinguser + MYSQL_PASSWORD: vendingpass + ports: + - "3306:3306" + volumes: + - mysql_data:/var/lib/mysql + +volumes: + mysql_data: diff --git a/fuse-starter-v20.0.0 b/fuse-starter-v20.0.0 new file mode 160000 index 0000000..02b9fb4 --- /dev/null +++ b/fuse-starter-v20.0.0 @@ -0,0 +1 @@ +Subproject commit 02b9fb4ef3d9c2bc8dedc78617bad62632b31e92 diff --git a/machine-operations b/machine-operations new file mode 160000 index 0000000..8492386 --- /dev/null +++ b/machine-operations @@ -0,0 +1 @@ +Subproject commit 8492386a976454f6b0fab0b85f1c7feaec1d0f54