rairo commited on
Commit
9b7a78d
·
verified ·
1 Parent(s): 001f680

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +99 -69
main.py CHANGED
@@ -2,10 +2,10 @@ import os
2
  import io
3
  import json
4
  import hashlib
5
- from datetime import datetime, time
6
  from PIL import Image
7
  import pytz
8
- from flask import Flask, request, jsonify
9
  from flask_cors import CORS
10
  import google.generativeai as genai
11
  import firebase_admin
@@ -13,7 +13,6 @@ from firebase_admin import credentials, db, storage, auth
13
  import pandas as pd
14
  import requests
15
  from urllib.parse import urlparse, unquote
16
- from datetime import datetime, timedelta
17
 
18
  app = Flask(__name__)
19
  CORS(app)
@@ -29,8 +28,6 @@ bucket = storage.bucket()
29
  api_key = os.environ['Gemini']
30
  FIREBASE_API_KEY = os.environ.get('FIREBASE_API_KEY')
31
 
32
-
33
-
34
  # Helper functions
35
  def configure_gemini():
36
  genai.configure(api_key=api_key)
@@ -74,7 +71,7 @@ def process_receipt(model, image):
74
  response = model.generate_content([prompt, image])
75
  return response.text
76
  except Exception as e:
77
- st.error(f"Gemini error: {str(e)}")
78
  return "{}"
79
 
80
 
@@ -86,22 +83,18 @@ def generate_report():
86
  Make sure the report is in plain text"""
87
  model = configure_gemini()
88
  try:
89
- # Get the transaction data from the request body
90
-
91
  transaction_data = request.get_json()
92
  transaction_json_string = json.dumps(transaction_data['transactions'])
93
-
94
- # Assuming model is defined/imported elsewhere in your Flask app
95
  response = model.generate_content([prompt, transaction_json_string])
96
  report = response.text
97
- return jsonify({"report": report}) # Wrap the report in an object
98
-
99
  except Exception as e:
100
  return jsonify({"error": str(e)}), 500
 
101
  # ========================================
102
  # Authentication Endpoints
103
  # ========================================
104
-
105
 
106
  # ========================================
107
  # Receipt Processing Endpoint (unchanged)
@@ -161,7 +154,7 @@ def process_receipt_endpoint():
161
  )
162
 
163
  except Exception as e:
164
- print(image)
165
  return jsonify({'error': str(e)}), 500
166
 
167
  def handle_manual_entry(uid, user_ref, user_data):
@@ -190,15 +183,13 @@ def validate_and_save_transaction(uid, user_data, data, file_hash, image_bytes,
190
  receipt_number = data.get('receipt_number')
191
  items = data.get('items', [])
192
 
193
- # Fetch existing transactions with the same receipt_number
194
  existing_transactions_with_receipt = transactions_ref.order_by_child('receipt_number').equal_to(receipt_number).get()
195
 
196
  if existing_transactions_with_receipt:
197
  for transaction_id, existing_transaction_data in existing_transactions_with_receipt.items():
198
- # Compare items lists - assuming order doesn't matter, so sorting for comparison
199
  existing_items = sorted(existing_transaction_data.get('items', []))
200
  current_items = sorted(items)
201
-
202
  if existing_items == current_items:
203
  return jsonify({'error': f"Transaction with Receipt #{receipt_number} and identical items already exists"}), 400
204
 
@@ -218,7 +209,6 @@ def validate_and_save_transaction(uid, user_data, data, file_hash, image_bytes,
218
  new_remaining = user_data['remaining_cash'] - total
219
  db.reference(f'users/{uid}').update({'remaining_cash': new_remaining})
220
 
221
- # Save transaction
222
  transaction_data = {
223
  'uid': uid,
224
  'total': total,
@@ -242,7 +232,6 @@ def validate_and_save_transaction(uid, user_data, data, file_hash, image_bytes,
242
  # Data Endpoints for Visualizations
243
  # ========================================
244
 
245
-
246
  @app.route('/api/user/spending-overview', methods=['GET'])
247
  def get_spending_overview():
248
  try:
@@ -256,17 +245,13 @@ def get_spending_overview():
256
  'recent_transactions': []
257
  })
258
 
259
- # Create dataframe
260
  df = pd.DataFrame.from_dict(transactions, orient='index')
261
-
262
- # Check if dataframe is empty
263
  if df.empty:
264
  return jsonify({
265
  'daily_spending': [],
266
  'recent_transactions': []
267
  })
268
 
269
- # Check if all the data is here, otherwise we delete the data.
270
  df.dropna(subset=['uid','total','items','date', 'receipt_number', 'timestamp','hash', 'manual_entry'], inplace=True)
271
  if df.empty:
272
  return jsonify({
@@ -293,9 +278,6 @@ def get_spending_overview():
293
  except Exception as e:
294
  return jsonify({'error': str(e)}), 500
295
 
296
-
297
- # ... (previous imports remain the same)
298
-
299
  # ========================================
300
  # Modified verify_admin function (now checks database is_admin flag)
301
  # ========================================
@@ -313,25 +295,23 @@ def verify_admin(auth_header):
313
  if not user_data or not user_data.get('is_admin', False):
314
  raise PermissionError('Admin access required')
315
 
316
- # **Set custom claim here AFTER admin verification is successful:**
317
  try:
318
  auth.set_custom_user_claims(uid, {"admin": True})
319
- print(f"Custom admin claim set for user {uid}") # Optional log
320
  except Exception as e:
321
- print(f"Error setting custom admin claim: {e}") # Log any errors
322
- raise PermissionError('Error setting admin claim, but admin verified') # Or handle error as needed
323
 
324
- return uid # Return uid as before
325
 
326
  # ========================================
327
- # New Admin Endpoints
328
  # ========================================
329
  @app.route('/api/admin/overview', methods=['GET'])
330
  def get_admin_overview():
331
  try:
332
  verify_admin(request.headers.get('Authorization', ''))
333
 
334
- # Get all users
335
  users_ref = db.reference('users')
336
  all_users = users_ref.get() or {}
337
  users_list = []
@@ -350,7 +330,6 @@ def get_admin_overview():
350
  'is_admin': user_data.get('is_admin', False)
351
  })
352
 
353
- # Get all transactions
354
  transactions_ref = db.reference('transactions')
355
  all_transactions = transactions_ref.get() or {}
356
  transactions_list = [{'id': tid, **data} for tid, data in all_transactions.items()]
@@ -373,18 +352,16 @@ def create_user():
373
  verify_admin(request.headers.get('Authorization', ''))
374
  data = request.get_json()
375
 
376
- # Create Firebase auth user
377
  user = auth.create_user(
378
  email=data['email'],
379
  password=data['password']
380
  )
381
 
382
- # Create database user record
383
  user_ref = db.reference(f'users/{user.uid}')
384
  user_data = {
385
  'daily_cash': data.get('daily_cash', 100),
386
  'remaining_cash': data.get('daily_cash', 100),
387
- 'last_reset': '2000-01-01T00:00:00+00:00', # Force reset on next check
388
  'is_admin': data.get('is_admin', False)
389
  }
390
  user_ref.set(user_data)
@@ -414,8 +391,6 @@ def update_user_limit(uid):
414
  return jsonify({'error': 'User not found'}), 404
415
 
416
  updates = {'daily_cash': new_limit}
417
-
418
- # Adjust remaining cash if it exceeds new limit
419
  current_remaining = user_data.get('remaining_cash', new_limit)
420
  if current_remaining > new_limit:
421
  updates['remaining_cash'] = new_limit
@@ -430,9 +405,85 @@ def update_user_limit(uid):
430
  except Exception as e:
431
  return jsonify({'error': str(e)}), 400
432
 
 
 
 
433
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
434
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
435
  # User management endpoint for profile
 
 
436
  @app.route('/api/user/profile', methods=['GET'])
437
  def get_user_profile():
438
  try:
@@ -454,26 +505,17 @@ def get_user_profile():
454
  user_data = db.reference(f'users/{uid}').get()
455
  return jsonify({'error': str(e)+ f'user data: {user_data}'}), 500
456
 
 
457
  # Receipt media endpoints
458
-
459
 
460
  def get_blob_from_image_url(image_url):
461
- """
462
- Extracts and returns the blob path from a Firebase Storage URL.
463
- Supports URLs of the form:
464
- https://storage.googleapis.com/<bucket>/<blob_path>
465
- or the default Firebase URL format.
466
- """
467
  parsed = urlparse(image_url)
468
- # If URL is in the storage.googleapis.com format:
469
  if parsed.netloc == "storage.googleapis.com":
470
- # Expected path: /<bucket>/<blob_path>
471
  parts = parsed.path.strip("/").split("/", 1)
472
  if len(parts) == 2:
473
  bucket_name, blob_path = parts
474
- # (Optionally, check if bucket_name matches our bucket.name)
475
  return blob_path
476
- # Otherwise, try the default format:
477
  prefix = f"/v0/b/{bucket.name}/o/"
478
  if parsed.path.startswith(prefix):
479
  encoded_blob_path = parsed.path[len(prefix):]
@@ -498,17 +540,14 @@ def view_receipt(transaction_id):
498
  if not blob_path:
499
  return jsonify({'error': 'Could not determine blob path from URL'}), 500
500
 
501
- print(f"Blob path for view: {blob_path}") # Debug log
502
-
503
  blob = bucket.blob(blob_path)
504
  if not blob.exists():
505
  print("Blob does not exist at path:", blob_path)
506
  return jsonify({'error': 'Blob not found'}), 404
507
 
508
  signed_url = blob.generate_signed_url(expiration=timedelta(minutes=10))
509
- print(f"Signed URL for view: {signed_url}") # Debug log
510
  r = requests.get(signed_url)
511
- print(f"View endpoint response status: {r.status_code}") # Debug log
512
  if r.status_code != 200:
513
  return jsonify({'error': 'Unable to fetch image from storage'}), 500
514
 
@@ -534,17 +573,14 @@ def download_receipt(transaction_id):
534
  if not blob_path:
535
  return jsonify({'error': 'Could not determine blob path from URL'}), 500
536
 
537
- print(f"Blob path for download: {blob_path}") # Debug log
538
-
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
- print(f"Signed URL for download: {signed_url}") # Debug log
546
  r = requests.get(signed_url)
547
- print(f"Download endpoint response status: {r.status_code}") # Debug log
548
  if r.status_code != 200:
549
  return jsonify({'error': 'Unable to fetch image from storage'}), 500
550
 
@@ -552,34 +588,29 @@ def download_receipt(transaction_id):
552
  io.BytesIO(r.content),
553
  mimetype='image/jpeg',
554
  as_attachment=True,
555
- attachment_filename='receipt.jpg' # Use download_name if using Flask 2.x
556
  )
557
  except Exception as e:
558
  print(f"Download receipt error: {str(e)}")
559
  return jsonify({'error': str(e)}), 500
560
 
561
-
562
-
563
- # delete users
564
 
565
  @app.route('/api/admin/users/<string:uid>', methods=['DELETE'])
566
  def delete_user(uid):
567
  try:
568
  verify_admin(request.headers.get('Authorization', ''))
569
 
570
- # Verify the user exists
571
  try:
572
  user = auth.get_user(uid)
573
  except auth.UserNotFoundError:
574
  return jsonify({'error': 'User not found'}), 404
575
 
576
- # Delete authentication user
577
  auth.delete_user(uid)
578
-
579
- # Delete database record
580
  db.reference(f'users/{uid}').delete()
581
 
582
- # Delete user's transactions
583
  transactions_ref = db.reference('transactions')
584
  user_transactions = transactions_ref.order_by_child('uid').equal_to(uid).get()
585
  if user_transactions:
@@ -593,6 +624,5 @@ def delete_user(uid):
593
  except Exception as e:
594
  return jsonify({'error': str(e)}), 500
595
 
596
-
597
  if __name__ == '__main__':
598
- 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, time, timedelta
6
  from PIL import Image
7
  import pytz
8
+ from flask import Flask, request, jsonify, send_file
9
  from flask_cors import CORS
10
  import google.generativeai as genai
11
  import firebase_admin
 
13
  import pandas as pd
14
  import requests
15
  from urllib.parse import urlparse, unquote
 
16
 
17
  app = Flask(__name__)
18
  CORS(app)
 
28
  api_key = os.environ['Gemini']
29
  FIREBASE_API_KEY = os.environ.get('FIREBASE_API_KEY')
30
 
 
 
31
  # Helper functions
32
  def configure_gemini():
33
  genai.configure(api_key=api_key)
 
71
  response = model.generate_content([prompt, image])
72
  return response.text
73
  except Exception as e:
74
+ print(f"Gemini error: {str(e)}")
75
  return "{}"
76
 
77
 
 
83
  Make sure the report is in plain text"""
84
  model = configure_gemini()
85
  try:
 
 
86
  transaction_data = request.get_json()
87
  transaction_json_string = json.dumps(transaction_data['transactions'])
 
 
88
  response = model.generate_content([prompt, transaction_json_string])
89
  report = response.text
90
+ return jsonify({"report": report})
 
91
  except Exception as e:
92
  return jsonify({"error": str(e)}), 500
93
+
94
  # ========================================
95
  # Authentication Endpoints
96
  # ========================================
97
+ # (Any existing authentication endpoints remain unchanged)
98
 
99
  # ========================================
100
  # Receipt Processing Endpoint (unchanged)
 
154
  )
155
 
156
  except Exception as e:
157
+ print(e)
158
  return jsonify({'error': str(e)}), 500
159
 
160
  def handle_manual_entry(uid, user_ref, user_data):
 
183
  receipt_number = data.get('receipt_number')
184
  items = data.get('items', [])
185
 
186
+ # Check for duplicate transactions based on receipt number and items.
187
  existing_transactions_with_receipt = transactions_ref.order_by_child('receipt_number').equal_to(receipt_number).get()
188
 
189
  if existing_transactions_with_receipt:
190
  for transaction_id, existing_transaction_data in existing_transactions_with_receipt.items():
 
191
  existing_items = sorted(existing_transaction_data.get('items', []))
192
  current_items = sorted(items)
 
193
  if existing_items == current_items:
194
  return jsonify({'error': f"Transaction with Receipt #{receipt_number} and identical items already exists"}), 400
195
 
 
209
  new_remaining = user_data['remaining_cash'] - total
210
  db.reference(f'users/{uid}').update({'remaining_cash': new_remaining})
211
 
 
212
  transaction_data = {
213
  'uid': uid,
214
  'total': total,
 
232
  # Data Endpoints for Visualizations
233
  # ========================================
234
 
 
235
  @app.route('/api/user/spending-overview', methods=['GET'])
236
  def get_spending_overview():
237
  try:
 
245
  'recent_transactions': []
246
  })
247
 
 
248
  df = pd.DataFrame.from_dict(transactions, orient='index')
 
 
249
  if df.empty:
250
  return jsonify({
251
  'daily_spending': [],
252
  'recent_transactions': []
253
  })
254
 
 
255
  df.dropna(subset=['uid','total','items','date', 'receipt_number', 'timestamp','hash', 'manual_entry'], inplace=True)
256
  if df.empty:
257
  return jsonify({
 
278
  except Exception as e:
279
  return jsonify({'error': str(e)}), 500
280
 
 
 
 
281
  # ========================================
282
  # Modified verify_admin function (now checks database is_admin flag)
283
  # ========================================
 
295
  if not user_data or not user_data.get('is_admin', False):
296
  raise PermissionError('Admin access required')
297
 
 
298
  try:
299
  auth.set_custom_user_claims(uid, {"admin": True})
300
+ print(f"Custom admin claim set for user {uid}")
301
  except Exception as e:
302
+ print(f"Error setting custom admin claim: {e}")
303
+ raise PermissionError('Error setting admin claim, but admin verified')
304
 
305
+ return uid
306
 
307
  # ========================================
308
+ # Existing Admin Endpoints
309
  # ========================================
310
  @app.route('/api/admin/overview', methods=['GET'])
311
  def get_admin_overview():
312
  try:
313
  verify_admin(request.headers.get('Authorization', ''))
314
 
 
315
  users_ref = db.reference('users')
316
  all_users = users_ref.get() or {}
317
  users_list = []
 
330
  'is_admin': user_data.get('is_admin', False)
331
  })
332
 
 
333
  transactions_ref = db.reference('transactions')
334
  all_transactions = transactions_ref.get() or {}
335
  transactions_list = [{'id': tid, **data} for tid, data in all_transactions.items()]
 
352
  verify_admin(request.headers.get('Authorization', ''))
353
  data = request.get_json()
354
 
 
355
  user = auth.create_user(
356
  email=data['email'],
357
  password=data['password']
358
  )
359
 
 
360
  user_ref = db.reference(f'users/{user.uid}')
361
  user_data = {
362
  'daily_cash': data.get('daily_cash', 100),
363
  'remaining_cash': data.get('daily_cash', 100),
364
+ 'last_reset': '2000-01-01T00:00:00+00:00',
365
  'is_admin': data.get('is_admin', False)
366
  }
367
  user_ref.set(user_data)
 
391
  return jsonify({'error': 'User not found'}), 404
392
 
393
  updates = {'daily_cash': new_limit}
 
 
394
  current_remaining = user_data.get('remaining_cash', new_limit)
395
  if current_remaining > new_limit:
396
  updates['remaining_cash'] = new_limit
 
405
  except Exception as e:
406
  return jsonify({'error': str(e)}), 400
407
 
408
+ # ========================================
409
+ # New Admin Endpoints for Updating Remaining Cash, Setting Cash Limits, and Resetting Password
410
+ # ========================================
411
 
412
+ @app.route('/api/admin/users/<string:uid>/remaining-cash', methods=['PUT'])
413
+ def update_remaining_cash(uid):
414
+ try:
415
+ verify_admin(request.headers.get('Authorization', ''))
416
+ data = request.get_json()
417
+ if 'remaining_cash' not in data:
418
+ return jsonify({'error': 'remaining_cash is required'}), 400
419
+ new_remaining_cash = float(data['remaining_cash'])
420
+ user_ref = db.reference(f'users/{uid}')
421
+ user_data = user_ref.get()
422
+ if not user_data:
423
+ return jsonify({'error': 'User not found'}), 404
424
+
425
+ user_ref.update({'remaining_cash': new_remaining_cash})
426
+ return jsonify({'success': True, 'remaining_cash': new_remaining_cash})
427
+ except Exception as e:
428
+ return jsonify({'error': str(e)}), 400
429
 
430
+ @app.route('/api/admin/users/<string:uid>/cash-limit-period', methods=['PUT'])
431
+ def update_cash_limit_period(uid):
432
+ try:
433
+ verify_admin(request.headers.get('Authorization', ''))
434
+ data = request.get_json()
435
+ start_date = data.get('start_date')
436
+ end_date = data.get('end_date')
437
+ cash_limit = data.get('cash_limit')
438
+ if not (start_date and end_date and cash_limit is not None):
439
+ return jsonify({'error': 'start_date, end_date, and cash_limit are required'}), 400
440
+ try:
441
+ datetime.strptime(start_date, '%Y-%m-%d')
442
+ datetime.strptime(end_date, '%Y-%m-%d')
443
+ except Exception as e:
444
+ return jsonify({'error': 'Invalid date format. Use YYYY-MM-DD for start_date and end_date'}), 400
445
+
446
+ user_ref = db.reference(f'users/{uid}')
447
+ user_data = user_ref.get()
448
+ if not user_data:
449
+ return jsonify({'error': 'User not found'}), 404
450
+
451
+ user_ref.update({
452
+ 'cash_limit_period': {
453
+ 'start_date': start_date,
454
+ 'end_date': end_date,
455
+ 'limit': float(cash_limit)
456
+ }
457
+ })
458
+
459
+ return jsonify({
460
+ 'success': True,
461
+ 'cash_limit_period': {
462
+ 'start_date': start_date,
463
+ 'end_date': end_date,
464
+ 'limit': float(cash_limit)
465
+ }
466
+ })
467
+ except Exception as e:
468
+ return jsonify({'error': str(e)}), 400
469
+
470
+ @app.route('/api/admin/users/<string:uid>/reset-password', methods=['PUT'])
471
+ def admin_reset_password(uid):
472
+ try:
473
+ verify_admin(request.headers.get('Authorization', ''))
474
+ data = request.get_json()
475
+ new_password = data.get('new_password')
476
+ if not new_password:
477
+ return jsonify({'error': 'new_password is required'}), 400
478
+ auth.update_user(uid, password=new_password)
479
+ return jsonify({'success': True, 'message': 'Password reset successfully'})
480
+ except Exception as e:
481
+ return jsonify({'error': str(e)}), 400
482
+
483
+ # ========================================
484
  # User management endpoint for profile
485
+ # ========================================
486
+
487
  @app.route('/api/user/profile', methods=['GET'])
488
  def get_user_profile():
489
  try:
 
505
  user_data = db.reference(f'users/{uid}').get()
506
  return jsonify({'error': str(e)+ f'user data: {user_data}'}), 500
507
 
508
+ # ========================================
509
  # Receipt media endpoints
510
+ # ========================================
511
 
512
  def get_blob_from_image_url(image_url):
 
 
 
 
 
 
513
  parsed = urlparse(image_url)
 
514
  if parsed.netloc == "storage.googleapis.com":
 
515
  parts = parsed.path.strip("/").split("/", 1)
516
  if len(parts) == 2:
517
  bucket_name, blob_path = parts
 
518
  return blob_path
 
519
  prefix = f"/v0/b/{bucket.name}/o/"
520
  if parsed.path.startswith(prefix):
521
  encoded_blob_path = parsed.path[len(prefix):]
 
540
  if not blob_path:
541
  return jsonify({'error': 'Could not determine blob path from URL'}), 500
542
 
543
+ print(f"Blob path for view: {blob_path}")
 
544
  blob = bucket.blob(blob_path)
545
  if not blob.exists():
546
  print("Blob does not exist at path:", blob_path)
547
  return jsonify({'error': 'Blob not found'}), 404
548
 
549
  signed_url = blob.generate_signed_url(expiration=timedelta(minutes=10))
 
550
  r = requests.get(signed_url)
 
551
  if r.status_code != 200:
552
  return jsonify({'error': 'Unable to fetch image from storage'}), 500
553
 
 
573
  if not blob_path:
574
  return jsonify({'error': 'Could not determine blob path from URL'}), 500
575
 
576
+ print(f"Blob path for download: {blob_path}")
 
577
  blob = bucket.blob(blob_path)
578
  if not blob.exists():
579
  print("Blob does not exist at path:", blob_path)
580
  return jsonify({'error': 'Blob not found'}), 404
581
 
582
  signed_url = blob.generate_signed_url(expiration=timedelta(minutes=10))
 
583
  r = requests.get(signed_url)
 
584
  if r.status_code != 200:
585
  return jsonify({'error': 'Unable to fetch image from storage'}), 500
586
 
 
588
  io.BytesIO(r.content),
589
  mimetype='image/jpeg',
590
  as_attachment=True,
591
+ attachment_filename='receipt.jpg'
592
  )
593
  except Exception as e:
594
  print(f"Download receipt error: {str(e)}")
595
  return jsonify({'error': str(e)}), 500
596
 
597
+ # ========================================
598
+ # Delete user endpoint
599
+ # ========================================
600
 
601
  @app.route('/api/admin/users/<string:uid>', methods=['DELETE'])
602
  def delete_user(uid):
603
  try:
604
  verify_admin(request.headers.get('Authorization', ''))
605
 
 
606
  try:
607
  user = auth.get_user(uid)
608
  except auth.UserNotFoundError:
609
  return jsonify({'error': 'User not found'}), 404
610
 
 
611
  auth.delete_user(uid)
 
 
612
  db.reference(f'users/{uid}').delete()
613
 
 
614
  transactions_ref = db.reference('transactions')
615
  user_transactions = transactions_ref.order_by_child('uid').equal_to(uid).get()
616
  if user_transactions:
 
624
  except Exception as e:
625
  return jsonify({'error': str(e)}), 500
626
 
 
627
  if __name__ == '__main__':
628
+ app.run(debug=True, host="0.0.0.0", port=7860)