SakibAhmed commited on
Commit
0668bbe
·
verified ·
1 Parent(s): 7ac866f

Upload 3 files

Browse files
Files changed (3) hide show
  1. Dockerfile +30 -0
  2. app.py +162 -0
  3. requirements.txt +3 -0
Dockerfile ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.9-slim
2
+
3
+ WORKDIR /app
4
+
5
+ # Install system dependencies
6
+ RUN apt-get update && apt-get install -y \
7
+ build-essential \
8
+ libpq-dev \
9
+ && rm -rf /var/lib/apt/lists/*
10
+
11
+ # Install Python packages
12
+ COPY requirements.txt .
13
+ RUN pip install --no-cache-dir -r requirements.txt
14
+
15
+ # Copy application code
16
+ COPY . .
17
+
18
+ # Create necessary files with proper permissions
19
+ RUN touch database.csv chat_history.csv && \
20
+ chmod 666 database.csv chat_history.csv
21
+
22
+ # Create a non-root user
23
+ RUN useradd -m -u 1000 user
24
+
25
+ # Give full permissions to the app directory
26
+ RUN chmod -R 777 /app
27
+
28
+ EXPOSE 5000
29
+
30
+ CMD ["gunicorn", "app:app", "--bind", "0.0.0.0:5000"]
app.py ADDED
@@ -0,0 +1,162 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, request, jsonify, render_template
2
+ from flask_cors import CORS # Import CORS
3
+ from dotenv import load_dotenv
4
+ import os
5
+ import logging
6
+ from datetime import datetime
7
+ import csv
8
+ import json # For logging complex items
9
+ from uuid import uuid4
10
+
11
+ load_dotenv()
12
+
13
+ app = Flask(__name__, template_folder='templates', static_folder='static')
14
+ CORS(app) # Enable CORS for all routes and all origins by default
15
+
16
+ # Configure logging for the mock API
17
+ MOCK_API_LOG_LEVEL = os.getenv("MOCK_API_LOG_LEVEL", "INFO").upper()
18
+ mock_numeric_level = getattr(logging, MOCK_API_LOG_LEVEL, logging.INFO)
19
+ logging.basicConfig(
20
+ level=mock_numeric_level,
21
+ format='%(asctime)s - MockOrderAPI - %(levelname)s - %(message)s'
22
+ )
23
+ mock_logger = logging.getLogger(__name__)
24
+ mock_logger.info(f"Mock Order API logging level set to: {MOCK_API_LOG_LEVEL}")
25
+
26
+
27
+ MOCK_API_SECRET_KEY = os.getenv('MOCK_API_FLASK_SECRET_KEY', 'a_different_secret_key_for_mock_api')
28
+ if MOCK_API_SECRET_KEY == 'a_different_secret_key_for_mock_api' and not os.getenv('MOCK_API_FLASK_SECRET_KEY'):
29
+ mock_logger.warning("Using default Flask secret key for Mock API. Set MOCK_API_FLASK_SECRET_KEY in .env if sessions are used.")
30
+ app.secret_key = MOCK_API_SECRET_KEY
31
+
32
+ ORDERS_CSV_FILE = 'received_orders.csv'
33
+ CSV_HEADER = ['timestamp', 'order_id', 'session_id', 'user_email', 'business_id', 'canonical_user_id', 'items_json_array']
34
+
35
+ def initialize_orders_csv():
36
+ if not os.path.exists(ORDERS_CSV_FILE):
37
+ try:
38
+ with open(ORDERS_CSV_FILE, 'w', newline='', encoding='utf-8') as f:
39
+ writer = csv.writer(f)
40
+ writer.writerow(CSV_HEADER)
41
+ mock_logger.info(f"Orders CSV file '{ORDERS_CSV_FILE}' created.")
42
+ except IOError as e:
43
+ mock_logger.error(f"Failed to create orders CSV file '{ORDERS_CSV_FILE}': {e}", exc_info=True)
44
+
45
+ @app.route('/')
46
+ def index():
47
+ """Serves the main dashboard page (index.html)."""
48
+ return render_template('index.html')
49
+
50
+ @app.route('/api/v1/orders/list', methods=['GET'])
51
+ def list_orders():
52
+ initialize_orders_csv()
53
+ orders = []
54
+ try:
55
+ if not os.path.exists(ORDERS_CSV_FILE):
56
+ mock_logger.warning(f"Attempted to list orders, but '{ORDERS_CSV_FILE}' does not exist.")
57
+ return jsonify({"status": "success", "orders": [], "message": "No orders file found."}), 200
58
+
59
+ with open(ORDERS_CSV_FILE, 'r', newline='', encoding='utf-8') as f:
60
+ reader = csv.DictReader(f)
61
+ for row_number, row in enumerate(reader, 1): # Start row_number from 1 for logging
62
+ # Basic check for expected number of columns based on header
63
+ if len(row) != len(CSV_HEADER):
64
+ mock_logger.warning(f"Row {row_number} in '{ORDERS_CSV_FILE}' has an incorrect number of columns. Expected {len(CSV_HEADER)}, got {len(row)}. Skipping row: {row}")
65
+ continue # Skip malformed rows
66
+
67
+ try:
68
+ items_str = row.get('items_json_array', '[]')
69
+ # Handle potential empty strings or malformed JSON before parsing
70
+ if not items_str or items_str.isspace():
71
+ row['items_json_array'] = []
72
+ else:
73
+ row['items_json_array'] = json.loads(items_str)
74
+
75
+ except json.JSONDecodeError:
76
+ mock_logger.warning(f"Could not parse items_json_array for order {row.get('order_id', 'N/A')} in row {row_number}: '{row.get('items_json_array', '')}'")
77
+ row['items_json_array'] = [] # Default to empty list on error
78
+ except TypeError: # Handles if items_json_array is None from DictReader
79
+ mock_logger.warning(f"items_json_array was None for order {row.get('order_id', 'N/A')} in row {row_number}. Defaulting to empty list.")
80
+ row['items_json_array'] = []
81
+
82
+ orders.append(row)
83
+
84
+ orders.sort(key=lambda x: x.get('timestamp', ''), reverse=True)
85
+ mock_logger.info(f"Successfully retrieved {len(orders)} orders from CSV.")
86
+ return jsonify({"status": "success", "orders": orders}), 200
87
+ except FileNotFoundError:
88
+ mock_logger.info(f"Orders CSV file '{ORDERS_CSV_FILE}' not found when listing orders.")
89
+ return jsonify({"status": "success", "orders": [], "message": "No orders recorded yet."}), 200
90
+ except Exception as e:
91
+ mock_logger.error(f"Error reading or parsing orders CSV: {e}", exc_info=True)
92
+ return jsonify({"status": "error", "message": "Failed to retrieve orders."}), 500
93
+
94
+
95
+ @app.route('/api/v1/orders/place', methods=['POST'])
96
+ def place_order():
97
+ initialize_orders_csv()
98
+ try:
99
+ order_data = request.json
100
+ if not order_data:
101
+ mock_logger.warning("Received empty payload for order placement.")
102
+ return jsonify({"status": "error", "message": "Empty payload."}), 400
103
+
104
+ user_info = order_data.get("user_info", {})
105
+ order_items = order_data.get("order_items", [])
106
+
107
+ if not isinstance(order_items, list) or not order_items:
108
+ mock_logger.warning(f"Invalid or empty order_items received: {order_items}")
109
+ return jsonify({"status": "error", "message": "Invalid or empty 'order_items' list is required."}), 400
110
+
111
+ mock_logger.info(f"--- ORDER RECEIVED by Mock Order API ---")
112
+ mock_logger.info(f"Timestamp: {datetime.now().isoformat()}")
113
+ mock_logger.info(f"User Info: {json.dumps(user_info)}")
114
+ mock_logger.info("Order Items:")
115
+ for item in order_items:
116
+ mock_logger.info(f" - Product: {item.get('product_name', 'N/A')}, SKU: {item.get('sku', 'N/A')}, Quantity: {item.get('quantity', 'N/A')}, Unit: {item.get('unit', 'N/A')}")
117
+
118
+ simulated_order_id = f"MOCK-{uuid4().hex[:8].upper()}"
119
+
120
+ try:
121
+ # Ensure the file has a header if it's empty or just created
122
+ is_new_file = not os.path.exists(ORDERS_CSV_FILE) or os.path.getsize(ORDERS_CSV_FILE) == 0
123
+
124
+ with open(ORDERS_CSV_FILE, 'a', newline='', encoding='utf-8') as f:
125
+ writer = csv.writer(f)
126
+ if is_new_file: # Should be handled by initialize_orders_csv, but as a safeguard
127
+ writer.writerow(CSV_HEADER)
128
+ mock_logger.info(f"Header written to new/empty {ORDERS_CSV_FILE} before appending order.")
129
+
130
+ items_json_str = json.dumps(order_items)
131
+ writer.writerow([
132
+ datetime.now().isoformat(),
133
+ simulated_order_id,
134
+ user_info.get('session_id', 'N/A'),
135
+ user_info.get('email', 'N/A'),
136
+ user_info.get('business_id', 'N/A'),
137
+ user_info.get('canonical_user_id', 'N/A'),
138
+ items_json_str
139
+ ])
140
+ mock_logger.info(f"Order {simulated_order_id} successfully written to {ORDERS_CSV_FILE}")
141
+ except IOError as e_csv:
142
+ mock_logger.error(f"Failed to write order {simulated_order_id} to CSV: {e_csv}", exc_info=True)
143
+ return jsonify({"status": "success", "message": "Order received but failed to log locally.", "order_id": simulated_order_id, "warning": "Internal logging error"}), 200
144
+
145
+
146
+ mock_logger.info(f"--- END OF ORDER PROCESSING (Order ID: {simulated_order_id}) ---")
147
+ return jsonify({"status": "success", "message": "Order received and logged by Mock API.", "order_id": simulated_order_id}), 200
148
+
149
+ except Exception as e:
150
+ mock_logger.error(f"Unexpected error in place_order: {e}", exc_info=True)
151
+ return jsonify({"status": "error", "message": "An unexpected internal server error occurred."}), 500
152
+
153
+ @app.route('/health', methods=['GET'])
154
+ def health_check():
155
+ return jsonify({"status": "healthy", "message": "Mock Order API is running."}), 200
156
+
157
+
158
+ if __name__ == '__main__':
159
+ port = int(os.getenv('MOCK_ORDER_API_PORT', 5002))
160
+ debug_mode = os.getenv('FLASK_DEBUG', 'False').lower() == 'true' # Example of using FLASK_DEBUG env var
161
+ mock_logger.info(f"Starting Mock Order API server on 0.0.0.0:{port} (Debug: {debug_mode})")
162
+ app.run(host='0.0.0.0', port=port, debug=debug_mode)
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ Flask==3.1.1
2
+ flask_cors==5.0.1
3
+ python-dotenv==1.1.0