Spaces:
Running
Running
Upload folder using huggingface_hub
Browse files
__pycache__/search_engine.cpython-311.pyc
CHANGED
Binary files a/__pycache__/search_engine.cpython-311.pyc and b/__pycache__/search_engine.cpython-311.pyc differ
|
|
app/__pycache__/ai_features.cpython-311.pyc
CHANGED
Binary files a/app/__pycache__/ai_features.cpython-311.pyc and b/app/__pycache__/ai_features.cpython-311.pyc differ
|
|
app/__pycache__/api.cpython-311.pyc
CHANGED
Binary files a/app/__pycache__/api.cpython-311.pyc and b/app/__pycache__/api.cpython-311.pyc differ
|
|
app/__pycache__/config.cpython-311.pyc
CHANGED
Binary files a/app/__pycache__/config.cpython-311.pyc and b/app/__pycache__/config.cpython-311.pyc differ
|
|
app/api.py
CHANGED
@@ -43,25 +43,48 @@ def get_product_image(product_id):
|
|
43 |
|
44 |
@api_bp.route('/products', methods=['GET'])
|
45 |
def get_products():
|
46 |
-
# ---
|
47 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
48 |
products_list = []
|
49 |
for p in products_cursor:
|
|
|
50 |
image_url = None
|
51 |
-
#
|
52 |
-
if
|
53 |
-
|
54 |
-
|
55 |
-
# Otherwise, use the fallback AI-generated URL if it exists
|
56 |
elif p.get('image_url'):
|
57 |
image_url = p.get('image_url')
|
58 |
|
59 |
products_list.append({
|
60 |
-
'id':
|
61 |
-
'name': p.get('name'),
|
62 |
'category': p.get('category'),
|
63 |
-
'modes': p.get('modes'),
|
64 |
-
'image_url': image_url,
|
65 |
'description': p.get('description', '')
|
66 |
})
|
67 |
return jsonify(products_list)
|
@@ -151,49 +174,86 @@ def handle_cart():
|
|
151 |
user_email = get_jwt_identity()
|
152 |
|
153 |
if request.method == 'GET':
|
154 |
-
|
155 |
-
|
156 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
157 |
|
158 |
-
|
159 |
-
if cart.get('items'):
|
160 |
-
product_ids = [ObjectId(item['productId']) for item in cart['items']]
|
161 |
-
if product_ids:
|
162 |
-
products_cursor = mongo.db.products.find({'_id': {'$in': product_ids}})
|
163 |
-
products = {str(p['_id']): p for p in products_cursor}
|
164 |
-
|
165 |
-
for item in cart['items']:
|
166 |
-
details = products.get(item['productId'])
|
167 |
-
if details:
|
168 |
-
mode = item.get('mode', 'piece')
|
169 |
-
mode_details = details.get('modes', {}).get(mode)
|
170 |
-
|
171 |
-
if mode_details:
|
172 |
-
price = mode_details.get('price')
|
173 |
-
|
174 |
-
image_url = None
|
175 |
-
if details.get('image_data'):
|
176 |
-
# FIX: Generate an absolute URL.
|
177 |
-
image_url = url_for('api.get_product_image', product_id=str(details['_id']), _external=True)
|
178 |
-
elif details.get('image_url'):
|
179 |
-
image_url = details.get('image_url')
|
180 |
-
|
181 |
-
populated_items.append({
|
182 |
-
'product': {
|
183 |
-
'id': str(details['_id']),
|
184 |
-
'name': details.get('name'),
|
185 |
-
'modes': details.get('modes'),
|
186 |
-
'image_url': image_url,
|
187 |
-
'price': price
|
188 |
-
},
|
189 |
-
'quantity': item['quantity'],
|
190 |
-
'mode': mode
|
191 |
-
})
|
192 |
|
193 |
-
|
194 |
-
'items':
|
195 |
-
|
196 |
-
|
197 |
|
198 |
if request.method == 'POST':
|
199 |
data = request.get_json()
|
@@ -330,66 +390,107 @@ def handle_orders():
|
|
330 |
return jsonify({"msg": "Order placed successfully! You will be redirected shortly to the Orders Page!", "orderId": str(order_id)}), 201
|
331 |
|
332 |
if request.method == 'GET':
|
333 |
-
|
|
|
334 |
if not user_orders: return jsonify([])
|
335 |
-
|
336 |
-
|
337 |
-
products_cursor = mongo.db.products.find({'_id': {'$in': list(all_product_ids)}})
|
338 |
-
products = {str(p['_id']): p for p in products_cursor}
|
339 |
-
|
340 |
for order in user_orders:
|
341 |
-
#
|
342 |
-
|
|
|
|
|
|
|
|
|
|
|
343 |
try:
|
344 |
serial_no = order.get('serial_no')
|
345 |
if serial_no:
|
346 |
invoices_response, _ = make_zoho_api_request('GET', '/invoices', params={'reference_number': serial_no})
|
347 |
if invoices_response and invoices_response.get('invoices'):
|
348 |
invoice = invoices_response['invoices'][0]
|
349 |
-
|
350 |
-
if
|
351 |
live_status = 'pending'
|
352 |
-
elif
|
353 |
live_status = 'Processing'
|
354 |
-
elif
|
355 |
live_status = 'Completed'
|
356 |
-
elif
|
357 |
live_status = 'cancelled'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
358 |
except Exception as e:
|
359 |
current_app.logger.error(f"Could not fetch Zoho invoice status for order {order.get('serial_no')}: {e}")
|
360 |
|
361 |
order['status'] = live_status
|
362 |
-
|
363 |
-
|
364 |
-
|
365 |
-
|
366 |
-
|
367 |
-
|
368 |
-
|
369 |
-
|
370 |
-
|
371 |
-
|
372 |
-
|
373 |
-
|
374 |
-
|
375 |
-
|
376 |
-
|
377 |
-
|
378 |
-
|
379 |
-
|
380 |
-
|
|
|
|
|
|
|
|
|
381 |
'product': {
|
382 |
-
'id':
|
383 |
-
'name':
|
384 |
-
'modes':
|
385 |
-
'image_url':
|
|
|
|
|
|
|
|
|
|
|
|
|
386 |
}
|
387 |
-
}
|
388 |
-
|
389 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
390 |
order['created_at'] = order['created_at'].isoformat()
|
391 |
order['delivery_date'] = order['delivery_date'] if isinstance(order['delivery_date'], str) else order['delivery_date'].isoformat()
|
392 |
-
|
|
|
393 |
|
394 |
@api_bp.route('/orders/<order_id>', methods=['GET'])
|
395 |
@jwt_required()
|
|
|
43 |
|
44 |
@api_bp.route('/products', methods=['GET'])
|
45 |
def get_products():
|
46 |
+
# --- OPTIMIZATION: Use projection to exclude large binary image data ---
|
47 |
+
# We only need to know if 'image_data' exists, not its content. This dramatically
|
48 |
+
# reduces the data transferred from MongoDB to the Flask app, speeding up the response.
|
49 |
+
products_cursor = mongo.db.products.find({}, {'image_data': 0})
|
50 |
+
products_list = []
|
51 |
+
|
52 |
+
# Re-fetch the documents that have image_data to check for existence
|
53 |
+
# This is a bit of a workaround because we can't check for existence with projection easily
|
54 |
+
# A better long-term solution would be a boolean field like `has_binary_image`.
|
55 |
+
# For now, we'll check based on the projected data. If a product *might* have an image, we can assume it does.
|
56 |
+
# The logic below is slightly adjusted. A product document will still have the key 'image_data' if it was not projected out.
|
57 |
+
# The previous code was fine, but this makes it explicit that we are avoiding the large field.
|
58 |
+
products_cursor = mongo.db.products.find(
|
59 |
+
{},
|
60 |
+
# Exclude the large image_data field from the initial query
|
61 |
+
{'image_data': 0, 'image_content_type': 0}
|
62 |
+
)
|
63 |
+
# Get a set of IDs for products that DO have binary image data in a separate, fast query
|
64 |
+
products_with_images = {
|
65 |
+
str(p['_id']) for p in mongo.db.products.find(
|
66 |
+
{'image_data': {'$exists': True, '$ne': None}},
|
67 |
+
{'_id': 1} # Only fetch the ID
|
68 |
+
)
|
69 |
+
}
|
70 |
+
|
71 |
products_list = []
|
72 |
for p in products_cursor:
|
73 |
+
product_id_str = str(p['_id'])
|
74 |
image_url = None
|
75 |
+
# Check against our pre-fetched set of IDs
|
76 |
+
if product_id_str in products_with_images:
|
77 |
+
image_url = url_for('api.get_product_image', product_id=product_id_str, _external=True)
|
78 |
+
# Fallback to the stored URL
|
|
|
79 |
elif p.get('image_url'):
|
80 |
image_url = p.get('image_url')
|
81 |
|
82 |
products_list.append({
|
83 |
+
'id': product_id_str,
|
84 |
+
'name': p.get('name'),
|
85 |
'category': p.get('category'),
|
86 |
+
'modes': p.get('modes'),
|
87 |
+
'image_url': image_url,
|
88 |
'description': p.get('description', '')
|
89 |
})
|
90 |
return jsonify(products_list)
|
|
|
174 |
user_email = get_jwt_identity()
|
175 |
|
176 |
if request.method == 'GET':
|
177 |
+
# --- OPTIMIZATION: Use MongoDB Aggregation Pipeline to fetch cart and products in one go ---
|
178 |
+
pipeline = [
|
179 |
+
# 1. Find the user's cart
|
180 |
+
{'$match': {'user_email': user_email}},
|
181 |
+
# 2. Deconstruct the items array to process each item
|
182 |
+
{'$unwind': '$items'},
|
183 |
+
# 3. Convert string productId to ObjectId for lookup
|
184 |
+
{'$addFields': {'productId_obj': {'$toObjectId': '$items.productId'}}},
|
185 |
+
# 4. Join with the products collection ($lookup is like a JOIN)
|
186 |
+
{
|
187 |
+
'$lookup': {
|
188 |
+
'from': 'products',
|
189 |
+
'localField': 'productId_obj',
|
190 |
+
'foreignField': '_id',
|
191 |
+
'as': 'productDetails'
|
192 |
+
}
|
193 |
+
},
|
194 |
+
# 5. Deconstruct the resulting productDetails array (it will have 1 element)
|
195 |
+
{'$unwind': '$productDetails'},
|
196 |
+
# 6. Re-shape the document to match the frontend's expected format
|
197 |
+
{
|
198 |
+
'$project': {
|
199 |
+
'_id': 0,
|
200 |
+
'deliveryDate': '$deliveryDate',
|
201 |
+
'item': {
|
202 |
+
'quantity': '$items.quantity',
|
203 |
+
'mode': '$items.mode',
|
204 |
+
'product': {
|
205 |
+
'id': {'$toString': '$productDetails._id'},
|
206 |
+
'name': '$productDetails.name',
|
207 |
+
'modes': '$productDetails.modes',
|
208 |
+
'price': {'$getField': {'field': '$items.mode', 'input': '$productDetails.modes.price'}},
|
209 |
+
'image_url': {
|
210 |
+
'$cond': {
|
211 |
+
'if': {'$and': [
|
212 |
+
{'$ne': ['$productDetails.image_data', None]},
|
213 |
+
{'$ne': ['$productDetails.image_data', ""]}
|
214 |
+
]},
|
215 |
+
'then': url_for('api.get_product_image', product_id=str(ObjectId()), _external=True).replace(str(ObjectId()),""), # Placeholder for url construction
|
216 |
+
'else': '$productDetails.image_url'
|
217 |
+
}
|
218 |
+
}
|
219 |
+
}
|
220 |
+
}
|
221 |
+
}
|
222 |
+
},
|
223 |
+
# 7. Dynamically construct the image URL
|
224 |
+
{
|
225 |
+
'$addFields': {
|
226 |
+
"item.product.image_url": {
|
227 |
+
'$cond': {
|
228 |
+
'if': {'$ne': ["$item.product.image_url", None]},
|
229 |
+
'then': {
|
230 |
+
'$concat': [
|
231 |
+
request.host_url.rstrip('/'),
|
232 |
+
'/api/product_image/',
|
233 |
+
"$item.product.id"
|
234 |
+
]
|
235 |
+
},
|
236 |
+
'else': None
|
237 |
+
}
|
238 |
+
}
|
239 |
+
}
|
240 |
+
},
|
241 |
+
# 8. Group all items back into a single cart document
|
242 |
+
{
|
243 |
+
'$group': {
|
244 |
+
'_id': '$_id',
|
245 |
+
'deliveryDate': {'$first': '$deliveryDate'},
|
246 |
+
'items': {'$push': '$item'}
|
247 |
+
}
|
248 |
+
}
|
249 |
+
]
|
250 |
|
251 |
+
result = list(mongo.db.carts.aggregate(pipeline))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
252 |
|
253 |
+
if not result:
|
254 |
+
return jsonify({'items': [], 'deliveryDate': None})
|
255 |
+
|
256 |
+
return jsonify(result[0])
|
257 |
|
258 |
if request.method == 'POST':
|
259 |
data = request.get_json()
|
|
|
390 |
return jsonify({"msg": "Order placed successfully! You will be redirected shortly to the Orders Page!", "orderId": str(order_id)}), 201
|
391 |
|
392 |
if request.method == 'GET':
|
393 |
+
user_orders_cursor = mongo.db.orders.find({'user_email': user_email}).sort('created_at', -1)
|
394 |
+
user_orders = list(user_orders_cursor)
|
395 |
if not user_orders: return jsonify([])
|
396 |
+
|
397 |
+
# Fetch status from DB, or from Zoho if not present in DB.
|
|
|
|
|
|
|
398 |
for order in user_orders:
|
399 |
+
# If status is present in our DB, use it and skip the API call.
|
400 |
+
if 'zoho_status' in order:
|
401 |
+
order['status'] = order['zoho_status']
|
402 |
+
continue
|
403 |
+
|
404 |
+
# If status is not in DB, fetch from Zoho, update DB, and then use it.
|
405 |
+
live_status = 'pending'
|
406 |
try:
|
407 |
serial_no = order.get('serial_no')
|
408 |
if serial_no:
|
409 |
invoices_response, _ = make_zoho_api_request('GET', '/invoices', params={'reference_number': serial_no})
|
410 |
if invoices_response and invoices_response.get('invoices'):
|
411 |
invoice = invoices_response['invoices'][0]
|
412 |
+
zoho_api_status = invoice.get('status')
|
413 |
+
if zoho_api_status == 'draft':
|
414 |
live_status = 'pending'
|
415 |
+
elif zoho_api_status == 'sent':
|
416 |
live_status = 'Processing'
|
417 |
+
elif zoho_api_status == 'paid':
|
418 |
live_status = 'Completed'
|
419 |
+
elif zoho_api_status == 'void':
|
420 |
live_status = 'cancelled'
|
421 |
+
|
422 |
+
# Save the newly fetched status to MongoDB for future requests
|
423 |
+
mongo.db.orders.update_one(
|
424 |
+
{'_id': order['_id']},
|
425 |
+
{'$set': {'zoho_status': live_status}}
|
426 |
+
)
|
427 |
+
|
428 |
except Exception as e:
|
429 |
current_app.logger.error(f"Could not fetch Zoho invoice status for order {order.get('serial_no')}: {e}")
|
430 |
|
431 |
order['status'] = live_status
|
432 |
+
|
433 |
+
# --- OPTIMIZATION: Use a single aggregation to populate product details for all orders ---
|
434 |
+
pipeline = [
|
435 |
+
{'$match': {'user_email': user_email}},
|
436 |
+
{'$sort': {'created_at': -1}},
|
437 |
+
{'$unwind': '$items'},
|
438 |
+
{'$addFields': {'productId_obj': {'$toObjectId': '$items.productId'}}},
|
439 |
+
{
|
440 |
+
'$lookup': {
|
441 |
+
'from': 'products',
|
442 |
+
'localField': 'productId_obj',
|
443 |
+
'foreignField': '_id',
|
444 |
+
'as': 'productDetails'
|
445 |
+
}
|
446 |
+
},
|
447 |
+
{'$unwind': '$productDetails'},
|
448 |
+
{
|
449 |
+
'$group': {
|
450 |
+
'_id': '$_id',
|
451 |
+
'items': {'$push': {
|
452 |
+
'quantity': '$items.quantity',
|
453 |
+
'mode': '$items.mode',
|
454 |
+
'price': {'$let': {'vars': {'mode_details': {'$getField': {'field': '$items.mode', 'input': '$productDetails.modes'}}}, 'in': '$$mode_details.price'}},
|
455 |
'product': {
|
456 |
+
'id': {'$toString': '$productDetails._id'},
|
457 |
+
'name': '$productDetails.name',
|
458 |
+
'modes': '$productDetails.modes',
|
459 |
+
'image_url': {
|
460 |
+
'$cond': {
|
461 |
+
'if': {'$ifNull': ['$productDetails.image_data', False]},
|
462 |
+
'then': {'$concat': [request.host_url.rstrip('/'), '/api/product_image/', {'$toString': '$productDetails._id'}]},
|
463 |
+
'else': '$productDetails.image_url'
|
464 |
+
}
|
465 |
+
}
|
466 |
}
|
467 |
+
}},
|
468 |
+
# Carry over all original order fields
|
469 |
+
'doc': {'$first': '$$ROOT'}
|
470 |
+
}
|
471 |
+
},
|
472 |
+
{
|
473 |
+
'$replaceRoot': {
|
474 |
+
'newRoot': {
|
475 |
+
'$mergeObjects': ['$doc', {'items': '$items'}]
|
476 |
+
}
|
477 |
+
}
|
478 |
+
},
|
479 |
+
{'$sort': {'created_at': -1}}
|
480 |
+
]
|
481 |
+
|
482 |
+
populated_orders = list(mongo.db.orders.aggregate(pipeline))
|
483 |
+
|
484 |
+
# Merge the live status back into the populated orders
|
485 |
+
status_map = {str(order['_id']): order['status'] for order in user_orders}
|
486 |
+
for order in populated_orders:
|
487 |
+
order_id_str = str(order['_id'])
|
488 |
+
order['status'] = status_map.get(order_id_str, 'pending')
|
489 |
+
order['_id'] = order_id_str # Convert ObjectId to string for JSON
|
490 |
order['created_at'] = order['created_at'].isoformat()
|
491 |
order['delivery_date'] = order['delivery_date'] if isinstance(order['delivery_date'], str) else order['delivery_date'].isoformat()
|
492 |
+
|
493 |
+
return jsonify(populated_orders)
|
494 |
|
495 |
@api_bp.route('/orders/<order_id>', methods=['GET'])
|
496 |
@jwt_required()
|
flask_session/aa71dde20eaf768ca7e5f90a25563ea6
CHANGED
Binary files a/flask_session/aa71dde20eaf768ca7e5f90a25563ea6 and b/flask_session/aa71dde20eaf768ca7e5f90a25563ea6 differ
|
|