rairo commited on
Commit
d152115
·
verified ·
1 Parent(s): ff78cef

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +279 -483
main.py CHANGED
@@ -2,7 +2,7 @@ import os
2
  import io
3
  import json
4
  import hashlib
5
- from datetime import datetime, time, timedelta
6
  from PIL import Image
7
  import pytz
8
  from flask import Flask, request, jsonify, send_file
@@ -18,16 +18,24 @@ app = Flask(__name__)
18
  CORS(app)
19
 
20
  # Firebase initialization
21
- cred = credentials.Certificate('datingi-firebase-adminsdk-obe4r-d63a25b54e.json')
22
  firebase_admin.initialize_app(cred, {
23
- 'databaseURL': 'https://datingi.firebaseio.com',
24
- 'storageBucket': 'datingi.firebasestorage.app'
25
  })
26
 
27
  bucket = storage.bucket()
28
  api_key = os.environ['Gemini']
29
- FIREBASE_API_KEY = os.environ.get('FIREBASE_API_KEY')
30
 
 
 
 
 
 
 
 
 
 
31
 
32
  # Helper functions
33
  def configure_gemini():
@@ -38,250 +46,9 @@ def verify_token(token):
38
  try:
39
  decoded_token = auth.verify_id_token(token)
40
  return decoded_token['uid']
41
- except Exception as e:
42
  return None
43
 
44
- def check_daily_reset(user_ref):
45
- try:
46
- user_data = user_ref.get()
47
- now = datetime.now(pytz.UTC)
48
- last_reset = datetime.fromisoformat(user_data.get('last_reset', '2000-01-01T00:00:00+00:00'))
49
-
50
- if now.time() >= time(8,0) and last_reset.date() < now.date():
51
- user_ref.update({
52
- 'remaining_cash': user_data['daily_cash'],
53
- 'last_reset': now.isoformat()
54
- })
55
- return True
56
- return False
57
- except Exception as e:
58
- print(f"Reset error: {str(e)}")
59
- return False
60
-
61
- # Process receipt image
62
- def process_receipt(model, image):
63
- prompt = """Analyze this image and determine if it's a receipt. If it is a receipt, extract:
64
- - Total amount (as float)
65
- - List of items purchased (array of strings)
66
- - Date of transaction (DD/MM/YYYY format)
67
- - Receipt number (as string)
68
- Return JSON format with keys: is_receipt (boolean), total, items, date, receipt_number.
69
- If not a receipt, return {"is_receipt": false}"""
70
-
71
- try:
72
- response = model.generate_content([prompt, image])
73
- return response.text
74
- except Exception as e:
75
- print(f"Gemini error: {str(e)}")
76
- return "{}"
77
-
78
-
79
- # Write report
80
- @app.route('/api/write-report', methods=['POST'])
81
- def generate_report():
82
- prompt = """You are the TrueSpend AI analyst, analyze this transaction data
83
- and write an insightful business report on the spending habits of the employees.
84
- Make sure the report is in plain text"""
85
- model = configure_gemini()
86
- try:
87
- transaction_data = request.get_json()
88
- transaction_json_string = json.dumps(transaction_data['transactions'])
89
- response = model.generate_content([prompt, transaction_json_string])
90
- report = response.text
91
- return jsonify({"report": report})
92
- except Exception as e:
93
- return jsonify({"error": str(e)}), 500
94
-
95
- # ========================================
96
- # Authentication Endpoints
97
- # ========================================
98
- # (Any existing authentication endpoints remain unchanged)
99
-
100
- # ========================================
101
- # Receipt Processing Endpoint (unchanged)
102
- # ========================================
103
-
104
- @app.route('/api/process-receipt', methods=['POST'])
105
- def process_receipt_endpoint():
106
- try:
107
- auth_header = request.headers.get('Authorization')
108
- token = auth_header.split('Bearer ')[1] if auth_header else None
109
- uid = verify_token(token) if token else None
110
-
111
- if not uid:
112
- return jsonify({'error': 'Invalid token'}), 401
113
-
114
- user_ref = db.reference(f'users/{uid}')
115
- user_data = user_ref.get()
116
- check_daily_reset(user_ref)
117
-
118
- # Handle manual entry
119
- if request.form.get('manual_entry') == 'true':
120
- return handle_manual_entry(uid, user_ref, user_data)
121
-
122
- # Handle image processing
123
- file = request.files.get('receipt')
124
- if not file:
125
- return jsonify({'error': 'No file uploaded'}), 400
126
-
127
- image_bytes = file.read()
128
- file_hash = hashlib.md5(image_bytes).hexdigest()
129
-
130
- transactions_ref = db.reference('transactions')
131
- existing = transactions_ref.order_by_child('hash').equal_to(file_hash).get()
132
- if existing:
133
- return jsonify({'error': 'Receipt already processed'}), 400
134
-
135
- image = Image.open(io.BytesIO(image_bytes))
136
- model = configure_gemini()
137
- result_text = process_receipt(model, image)
138
-
139
- try:
140
- json_str = result_text[result_text.find('{'):result_text.rfind('}')+1]
141
- data = json.loads(json_str)
142
- except json.JSONDecodeError:
143
- return jsonify({'error': 'Failed to parse receipt data', 'raw_response': result_text}), 400
144
-
145
- if not data.get('is_receipt', False):
146
- return jsonify({'error': 'Not a valid receipt'}), 400
147
-
148
- return validate_and_save_transaction(
149
- uid=uid,
150
- user_data=user_data,
151
- data=data,
152
- file_hash=file_hash,
153
- image_bytes=image_bytes,
154
- manual=False
155
- )
156
-
157
- except Exception as e:
158
- print(e)
159
- return jsonify({'error': str(e)}), 500
160
-
161
- def handle_manual_entry(uid, user_ref, user_data):
162
- try:
163
- data = {
164
- 'total': float(request.form.get('total')),
165
- 'items': [item.strip() for item in request.form.get('items', '').split(',')],
166
- 'date': request.form.get('date'),
167
- 'receipt_number': request.form.get('receipt_number'),
168
- 'is_receipt': True
169
- }
170
-
171
- return validate_and_save_transaction(
172
- uid=uid,
173
- user_data=user_data,
174
- data=data,
175
- file_hash=hashlib.md5(str(datetime.now()).encode()).hexdigest(),
176
- image_bytes=None,
177
- manual=True
178
- )
179
- except Exception as e:
180
- return jsonify({'error': str(e)}), 400
181
-
182
- def validate_and_save_transaction(uid, user_data, data, file_hash, image_bytes, manual=False):
183
- transactions_ref = db.reference('transactions')
184
- receipt_number = data.get('receipt_number')
185
- items = data.get('items', [])
186
-
187
- # Check for duplicate transactions based on receipt number and items.
188
- existing_transactions_with_receipt = transactions_ref.order_by_child('receipt_number').equal_to(receipt_number).get()
189
-
190
- if existing_transactions_with_receipt:
191
- for transaction_id, existing_transaction_data in existing_transactions_with_receipt.items():
192
- existing_items = sorted(existing_transaction_data.get('items', []))
193
- current_items = sorted(items)
194
- if existing_items == current_items:
195
- return jsonify({'error': f"Transaction with Receipt #{receipt_number} and identical items already exists"}), 400
196
-
197
- total = float(data.get('total', 0))
198
- if total > user_data['remaining_cash']:
199
- return jsonify({'error': 'Insufficient funds'}), 400
200
-
201
- # Upload image if available
202
- image_url = None
203
- if image_bytes and not manual:
204
- timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
205
- blob = bucket.blob(f'receipts/{uid}/{timestamp}_{file_hash}.jpg')
206
- blob.upload_from_string(image_bytes, content_type='image/jpeg')
207
- image_url = blob.public_url
208
-
209
- # Update user cash
210
- new_remaining = user_data['remaining_cash'] - total
211
- db.reference(f'users/{uid}').update({'remaining_cash': new_remaining})
212
-
213
- transaction_data = {
214
- 'uid': uid,
215
- 'total': total,
216
- 'items': items,
217
- 'date': data.get('date'),
218
- 'receipt_number': receipt_number,
219
- 'timestamp': datetime.now(pytz.UTC).isoformat(),
220
- 'hash': file_hash,
221
- 'image_url': image_url,
222
- 'manual_entry': manual
223
- }
224
-
225
- new_transaction_ref = transactions_ref.push(transaction_data)
226
- return jsonify({
227
- 'success': True,
228
- 'transaction': {**transaction_data, 'id': new_transaction_ref.key},
229
- 'remaining_cash': new_remaining
230
- })
231
-
232
- # ========================================
233
- # Data Endpoints for Visualizations
234
- # ========================================
235
-
236
- @app.route('/api/user/spending-overview', methods=['GET'])
237
- def get_spending_overview():
238
- try:
239
- uid = verify_token(request.headers.get('Authorization', '').split(' ')[1])
240
- transactions_ref = db.reference('transactions')
241
- transactions = transactions_ref.order_by_child('uid').equal_to(uid).get()
242
-
243
- if transactions is None:
244
- return jsonify({
245
- 'daily_spending': [],
246
- 'recent_transactions': []
247
- })
248
-
249
- df = pd.DataFrame.from_dict(transactions, orient='index')
250
- if df.empty:
251
- return jsonify({
252
- 'daily_spending': [],
253
- 'recent_transactions': []
254
- })
255
-
256
- df.dropna(subset=['uid','total','items','date', 'receipt_number', 'timestamp','hash', 'manual_entry'], inplace=True)
257
- if df.empty:
258
- return jsonify({
259
- 'daily_spending': [],
260
- 'recent_transactions': []
261
- })
262
-
263
- df['date'] = pd.to_datetime(df['date'], format='%d/%m/%Y', errors='coerce')
264
- df.dropna(subset=['date'], inplace=True)
265
- if df.empty:
266
- return jsonify({
267
- 'daily_spending': [],
268
- 'recent_transactions': []
269
- })
270
-
271
- daily_spending = df.groupby(df['date'].dt.date)['total'].sum().reset_index()
272
-
273
- return jsonify({
274
- 'daily_spending': daily_spending.to_dict(orient='records'),
275
- 'recent_transactions': df.sort_values(by='timestamp', ascending=False)
276
- .head(10)
277
- .to_dict(orient='records')
278
- })
279
- except Exception as e:
280
- return jsonify({'error': str(e)}), 500
281
-
282
- # ========================================
283
- # Modified verify_admin function (now checks database is_admin flag)
284
- # ========================================
285
  def verify_admin(auth_header):
286
  if not auth_header or not auth_header.startswith('Bearer '):
287
  raise ValueError('Invalid token')
@@ -296,295 +63,324 @@ def verify_admin(auth_header):
296
  if not user_data or not user_data.get('is_admin', False):
297
  raise PermissionError('Admin access required')
298
 
299
- try:
300
- auth.set_custom_user_claims(uid, {"admin": True})
301
- print(f"Custom admin claim set for user {uid}")
302
- except Exception as e:
303
- print(f"Error setting custom admin claim: {e}")
304
- raise PermissionError('Error setting admin claim, but admin verified')
305
-
306
  return uid
307
 
308
- # ========================================
309
- # Existing Admin Endpoints
310
- # ========================================
311
- @app.route('/api/admin/overview', methods=['GET'])
312
- def get_admin_overview():
313
  try:
314
  verify_admin(request.headers.get('Authorization', ''))
315
 
316
- users_ref = db.reference('users')
317
- all_users = users_ref.get() or {}
318
- users_list = []
319
- for uid, user_data in all_users.items():
320
- try:
321
- auth_user = auth.get_user(uid)
322
- email = auth_user.email
323
- except:
324
- email = "Deleted User"
325
- users_list.append({
326
- 'uid': uid,
327
- 'email': email,
328
- 'daily_cash': user_data.get('daily_cash', 0),
329
- 'remaining_cash': user_data.get('remaining_cash', 0),
330
- 'last_reset': user_data.get('last_reset'),
331
- 'is_admin': user_data.get('is_admin', False)
332
- })
333
-
334
- transactions_ref = db.reference('transactions')
335
- all_transactions = transactions_ref.get() or {}
336
- transactions_list = [{'id': tid, **data} for tid, data in all_transactions.items()]
337
-
338
- return jsonify({
339
- 'users': users_list,
340
- 'transactions': transactions_list,
341
- 'analytics': {
342
- 'total_users': len(users_list),
343
- 'total_transactions': len(transactions_list),
344
- 'total_spent': sum(t['total'] for t in transactions_list)
345
- }
 
346
  })
 
 
 
 
 
 
347
  except Exception as e:
348
  return jsonify({'error': str(e)}), 500
349
 
350
- @app.route('/api/admin/users', methods=['POST'])
351
- def create_user():
352
  try:
353
  verify_admin(request.headers.get('Authorization', ''))
354
- data = request.get_json()
355
 
356
- user = auth.create_user(
357
- email=data['email'],
358
- password=data['password']
359
- )
360
-
361
- user_ref = db.reference(f'users/{user.uid}')
362
- user_data = {
363
- 'daily_cash': data.get('daily_cash', 0),
364
- 'remaining_cash': data.get('daily_cash', 0),
365
- 'last_reset': '2025-01-01T00:00:00+00:00',
366
- 'is_admin': data.get('is_admin', False)
367
- }
368
- user_ref.set(user_data)
 
 
 
 
 
 
 
 
 
 
 
 
 
369
 
370
- return jsonify({
371
- 'success': True,
372
- 'user': {
373
- 'uid': user.uid,
374
- 'email': user.email,
375
- **user_data
376
- }
377
- }), 201
378
  except Exception as e:
379
- return jsonify({'error': str(e)}), 400
380
 
381
- @app.route('/api/admin/users/<string:uid>/limit', methods=['PUT'])
382
- def update_user_limit(uid):
 
383
  try:
384
- verify_admin(request.headers.get('Authorization', ''))
385
  data = request.get_json()
386
- new_limit = float(data['daily_cash'])
 
387
 
388
- user_ref = db.reference(f'users/{uid}')
389
- user_data = user_ref.get()
 
390
 
391
- if not user_data:
392
- return jsonify({'error': 'User not found'}), 404
 
 
393
 
394
- updates = {'daily_cash': new_limit}
395
- current_remaining = user_data.get('remaining_cash', new_limit)
396
- if current_remaining > new_limit:
397
- updates['remaining_cash'] = new_limit
 
 
 
 
 
 
 
398
 
399
- user_ref.update(updates)
 
 
400
 
401
- return jsonify({
402
- 'success': True,
403
- 'new_daily_cash': new_limit,
404
- 'updated_remaining': updates.get('remaining_cash', current_remaining)
405
  })
 
 
 
406
  except Exception as e:
407
- return jsonify({'error': str(e)}), 400
408
-
409
- # ========================================
410
- # New Admin Endpoints for Updating Remaining Cash, Setting Cash Limits, and Resetting Password
411
- # ========================================
412
 
413
- @app.route('/api/admin/users/<string:uid>/remaining-cash', methods=['PUT'])
414
- def update_remaining_cash(uid):
415
  try:
416
- verify_admin(request.headers.get('Authorization', ''))
417
  data = request.get_json()
418
- if 'remaining_cash' not in data:
419
- return jsonify({'error': 'remaining_cash is required'}), 400
420
- new_remaining_cash = float(data['remaining_cash'])
421
- user_ref = db.reference(f'users/{uid}')
422
- user_data = user_ref.get()
423
- if not user_data:
424
- return jsonify({'error': 'User not found'}), 404
425
-
426
- user_ref.update({'remaining_cash': new_remaining_cash})
427
- return jsonify({'success': True, 'remaining_cash': new_remaining_cash})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
428
  except Exception as e:
429
- return jsonify({'error': str(e)}), 400
430
-
431
 
432
- @app.route('/api/admin/users/<string:uid>/reset-password', methods=['PUT'])
433
- def admin_reset_password(uid):
 
434
  try:
435
  verify_admin(request.headers.get('Authorization', ''))
436
  data = request.get_json()
437
- new_password = data.get('new_password')
438
- if not new_password:
439
- return jsonify({'error': 'new_password is required'}), 400
440
- auth.update_user(uid, password=new_password)
441
- return jsonify({'success': True, 'message': 'Password reset successfully'})
442
- except Exception as e:
443
- return jsonify({'error': str(e)}), 400
444
-
445
- # ========================================
446
- # User management endpoint for profile
447
- # ========================================
448
-
449
- @app.route('/api/user/profile', methods=['GET'])
450
- def get_user_profile():
451
- try:
452
- uid = verify_token(request.headers.get('Authorization', '').split(' ')[1])
453
- user = auth.get_user(uid)
454
- user_data = db.reference(f'users/{uid}').get()
 
 
 
 
 
 
455
 
456
  return jsonify({
457
- 'uid': uid,
458
- 'email': user.email,
459
- 'daily_cash': user_data.get('daily_cash', 0),
460
- 'remaining_cash': user_data.get('remaining_cash', 0),
461
- 'last_reset': user_data.get('last_reset'),
462
- 'is_admin': user_data.get('is_admin', False)
463
  })
 
464
  except Exception as e:
465
- uid = verify_token(request.headers.get('Authorization', '').split(' ')[1])
466
- user = auth.get_user(uid)
467
- user_data = db.reference(f'users/{uid}').get()
468
- return jsonify({'error': str(e)+ f'user data: {user_data}'}), 500
469
-
470
- # ========================================
471
- # Receipt media endpoints
472
- # ========================================
473
-
474
- def get_blob_from_image_url(image_url):
475
- parsed = urlparse(image_url)
476
- if parsed.netloc == "storage.googleapis.com":
477
- parts = parsed.path.strip("/").split("/", 1)
478
- if len(parts) == 2:
479
- bucket_name, blob_path = parts
480
- return blob_path
481
- prefix = f"/v0/b/{bucket.name}/o/"
482
- if parsed.path.startswith(prefix):
483
- encoded_blob_path = parsed.path[len(prefix):]
484
- blob_path = unquote(encoded_blob_path)
485
- return blob_path
486
- return None
487
 
488
- @app.route('/api/admin/receipt/<string:transaction_id>/view', methods=['GET'])
489
- def view_receipt(transaction_id):
 
490
  try:
491
- verify_admin(request.headers.get('Authorization', ''))
492
- transaction_ref = db.reference(f'transactions/{transaction_id}')
493
- transaction_data = transaction_ref.get()
494
- if not transaction_data:
495
- return jsonify({'error': 'Transaction not found'}), 404
496
-
497
- image_url = transaction_data.get('image_url')
498
- if not image_url:
499
- return jsonify({'error': 'No receipt image found for this transaction'}), 404
500
-
501
- blob_path = get_blob_from_image_url(image_url)
502
- if not blob_path:
503
- return jsonify({'error': 'Could not determine blob path from URL'}), 500
504
-
505
- print(f"Blob path for view: {blob_path}")
506
- blob = bucket.blob(blob_path)
507
- if not blob.exists():
508
- print("Blob does not exist at path:", blob_path)
509
- return jsonify({'error': 'Blob not found'}), 404
510
-
511
- signed_url = blob.generate_signed_url(expiration=timedelta(minutes=10))
512
- r = requests.get(signed_url)
513
- if r.status_code != 200:
514
- return jsonify({'error': 'Unable to fetch image from storage'}), 500
515
-
516
- return send_file(io.BytesIO(r.content), mimetype='image/jpeg')
517
  except Exception as e:
518
- print(f"View receipt error: {str(e)}")
519
  return jsonify({'error': str(e)}), 500
520
 
521
- @app.route('/api/admin/receipt/<string:transaction_id>/download', methods=['GET'])
522
- def download_receipt(transaction_id):
 
523
  try:
524
- verify_admin(request.headers.get('Authorization', ''))
525
- transaction_ref = db.reference(f'transactions/{transaction_id}')
526
- transaction_data = transaction_ref.get()
527
- if not transaction_data:
528
- return jsonify({'error': 'Transaction not found'}), 404
529
-
530
- image_url = transaction_data.get('image_url')
531
- if not image_url:
532
- return jsonify({'error': 'No receipt image found for this transaction'}), 404
533
-
534
- blob_path = get_blob_from_image_url(image_url)
535
- if not blob_path:
536
- return jsonify({'error': 'Could not determine blob path from URL'}), 500
537
-
538
- print(f"Blob path for download: {blob_path}")
539
- blob = bucket.blob(blob_path)
540
- if not blob.exists():
541
- print("Blob does not exist at path:", blob_path)
542
- return jsonify({'error': 'Blob not found'}), 404
543
-
544
- signed_url = blob.generate_signed_url(expiration=timedelta(minutes=10))
545
- r = requests.get(signed_url)
546
- if r.status_code != 200:
547
- return jsonify({'error': 'Unable to fetch image from storage'}), 500
548
-
549
- return send_file(
550
- io.BytesIO(r.content),
551
- mimetype='image/jpeg',
552
- as_attachment=True,
553
- attachment_filename='receipt.jpg'
554
- )
555
  except Exception as e:
556
- print(f"Download receipt error: {str(e)}")
557
  return jsonify({'error': str(e)}), 500
558
 
559
- # ========================================
560
- # Delete user endpoint
561
- # ========================================
562
-
563
- @app.route('/api/admin/users/<string:uid>', methods=['DELETE'])
564
- def delete_user(uid):
565
  try:
566
  verify_admin(request.headers.get('Authorization', ''))
567
 
568
- try:
569
- user = auth.get_user(uid)
570
- except auth.UserNotFoundError:
571
- return jsonify({'error': 'User not found'}), 404
572
-
573
- auth.delete_user(uid)
574
- db.reference(f'users/{uid}').delete()
 
 
 
 
 
575
 
576
- transactions_ref = db.reference('transactions')
577
- user_transactions = transactions_ref.order_by_child('uid').equal_to(uid).get()
578
- if user_transactions:
579
- for transaction_id in user_transactions.keys():
580
- transactions_ref.child(transaction_id).delete()
581
 
582
  return jsonify({
583
- 'success': True,
584
- 'message': f'User {uid} and all associated data deleted successfully'
 
 
 
 
 
 
585
  })
 
586
  except Exception as e:
587
  return jsonify({'error': str(e)}), 500
588
 
589
  if __name__ == '__main__':
590
- app.run(debug=True, host="0.0.0.0", port=7860)
 
2
  import io
3
  import json
4
  import hashlib
5
+ from datetime import datetime, timedelta
6
  from PIL import Image
7
  import pytz
8
  from flask import Flask, request, jsonify, send_file
 
18
  CORS(app)
19
 
20
  # Firebase initialization
21
+ cred = credentials.Certificate('firebase-adminsdk.json')
22
  firebase_admin.initialize_app(cred, {
23
+ 'databaseURL': 'https://your-db-url.firebaseio.com',
24
+ 'storageBucket': 'your-storage-bucket.app'
25
  })
26
 
27
  bucket = storage.bucket()
28
  api_key = os.environ['Gemini']
 
29
 
30
+ # Product Categories
31
+ CATEGORIES = [
32
+ 'Masks',
33
+ 'Gloves',
34
+ 'Face Shields',
35
+ 'Protective Suits',
36
+ 'Sanitizers',
37
+ 'Disinfectants'
38
+ ]
39
 
40
  # Helper functions
41
  def configure_gemini():
 
46
  try:
47
  decoded_token = auth.verify_id_token(token)
48
  return decoded_token['uid']
49
+ except Exception:
50
  return None
51
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  def verify_admin(auth_header):
53
  if not auth_header or not auth_header.startswith('Bearer '):
54
  raise ValueError('Invalid token')
 
63
  if not user_data or not user_data.get('is_admin', False):
64
  raise PermissionError('Admin access required')
65
 
 
 
 
 
 
 
 
66
  return uid
67
 
68
+ # Product Management Endpoints
69
+ @app.route('/api/admin/products', methods=['POST'])
70
+ def create_product():
 
 
71
  try:
72
  verify_admin(request.headers.get('Authorization', ''))
73
 
74
+ if 'image' not in request.files:
75
+ return jsonify({'error': 'No image file uploaded'}), 400
76
+
77
+ file = request.files['image']
78
+ name = request.form.get('name')
79
+ category = request.form.get('category')
80
+ price = float(request.form.get('price'))
81
+ description = request.form.get('description')
82
+ stock = int(request.form.get('stock', 0))
83
+
84
+ if category not in CATEGORIES:
85
+ return jsonify({'error': 'Invalid category'}), 400
86
+
87
+ # Upload image
88
+ image_bytes = file.read()
89
+ timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
90
+ file_hash = hashlib.md5(image_bytes).hexdigest()
91
+ blob = bucket.blob(f'products/{timestamp}_{file_hash}.jpg')
92
+ blob.upload_from_string(image_bytes, content_type='image/jpeg')
93
+ image_url = blob.public_url
94
+
95
+ # Create product
96
+ product_ref = db.reference('products').push({
97
+ 'name': name,
98
+ 'category': category,
99
+ 'price': price,
100
+ 'description': description,
101
+ 'stock': stock,
102
+ 'image_url': image_url,
103
+ 'created_at': datetime.now(pytz.UTC).isoformat(),
104
+ 'active': True
105
  })
106
+
107
+ return jsonify({
108
+ 'success': True,
109
+ 'product_id': product_ref.key
110
+ }), 201
111
+
112
  except Exception as e:
113
  return jsonify({'error': str(e)}), 500
114
 
115
+ @app.route('/api/admin/products/<string:product_id>', methods=['PUT'])
116
+ def update_product(product_id):
117
  try:
118
  verify_admin(request.headers.get('Authorization', ''))
 
119
 
120
+ updates = {}
121
+ if 'name' in request.form:
122
+ updates['name'] = request.form['name']
123
+ if 'category' in request.form and request.form['category'] in CATEGORIES:
124
+ updates['category'] = request.form['category']
125
+ if 'price' in request.form:
126
+ updates['price'] = float(request.form['price'])
127
+ if 'description' in request.form:
128
+ updates['description'] = request.form['description']
129
+ if 'stock' in request.form:
130
+ updates['stock'] = int(request.form['stock'])
131
+ if 'active' in request.form:
132
+ updates['active'] = request.form['active'].lower() == 'true'
133
+
134
+ if 'image' in request.files:
135
+ file = request.files['image']
136
+ image_bytes = file.read()
137
+ timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
138
+ file_hash = hashlib.md5(image_bytes).hexdigest()
139
+ blob = bucket.blob(f'products/{timestamp}_{file_hash}.jpg')
140
+ blob.upload_from_string(image_bytes, content_type='image/jpeg')
141
+ updates['image_url'] = blob.public_url
142
+
143
+ db.reference(f'products/{product_id}').update(updates)
144
+
145
+ return jsonify({'success': True})
146
 
 
 
 
 
 
 
 
 
147
  except Exception as e:
148
+ return jsonify({'error': str(e)}), 500
149
 
150
+ # Shopping Cart and Order Management
151
+ @app.route('/api/cart/add', methods=['POST'])
152
+ def add_to_cart():
153
  try:
 
154
  data = request.get_json()
155
+ product_id = data['product_id']
156
+ quantity = int(data['quantity'])
157
 
158
+ # Get user ID if logged in
159
+ auth_header = request.headers.get('Authorization')
160
+ uid = verify_token(auth_header.split(' ')[1]) if auth_header else None
161
 
162
+ # Use session ID for non-logged in users
163
+ session_id = data.get('session_id')
164
+ if not uid and not session_id:
165
+ return jsonify({'error': 'Either login or provide session_id'}), 400
166
 
167
+ cart_id = uid if uid else f'session_{session_id}'
168
+ cart_ref = db.reference(f'carts/{cart_id}')
169
+
170
+ # Check product availability
171
+ product = db.reference(f'products/{product_id}').get()
172
+ if not product:
173
+ return jsonify({'error': 'Product not found'}), 404
174
+ if not product.get('active', False):
175
+ return jsonify({'error': 'Product not available'}), 400
176
+ if product.get('stock', 0) < quantity:
177
+ return jsonify({'error': 'Insufficient stock'}), 400
178
 
179
+ # Update cart
180
+ cart_item = cart_ref.child(product_id).get() or {'quantity': 0}
181
+ new_quantity = cart_item['quantity'] + quantity
182
 
183
+ cart_ref.child(product_id).set({
184
+ 'quantity': new_quantity,
185
+ 'price': product['price'],
186
+ 'name': product['name']
187
  })
188
+
189
+ return jsonify({'success': True})
190
+
191
  except Exception as e:
192
+ return jsonify({'error': str(e)}), 500
 
 
 
 
193
 
194
+ @app.route('/api/orders', methods=['POST'])
195
+ def create_order():
196
  try:
 
197
  data = request.get_json()
198
+ auth_header = request.headers.get('Authorization')
199
+ uid = verify_token(auth_header.split(' ')[1]) if auth_header else None
200
+
201
+ cart_id = uid if uid else f'session_{data["session_id"]}'
202
+ cart_ref = db.reference(f'carts/{cart_id}')
203
+ cart = cart_ref.get()
204
+
205
+ if not cart:
206
+ return jsonify({'error': 'Cart is empty'}), 400
207
+
208
+ # Validate stock and calculate total
209
+ total = 0
210
+ items = []
211
+ for product_id, item in cart.items():
212
+ product = db.reference(f'products/{product_id}').get()
213
+ if not product or not product.get('active', False):
214
+ return jsonify({'error': f'Product {product_id} not available'}), 400
215
+ if product['stock'] < item['quantity']:
216
+ return jsonify({'error': f'Insufficient stock for {product["name"]}'}), 400
217
+
218
+ total += item['quantity'] * product['price']
219
+ items.append({
220
+ 'product_id': product_id,
221
+ 'quantity': item['quantity'],
222
+ 'price': product['price'],
223
+ 'name': product['name']
224
+ })
225
+
226
+ # Update stock
227
+ db.reference(f'products/{product_id}').update({
228
+ 'stock': product['stock'] - item['quantity']
229
+ })
230
+
231
+ # Create order
232
+ order_ref = db.reference('orders').push({
233
+ 'user_id': uid,
234
+ 'session_id': None if uid else data.get('session_id'),
235
+ 'items': items,
236
+ 'total': total,
237
+ 'status': 'pending',
238
+ 'shipping_address': data.get('shipping_address'),
239
+ 'contact_email': data.get('contact_email'),
240
+ 'created_at': datetime.now(pytz.UTC).isoformat()
241
+ })
242
+
243
+ # Clear cart
244
+ cart_ref.delete()
245
+
246
+ return jsonify({
247
+ 'success': True,
248
+ 'order_id': order_ref.key
249
+ })
250
+
251
  except Exception as e:
252
+ return jsonify({'error': str(e)}), 500
 
253
 
254
+ # AI Reporting
255
+ @app.route('/api/admin/reports', methods=['POST'])
256
+ def generate_report():
257
  try:
258
  verify_admin(request.headers.get('Authorization', ''))
259
  data = request.get_json()
260
+ start_date = datetime.fromisoformat(data['start_date'])
261
+ end_date = datetime.fromisoformat(data['end_date'])
262
+
263
+ # Get orders within date range
264
+ orders_ref = db.reference('orders')
265
+ orders = orders_ref.get() or {}
266
+
267
+ filtered_orders = {
268
+ k: v for k, v in orders.items()
269
+ if start_date <= datetime.fromisoformat(v['created_at']) <= end_date
270
+ }
271
+
272
+ prompt = f"""Analyze this e-commerce data for medical PPE products and generate a comprehensive business report.
273
+ Focus on:
274
+ 1. Total sales and revenue
275
+ 2. Popular products and categories
276
+ 3. Customer behavior patterns
277
+ 4. Stock management recommendations
278
+ 5. Market trends and insights
279
+
280
+ Data period: {start_date.date()} to {end_date.date()}"""
281
+
282
+ model = configure_gemini()
283
+ response = model.generate_content([prompt, json.dumps(filtered_orders)])
284
 
285
  return jsonify({
286
+ 'report': response.text,
287
+ 'data': {
288
+ 'total_orders': len(filtered_orders),
289
+ 'total_revenue': sum(order['total'] for order in filtered_orders.values())
290
+ }
 
291
  })
292
+
293
  except Exception as e:
294
+ return jsonify({'error': str(e)}), 500
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
295
 
296
+ # Product Browsing
297
+ @app.route('/api/products', methods=['GET'])
298
+ def get_products():
299
  try:
300
+ category = request.args.get('category')
301
+ search = request.args.get('search', '').lower()
302
+
303
+ products_ref = db.reference('products')
304
+ products = products_ref.get() or {}
305
+
306
+ filtered_products = []
307
+ for product_id, product in products.items():
308
+ if not product.get('active', False):
309
+ continue
310
+ if category and product['category'] != category:
311
+ continue
312
+ if search and search not in product['name'].lower():
313
+ continue
314
+
315
+ filtered_products.append({
316
+ 'id': product_id,
317
+ **product
318
+ })
319
+
320
+ return jsonify(filtered_products)
321
+
 
 
 
 
322
  except Exception as e:
 
323
  return jsonify({'error': str(e)}), 500
324
 
325
+ # User Order History
326
+ @app.route('/api/user/orders', methods=['GET'])
327
+ def get_user_orders():
328
  try:
329
+ auth_header = request.headers.get('Authorization')
330
+ if not auth_header:
331
+ return jsonify({'error': 'Authorization required'}), 401
332
+
333
+ uid = verify_token(auth_header.split(' ')[1])
334
+ if not uid:
335
+ return jsonify({'error': 'Invalid token'}), 401
336
+
337
+ orders_ref = db.reference('orders')
338
+ orders = orders_ref.order_by_child('user_id').equal_to(uid).get() or {}
339
+
340
+ return jsonify([{
341
+ 'id': order_id,
342
+ **order_data
343
+ } for order_id, order_data in orders.items()])
344
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
345
  except Exception as e:
 
346
  return jsonify({'error': str(e)}), 500
347
 
348
+ # Admin Dashboard Data
349
+ @app.route('/api/admin/dashboard', methods=['GET'])
350
+ def get_admin_dashboard():
 
 
 
351
  try:
352
  verify_admin(request.headers.get('Authorization', ''))
353
 
354
+ # Get recent orders
355
+ orders_ref = db.reference('orders')
356
+ recent_orders = orders_ref.order_by_child('created_at').limit_to_last(10).get() or {}
357
+
358
+ # Get low stock products
359
+ products_ref = db.reference('products')
360
+ products = products_ref.get() or {}
361
+ low_stock = [
362
+ {'id': pid, **p}
363
+ for pid, p in products.items()
364
+ if p.get('active', False) and p.get('stock', 0) < 10
365
+ ]
366
 
367
+ # Calculate statistics
368
+ total_revenue = sum(order['total'] for order in recent_orders.values())
369
+ total_orders = len(recent_orders)
 
 
370
 
371
  return jsonify({
372
+ 'recent_orders': recent_orders,
373
+ 'low_stock_products': low_stock,
374
+ 'statistics': {
375
+ 'total_revenue': total_revenue,
376
+ 'total_orders': total_orders,
377
+ 'total_products': len(products),
378
+ 'low_stock_count': len(low_stock)
379
+ }
380
  })
381
+
382
  except Exception as e:
383
  return jsonify({'error': str(e)}), 500
384
 
385
  if __name__ == '__main__':
386
+ app.run(debug=True, host="0.0.0.0", port=7860)