Spaces:
Running
Running
Upload 6 files
Browse files- .gitattributes +1 -0
- Dockerfile +17 -0
- app.py +389 -0
- config.json +23 -0
- database.py +603 -0
- forget.py +104 -0
- 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
|