abidjoyia commited on
Commit
3cd2ab5
·
verified ·
1 Parent(s): a3ab9c1

Upload 6 files

Browse files
Files changed (7) hide show
  1. .gitattributes +1 -0
  2. Dockerfile +17 -0
  3. app.py +389 -0
  4. config.json +23 -0
  5. database.py +603 -0
  6. forget.py +104 -0
  7. logo.png +3 -0
.gitattributes ADDED
@@ -0,0 +1 @@
 
 
1
+ logo.png filter=lfs diff=lfs merge=lfs -text
Dockerfile ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.9-slim
2
+
3
+ WORKDIR /app
4
+ FROM python:3.10
5
+ RUN apt-get update && apt-get install -y ffmpeg
6
+ COPY requirements.txt .
7
+ RUN apt-get update && apt-get install -y \
8
+ libsndfile1 \
9
+ && rm -rf /var/lib/apt/lists/*
10
+
11
+ RUN pip install --no-cache-dir -r requirements.txt
12
+
13
+ COPY . .
14
+
15
+ EXPOSE 7860
16
+
17
+ CMD ["python", "app.py"]
app.py ADDED
@@ -0,0 +1,389 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ os.environ["NUMBA_CACHE_DIR"] = "/tmp/numba_cache"
3
+ os.environ["NUMBA_DISABLE_JIT"] = "1"
4
+ from apscheduler.schedulers.background import BackgroundScheduler
5
+ import requests
6
+ import time
7
+ import logging
8
+ from flask import Flask, request, jsonify, send_file
9
+ from flask_cors import CORS
10
+ from predict import handle_predict
11
+ from database import (
12
+ init_db, save_prediction, get_history, register_user, authenticate_user,
13
+ get_user_profile, update_user_profile, get_farm_details_from_db,
14
+ update_farm_details_in_db, get_farm_detailss_from_db, get_hives_from_db,
15
+ get_hive_detail_from_db, add_hive_to_db, delete_hive_from_db, update_hive_health_in_db,
16
+ add_task_to_db, get_user_tasks_from_db, get_task_detail_from_db,
17
+ delete_task_from_db, update_task_status_to_completed, update_hive_in_db,
18
+ generate_reset_code, verify_reset_code, update_user_password, change_user_password, get_all_notifications, add_notification,
19
+ mark_notification_as_read, get_user_cities
20
+ )
21
+ from forget import forgot_password, verify_reset_code_endpoint, reset_password
22
+ from report import generate_report
23
+ from datetime import datetime
24
+
25
+ # Configure logging
26
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
27
+ logger = logging.getLogger(__name__)
28
+
29
+ app = Flask(__name__)
30
+ app.secret_key = os.urandom(24)
31
+ CORS(app)
32
+
33
+ # Forgot password endpoint
34
+ @app.route('/forgot_password', methods=['POST'])
35
+ def forgot_password_endpoint():
36
+ return forgot_password(request)
37
+
38
+ # Verify reset code endpoint
39
+ @app.route('/verify_reset_code', methods=['POST'])
40
+ def verify_reset_code_route():
41
+ return verify_reset_code_endpoint(request)
42
+
43
+ # Reset password endpoint
44
+ @app.route('/reset_password', methods=['POST'])
45
+ def reset_password_endpoint():
46
+ return reset_password(request)
47
+
48
+ @app.route('/change_password', methods=['POST'])
49
+ def change_password():
50
+ data = request.json
51
+ user_id = data.get('user_id')
52
+ current_password = data.get('password')
53
+ new_password = data.get('new_password')
54
+
55
+ if not all([user_id, current_password, new_password]):
56
+ return jsonify({"error": "Missing required fields"}), 400
57
+
58
+ result = change_user_password(user_id, current_password, new_password)
59
+ if result:
60
+ return jsonify({"message": "Password changed successfully"}), 200
61
+ return jsonify({"error": "Current password incorrect"}), 401
62
+
63
+ # Existing endpoints (unchanged)
64
+ @app.route('/predict', methods=['POST'])
65
+ def predict():
66
+ return handle_predict(request, save_prediction)
67
+
68
+ @app.route('/signup', methods=['POST'])
69
+ def signup():
70
+ data = request.json
71
+ result = register_user(data['fullName'], data['email'], data['password'])
72
+ if result == "email already exist":
73
+ return jsonify({"message": result}), 215
74
+ return jsonify({"message": result}), 200
75
+
76
+ @app.route('/login', methods=['POST'])
77
+ def login():
78
+ data = request.json
79
+ result = authenticate_user(data['email'], data['password'])
80
+ if "error" in result:
81
+ return jsonify(result), 415
82
+ return jsonify(result), 200
83
+
84
+ @app.route('/profile', methods=['GET'])
85
+ def profile():
86
+ user_id = request.args.get('user_id')
87
+ return jsonify(get_user_profile(user_id))
88
+
89
+ @app.route('/profile/update', methods=['POST'])
90
+ def update_profile():
91
+ data = request.json
92
+ return jsonify(update_user_profile(
93
+ data['user_id'], data['fullname'], data['country'], data['city'],
94
+ data['gender'], data['phone_number']
95
+ ))
96
+
97
+ @app.route('/history', methods=['GET'])
98
+ def history():
99
+ user_id = request.args.get('user_id')
100
+ return jsonify({"history": get_history(user_id)})
101
+
102
+ @app.route('/farm', methods=['GET'])
103
+ def get_farm_details():
104
+ user_id = request.args.get('user_id')
105
+ if not user_id:
106
+ return jsonify({"error": "User ID is required"}), 400
107
+ farm = get_farm_details_from_db(user_id)
108
+ return jsonify(farm) if farm else jsonify({"error": "Farm details not found"}), 404
109
+
110
+ @app.route('/farm/update', methods=['POST'])
111
+ def update_farm():
112
+ data = request.json
113
+ return jsonify(update_farm_details_in_db(
114
+ data['user_id'], data['fullname'], data['country'], data['city'], data['zip']
115
+ ))
116
+
117
+ @app.route('/farms', methods=['GET'])
118
+ def get_farm():
119
+ user_id = request.args.get('user_id')
120
+ if not user_id:
121
+ return jsonify({"error": "User ID is required"}), 400
122
+ farm = get_farm_details_from_db(user_id)
123
+ return jsonify(farm) if farm else jsonify({"error": "No farm registered"}), 404
124
+
125
+ @app.route('/hives', methods=['GET'])
126
+ def get_hives():
127
+ farm_id = request.args.get('farm_id')
128
+ if not farm_id:
129
+ return jsonify({"error": "Farm ID is required"}), 400
130
+ hives = get_hives_from_db(farm_id)
131
+ return jsonify(hives)
132
+
133
+ @app.route('/hives', methods=['POST'])
134
+ def add_hive():
135
+ data = request.json
136
+ if not all(key in data for key in ['farm_id', 'hive_number', 'bee_type', 'number_of_frames', 'health_status']):
137
+ return jsonify({"error": "Missing required fields"}), 400
138
+ try:
139
+ hive_id = add_hive_to_db(
140
+ data['farm_id'],
141
+ data['hive_number'],
142
+ data['bee_type'],
143
+ data['number_of_frames'],
144
+ data['health_status'],
145
+ data.get('notes', '')
146
+ )
147
+ return jsonify({"success": True, "hive_id": hive_id}), 201
148
+ except Exception as e:
149
+ return jsonify({"error": str(e)}), 500
150
+
151
+ @app.route('/hives', methods=['DELETE'])
152
+ def delete_hive():
153
+ hive_id = request.args.get('hive_id')
154
+ if not hive_id:
155
+ return jsonify({"error": "Hive ID is required"}), 400
156
+ try:
157
+ deleted = delete_hive_from_db(hive_id)
158
+ if deleted:
159
+ return jsonify({"success": True}), 200
160
+ else:
161
+ return jsonify({"error": "Hive not found"}), 404
162
+ except Exception as e:
163
+ return jsonify({"error": str(e)}), 500
164
+
165
+ @app.route('/hive_detail', methods=['GET'])
166
+ def get_hives_detail():
167
+ hive_id = request.args.get('hive_id')
168
+ if not hive_id:
169
+ return jsonify({"error": "Hive Id is required"}), 400
170
+ hivess = get_hive_detail_from_db(hive_id)
171
+ return jsonify(hivess)
172
+
173
+ @app.route('/manual_health_update', methods=['POST'])
174
+ def update_hive_health():
175
+ data = request.json
176
+ if not all(key in data for key in ['hive_id', 'health_status']):
177
+ return jsonify({"error": "Missing required fields"}), 400
178
+ try:
179
+ update_hive_health_in_db(data['hive_id'], data['health_status'])
180
+ return jsonify({"success": True, "message": "Health status updated"}), 200
181
+ except Exception as e:
182
+ return jsonify({"error": str(e)}), 500
183
+
184
+ @app.route('/edit_hive', methods=['POST'])
185
+ def update_hive():
186
+ data = request.json
187
+ if 'hive_id' not in data:
188
+ return jsonify({"error": "hive_id is required"}), 400
189
+ update_data = {k: v for k, v in data.items() if k in {'hive_id', 'number_of_frames', 'bee_type', 'notes'}}
190
+ if len(update_data) <= 1:
191
+ return jsonify({"error": "No valid fields provided"}), 400
192
+ try:
193
+ result = update_hive_in_db(
194
+ update_data['hive_id'],
195
+ update_data.get('number_of_frames'),
196
+ update_data.get('bee_type'),
197
+ update_data.get('notes')
198
+ )
199
+ if not result:
200
+ return jsonify({"error": "Hive not found"}), 404
201
+ return jsonify({"success": True, "hive_id": update_data['hive_id']}), 200
202
+ except Exception as e:
203
+ return jsonify({"error": str(e)}), 500
204
+
205
+ @app.route('/report', methods=['GET'])
206
+ def get_report():
207
+ user_id = request.args.get('user_id')
208
+ if not user_id:
209
+ return jsonify({"error": "User ID is required"}), 400
210
+ try:
211
+ buffer = generate_report(user_id)
212
+ buffer.seek(0)
213
+ return send_file(
214
+ buffer,
215
+ as_attachment=True,
216
+ download_name=f"bee_hive_report_{user_id}_{datetime.now().strftime('%Y%m%d')}.pdf",
217
+ mimetype='application/pdf'
218
+ )
219
+ except Exception as e:
220
+ return jsonify({"error": str(e)}), 500
221
+
222
+ @app.route('/addtask', methods=['POST'])
223
+ def add_task():
224
+ data = request.json
225
+ required_fields = ['user_id', 'task_name', 'description', 'deadline_date', 'status']
226
+ if not all(key in data for key in required_fields):
227
+ return jsonify({"error": "Missing required fields"}), 400
228
+ try:
229
+ task_id = add_task_to_db(
230
+ data['user_id'],
231
+ data['task_name'],
232
+ data.get('hive_id'),
233
+ data['description'],
234
+ data['deadline_date'],
235
+ data['status']
236
+ )
237
+ return jsonify({"success": True, "task_id": task_id}), 201
238
+ except Exception as e:
239
+ return jsonify({"error": str(e)}), 500
240
+
241
+ @app.route('/showtasks', methods=['GET'])
242
+ def get_user_tasks():
243
+ user_id = request.args.get('user_id')
244
+ if not user_id:
245
+ return jsonify({"error": "User ID is required"}), 400
246
+ try:
247
+ tasks = get_user_tasks_from_db(user_id)
248
+ return jsonify({"tasks": tasks}), 200
249
+ except Exception as e:
250
+ return jsonify({"error": str(e)}), 500
251
+
252
+ @app.route('/task_detail', methods=['GET'])
253
+ def get_task_detail():
254
+ task_id = request.args.get('task_id')
255
+ if not task_id:
256
+ return jsonify({"error": "Task ID is required"}), 400
257
+ try:
258
+ task = get_task_detail_from_db(task_id)
259
+ if task:
260
+ return jsonify(task), 200
261
+ return jsonify({"error": "Task not found"}), 404
262
+ except Exception as e:
263
+ return jsonify({"error": str(e)}), 500
264
+
265
+ @app.route('/task_complete', methods=['POST'])
266
+ def mark_task_complete():
267
+ data = request.json
268
+ task_id = data.get('task_id')
269
+ if not task_id:
270
+ return jsonify({"error": "Task ID is required"}), 400
271
+ try:
272
+ updated = update_task_status_to_completed(task_id)
273
+ if updated:
274
+ return jsonify({"success": True, "message": "Task marked as completed"}), 200
275
+ return jsonify({"error": "Task not found"}), 404
276
+ except Exception as e:
277
+ return jsonify({"error": str(e)}), 500
278
+
279
+ @app.route('/delete_task', methods=['DELETE'])
280
+ def delete_task():
281
+ data = request.get_json()
282
+ task_id = data.get("task_id") if data else None
283
+ if not task_id:
284
+ return jsonify({"error": "Task ID is required"}), 400
285
+ try:
286
+ deleted = delete_task_from_db(task_id)
287
+ if deleted:
288
+ return jsonify({"success": True}), 200
289
+ else:
290
+ return jsonify({"error": "Task not found"}), 404
291
+ except Exception as e:
292
+ return jsonify({"error": str(e)}), 500
293
+
294
+ @app.route('/notifications', methods=['GET'])
295
+ def get_notifications():
296
+ user_id = request.args.get('user_id')
297
+ if not user_id:
298
+ return jsonify({"error": "User ID is required"}), 400
299
+ try:
300
+ notifications = get_all_notifications(user_id)
301
+ return jsonify({"notifications": notifications}), 200
302
+ except TypeError as e:
303
+ return jsonify({"error": str(e)}), 500
304
+
305
+ @app.route('/notifications/mark_read', methods=['POST'])
306
+ def mark_notifications_read():
307
+ data = request.json
308
+ notification_id = data.get('notification_id')
309
+ if not notification_id:
310
+ return jsonify({"error": "Notification ID is required"}), 400
311
+ try:
312
+ mark_notification_as_read(notification_id)
313
+ return jsonify({"message": "Notification marked as read"}), 200
314
+ except Exception as e:
315
+ return jsonify({"error)": str(e)}), 500
316
+
317
+ @app.route('/add_notifications', methods=['POST'])
318
+ def create_notification():
319
+ data = request.json
320
+ required_fields = ['user_id', 'textt']
321
+ if not all(key in data for key in required_fields):
322
+ return jsonify({"error": "Missing required fields"}), 400
323
+ try:
324
+ notification_id = add_notification(
325
+ data['user_id'],
326
+ data['textt']
327
+ )
328
+ return jsonify({"success": True, "notification_id": notification_id}), 201
329
+ except Exception as e:
330
+ return jsonify({"error": str(e)}), 500
331
+
332
+ @app.route('/check_weather', methods=['POST'])
333
+ def trigger_weather_check():
334
+ try:
335
+ check_weather_conditions()
336
+ return jsonify({"message": "Weather check triggered successfully"}), 200
337
+ except Exception as e:
338
+ logger.error(f"Error triggering weather check: {str(e)}")
339
+ return jsonify({"error": str(e)}), 500
340
+
341
+ def check_weather_conditions():
342
+ logger.info("Checking weather conditions for users")
343
+ # Wake up the database
344
+ try:
345
+ response = requests.get("https://bilalhasanniazi-bee-notbee.hf.space/profile?user_id=0")
346
+ if response.status_code == 200:
347
+ logger.info("Database wake-up API called successfully")
348
+ else:
349
+ logger.warning(f"Database wake-up API failed with status {response.status_code}")
350
+ except Exception as e:
351
+ logger.error(f"Error calling database wake-up API: {str(e)}")
352
+
353
+ # Wait for 10 seconds
354
+ time.sleep(10)
355
+
356
+ user_cities = get_user_cities()
357
+ weather_api_key = "9fb23a5a66764b61a43163911251605"
358
+ base_url = "http://api.weatherapi.com/v1/forecast.json"
359
+
360
+ for user_id, city in user_cities.items():
361
+ try:
362
+ # Fetch weather data
363
+ response = requests.get(f"{base_url}?key={weather_api_key}&q={city}")
364
+ if response.status_code != 200:
365
+ logger.warning(f"Failed to fetch weather for city {city}: {response.status_code}")
366
+ continue
367
+
368
+ data = response.json()
369
+ if "error" in data:
370
+ logger.warning(f"Invalid city {city} for user {user_id}: {data['error']['message']}")
371
+ continue
372
+
373
+ temp_c = data["current"]["temp_c"]
374
+ if temp_c > 45:
375
+ notification_text = "Temperature is too hot, take safety precautions to protect bees"
376
+ add_notification(user_id, notification_text)
377
+ logger.info(f"Notification added for user {user_id} in {city}: {notification_text}")
378
+
379
+ except Exception as e:
380
+ logger.error(f"Error processing weather for user {user_id}, city {city}: {str(e)}")
381
+
382
+ # Initialize scheduler
383
+ scheduler = BackgroundScheduler()
384
+ scheduler.add_job(check_weather_conditions, 'interval', hours=24)
385
+ scheduler.start()
386
+
387
+ if __name__ == '__main__':
388
+ logger.info("Starting Flask application")
389
+ app.run(debug=False, host='0.0.0.0', port=7860)
config.json ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "_name_or_path": "google/vit-base-patch16-224-in21k",
3
+ "architectures": [
4
+ "ViTForImageClassification"
5
+ ],
6
+ "attention_probs_dropout_prob": 0.0,
7
+ "encoder_stride": 16,
8
+ "hidden_act": "gelu",
9
+ "hidden_dropout_prob": 0.0,
10
+ "hidden_size": 768,
11
+ "image_size": 224,
12
+ "initializer_range": 0.02,
13
+ "intermediate_size": 3072,
14
+ "layer_norm_eps": 1e-12,
15
+ "model_type": "vit",
16
+ "num_attention_heads": 12,
17
+ "num_channels": 3,
18
+ "num_hidden_layers": 12,
19
+ "patch_size": 16,
20
+ "qkv_bias": true,
21
+ "torch_dtype": "float32",
22
+ "transformers_version": "4.47.0"
23
+ }
database.py ADDED
@@ -0,0 +1,603 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import psycopg2
2
+ from psycopg2 import pool
3
+ from datetime import datetime, timedelta
4
+ import os
5
+ import json
6
+ import secrets
7
+ import time
8
+ from functools import wraps
9
+
10
+ # Load and parse DATABASE_CONFIG from environment variable
11
+ database_config_json = os.getenv("neon_db")
12
+ DB_CONFIG = json.loads(database_config_json)
13
+
14
+ # Initialize connection pool
15
+ db_pool = psycopg2.pool.SimpleConnectionPool(1, 20, **DB_CONFIG)
16
+
17
+ # Retry decorator for handling EOF errors
18
+ def retry_on_eof(max_attempts=2, delay=3):
19
+ def decorator(func):
20
+ @wraps(func)
21
+ def wrapper(*args, **kwargs):
22
+ attempts = 0
23
+ while attempts < max_attempts:
24
+ try:
25
+ return func(*args, **kwargs)
26
+ except psycopg2.OperationalError as e:
27
+ if "SSL SYSCALL error: EOF detected" in str(e):
28
+ print(f"EOF detected in {func.__name__}, retrying {attempts + 1}/{max_attempts} after {delay}s")
29
+ attempts += 1
30
+ if attempts == max_attempts:
31
+ raise # Re-raise the exception if max attempts reached
32
+ time.sleep(delay) # Wait before retrying
33
+ # Re-establish connection if closed
34
+ conn = db_pool.getconn()
35
+ if conn.closed:
36
+ db_pool.putconn(conn, close=True)
37
+ conn = db_pool.getconn()
38
+ else:
39
+ raise # Re-raise other OperationalErrors
40
+ return func(*args, **kwargs)
41
+ return wrapper
42
+ return decorator
43
+
44
+ @retry_on_eof(max_attempts=2, delay=3)
45
+ def init_db():
46
+ conn = db_pool.getconn()
47
+ try:
48
+ with conn.cursor() as c:
49
+ c.execute("CREATE TABLE IF NOT EXISTS users (user_id SERIAL PRIMARY KEY, email TEXT UNIQUE, password TEXT)")
50
+ c.execute("""
51
+ CREATE TABLE IF NOT EXISTS predictions (
52
+ pred_id SERIAL PRIMARY KEY,
53
+ user_id INT,
54
+ timestamp TEXT,
55
+ audio_name TEXT,
56
+ result TEXT,
57
+ audio BYTEA,
58
+ hive_id INT,
59
+ FOREIGN KEY (hive_id) REFERENCES Hive(hive_id)
60
+ )
61
+ """)
62
+ c.execute("CREATE TABLE IF NOT EXISTS farm (farm_id SERIAL PRIMARY KEY, user_id INT UNIQUE, fullname TEXT, country TEXT, city TEXT, zip TEXT, hives INT DEFAULT 0)")
63
+ c.execute("""
64
+ CREATE TABLE IF NOT EXISTS Hive (
65
+ hive_id SERIAL PRIMARY KEY,
66
+ farm_id INT,
67
+ hive_number INT,
68
+ bee_type TEXT,
69
+ number_of_frames INT,
70
+ creation_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
71
+ health_status TEXT,
72
+ notes TEXT,
73
+ FOREIGN KEY (farm_id) REFERENCES farm(farm_id)
74
+ )
75
+ """)
76
+ conn.commit()
77
+ finally:
78
+ db_pool.putconn(conn)
79
+
80
+ @retry_on_eof(max_attempts=2, delay=3)
81
+ def generate_reset_code(email):
82
+ conn = db_pool.getconn()
83
+ try:
84
+ with conn.cursor() as c:
85
+ # Check if email exists
86
+ c.execute("SELECT user_id FROM users WHERE email = %s", (email,))
87
+ user = c.fetchone()
88
+ if not user:
89
+ return None
90
+ # Generate 6-digit code
91
+ code = ''.join(str(secrets.randbelow(10)) for _ in range(6))
92
+ # Set expiration (15 minutes from now)
93
+ expires_at = datetime.now() + timedelta(minutes=15)
94
+ # Store code
95
+ c.execute(
96
+ """
97
+ INSERT INTO password_reset_codes (user_id, code, expires_at)
98
+ VALUES (%s, %s, %s)
99
+ ON CONFLICT (user_id) DO UPDATE
100
+ SET code = %s, expires_at = %s
101
+ """,
102
+ (user[0], code, expires_at, code, expires_at)
103
+ )
104
+ conn.commit()
105
+ return code
106
+ finally:
107
+ db_pool.putconn(conn)
108
+
109
+ @retry_on_eof(max_attempts=2, delay=3)
110
+ def verify_reset_code(email, code):
111
+ conn = db_pool.getconn()
112
+ try:
113
+ with conn.cursor() as c:
114
+ c.execute(
115
+ """
116
+ SELECT code, expires_at
117
+ FROM password_reset_codes prc
118
+ JOIN users u ON prc.user_id = u.user_id
119
+ WHERE u.email = %s AND prc.code = %s
120
+ """,
121
+ (email, code)
122
+ )
123
+ result = c.fetchone()
124
+ if result and result[1] > datetime.now():
125
+ # Delete code after verification
126
+ c.execute("DELETE FROM password_reset_codes WHERE code = %s", (code,))
127
+ conn.commit()
128
+ return True
129
+ return False
130
+ finally:
131
+ db_pool.putconn(conn)
132
+
133
+ @retry_on_eof(max_attempts=2, delay=3)
134
+ def update_user_password(email, new_password):
135
+ conn = db_pool.getconn()
136
+ try:
137
+ with conn.cursor() as c:
138
+ c.execute(
139
+ "UPDATE users SET password = %s WHERE email = %s RETURNING user_id",
140
+ (new_password, email)
141
+ )
142
+ result = c.fetchone()
143
+ conn.commit()
144
+ return bool(result)
145
+ finally:
146
+ db_pool.putconn(conn)
147
+
148
+ @retry_on_eof(max_attempts=2, delay=3)
149
+ def change_user_password(user_id, current_password, new_password):
150
+ conn = db_pool.getconn()
151
+ try:
152
+ with conn.cursor() as c:
153
+ # Verify user exists and current password matches
154
+ c.execute(
155
+ "SELECT user_id FROM users WHERE user_id = %s AND password = %s",
156
+ (user_id, current_password)
157
+ )
158
+ user = c.fetchone()
159
+ if not user:
160
+ return False
161
+
162
+ # Update password
163
+ c.execute(
164
+ "UPDATE users SET password = %s WHERE user_id = %s",
165
+ (new_password, user_id)
166
+ )
167
+ conn.commit()
168
+ return True
169
+ finally:
170
+ db_pool.putconn(conn)
171
+
172
+ @retry_on_eof(max_attempts=2, delay=3)
173
+ def add_hive_to_db(farm_id, hive_number, bee_type, number_of_frames, health_status, notes):
174
+ conn = db_pool.getconn()
175
+ try:
176
+ with conn.cursor() as c:
177
+ c.execute("""
178
+ INSERT INTO Hive (farm_id, hive_number, bee_type, number_of_frames, health_status, notes)
179
+ VALUES (%s, %s, %s, %s, %s, %s)
180
+ RETURNING hive_id
181
+ """, (farm_id, hive_number, bee_type, number_of_frames, health_status, notes))
182
+ hive_id = c.fetchone()[0]
183
+ c.execute("UPDATE farm SET hives = hives + 1 WHERE farm_id = %s", (farm_id,))
184
+ conn.commit()
185
+ return hive_id
186
+ finally:
187
+ db_pool.putconn(conn)
188
+
189
+ @retry_on_eof(max_attempts=2, delay=3)
190
+ def delete_hive_from_db(hive_id):
191
+ conn = db_pool.getconn()
192
+ try:
193
+ with conn.cursor() as c:
194
+ c.execute("SELECT farm_id FROM Hive WHERE hive_id = %s", (hive_id,))
195
+ farm = c.fetchone()
196
+ if farm:
197
+ farm_id = farm[0]
198
+ c.execute("DELETE FROM Hive WHERE hive_id = %s", (hive_id,))
199
+ if c.rowcount > 0:
200
+ c.execute("UPDATE farm SET hives = hives - 1 WHERE farm_id = %s", (farm_id,))
201
+ conn.commit()
202
+ return True
203
+ return False
204
+ finally:
205
+ db_pool.putconn(conn)
206
+
207
+ @retry_on_eof(max_attempts=2, delay=3)
208
+ def register_user(fullname, email, password=None, google_id=None):
209
+ conn = db_pool.getconn()
210
+ try:
211
+ with conn.cursor() as c:
212
+ # Check if email or google_id already exists
213
+ c.execute("SELECT email, google_id FROM users WHERE email = %s OR google_id = %s", (email, google_id))
214
+ if c.fetchone():
215
+ return "email already exist"
216
+
217
+ # Insert new user with default values
218
+ c.execute(
219
+ "INSERT INTO users (fullname, email, password, google_id, country, city, gender, phone_number) VALUES (%s, %s, %s, %s, %s, %s, %s, %s) RETURNING user_id",
220
+ (fullname, email, password, google_id, "Pakistan", "Karachi", "Male", "0")
221
+ )
222
+ user_id = c.fetchone()[0]
223
+
224
+ # Create farm record for the new user with default values
225
+ c.execute(
226
+ "INSERT INTO farm (user_id, fullname, country, city, zip, hives) VALUES (%s, %s, %s, %s, %s, %s)",
227
+ (user_id, "user farm", "Pakistan", "Karachi", "24700", 0)
228
+ )
229
+
230
+ conn.commit()
231
+ return "Signup successful"
232
+ finally:
233
+ db_pool.putconn(conn)
234
+
235
+ @retry_on_eof(max_attempts=2, delay=3)
236
+ def authenticate_google_user(google_id, email):
237
+ conn = db_pool.getconn()
238
+ try:
239
+ with conn.cursor() as c:
240
+ # Get user_id and farm_id by google_id or email
241
+ c.execute("""
242
+ SELECT u.user_id, f.farm_id
243
+ FROM users u
244
+ LEFT JOIN farm f ON u.user_id = f.user_id
245
+ WHERE u.google_id = %s OR u.email = %s
246
+ """, (google_id, email))
247
+ result = c.fetchone()
248
+ return {"user_id": result[0], "farm_id": result[1]} if result else {"error": "User not found"}
249
+ finally:
250
+ db_pool.putconn(conn)
251
+
252
+ @retry_on_eof(max_attempts=2, delay=3)
253
+ def authenticate_user(email, password):
254
+ conn = db_pool.getconn()
255
+ try:
256
+ with conn.cursor() as c:
257
+ # Get user_id and farm_id
258
+ c.execute("""
259
+ SELECT u.user_id, f.farm_id
260
+ FROM users u
261
+ LEFT JOIN farm f ON u.user_id = f.user_id
262
+ WHERE u.email = %s AND u.password = %s
263
+ """, (email, password))
264
+ result = c.fetchone()
265
+ return {"user_id": result[0], "farm_id": result[1]} if result else {"error": "Invalid credentials"}
266
+ finally:
267
+ db_pool.putconn(conn)
268
+
269
+ @retry_on_eof(max_attempts=2, delay=3)
270
+ def save_prediction(user_id, audio_name, result, file_id, hive_id=None, user_predict=None):
271
+ conn = db_pool.getconn()
272
+ try:
273
+ with conn.cursor() as c:
274
+ # Add 5 hours to server time
275
+ timestamp = (datetime.now() + timedelta(hours=5)).strftime("%Y-%m-%d %H:%M:%S")
276
+ c.execute(
277
+ "INSERT INTO predictions (user_id, timestamp, audio_name, result, file_id, hive_id, user_predict) "
278
+ "VALUES (%s, %s, %s, %s, %s, %s, %s)",
279
+ (user_id, timestamp, audio_name, result, file_id, hive_id, user_predict)
280
+ )
281
+ conn.commit()
282
+ finally:
283
+ db_pool.putconn(conn)
284
+
285
+ @retry_on_eof(max_attempts=2, delay=3)
286
+ def get_history(user_id):
287
+ conn = db_pool.getconn()
288
+ try:
289
+ with conn.cursor() as c:
290
+ c.execute("""
291
+ SELECT p.timestamp, p.audio_name, p.result, p.hive_id, h.hive_number
292
+ FROM predictions p
293
+ LEFT JOIN Hive h ON p.hive_id = h.hive_id
294
+ WHERE p.user_id = %s
295
+ ORDER BY p.timestamp DESC
296
+ """, (user_id,))
297
+ return [{
298
+ "timestamp": row[0],
299
+ "audio_name": row[1],
300
+ "result": row[2],
301
+ "hive_number": row[4] if row[3] else None
302
+ } for row in c.fetchall()]
303
+ finally:
304
+ db_pool.putconn(conn)
305
+
306
+ @retry_on_eof(max_attempts=2, delay=3)
307
+ def get_user_profile(user_id):
308
+ conn = db_pool.getconn()
309
+ try:
310
+ with conn.cursor() as c:
311
+ c.execute("SELECT email, fullname, country, city, gender, phone_number FROM users WHERE user_id = %s", (user_id,))
312
+ user = c.fetchone()
313
+ return {
314
+ "email": user[0], "fullname": user[1], "country": user[2],
315
+ "city": user[3], "gender": user[4], "phone_number": user[5]
316
+ } if user else {"error": "User not found"}
317
+ finally:
318
+ db_pool.putconn(conn)
319
+
320
+ @retry_on_eof(max_attempts=2, delay=3)
321
+ def update_user_profile(user_id, fullname, country, city, gender, phone_number):
322
+ conn = db_pool.getconn()
323
+ try:
324
+ with conn.cursor() as c:
325
+ c.execute("""
326
+ UPDATE users
327
+ SET fullname = %s, country = %s, city = %s, gender = %s, phone_number = %s
328
+ WHERE user_id = %s
329
+ """, (fullname, country, city, gender, phone_number, user_id))
330
+ conn.commit()
331
+ return {"message": "Profile updated successfully"}
332
+ finally:
333
+ db_pool.putconn(conn)
334
+
335
+ @retry_on_eof(max_attempts=2, delay=3)
336
+ def get_farm_details_from_db(user_id):
337
+ conn = db_pool.getconn()
338
+ try:
339
+ with conn.cursor() as c:
340
+ c.execute("SELECT farm_id, fullname, country, city, zip, hives FROM farm WHERE user_id = %s", (user_id,))
341
+ farm = c.fetchone()
342
+ return {
343
+ "farm_id": farm[0],
344
+ "fullname": farm[1],
345
+ "country": farm[2],
346
+ "city": farm[3],
347
+ "zip": farm[4]
348
+ } if farm else None
349
+ finally:
350
+ db_pool.putconn(conn)
351
+
352
+ @retry_on_eof(max_attempts=2, delay=3)
353
+ def update_farm_details_in_db(user_id, fullname, country, city, zip_code):
354
+ conn = db_pool.getconn()
355
+ try:
356
+ with conn.cursor() as c:
357
+ c.execute("SELECT farm_id FROM farm WHERE user_id = %s", (user_id,))
358
+ existing_farm = c.fetchone()
359
+
360
+ if existing_farm:
361
+ c.execute("""
362
+ UPDATE farm
363
+ SET fullname = %s, country = %s, city = %s, zip = %s
364
+ WHERE user_id = %s
365
+ """, (fullname, country, city, zip_code, user_id))
366
+ else:
367
+ c.execute("""
368
+ INSERT INTO farm (user_id, fullname, country, city, zip)
369
+ VALUES (%s, %s, %s, %s, %s)
370
+ """, (user_id, fullname, country, city, zip_code))
371
+
372
+ conn.commit()
373
+ return {"message": "Farm details updated successfully"}
374
+ finally:
375
+ db_pool.putconn(conn)
376
+
377
+ @retry_on_eof(max_attempts=2, delay=3)
378
+ def get_farm_detailss_from_db(user_id):
379
+ conn = db_pool.getconn()
380
+ try:
381
+ with conn.cursor() as c:
382
+ c.execute("SELECT farm_id FROM farm WHERE user_id = %s", (user_id,))
383
+ farm = c.fetchone()
384
+ return {"farm_id": farm[0]} if farm else None
385
+ finally:
386
+ db_pool.putconn(conn)
387
+
388
+ @retry_on_eof(max_attempts=2, delay=3)
389
+ def get_hives_from_db(farm_id):
390
+ conn = db_pool.getconn()
391
+ try:
392
+ with conn.cursor() as c:
393
+ c.execute("SELECT hive_id, hive_number, health_status FROM Hive WHERE farm_id = %s ORDER BY hive_number", (farm_id,))
394
+ return [{"hive_id": row[0], "hive_number": row[1], "health_status": row[2]} for row in c.fetchall()]
395
+ finally:
396
+ db_pool.putconn(conn)
397
+
398
+ @retry_on_eof(max_attempts=2, delay=3)
399
+ def get_hive_detail_from_db(hive_id):
400
+ conn = db_pool.getconn()
401
+ try:
402
+ with conn.cursor() as c:
403
+ c.execute(
404
+ "SELECT hive_number, bee_type, number_of_frames, creation_date, health_status, notes FROM Hive WHERE hive_id = %s",
405
+ (hive_id,)
406
+ )
407
+ row = c.fetchone()
408
+ if row:
409
+ return {
410
+ "hive_number": row[0],
411
+ "bee_type": row[1],
412
+ "number_of_frames": row[2],
413
+ "creation_date": row[3],
414
+ "health_status": row[4],
415
+ "notes": row[5]
416
+ }
417
+ return {"error": "Hive not found"}
418
+ finally:
419
+ db_pool.putconn(conn)
420
+
421
+ @retry_on_eof(max_attempts=2, delay=3)
422
+ def update_hive_health_in_db(hive_id, health_status):
423
+ conn = db_pool.getconn()
424
+ try:
425
+ with conn.cursor() as c:
426
+ c.execute("UPDATE hive SET health_status = %s WHERE hive_id = %s", (health_status, hive_id))
427
+ if c.rowcount == 0:
428
+ raise Exception("Hive not found")
429
+ conn.commit()
430
+ finally:
431
+ db_pool.putconn(conn)
432
+
433
+ @retry_on_eof(max_attempts=2, delay=3)
434
+ def update_hive_in_db(hive_id, number_of_frames, bee_type, notes):
435
+ conn = db_pool.getconn()
436
+ try:
437
+ with conn.cursor() as c:
438
+ c.execute(
439
+ """
440
+ UPDATE Hive
441
+ SET number_of_frames = %s, bee_type = %s, notes = %s
442
+ WHERE hive_id = %s
443
+ RETURNING hive_id
444
+ """,
445
+ (number_of_frames, bee_type, notes, hive_id)
446
+ )
447
+ result = c.fetchone()
448
+ conn.commit()
449
+ return bool(result)
450
+ finally:
451
+ db_pool.putconn(conn)
452
+
453
+ @retry_on_eof(max_attempts=2, delay=3)
454
+ def add_task_to_db(user_id, task_name, hive_id, description, deadline_date, status):
455
+ conn = db_pool.getconn()
456
+ try:
457
+ with conn.cursor() as c:
458
+ c.execute("""
459
+ INSERT INTO Task (user_id, task_name, hive_id, description, deadline_date, status)
460
+ VALUES (%s, %s, %s, %s, %s, %s)
461
+ RETURNING task_id
462
+ """, (user_id, task_name, None if hive_id is None else hive_id, description, deadline_date, status))
463
+ task_id = c.fetchone()[0]
464
+ conn.commit()
465
+ return task_id
466
+ finally:
467
+ db_pool.putconn(conn)
468
+
469
+ @retry_on_eof(max_attempts=2, delay=3)
470
+ def get_user_tasks_from_db(user_id):
471
+ conn = db_pool.getconn()
472
+ try:
473
+ with conn.cursor() as c:
474
+ c.execute("""
475
+ SELECT task_id, task_name, status, deadline_date
476
+ FROM Task
477
+ WHERE user_id = %s
478
+ ORDER BY status = 'pending' DESC, deadline_date ASC NULLS LAST, task_id
479
+ """, (user_id,))
480
+ return [{
481
+ "task_id": row[0],
482
+ "task_name": row[1],
483
+ "status": row[2],
484
+ "deadline_date": row[3].strftime("%Y-%m-%d") if row[3] else None
485
+ } for row in c.fetchall()]
486
+ finally:
487
+ db_pool.putconn(conn)
488
+
489
+ @retry_on_eof(max_attempts=2, delay=3)
490
+ def get_task_detail_from_db(task_id):
491
+ conn = db_pool.getconn()
492
+ try:
493
+ with conn.cursor() as c:
494
+ c.execute("""
495
+ SELECT t.task_id, t.user_id, t.task_name, h.hive_number, t.description,
496
+ t.deadline_date, t.status
497
+ FROM Task t
498
+ LEFT JOIN Hive h ON t.hive_id = h.hive_id
499
+ WHERE t.task_id = %s
500
+ """, (task_id,))
501
+ row = c.fetchone()
502
+ if row:
503
+ return {
504
+ "task_id": row[0],
505
+ "task_name": row[2],
506
+ "hive_number": row[3],
507
+ "description": row[4],
508
+ "deadline_date": row[5].strftime("%Y-%m-%d") if row[5] else None,
509
+ "status": row[6]
510
+ }
511
+ return None
512
+ finally:
513
+ db_pool.putconn(conn)
514
+
515
+ @retry_on_eof(max_attempts=2, delay=3)
516
+ def update_task_status_to_completed(task_id):
517
+ conn = db_pool.getconn()
518
+ try:
519
+ with conn.cursor() as c:
520
+ c.execute("""
521
+ UPDATE Task
522
+ SET status = 'completed'
523
+ WHERE task_id = %s
524
+ """, (task_id,))
525
+ if c.rowcount > 0:
526
+ conn.commit()
527
+ return True
528
+ return False
529
+ finally:
530
+ db_pool.putconn(conn)
531
+
532
+ @retry_on_eof(max_attempts=2, delay=3)
533
+ def delete_task_from_db(task_id):
534
+ conn = db_pool.getconn()
535
+ try:
536
+ with conn.cursor() as c:
537
+ c.execute("DELETE FROM Task WHERE task_id = %s", (task_id,))
538
+ if c.rowcount > 0:
539
+ conn.commit()
540
+ return True
541
+ return False
542
+ finally:
543
+ db_pool.putconn(conn)
544
+
545
+ @retry_on_eof(max_attempts=2, delay=3)
546
+ def get_all_notifications(user_id):
547
+ conn = db_pool.getconn()
548
+ try:
549
+ with conn.cursor() as c:
550
+ c.execute("""
551
+ SELECT notification_id, text, created_at, read_status
552
+ FROM notifications
553
+ WHERE user_id = %s
554
+ ORDER BY read_status = 'unread' DESC, created_at DESC
555
+ """, (user_id,))
556
+ return [{
557
+ "notification_id": row[0],
558
+ "text": row[1],
559
+ "created_at": row[2].strftime("%Y-%m-%d %H:%M:%S"),
560
+ "read_status": row[3]
561
+ } for row in c.fetchall()]
562
+ finally:
563
+ db_pool.putconn(conn)
564
+
565
+ @retry_on_eof(max_attempts=2, delay=3)
566
+ def add_notification(user_id, text):
567
+ conn = db_pool.getconn()
568
+ try:
569
+ with conn.cursor() as c:
570
+ c.execute("""
571
+ INSERT INTO notifications (user_id, text, read_status, created_at)
572
+ VALUES (%s, %s, %s, CURRENT_TIMESTAMP + INTERVAL '5 hours')
573
+ RETURNING notification_id
574
+ """, (user_id, text, 'unread'))
575
+ notification_id = c.fetchone()[0]
576
+ conn.commit()
577
+ return notification_id
578
+ finally:
579
+ db_pool.putconn(conn)
580
+
581
+ @retry_on_eof(max_attempts=2, delay=3)
582
+ def mark_notification_as_read(notification_id):
583
+ conn = db_pool.getconn()
584
+ try:
585
+ with conn.cursor() as c:
586
+ c.execute("""
587
+ UPDATE notifications
588
+ SET read_status = 'read'
589
+ WHERE notification_id = %s
590
+ """, (notification_id,))
591
+ conn.commit()
592
+ finally:
593
+ db_pool.putconn(conn)
594
+
595
+ @retry_on_eof(max_attempts=2, delay=3)
596
+ def get_user_cities():
597
+ conn = db_pool.getconn()
598
+ try:
599
+ with conn.cursor() as c:
600
+ c.execute("SELECT user_id, city FROM farm WHERE city IS NOT NULL AND city != ''")
601
+ return {row[0]: row[1] for row in c.fetchall()}
602
+ finally:
603
+ db_pool.putconn(conn)
forget.py ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import logging
3
+ from flask import jsonify
4
+ from sendgrid import SendGridAPIClient
5
+ from sendgrid.helpers.mail import Mail
6
+ from database import generate_reset_code, verify_reset_code, update_user_password
7
+
8
+ # Configure logging
9
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
10
+ logger = logging.getLogger(__name__)
11
+
12
+ # Email configuration using environment variables
13
+ EMAIL_ADDRESS = os.environ.get('EMAIL_ADDRESS')
14
+ SENDGRID_API_KEY = os.environ.get('SENDGRID_API_KEY')
15
+ SENDER_NAME = os.environ.get('SENDER_NAME', 'BeeGuardian Team')
16
+ COMPANY_ADDRESS = os.environ.get('COMPANY_ADDRESS', 'Namal University')
17
+ COMPANY_CITY = os.environ.get('COMPANY_CITY', 'Mianwali')
18
+ COMPANY_STATE = os.environ.get('COMPANY_STATE', 'Punjab')
19
+ COMPANY_ZIP = os.environ.get('COMPANY_ZIP', '42200')
20
+ COMPANY_COUNTRY = os.environ.get('COMPANY_COUNTRY', 'Pakistan')
21
+
22
+ # Send email with reset code using SendGrid
23
+ def send_reset_email(email, code):
24
+ if not EMAIL_ADDRESS or not SENDGRID_API_KEY:
25
+ logger.error("Email configuration missing")
26
+ return False, "Email configuration missing"
27
+
28
+ # Email content with BeeGuardian intro and CAN-SPAM compliant footer
29
+ footer = (
30
+ f"\n\n--\n"
31
+ f"{SENDER_NAME}\n"
32
+ f"{COMPANY_ADDRESS}\n"
33
+ f"{COMPANY_CITY}, {COMPANY_STATE} {COMPANY_ZIP}\n"
34
+ f"{COMPANY_COUNTRY}"
35
+ )
36
+ content = (
37
+ f"Welcome to BeeGuardian! We are a team of students from Namal University Mianwali who created an innovative app to help beekeepers monitor bee health using mobile phone recordings. Our mission is to protect bees and support biodiversity.\n\n"
38
+ f"To reset your password, please use this code: {code}\n"
39
+ f"This code is valid for 15 minutes.{footer}"
40
+ )
41
+
42
+ message = Mail(
43
+ from_email=(EMAIL_ADDRESS, SENDER_NAME),
44
+ to_emails=email,
45
+ subject='Password Reset Code for BeeGuardian',
46
+ plain_text_content=content
47
+ )
48
+ try:
49
+ sg = SendGridAPIClient(SENDGRID_API_KEY)
50
+ response = sg.send(message)
51
+ return True, None
52
+ except Exception as e:
53
+ logger.error(f"Failed to send email to {email}: {str(e)}")
54
+ return False, str(e)
55
+
56
+ # Forgot password logic
57
+ def forgot_password(request):
58
+ data = request.json
59
+ if 'email' not in data:
60
+ logger.warning("Forgot password request missing email")
61
+ return jsonify({"error": "Email is required"}), 400
62
+ try:
63
+ code = generate_reset_code(data['email'])
64
+ if not code:
65
+ return jsonify({"error": "Email not found"}), 404
66
+ success, error_msg = send_reset_email(data['email'], code)
67
+ if success:
68
+ return jsonify({"success": True, "message": "Reset code sent to email"}), 200
69
+ logger.error(f"Failed to send reset email: {error_msg}")
70
+ return jsonify({"error": f"Failed to send email: {error_msg}"}), 500
71
+ except Exception as e:
72
+ logger.error(f"Error in forgot_password for {data['email']}: {str(e)}")
73
+ return jsonify({"error": str(e)}), 500
74
+
75
+ # Verify reset code logic
76
+ def verify_reset_code_endpoint(request):
77
+ data = request.json
78
+ if not all(key in data for key in ['email', 'code']):
79
+ logger.warning("Verify reset code request missing email or code")
80
+ return jsonify({"error": "Email and code are required"}), 400
81
+ try:
82
+ valid = verify_reset_code(data['email'], data['code'])
83
+ if valid:
84
+ return jsonify({"success": True, "message": "Code verified"}), 200
85
+ return jsonify({"error": "Invalid or expired code"}), 400
86
+ except Exception as e:
87
+ logger.error(f"Error in verify_reset_code for {data['email']}: {str(e)}")
88
+ return jsonify({"error": str(e)}), 500
89
+
90
+ # Reset password logic
91
+ def reset_password(request):
92
+ data = request.json
93
+ if not all(key in data for key in ['email', 'new_password']):
94
+ logger.warning("Reset password request missing required fields")
95
+ return jsonify({"error": "Email and new password are required"}), 400
96
+ try:
97
+ updated = update_user_password(data['email'], data['new_password'])
98
+ if updated:
99
+ return jsonify({"success": True, "message": "Password reset successfully"}), 200
100
+ logger.error(f"Failed to update password for {data['email']}")
101
+ return jsonify({"error": "Failed to update password"}), 500
102
+ except Exception as e:
103
+ logger.error(f"Error in reset_password for {data['email']}: {str(e)}")
104
+ return jsonify({"error": str(e)}), 500
logo.png ADDED

Git LFS Details

  • SHA256: 4eb523bd5f85dfd2656fc42bfb776822d1ff393cb2d793984839f06ac8f6c2ac
  • Pointer size: 131 Bytes
  • Size of remote file: 249 kB