rairo commited on
Commit
d808a4a
·
verified ·
1 Parent(s): 20244a3

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +152 -234
main.py CHANGED
@@ -4,13 +4,13 @@ import json
4
  import hashlib
5
  from datetime import datetime, time
6
  from PIL import Image
7
-
8
  from flask import Flask, request, jsonify
9
  from flask_cors import CORS
10
  import google.generativeai as genai
11
  import firebase_admin
12
  from firebase_admin import credentials, db, storage, auth
13
- import pytz
14
 
15
  app = Flask(__name__)
16
  CORS(app)
@@ -18,93 +18,18 @@ CORS(app)
18
  # Firebase initialization
19
  cred = credentials.Certificate('mydateproject-e7994-firebase-adminsdk-mpa9a-2fd9c32a98.json')
20
  firebase_admin.initialize_app(cred, {
21
- 'databaseURL': 'your-database-url',
22
- 'storageBucket': 'your-bucket-name.appspot.com'
23
  })
24
 
25
- # Initialize Firebase storage bucket
26
  bucket = storage.bucket()
27
-
28
- # Gemini API configuration
29
  api_key = os.environ['Gemini']
30
 
31
-
32
-
33
-
34
- # admin create user.
35
- @app.route('/api/admin/create-user', methods=['POST'])
36
- def create_user():
37
- try:
38
- # Verify admin token
39
- auth_header = request.headers.get('Authorization')
40
- if not auth_header or not auth_header.startswith('Bearer '):
41
- return jsonify({'error': 'No token provided'}), 401
42
-
43
- token = auth_header.split('Bearer ')[1]
44
- admin_uid = verify_token(token)
45
- if not admin_uid:
46
- return jsonify({'error': 'Invalid token'}), 401
47
-
48
- # Verify admin status
49
- admin = auth.get_user(admin_uid)
50
- if not admin.custom_claims or not admin.custom_claims.get('admin'):
51
- return jsonify({'error': 'Unauthorized'}), 403
52
-
53
- # Get user creation data
54
- data = request.json
55
- email = data.get('email')
56
- password = data.get('password')
57
- daily_cash = data.get('daily_cash', 0.0)
58
- is_admin = data.get('is_admin', False)
59
-
60
- if not email or not password:
61
- return jsonify({'error': 'Email and password required'}), 400
62
-
63
- # Create user in Firebase Auth
64
- user = auth.create_user(
65
- email=email,
66
- password=password
67
- )
68
-
69
- # Set admin claim if requested
70
- if is_admin:
71
- auth.set_custom_user_claims(user.uid, {'admin': True})
72
-
73
- # Create user data in Realtime Database
74
- user_ref = db.reference(f'users/{user.uid}')
75
- user_ref.set({
76
- 'email': email,
77
- 'daily_cash': 100,
78
- 'remaining_cash': daily_cash,
79
- 'last_reset': datetime.now(pytz.UTC).isoformat(),
80
- 'is_admin': is_admin
81
- })
82
-
83
- return jsonify({
84
- 'success': True,
85
- 'uid': user.uid,
86
- 'email': email
87
- })
88
-
89
- except Exception as e:
90
- return jsonify({'error': str(e)}), 500
91
-
92
- def configure_gemini(api_key):
93
  genai.configure(api_key=api_key)
94
  return genai.GenerativeModel('gemini-2.0-flash-thinking-exp')
95
 
96
- def process_receipt(model, image):
97
- prompt = """Analyze this image and determine if it's a receipt. If it is a receipt, extract:
98
- - Total amount (as float)
99
- - List of items purchased (array of strings)
100
- - Date of transaction (DD/MM/YYYY format)
101
- - Receipt number (as string)
102
- Return JSON format with keys: is_receipt (boolean), total, items, date, receipt_number.
103
- If not a receipt, return {"is_receipt": false}"""
104
-
105
- response = model.generate_content([prompt, image])
106
- return response.text
107
-
108
  def verify_token(token):
109
  try:
110
  decoded_token = auth.verify_id_token(token)
@@ -115,9 +40,6 @@ def verify_token(token):
115
  def check_daily_reset(user_ref):
116
  try:
117
  user_data = user_ref.get()
118
- if not user_data:
119
- return False
120
-
121
  now = datetime.now(pytz.UTC)
122
  last_reset = datetime.fromisoformat(user_data.get('last_reset', '2000-01-01T00:00:00+00:00'))
123
 
@@ -132,204 +54,200 @@ def check_daily_reset(user_ref):
132
  print(f"Reset error: {str(e)}")
133
  return False
134
 
135
- @app.route('/api/login', methods=['POST'])
136
- def login():
137
- try:
138
- data = request.json
139
- email = data.get('email')
140
- password = data.get('password')
141
-
142
- # Use Firebase Auth REST API to sign in
143
- user = auth.get_user_by_email(email)
144
- return jsonify({
145
- 'uid': user.uid,
146
- 'email': user.email
147
- })
148
- except Exception as e:
149
- return jsonify({'error': str(e)}), 401
150
-
151
  @app.route('/api/process-receipt', methods=['POST'])
152
  def process_receipt_endpoint():
153
  try:
154
- # Verify authentication
155
  auth_header = request.headers.get('Authorization')
156
- if not auth_header or not auth_header.startswith('Bearer '):
157
- return jsonify({'error': 'No token provided'}), 401
158
 
159
- token = auth_header.split('Bearer ')[1]
160
- uid = verify_token(token)
161
  if not uid:
162
  return jsonify({'error': 'Invalid token'}), 401
163
 
164
- if 'receipt' not in request.files:
165
- return jsonify({'error': 'No file uploaded'}), 400
166
-
167
- file = request.files['receipt']
168
- if file.filename == '':
169
- return jsonify({'error': 'No file selected'}), 400
170
-
171
- # Get user reference
172
  user_ref = db.reference(f'users/{uid}')
173
  user_data = user_ref.get()
174
-
175
- if not user_data:
176
- return jsonify({'error': 'User not found'}), 404
177
-
178
- # Check daily reset
179
  check_daily_reset(user_ref)
180
-
181
- # Read file and compute hash
 
 
 
 
 
 
 
 
182
  image_bytes = file.read()
183
  file_hash = hashlib.md5(image_bytes).hexdigest()
184
 
185
- # Check for duplicate receipt
186
  transactions_ref = db.reference('transactions')
187
- existing_transactions = transactions_ref.order_by_child('hash').equal_to(file_hash).get()
188
- if existing_transactions:
189
  return jsonify({'error': 'Receipt already processed'}), 400
190
 
191
- # Process image with Gemini
192
  image = Image.open(io.BytesIO(image_bytes))
193
- model = configure_gemini(api_key)
194
  result_text = process_receipt(model, image)
195
 
196
- # Parse Gemini response
197
- json_str = result_text[result_text.find('{'):result_text.rfind('}')+1]
198
- data = json.loads(json_str)
 
 
199
 
200
  if not data.get('is_receipt', False):
201
  return jsonify({'error': 'Not a valid receipt'}), 400
202
 
203
- # Validate total against remaining cash
204
- total = float(data.get('total', 0))
205
- if total > user_data['remaining_cash']:
206
- return jsonify({'error': 'Insufficient funds'}), 400
207
-
208
- # Upload image to Firebase Storage
209
- timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
210
- blob = bucket.blob(f'receipts/{uid}/{timestamp}_{file_hash}.jpg')
211
- blob.upload_from_string(image_bytes, content_type='image/jpeg')
212
 
213
- # Update user's remaining cash
214
- new_remaining = user_data['remaining_cash'] - total
215
- user_ref.update({'remaining_cash': new_remaining})
216
 
217
- # Save transaction
218
- transaction_data = {
219
- 'uid': uid,
220
- 'total': total,
221
- 'items': data.get('items', []),
222
- 'date': data.get('date'),
223
- 'receipt_number': data.get('receipt_number'),
224
- 'timestamp': datetime.now().isoformat(),
225
- 'hash': file_hash,
226
- 'image_url': blob.public_url
227
  }
228
-
229
- new_transaction_ref = transactions_ref.push(transaction_data)
230
-
231
- return jsonify({
232
- 'success': True,
233
- 'transaction_id': new_transaction_ref.key,
234
- 'remaining_cash': new_remaining,
235
- **transaction_data
236
- })
237
 
 
 
 
 
 
 
 
 
238
  except Exception as e:
239
- return jsonify({'error': str(e)}), 500
 
 
 
 
 
 
 
 
 
 
 
240
 
241
- @app.route('/api/user/transactions', methods=['GET'])
242
- def get_user_transactions():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
243
  try:
244
- auth_header = request.headers.get('Authorization')
245
- if not auth_header or not auth_header.startswith('Bearer '):
246
- return jsonify({'error': 'No token provided'}), 401
247
 
248
- token = auth_header.split('Bearer ')[1]
249
- uid = verify_token(token)
250
- if not uid:
251
- return jsonify({'error': 'Invalid token'}), 401
252
 
253
- transactions_ref = db.reference('transactions')
254
- user_transactions = transactions_ref.order_by_child('uid').equal_to(uid).get()
255
 
256
- return jsonify(user_transactions)
 
 
 
 
 
257
  except Exception as e:
258
  return jsonify({'error': str(e)}), 500
259
 
260
- @app.route('/api/admin/users', methods=['GET'])
261
- def get_all_users():
262
  try:
263
- auth_header = request.headers.get('Authorization')
264
- if not auth_header or not auth_header.startswith('Bearer '):
265
- return jsonify({'error': 'No token provided'}), 401
266
 
267
- token = auth_header.split('Bearer ')[1]
268
- uid = verify_token(token)
269
- if not uid:
270
- return jsonify({'error': 'Invalid token'}), 401
271
-
272
- # Verify admin status
273
- user = auth.get_user(uid)
274
- if not user.custom_claims or not user.custom_claims.get('admin'):
275
- return jsonify({'error': 'Unauthorized'}), 403
276
-
277
  users_ref = db.reference('users')
278
- users = users_ref.get()
279
 
280
- return jsonify(users)
281
- except Exception as e:
282
- return jsonify({'error': str(e)}), 500
283
-
284
- @app.route('/api/admin/update-user', methods=['POST'])
285
- def update_user():
286
- try:
287
- auth_header = request.headers.get('Authorization')
288
- if not auth_header or not auth_header.startswith('Bearer '):
289
- return jsonify({'error': 'No token provided'}), 401
290
 
291
- token = auth_header.split('Bearer ')[1]
292
- admin_uid = verify_token(token)
293
- if not admin_uid:
294
- return jsonify({'error': 'Invalid token'}), 401
295
-
296
- # Verify admin status
297
- admin = auth.get_user(admin_uid)
298
- if not admin.custom_claims or not admin.custom_claims.get('admin'):
299
- return jsonify({'error': 'Unauthorized'}), 403
300
-
301
- data = request.json
302
- user_id = data.get('uid')
303
- updates = data.get('updates', {})
304
-
305
- user_ref = db.reference(f'users/{user_id}')
306
- user_ref.update(updates)
307
-
308
- return jsonify({'success': True})
309
  except Exception as e:
310
  return jsonify({'error': str(e)}), 500
311
 
312
- @app.route('/api/admin/transactions', methods=['GET'])
313
- def get_all_transactions():
314
- try:
315
- auth_header = request.headers.get('Authorization')
316
- if not auth_header or not auth_header.startswith('Bearer '):
317
- return jsonify({'error': 'No token provided'}), 401
318
-
319
- token = auth_header.split('Bearer ')[1]
320
- uid = verify_token(token)
321
- if not uid:
322
- return jsonify({'error': 'Invalid token'}), 401
323
 
324
- # Verify admin status
 
 
 
 
325
  user = auth.get_user(uid)
326
- if not user.custom_claims or not user.custom_claims.get('admin'):
327
- return jsonify({'error': 'Unauthorized'}), 403
328
-
329
- transactions_ref = db.reference('transactions')
330
- transactions = transactions_ref.get()
331
 
332
- return jsonify(transactions)
 
 
 
 
 
 
 
333
  except Exception as e:
334
  return jsonify({'error': str(e)}), 500
335
 
 
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
12
  from firebase_admin import credentials, db, storage, auth
13
+ import pandas as pd
14
 
15
  app = Flask(__name__)
16
  CORS(app)
 
18
  # Firebase initialization
19
  cred = credentials.Certificate('mydateproject-e7994-firebase-adminsdk-mpa9a-2fd9c32a98.json')
20
  firebase_admin.initialize_app(cred, {
21
+ 'databaseURL': 'https://mydateproject-e7994.firebaseio.com',
22
+ 'storageBucket': 'mydateproject-e7994.firebasestorage.app'
23
  })
24
 
 
25
  bucket = storage.bucket()
 
 
26
  api_key = os.environ['Gemini']
27
 
28
+ # Helper functions
29
+ def configure_gemini():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  genai.configure(api_key=api_key)
31
  return genai.GenerativeModel('gemini-2.0-flash-thinking-exp')
32
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  def verify_token(token):
34
  try:
35
  decoded_token = auth.verify_id_token(token)
 
40
  def check_daily_reset(user_ref):
41
  try:
42
  user_data = user_ref.get()
 
 
 
43
  now = datetime.now(pytz.UTC)
44
  last_reset = datetime.fromisoformat(user_data.get('last_reset', '2000-01-01T00:00:00+00:00'))
45
 
 
54
  print(f"Reset error: {str(e)}")
55
  return False
56
 
57
+ # Enhanced receipt processing endpoint
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
  @app.route('/api/process-receipt', methods=['POST'])
59
  def process_receipt_endpoint():
60
  try:
 
61
  auth_header = request.headers.get('Authorization')
62
+ token = auth_header.split('Bearer ')[1] if auth_header else None
63
+ uid = verify_token(token) if token else None
64
 
 
 
65
  if not uid:
66
  return jsonify({'error': 'Invalid token'}), 401
67
 
 
 
 
 
 
 
 
 
68
  user_ref = db.reference(f'users/{uid}')
69
  user_data = user_ref.get()
 
 
 
 
 
70
  check_daily_reset(user_ref)
71
+
72
+ # Handle manual entry
73
+ if request.form.get('manual_entry') == 'true':
74
+ return handle_manual_entry(uid, user_ref, user_data)
75
+
76
+ # Handle image processing
77
+ file = request.files.get('receipt')
78
+ if not file:
79
+ return jsonify({'error': 'No file uploaded'}), 400
80
+
81
  image_bytes = file.read()
82
  file_hash = hashlib.md5(image_bytes).hexdigest()
83
 
 
84
  transactions_ref = db.reference('transactions')
85
+ existing = transactions_ref.order_by_child('hash').equal_to(file_hash).get()
86
+ if existing:
87
  return jsonify({'error': 'Receipt already processed'}), 400
88
 
 
89
  image = Image.open(io.BytesIO(image_bytes))
90
+ model = configure_gemini()
91
  result_text = process_receipt(model, image)
92
 
93
+ try:
94
+ json_str = result_text[result_text.find('{'):result_text.rfind('}')+1]
95
+ data = json.loads(json_str)
96
+ except json.JSONDecodeError:
97
+ return jsonify({'error': 'Failed to parse receipt data', 'raw_response': result_text}), 400
98
 
99
  if not data.get('is_receipt', False):
100
  return jsonify({'error': 'Not a valid receipt'}), 400
101
 
102
+ return validate_and_save_transaction(
103
+ uid=uid,
104
+ user_data=user_data,
105
+ data=data,
106
+ file_hash=file_hash,
107
+ image_bytes=image_bytes,
108
+ manual=False
109
+ )
 
110
 
111
+ except Exception as e:
112
+ return jsonify({'error': str(e)}), 500
 
113
 
114
+ def handle_manual_entry(uid, user_ref, user_data):
115
+ try:
116
+ data = {
117
+ 'total': float(request.form.get('total')),
118
+ 'items': [item.strip() for item in request.form.get('items', '').split(',')],
119
+ 'date': request.form.get('date'),
120
+ 'receipt_number': request.form.get('receipt_number'),
121
+ 'is_receipt': True
 
 
122
  }
 
 
 
 
 
 
 
 
 
123
 
124
+ return validate_and_save_transaction(
125
+ uid=uid,
126
+ user_data=user_data,
127
+ data=data,
128
+ file_hash=hashlib.md5(str(datetime.now()).encode()).hexdigest(),
129
+ image_bytes=None,
130
+ manual=True
131
+ )
132
  except Exception as e:
133
+ return jsonify({'error': str(e)}), 400
134
+
135
+ def validate_and_save_transaction(uid, user_data, data, file_hash, image_bytes, manual=False):
136
+ transactions_ref = db.reference('transactions')
137
+ existing = transactions_ref.order_by_child('receipt_number').equal_to(data['receipt_number']).get()
138
+
139
+ if existing:
140
+ return jsonify({'error': f"Receipt #{data['receipt_number']} already exists"}), 400
141
+
142
+ total = float(data.get('total', 0))
143
+ if total > user_data['remaining_cash']:
144
+ return jsonify({'error': 'Insufficient funds'}), 400
145
 
146
+ # Upload image if available
147
+ image_url = None
148
+ if image_bytes and not manual:
149
+ timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
150
+ blob = bucket.blob(f'receipts/{uid}/{timestamp}_{file_hash}.jpg')
151
+ blob.upload_from_string(image_bytes, content_type='image/jpeg')
152
+ image_url = blob.public_url
153
+
154
+ # Update user cash
155
+ new_remaining = user_data['remaining_cash'] - total
156
+ db.reference(f'users/{uid}').update({'remaining_cash': new_remaining})
157
+
158
+ # Save transaction
159
+ transaction_data = {
160
+ 'uid': uid,
161
+ 'total': total,
162
+ 'items': data.get('items', []),
163
+ 'date': data.get('date'),
164
+ 'receipt_number': data.get('receipt_number'),
165
+ 'timestamp': datetime.now(pytz.UTC).isoformat(),
166
+ 'hash': file_hash,
167
+ 'image_url': image_url,
168
+ 'manual_entry': manual
169
+ }
170
+
171
+ new_transaction_ref = transactions_ref.push(transaction_data)
172
+ return jsonify({
173
+ 'success': True,
174
+ 'transaction': {**transaction_data, 'id': new_transaction_ref.key},
175
+ 'remaining_cash': new_remaining
176
+ })
177
+
178
+ # Enhanced data endpoints for visualizations
179
+ @app.route('/api/user/spending-overview', methods=['GET'])
180
+ def get_spending_overview():
181
  try:
182
+ uid = verify_token(request.headers.get('Authorization', '').split(' ')[1])
183
+ transactions_ref = db.reference('transactions')
184
+ transactions = transactions_ref.order_by_child('uid').equal_to(uid).get()
185
 
186
+ df = pd.DataFrame(transactions.values())
187
+ if df.empty:
188
+ return jsonify({'error': 'No transactions found'}), 404
 
189
 
190
+ df['date'] = pd.to_datetime(df['date'], format='%d/%m/%Y', errors='coerce')
191
+ daily_spending = df.groupby(df['date'].dt.date)['total'].sum().reset_index()
192
 
193
+ return jsonify({
194
+ 'daily_spending': daily_spending.to_dict(orient='records'),
195
+ 'recent_transactions': df.sort_values(by='timestamp', ascending=False)
196
+ .head(10)
197
+ .to_dict(orient='records')
198
+ })
199
  except Exception as e:
200
  return jsonify({'error': str(e)}), 500
201
 
202
+ @app.route('/api/admin/global-overview', methods=['GET'])
203
+ def get_global_overview():
204
  try:
205
+ verify_admin(request.headers.get('Authorization', ''))
 
 
206
 
207
+ transactions_ref = db.reference('transactions')
 
 
 
 
 
 
 
 
 
208
  users_ref = db.reference('users')
 
209
 
210
+ transactions = pd.DataFrame(transactions_ref.get().values())
211
+ users = pd.DataFrame(users_ref.get().values())
 
 
 
 
 
 
 
 
212
 
213
+ merged = pd.merge(transactions, users, left_on='uid', right_on='uid')
214
+
215
+ return jsonify({
216
+ 'user_spending': merged.groupby('uid')['total'].sum().to_dict(),
217
+ 'all_transactions': transactions.sort_values(by='timestamp', ascending=False)
218
+ .to_dict(orient='records')
219
+ })
 
 
 
 
 
 
 
 
 
 
 
220
  except Exception as e:
221
  return jsonify({'error': str(e)}), 500
222
 
223
+ # Helper function to verify admin status
224
+ def verify_admin(auth_header):
225
+ if not auth_header.startswith('Bearer '):
226
+ raise ValueError('Invalid token')
227
+
228
+ token = auth_header.split(' ')[1]
229
+ uid = verify_token(token)
230
+ user = auth.get_user(uid)
231
+
232
+ if not user.custom_claims or not user.custom_claims.get('admin'):
233
+ raise PermissionError('Admin access required')
234
 
235
+ # User management endpoints
236
+ @app.route('/api/user/profile', methods=['GET'])
237
+ def get_user_profile():
238
+ try:
239
+ uid = verify_token(request.headers.get('Authorization', '').split(' ')[1])
240
  user = auth.get_user(uid)
241
+ user_data = db.reference(f'users/{uid}').get()
 
 
 
 
242
 
243
+ return jsonify({
244
+ 'uid': uid,
245
+ 'email': user.email,
246
+ 'daily_cash': user_data.get('daily_cash', 0),
247
+ 'remaining_cash': user_data.get('remaining_cash', 0),
248
+ 'last_reset': user_data.get('last_reset'),
249
+ 'is_admin': user_data.get('is_admin', False)
250
+ })
251
  except Exception as e:
252
  return jsonify({'error': str(e)}), 500
253