rairo commited on
Commit
2b2cffb
·
verified ·
1 Parent(s): 526059c

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +330 -33
main.py CHANGED
@@ -2,39 +2,201 @@ import os
2
  import io
3
  import json
4
  import hashlib
5
- from datetime import datetime
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
 
12
  app = Flask(__name__)
13
- CORS(app) # Enable CORS for all routes
14
 
15
- # Use the Gemini API key from the environment, or set it here for testing.
16
- api_key = os.environ.get('Gemini', 'YOUR_GEMINI_API_KEY')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
 
18
  def configure_gemini(api_key):
19
  genai.configure(api_key=api_key)
20
  return genai.GenerativeModel('gemini-2.0-flash-thinking-exp')
21
 
22
  def process_receipt(model, image):
23
- prompt = (
24
- "Analyze this image and determine if it's a receipt. If it is a receipt, extract:\n"
25
- " - Total amount (as float)\n"
26
- " - List of items purchased (array of strings)\n"
27
- " - Date of transaction (DD/MM/YYYY format)\n"
28
- " - Receipt number (as string)\n"
29
- "Return JSON format with keys: is_receipt (boolean), total, items, date, receipt_number.\n"
30
- "If not a receipt, return {\"is_receipt\": false}"
31
- )
32
  response = model.generate_content([prompt, image])
33
  return response.text
34
 
35
- @app.route('/process-receipt', methods=['POST'])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  def process_receipt_endpoint():
37
  try:
 
 
 
 
 
 
 
 
 
 
38
  if 'receipt' not in request.files:
39
  return jsonify({'error': 'No file uploaded'}), 400
40
 
@@ -42,36 +204,171 @@ def process_receipt_endpoint():
42
  if file.filename == '':
43
  return jsonify({'error': 'No file selected'}), 400
44
 
45
- # Read file bytes and compute a hash (for duplicate checking or logging)
 
 
 
 
 
 
 
 
 
 
46
  image_bytes = file.read()
47
  file_hash = hashlib.md5(image_bytes).hexdigest()
48
 
49
- # Open the image using Pillow
50
- image = Image.open(io.BytesIO(image_bytes))
 
 
 
51
 
52
- # Configure Gemini and process the receipt image
 
53
  model = configure_gemini(api_key)
54
  result_text = process_receipt(model, image)
55
 
56
- # Attempt to extract JSON from the response text
57
- json_start = result_text.find('{')
58
- json_end = result_text.rfind('}')
59
- if json_start == -1 or json_end == -1:
60
- return jsonify({'error': 'Invalid response format', 'raw': result_text}), 500
61
-
62
- json_str = result_text[json_start:json_end+1]
63
- # Clean up any markdown formatting if necessary
64
- json_str = json_str.replace('```json', '').replace('```', '')
65
  data = json.loads(json_str)
66
 
67
- # Optionally, add metadata to the response
68
- data['file_hash'] = file_hash
69
- data['timestamp'] = datetime.now().isoformat()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
 
71
- return jsonify(data)
 
 
 
72
  except Exception as e:
73
  return jsonify({'error': str(e)}), 500
74
 
75
  if __name__ == '__main__':
76
- # The server listens on all interfaces at port 7860.
77
- 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
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)
17
 
18
+ # Firebase initialization
19
+ cred = credentials.Certificate('path/to/your/serviceAccountKey.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
+ def initialize_admin():
33
+ # Initialize Firebase Admin SDK
34
+ cred = credentials.Certificate('path/to/your/serviceAccountKey.json')
35
+ firebase_admin.initialize_app(cred, {
36
+ 'databaseURL': 'your-database-url'
37
+ })
38
+
39
+ try:
40
+ # Create admin user in Firebase Auth
41
+ admin_email = "admin@yourdomain.com" # Change this
42
+ admin_password = "your-secure-password" # Change this
43
+
44
+ # Create the user
45
+ admin_user = auth.create_user(
46
+ email=admin_email,
47
+ password=admin_password
48
+ )
49
+
50
+ # Set admin custom claim
51
+ auth.set_custom_user_claims(admin_user.uid, {'admin': True})
52
+
53
+ # Create admin data in Realtime Database
54
+ admin_ref = db.reference(f'users/{admin_user.uid}')
55
+ admin_ref.set({
56
+ 'email': admin_email,
57
+ 'daily_cash': 0.0, # Admins typically don't need petty cash
58
+ 'remaining_cash': 0.0,
59
+ 'last_reset': datetime.now(pytz.UTC).isoformat(),
60
+ 'is_admin': True
61
+ })
62
+
63
+ print(f"Admin account created successfully!")
64
+ print(f"Email: {admin_email}")
65
+ print(f"UID: {admin_user.uid}")
66
+
67
+ except Exception as e:
68
+ print(f"Error creating admin account: {str(e)}")
69
+
70
+ # admin create user.
71
+ @app.route('/api/admin/create-user', methods=['POST'])
72
+ def create_user():
73
+ try:
74
+ # Verify admin token
75
+ auth_header = request.headers.get('Authorization')
76
+ if not auth_header or not auth_header.startswith('Bearer '):
77
+ return jsonify({'error': 'No token provided'}), 401
78
+
79
+ token = auth_header.split('Bearer ')[1]
80
+ admin_uid = verify_token(token)
81
+ if not admin_uid:
82
+ return jsonify({'error': 'Invalid token'}), 401
83
+
84
+ # Verify admin status
85
+ admin = auth.get_user(admin_uid)
86
+ if not admin.custom_claims or not admin.custom_claims.get('admin'):
87
+ return jsonify({'error': 'Unauthorized'}), 403
88
+
89
+ # Get user creation data
90
+ data = request.json
91
+ email = data.get('email')
92
+ password = data.get('password')
93
+ daily_cash = data.get('daily_cash', 0.0)
94
+ is_admin = data.get('is_admin', False)
95
+
96
+ if not email or not password:
97
+ return jsonify({'error': 'Email and password required'}), 400
98
+
99
+ # Create user in Firebase Auth
100
+ user = auth.create_user(
101
+ email=email,
102
+ password=password
103
+ )
104
+
105
+ # Set admin claim if requested
106
+ if is_admin:
107
+ auth.set_custom_user_claims(user.uid, {'admin': True})
108
+
109
+ # Create user data in Realtime Database
110
+ user_ref = db.reference(f'users/{user.uid}')
111
+ user_ref.set({
112
+ 'email': email,
113
+ 'daily_cash': daily_cash,
114
+ 'remaining_cash': daily_cash,
115
+ 'last_reset': datetime.now(pytz.UTC).isoformat(),
116
+ 'is_admin': is_admin
117
+ })
118
+
119
+ return jsonify({
120
+ 'success': True,
121
+ 'uid': user.uid,
122
+ 'email': email
123
+ })
124
+
125
+ except Exception as e:
126
+ return jsonify({'error': str(e)}), 500
127
 
128
  def configure_gemini(api_key):
129
  genai.configure(api_key=api_key)
130
  return genai.GenerativeModel('gemini-2.0-flash-thinking-exp')
131
 
132
  def process_receipt(model, image):
133
+ prompt = """Analyze this image and determine if it's a receipt. If it is a receipt, extract:
134
+ - Total amount (as float)
135
+ - List of items purchased (array of strings)
136
+ - Date of transaction (DD/MM/YYYY format)
137
+ - Receipt number (as string)
138
+ Return JSON format with keys: is_receipt (boolean), total, items, date, receipt_number.
139
+ If not a receipt, return {"is_receipt": false}"""
140
+
 
141
  response = model.generate_content([prompt, image])
142
  return response.text
143
 
144
+ def verify_token(token):
145
+ try:
146
+ decoded_token = auth.verify_id_token(token)
147
+ return decoded_token['uid']
148
+ except Exception as e:
149
+ return None
150
+
151
+ def check_daily_reset(user_ref):
152
+ try:
153
+ user_data = user_ref.get()
154
+ if not user_data:
155
+ return False
156
+
157
+ now = datetime.now(pytz.UTC)
158
+ last_reset = datetime.fromisoformat(user_data.get('last_reset', '2000-01-01T00:00:00+00:00'))
159
+
160
+ if now.time() >= time(8,0) and last_reset.date() < now.date():
161
+ user_ref.update({
162
+ 'remaining_cash': user_data['daily_cash'],
163
+ 'last_reset': now.isoformat()
164
+ })
165
+ return True
166
+ return False
167
+ except Exception as e:
168
+ print(f"Reset error: {str(e)}")
169
+ return False
170
+
171
+ @app.route('/api/login', methods=['POST'])
172
+ def login():
173
+ try:
174
+ data = request.json
175
+ email = data.get('email')
176
+ password = data.get('password')
177
+
178
+ # Use Firebase Auth REST API to sign in
179
+ user = auth.get_user_by_email(email)
180
+ return jsonify({
181
+ 'uid': user.uid,
182
+ 'email': user.email
183
+ })
184
+ except Exception as e:
185
+ return jsonify({'error': str(e)}), 401
186
+
187
+ @app.route('/api/process-receipt', methods=['POST'])
188
  def process_receipt_endpoint():
189
  try:
190
+ # Verify authentication
191
+ auth_header = request.headers.get('Authorization')
192
+ if not auth_header or not auth_header.startswith('Bearer '):
193
+ return jsonify({'error': 'No token provided'}), 401
194
+
195
+ token = auth_header.split('Bearer ')[1]
196
+ uid = verify_token(token)
197
+ if not uid:
198
+ return jsonify({'error': 'Invalid token'}), 401
199
+
200
  if 'receipt' not in request.files:
201
  return jsonify({'error': 'No file uploaded'}), 400
202
 
 
204
  if file.filename == '':
205
  return jsonify({'error': 'No file selected'}), 400
206
 
207
+ # Get user reference
208
+ user_ref = db.reference(f'users/{uid}')
209
+ user_data = user_ref.get()
210
+
211
+ if not user_data:
212
+ return jsonify({'error': 'User not found'}), 404
213
+
214
+ # Check daily reset
215
+ check_daily_reset(user_ref)
216
+
217
+ # Read file and compute hash
218
  image_bytes = file.read()
219
  file_hash = hashlib.md5(image_bytes).hexdigest()
220
 
221
+ # Check for duplicate receipt
222
+ transactions_ref = db.reference('transactions')
223
+ existing_transactions = transactions_ref.order_by_child('hash').equal_to(file_hash).get()
224
+ if existing_transactions:
225
+ return jsonify({'error': 'Receipt already processed'}), 400
226
 
227
+ # Process image with Gemini
228
+ image = Image.open(io.BytesIO(image_bytes))
229
  model = configure_gemini(api_key)
230
  result_text = process_receipt(model, image)
231
 
232
+ # Parse Gemini response
233
+ json_str = result_text[result_text.find('{'):result_text.rfind('}')+1]
 
 
 
 
 
 
 
234
  data = json.loads(json_str)
235
 
236
+ if not data.get('is_receipt', False):
237
+ return jsonify({'error': 'Not a valid receipt'}), 400
238
+
239
+ # Validate total against remaining cash
240
+ total = float(data.get('total', 0))
241
+ if total > user_data['remaining_cash']:
242
+ return jsonify({'error': 'Insufficient funds'}), 400
243
+
244
+ # Upload image to Firebase Storage
245
+ timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
246
+ blob = bucket.blob(f'receipts/{uid}/{timestamp}_{file_hash}.jpg')
247
+ blob.upload_from_string(image_bytes, content_type='image/jpeg')
248
+
249
+ # Update user's remaining cash
250
+ new_remaining = user_data['remaining_cash'] - total
251
+ user_ref.update({'remaining_cash': new_remaining})
252
+
253
+ # Save transaction
254
+ transaction_data = {
255
+ 'uid': uid,
256
+ 'total': total,
257
+ 'items': data.get('items', []),
258
+ 'date': data.get('date'),
259
+ 'receipt_number': data.get('receipt_number'),
260
+ 'timestamp': datetime.now().isoformat(),
261
+ 'hash': file_hash,
262
+ 'image_url': blob.public_url
263
+ }
264
+
265
+ new_transaction_ref = transactions_ref.push(transaction_data)
266
+
267
+ return jsonify({
268
+ 'success': True,
269
+ 'transaction_id': new_transaction_ref.key,
270
+ 'remaining_cash': new_remaining,
271
+ **transaction_data
272
+ })
273
+
274
+ except Exception as e:
275
+ return jsonify({'error': str(e)}), 500
276
+
277
+ @app.route('/api/user/transactions', methods=['GET'])
278
+ def get_user_transactions():
279
+ try:
280
+ auth_header = request.headers.get('Authorization')
281
+ if not auth_header or not auth_header.startswith('Bearer '):
282
+ return jsonify({'error': 'No token provided'}), 401
283
+
284
+ token = auth_header.split('Bearer ')[1]
285
+ uid = verify_token(token)
286
+ if not uid:
287
+ return jsonify({'error': 'Invalid token'}), 401
288
+
289
+ transactions_ref = db.reference('transactions')
290
+ user_transactions = transactions_ref.order_by_child('uid').equal_to(uid).get()
291
+
292
+ return jsonify(user_transactions)
293
+ except Exception as e:
294
+ return jsonify({'error': str(e)}), 500
295
+
296
+ @app.route('/api/admin/users', methods=['GET'])
297
+ def get_all_users():
298
+ try:
299
+ auth_header = request.headers.get('Authorization')
300
+ if not auth_header or not auth_header.startswith('Bearer '):
301
+ return jsonify({'error': 'No token provided'}), 401
302
+
303
+ token = auth_header.split('Bearer ')[1]
304
+ uid = verify_token(token)
305
+ if not uid:
306
+ return jsonify({'error': 'Invalid token'}), 401
307
+
308
+ # Verify admin status
309
+ user = auth.get_user(uid)
310
+ if not user.custom_claims or not user.custom_claims.get('admin'):
311
+ return jsonify({'error': 'Unauthorized'}), 403
312
+
313
+ users_ref = db.reference('users')
314
+ users = users_ref.get()
315
+
316
+ return jsonify(users)
317
+ except Exception as e:
318
+ return jsonify({'error': str(e)}), 500
319
+
320
+ @app.route('/api/admin/update-user', methods=['POST'])
321
+ def update_user():
322
+ try:
323
+ auth_header = request.headers.get('Authorization')
324
+ if not auth_header or not auth_header.startswith('Bearer '):
325
+ return jsonify({'error': 'No token provided'}), 401
326
+
327
+ token = auth_header.split('Bearer ')[1]
328
+ admin_uid = verify_token(token)
329
+ if not admin_uid:
330
+ return jsonify({'error': 'Invalid token'}), 401
331
+
332
+ # Verify admin status
333
+ admin = auth.get_user(admin_uid)
334
+ if not admin.custom_claims or not admin.custom_claims.get('admin'):
335
+ return jsonify({'error': 'Unauthorized'}), 403
336
+
337
+ data = request.json
338
+ user_id = data.get('uid')
339
+ updates = data.get('updates', {})
340
+
341
+ user_ref = db.reference(f'users/{user_id}')
342
+ user_ref.update(updates)
343
+
344
+ return jsonify({'success': True})
345
+ except Exception as e:
346
+ return jsonify({'error': str(e)}), 500
347
+
348
+ @app.route('/api/admin/transactions', methods=['GET'])
349
+ def get_all_transactions():
350
+ try:
351
+ auth_header = request.headers.get('Authorization')
352
+ if not auth_header or not auth_header.startswith('Bearer '):
353
+ return jsonify({'error': 'No token provided'}), 401
354
+
355
+ token = auth_header.split('Bearer ')[1]
356
+ uid = verify_token(token)
357
+ if not uid:
358
+ return jsonify({'error': 'Invalid token'}), 401
359
+
360
+ # Verify admin status
361
+ user = auth.get_user(uid)
362
+ if not user.custom_claims or not user.custom_claims.get('admin'):
363
+ return jsonify({'error': 'Unauthorized'}), 403
364
 
365
+ transactions_ref = db.reference('transactions')
366
+ transactions = transactions_ref.get()
367
+
368
+ return jsonify(transactions)
369
  except Exception as e:
370
  return jsonify({'error': str(e)}), 500
371
 
372
  if __name__ == '__main__':
373
+ initialize_admin()
374
+ app.run(debug=True, host="0.0.0.0", port=7860)