from flask import Flask, request, jsonify, send_file from pymongo import MongoClient from pydantic import BaseModel from flask_limiter import Limiter from flask_limiter.util import get_remote_address from flask_cors import CORS from functools import wraps import datetime import uvicorn import bcrypt import jwt import os import re import uuid from gtts import gTTS from bson import ObjectId # app = FastAPI() app = Flask(__name__) CORS(app, resources={r"/*": {"origins": ["https://bhagvat-ras.static.hf.space", "http://localhost:7860"]}}) SECRET_KEY = os.getenv("VERA_API_KEY") # Rate limiter (create it before using @limiter.limit) limiter = Limiter(get_remote_address, app=app, default_limits=["200/hour"]) # MongoDB connection client = MongoClient("mongodb+srv://veratheai24:veratheai24@cluster0.b15rk44.mongodb.net/?retryWrites=true&w=majority&appName=Cluster0") # Database connections app_info_db = client['app_info_db'] user_db = client['user_db'] users_collection = user_db['users'] # Collection for user data chat_db = client['user_chat_db'] chat_collection = chat_db['chats'] # Single collection for all chat history shastra_db = client['shastra'] donation_db = client['donation_db'] donation_collection = donation_db['donations'] # Token-required decorator def token_required(f): @wraps(f) def decorated(*args, **kwargs): token = request.headers.get('Authorization') # Expecting 'Bearer ' if not token: return {"message": "Token is missing!"}, 403 try: # Remove 'Bearer' from the token string if present token = token.split(" ")[1] if " " in token else token decoded_token = jwt.decode(token, SECRET_KEY, algorithms=["HS256"]) except jwt.ExpiredSignatureError: return {"message": "Token has expired!"}, 403 except jwt.InvalidTokenError: return {"message": "Invalid token!"}, 403 # Store decoded token data in kwargs for the route function kwargs['user_info'] = decoded_token return f(*args, **kwargs) return decorated # Function to fetch all chapters from 'shastra' database @app.route("/get_chapters", methods=["GET"]) def get_chapters(): try: # List all collections in the 'shastra' database collections = shastra_db.list_collection_names() data = {} # Fetch data from each collection and store it in 'data' for collection_name in collections: collection_data = list(shastra_db[collection_name].find({}, {"_id": 0})) # Exclude '_id' from results data[collection_name] = collection_data # Debugging output for verification # print("Fetched collections:", collections) # print("Fetched data sample:", data) return jsonify({"success": True, "data": data}), 200 except Exception as e: error_message = f"Error fetching chapters: {e}" print(error_message) return jsonify({"success": False, "message": error_message}), 500 @app.route("/get_chapter_details", methods=["GET"]) def get_chapter_details(): try: # Get the query parameter for chapterTitle chapter_title = request.args.get("chapterTitle") print(f"Requested chapterTitle: {chapter_title}") if not chapter_title: return jsonify({"success": False, "message": "chapterTitle parameter is required."}), 400 # Fetch the chapter details from the 'Bhagwat Charcha' collection collection = shastra_db["Bhagwat Mahapuran"] # Use aggregation to search for the chapter inside the nested 'chapters' array result = collection.aggregate([ {"$unwind": "$chapters"}, # Unwind the chapters array {"$match": {"chapters.chapterTitle": chapter_title}}, # Match the specific chapter {"$project": {"_id": 0, "chapterDetails": "$chapters"}} # Project only the matching chapter ]) # Get the first result from the aggregation chapter_details = next(result, None) if not chapter_details: return jsonify({"success": False, "message": "Chapter not found."}), 404 # Return the chapter details return jsonify({"success": True, "data": chapter_details["chapterDetails"]}), 200 except Exception as e: error_message = f"Error fetching chapter details: {e}" print(error_message) return jsonify({"success": False, "message": error_message}), 500 @app.route("/get_chapter_titles", methods=["GET"]) def get_chapter_titles(): try: # List all collections in the 'shastra' database collections = shastra_db.list_collection_names() data = {} # Iterate over each collection to gather chapter titles for collection_name in collections: collection_data = list(shastra_db[collection_name].find({}, {"_id": 0, "chapterTitle": 1, "skandaTitle": 1, "chapters": 1})) data[collection_name] = [] for entry in collection_data: if "chapters" in entry and isinstance(entry["chapters"], list): # Handle grouped chapters under a Skanda skanda_entry = { "skandaTitle": entry.get("skandaTitle", ""), "chapters": [ {"chapterTitle": chapter["chapterTitle"]} for chapter in entry["chapters"] ] } data[collection_name].append(skanda_entry) else: # Handle standalone chapters data[collection_name].append({ "chapterTitle": entry.get("chapterTitle", "") }) return jsonify({"success": True, "data": data}), 200 except Exception as e: error_message = f"Error fetching chapter titles: {e}" print(error_message) return jsonify({"success": False, "message": error_message}), 500 # Function to fetch the audio URL for a given sloke def get_audio_from_db(sloke_text): try: # Access the collection collection = shastra_db["Bhagwat Mahapuran"] # Replace with your collection name print(f"Searching for sloke: {sloke_text}") # Debugging log # Use aggregation to search for the chapter containing the sloke result = collection.aggregate([ {"$unwind": "$chapters"}, # Unwind the chapters array {"$match": {"chapters.slokes": {"$regex": sloke_text}}}, # {"$match": {"chapters.slokes": {"$regex": r"^(श्लोक\s*\d+:)?\s*" + re.escape(sloke_text)}}}, # Match the sloke text with optional prefix {"$project": { "_id": 0, "slokes": "$chapters.slokes", "audioLinks": "$chapters.audioLinks" }} # Project slokes and audioLinks ]) # Get the first result from the aggregation chapter_details = next(result, None) if not chapter_details: return None # No matching sloke found # Split the slokes into individual slokes slokes = chapter_details["slokes"].split("\n") # Assuming slokes are newline-separated audio_links = chapter_details.get("audioLinks", []) # Get the audio links array # print(f"Audio Links: {audio_links}") # Debugging log # Extract the slok number from the provided text slok_number_match = re.search(r"श्लोक\s*(\d+)", sloke_text) if not slok_number_match: print(f"Slok number not found in text: {sloke_text}") return None slok_number = int(slok_number_match.group(1)) # Extracted slok number print(f"Slok Number: {slok_number}") # Debugging log # The slok number corresponds to the index in the audioLinks array (0-based index) if 1 <= slok_number <= len(audio_links): # Return the audio link for the slok number return audio_links[slok_number - 1] else: print(f"Audio link for slok number {slok_number} not found") return None except Exception as e: print(f"Error fetching audio URL: {e}") return None def convert_drive_link(shareable_link): if "drive.google.com" in shareable_link and "/file/d/" in shareable_link: file_id = shareable_link.split('/d/')[1].split('/')[0] return f"https://drive.google.com/uc?export=download&id={file_id}" return shareable_link @app.route('/get_audio_url', methods=['GET']) def get_audio_url(): sloke_text = request.args.get('slokeText') # Use slokeText as the parameter if not sloke_text: return jsonify({'success': False, 'message': 'Sloke text is required'}), 400 # Fetch the audio URL from MongoDB audio_url = get_audio_from_db(sloke_text) if audio_url: # audio_url = convert_drive_link(audio_url) return jsonify({'success': True, 'audioUrl': audio_url}) else: return jsonify({'success': False, 'message': 'Audio not found'}), 404 # Function to register a user and store their chat history in the same collection @app.route("/register_user", methods=["POST"]) def register_user(): data = request.get_json() name = data.get("name") username = data.get("username") email = data.get("email") password = data.get("password") device_id = data.get("device_id") # Hash the password for security hashed_password = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt()) # Check if the user already exists in MongoDB existing_user = users_collection.find_one({"email": email}) if existing_user: return {"message": "User already registered!"}, 409 # Use 409 Conflict # Register the user in the 'users' collection user_data = { "name": name, "username": username, "email": email, "password": hashed_password, "device_id": device_id, "registered_on": datetime.datetime.now() } users_collection.insert_one(user_data) # Generate JWT token token = jwt.encode({ "email": email, "exp": datetime.datetime.utcnow() + datetime.timedelta(days=14) # Expire in 14 days }, SECRET_KEY, algorithm="HS256") return {"success": True, "message": f"User {username} registered!", "token": token}, 201 @app.route('/login', methods=['POST']) def login(): data = request.get_json() email = data.get('username') password = data.get('password') # Fetch the user from the MongoDB database by email user = users_collection.find_one({"email": email}) if not user or not bcrypt.checkpw(password.encode('utf-8'), user['password']): return jsonify({"message": "Invalid credentials!"}), 401 # Generate a JWT token token = jwt.encode({ "email": email, "exp": datetime.datetime.utcnow() + datetime.timedelta(days=14) # Token expires in 14 days }, SECRET_KEY, algorithm="HS256") return jsonify({"success": True, "message": "Login successful!", "token": token}), 200 # Function to store chat history for unregistered users @app.route('/store_anonymous_chat', methods=["POST"]) def store_anonymous_chat(): data = request.get_json() user_prompt = data.get('user_data') bot_prompt = data.get('bot_data') bot_data_list = bot_prompt.split('\n') category = bot_data_list[0] if bot_data_list else "General" bot_data = '\n'.join(bot_data_list[1:]) anonymous_chat_entry = { "user_type": "anonymous", # Flag to identify unregistered users "user_data": user_prompt, "bot_data": bot_data, "category": category, "timestamp": datetime.datetime.now() } # Save chat asynchronously using Celery chat_collection.insert_one(anonymous_chat_entry) # chat_collection.insert_one(anonymous_chat_entry) return {"message": "Anonymous chat saved successfully!"}, 201 # Function to store chat history for registered users @app.route('/store_registered_chat/', methods=["POST"]) def store_registered_chat(username: str): data = request.get_json() user_prompt = data.get('user_data') bot_prompt = data.get('bot_data') # Check if the user exists user_exists = users_collection.find_one({"email": username}) if not user_exists: return {"message": "User not found!"}, 404 bot_data_list = bot_prompt.split('\n') category = bot_data_list[0] if bot_data_list else "General" bot_data = '\n'.join(bot_data_list[1:]) # Store chat in the unified chat collection with a user identifier chat_entry = { "user_type": "registered", "username": username, "user_data": user_prompt, "bot_data": bot_data, "category": category, "timestamp": datetime.datetime.now() } # Save chat asynchronously using Celery chat_collection.insert_one(chat_entry) # chat_collection.insert_one(chat_entry) return {"message": "Chat saved successfully for registered user!"}, 201 # Function to register a user from google and store their chat history in the same collection @app.route("/register_google_user", methods=["POST"]) def register_google_user(): data = request.get_json() name = data.get("name") username = data.get("username") email = data.get("email") google_id = data.get("google_id") device_id = data.get("device_id") # Check if the user already exists in MongoDB existing_user = users_collection.find_one({"email": email}) if existing_user: # Generate JWT token token = jwt.encode({ "email": email, "exp": datetime.datetime.utcnow() + datetime.timedelta(days=14) # Expire in 14 days }, SECRET_KEY, algorithm="HS256") return {"success": True, "message": "User already registered!", "token": token}, 201 # Register the user in the 'users' collection user_data = { "name": name, "username": username, "email": email, "google_id": google_id, "device_id": device_id, "registered_on": datetime.datetime.now() } users_collection.insert_one(user_data) # Generate JWT token token = jwt.encode({ "email": email, "exp": datetime.datetime.utcnow() + datetime.timedelta(days=14) # Expire in 14 days }, SECRET_KEY, algorithm="HS256") return {"success": True, "message": f"User {username} registered!", "token": token}, 201 # Function to fetch chat history for a specific user (registered or anonymous) @app.route('/get_chat_history/', methods=["GET"]) def get_chat_history(username: str): # Fetch chat history for registered user chat_history = chat_collection.find({"username": username}) if chat_history.count() == 0: return jsonify({"message": "No chat history found!"}), 404 return jsonify([{"user_data": chat["user_data"], "bot_data": chat["bot_data"], "timestamp": chat["timestamp"]} for chat in chat_history]), 200 # Protected route example @app.route('/protected', methods=['GET']) @token_required # Apply the token_required decorator def protected(): return {"message": "This is a protected route."}, 200 @app.route('/validate_token', methods=['GET']) @token_required def validate_token(user_info): return { "message": "Token is valid!", "email": user_info["email"] # Return the user info extracted from the token }, 200 @app.route('/generate_sanskrit_audio', methods=['POST']) @limiter.limit("30/minute") def generate_sanskrit_audio(): data = request.get_json() text = data.get('cleanText', '') # Check if text is provided if not text: return {"error": "No text provided"}, 400 try: # Generate audio using gTTS audio_file = f'output_{uuid.uuid4()}.mp3' # Unique file name tts = gTTS(text=text, lang='hi', slow=False) # 'sa' is for Sanskrit tts.save(audio_file) # Send the audio file back response = send_file(audio_file, mimetype='audio/mp3', as_attachment=True) # Cleanup: remove the audio file after sending os.remove(audio_file) return response except Exception as e: return {"error": str(e)}, 500 @app.route('/save_donation', methods=["POST"]) def save_donation(): """ Save donation details to the MongoDB collection. """ data = request.get_json() # Extract user-provided data name = data.get('name') email = data.get('email') amount = data.get('amount') payment_method = data.get('payment_method', 'UPI') # Default payment method is UPI if not name or not email or not amount: return {"error": "Name, email, and amount are required fields!"}, 400 # Create the donation entry donation_entry = { "user_type": "anonymous", # Assuming donations are anonymous by default "name": name, "email": email, "amount": amount, "payment_method": payment_method, "timestamp": datetime.datetime.now() } try: # Insert the donation entry into the MongoDB collection donation_collection.insert_one(donation_entry) return {"message": "Donation saved successfully!"}, 201 except Exception as e: return {"error": f"An error occurred: {str(e)}"}, 500 @app.get('/chats/') def list_chats(username): items = list(chat_collection.find({'username': username}, {'_id':1,'user_data':1,'bot_data':1,'timestamp':1,'category':1}).sort('timestamp', -1)) out = [] for it in items: out.append({ 'id': str(it['_id']), 'title': (it.get('user_data') or 'Chat')[:40], 'timestamp': it.get('timestamp'), 'category': it.get('category','General') }) return jsonify(out), 200 @app.patch('/chat//rename') def rename_chat(chat_id): title = (request.get_json() or {}).get('title') if not title: return jsonify({'error':'title required'}), 400 chat_collection.update_one({'_id': ObjectId(chat_id)}, {'$set': {'title': title}}) return jsonify({'ok': True}), 200 @app.delete('/chat/') def delete_chat(chat_id): chat_collection.delete_one({'_id': ObjectId(chat_id)}) return jsonify({'ok': True}), 200 # Simple memory K/V mem = client['user_memory_db'] mem_col = mem['memory'] @app.post('/memory/') def save_memory(username): data = request.get_json() or {} key, value = data.get('key'), data.get('value') if not key: return jsonify({'error':'key required'}), 400 mem_col.update_one({'username': username, 'key': key}, {'$set': {'value': value, 'updated': datetime.datetime.now()}}, upsert=True) return jsonify({'ok': True}), 200 @app.get('/memory/') def get_memory(username): items = list(mem_col.find({'username': username}, {'_id':0})) return jsonify(items), 200 @app.delete('/memory//') def delete_memory(username, key): mem_col.delete_one({'username': username, 'key': key}) return jsonify({'ok': True}), 200 if __name__ == "__main__": app.run(host="0.0.0.0", port=7860)