ada-web

Ada AI is a specialized coding...
Log | Files | Refs | README | LICENSE

commit 32720f5ced6b6ec1f831fc0c0c0c11907f5f50fa
parent 8d60cacdd3cb8e4033c8fdde5575bd45ce969660
Author: AMIT DUTTA <amitdutta4255@gmail.com>
Date:   Tue, 20 Jan 2026 19:30:01 +0530

Update backend.py
Diffstat:
Mbackend.py | 164++++++++++++++++++++++++++++++++++++++++++++++---------------------------------
1 file changed, 96 insertions(+), 68 deletions(-)

diff --git a/backend.py b/backend.py @@ -20,8 +20,6 @@ CORS(app, resources={r"/api/*": {"origins": [ # 1. Firebase Admin Init (Server-Side Security) # Ensure you have your service account json or environment variables set up in Render -# For Render/Cloud, credentials.ApplicationDefault() often works if env vars are correct. -# Otherwise, use a service account JSON file. try: if not firebase_admin._apps: # Check for FIREBASE_CREDENTIALS environment variable (JSON string) @@ -31,22 +29,22 @@ try: # Parse JSON string from environment variable cred_dict = json.loads(firebase_creds_json) cred = credentials.Certificate(cred_dict) - print("✅ Using Firebase credentials from FIREBASE_CREDENTIALS environment variable") + print("Using Firebase credentials from FIREBASE_CREDENTIALS environment variable") elif os.path.exists("firebase-adminsdk.json"): cred = credentials.Certificate("firebase-adminsdk.json") - print("✅ Using Firebase credentials from firebase-adminsdk.json file") + print("Using Firebase credentials from firebase-adminsdk.json file") else: # Try application default credentials cred = credentials.ApplicationDefault() - print("✅ Using Firebase Application Default Credentials") + print("Using Firebase Application Default Credentials") firebase_admin.initialize_app(cred) - print("✅ Firebase Admin SDK initialized successfully") + print("Firebase Admin SDK initialized successfully") db = firestore.client() - print("✅ Firestore client initialized successfully") + print("Firestore client initialized successfully") except Exception as e: - print(f"❌ Firebase initialization error: {type(e).__name__}: {e}") - print("⚠️ API will run but Firestore features will not work") + print(f"Firebase initialization error: {type(e).__name__}: {e}") + print("API will run but Firestore features will not work") db = None # 2. Gemini Init @@ -88,19 +86,19 @@ model = genai.GenerativeModel( system_instruction=SYSTEM_PROMPT, ) -# --- HELPER: Verify Firebase Token --- +# --- HELPER: Verify Firebase Token & Check Guest Status --- def verify_token(auth_header): """ Verifies the Firebase ID Token sent from the client. - Returns user_uid if valid, None otherwise. - Enhanced with better error messages and logging. + Returns a dictionary {'uid': str, 'is_guest': bool} if valid, None otherwise. + Enhanced to detect Anonymous (Guest) users. """ if not auth_header: - print("❌ No Authorization header provided") + print("No Authorization header provided") return None if not auth_header.startswith("Bearer "): - print("❌ Invalid Authorization header format (must start with 'Bearer ')") + print("Invalid Authorization header format (must start with 'Bearer ')") return None token = auth_header.split("Bearer ")[1] @@ -108,21 +106,39 @@ def verify_token(auth_header): try: decoded_token = auth.verify_id_token(token) user_id = decoded_token['uid'] - # print(f"✅ Token verified for user: {user_id}") # verbose - return user_id + + # Check the sign-in provider to see if it's an anonymous user + firebase_claim = decoded_token.get('firebase', {}) + provider = firebase_claim.get('sign_in_provider') + is_guest = (provider == 'anonymous') + + return {'uid': user_id, 'is_guest': is_guest} + except auth.InvalidIdTokenError: - print("❌ Invalid ID token - token is malformed or invalid") + print("Invalid ID token - token is malformed or invalid") return None except auth.ExpiredIdTokenError: - print("❌ Token has expired - user needs to refresh their token") + print("Token has expired - user needs to refresh their token") return None except auth.RevokedIdTokenError: - print("❌ Token has been revoked") + print("Token has been revoked") return None except Exception as e: - print(f"❌ Token verification error: {type(e).__name__}: {e}") + print(f"Token verification error: {type(e).__name__}: {e}") return None +def get_user_ref(user_data): + """ + Returns the Firestore Document Reference for the user. + Routes guests to 'temp-users' and registered users to 'users'. + """ + if not db: + raise Exception("Database unavailable") + + # TRAFFIC ROUTING LOGIC + collection_name = 'temp-users' if user_data['is_guest'] else 'users' + return db.collection(collection_name).document(user_data['uid']) + # --- ROUTES --- @app.route('/') @@ -130,7 +146,7 @@ def home(): return jsonify({ "status": "online", "service": "Ada AI Coding Backend", - "version": "1.0.0", + "version": "1.1.0", "model": "gemini-2.5-flash-preview-09-2025" }) @@ -146,8 +162,8 @@ def health(): def generate_title(): """Generates a short 3-5 word title for the chat history.""" # Verify auth even for titles to prevent abuse - user_uid = verify_token(request.headers.get('Authorization')) - if not user_uid: + user_data = verify_token(request.headers.get('Authorization')) + if not user_data: return jsonify({ "error": "Unauthorized", "message": "Invalid or missing authentication token. Please sign in again." @@ -161,21 +177,22 @@ def generate_title(): res = title_model.generate_content(f"Summarize this coding query into a 3-5 word title: '{message}'") return jsonify({"title": res.text.strip()}) except Exception as e: - print(f"❌ Error generating title: {type(e).__name__}: {e}") + print(f"Error generating title: {type(e).__name__}: {e}") return jsonify({"title": "New Chat"}) @app.route('/api/profile', methods=['GET']) def get_profile(): """Get user profile from Firestore.""" - user_uid = verify_token(request.headers.get('Authorization')) - if not user_uid: + user_data = verify_token(request.headers.get('Authorization')) + if not user_data: return jsonify({"error": "Unauthorized"}), 401 if not db: return jsonify({"error": "Database unavailable"}), 503 try: - user_ref = db.collection('users').document(user_uid) + # Use helper to get correct collection path + user_ref = get_user_ref(user_data) doc = user_ref.get() if doc.exists: @@ -183,8 +200,9 @@ def get_profile(): else: # Create default profile default_profile = { - "uid": user_uid, - "displayName": "", + "uid": user_data['uid'], + "isGuest": user_data['is_guest'], + "displayName": "Guest User" if user_data['is_guest'] else "", "email": "", "photoURL": "", "age": "", @@ -201,14 +219,14 @@ def get_profile(): user_ref.set(default_profile) return jsonify(default_profile) except Exception as e: - print(f"❌ Error getting profile: {type(e).__name__}: {e}") + print(f"Error getting profile: {type(e).__name__}: {e}") return jsonify({"error": "Failed to get profile"}), 500 @app.route('/api/profile', methods=['PUT']) def update_profile(): """Update user profile in Firestore.""" - user_uid = verify_token(request.headers.get('Authorization')) - if not user_uid: + user_data = verify_token(request.headers.get('Authorization')) + if not user_data: return jsonify({"error": "Unauthorized"}), 401 if not db: @@ -220,26 +238,28 @@ def update_profile(): update_data = {k: v for k, v in data.items() if k in allowed_fields} update_data['updatedAt'] = datetime.datetime.now(datetime.timezone.utc) - user_ref = db.collection('users').document(user_uid) + user_ref = get_user_ref(user_data) user_ref.update(update_data) return jsonify({"success": True, "message": "Profile updated"}) except Exception as e: - print(f"❌ Error updating profile: {type(e).__name__}: {e}") + print(f"Error updating profile: {type(e).__name__}: {e}") return jsonify({"error": "Failed to update profile"}), 500 @app.route('/api/chats', methods=['GET']) def get_chats(): """Get all user's chat sessions from Firestore.""" - user_uid = verify_token(request.headers.get('Authorization')) - if not user_uid: + user_data = verify_token(request.headers.get('Authorization')) + if not user_data: return jsonify({"error": "Unauthorized"}), 401 if not db: return jsonify({"error": "Database unavailable"}), 503 try: - chats_ref = db.collection('users').document(user_uid).collection('chats') + user_ref = get_user_ref(user_data) + chats_ref = user_ref.collection('chats') + # Order by updatedAt descending (most recent first) docs = chats_ref.order_by('updatedAt', direction='DESCENDING').stream() @@ -259,21 +279,22 @@ def get_chats(): return jsonify({"chats": chats}) except Exception as e: - print(f"❌ Error getting chats: {type(e).__name__}: {e}") + print(f"Error getting chats: {type(e).__name__}: {e}") return jsonify({"error": "Failed to get chats"}), 500 @app.route('/api/chats/<chat_id>', methods=['GET']) def get_chat(chat_id): """Get specific chat session.""" - user_uid = verify_token(request.headers.get('Authorization')) - if not user_uid: + user_data = verify_token(request.headers.get('Authorization')) + if not user_data: return jsonify({"error": "Unauthorized"}), 401 if not db: return jsonify({"error": "Database unavailable"}), 503 try: - chat_ref = db.collection('users').document(user_uid).collection('chats').document(chat_id) + user_ref = get_user_ref(user_data) + chat_ref = user_ref.collection('chats').document(chat_id) doc = chat_ref.get() if not doc.exists: @@ -283,32 +304,33 @@ def get_chat(chat_id): chat_data['id'] = doc.id return jsonify(chat_data) except Exception as e: - print(f"❌ Error getting chat: {type(e).__name__}: {e}") + print(f"Error getting chat: {type(e).__name__}: {e}") return jsonify({"error": "Failed to get chat"}), 500 @app.route('/api/chats/<chat_id>', methods=['DELETE']) def delete_chat(chat_id): """Delete a chat session.""" - user_uid = verify_token(request.headers.get('Authorization')) - if not user_uid: + user_data = verify_token(request.headers.get('Authorization')) + if not user_data: return jsonify({"error": "Unauthorized"}), 401 if not db: return jsonify({"error": "Database unavailable"}), 503 try: - chat_ref = db.collection('users').document(user_uid).collection('chats').document(chat_id) + user_ref = get_user_ref(user_data) + chat_ref = user_ref.collection('chats').document(chat_id) chat_ref.delete() return jsonify({"success": True, "message": "Chat deleted"}) except Exception as e: - print(f"❌ Error deleting chat: {type(e).__name__}: {e}") + print(f"Error deleting chat: {type(e).__name__}: {e}") return jsonify({"error": "Failed to delete chat"}), 500 @app.route('/api/chats/<chat_id>/rename', methods=['PUT']) def rename_chat(chat_id): """Rename a chat session.""" - user_uid = verify_token(request.headers.get('Authorization')) - if not user_uid: + user_data = verify_token(request.headers.get('Authorization')) + if not user_data: return jsonify({"error": "Unauthorized"}), 401 if not db: @@ -318,7 +340,8 @@ def rename_chat(chat_id): data = request.json new_title = data.get('title', 'New Chat') - chat_ref = db.collection('users').document(user_uid).collection('chats').document(chat_id) + user_ref = get_user_ref(user_data) + chat_ref = user_ref.collection('chats').document(chat_id) chat_ref.update({ 'title': new_title, 'updatedAt': datetime.datetime.now(datetime.timezone.utc) @@ -326,21 +349,22 @@ def rename_chat(chat_id): return jsonify({"success": True, "message": "Chat renamed"}) except Exception as e: - print(f"❌ Error renaming chat: {type(e).__name__}: {e}") + print(f"Error renaming chat: {type(e).__name__}: {e}") return jsonify({"error": "Failed to rename chat"}), 500 @app.route('/api/chats/<chat_id>/export', methods=['GET']) def export_chat(chat_id): """Export chat as JSON.""" - user_uid = verify_token(request.headers.get('Authorization')) - if not user_uid: + user_data = verify_token(request.headers.get('Authorization')) + if not user_data: return jsonify({"error": "Unauthorized"}), 401 if not db: return jsonify({"error": "Database unavailable"}), 503 try: - chat_ref = db.collection('users').document(user_uid).collection('chats').document(chat_id) + user_ref = get_user_ref(user_data) + chat_ref = user_ref.collection('chats').document(chat_id) doc = chat_ref.get() if not doc.exists: @@ -361,14 +385,14 @@ def export_chat(chat_id): return jsonify(chat_data) except Exception as e: - print(f"❌ Error exporting chat: {type(e).__name__}: {e}") + print(f"Error exporting chat: {type(e).__name__}: {e}") return jsonify({"error": "Failed to export chat"}), 500 @app.route('/api/chats/<chat_id>/pin', methods=['PUT']) def pin_chat(chat_id): """Toggle pin status of a chat.""" - user_uid = verify_token(request.headers.get('Authorization')) - if not user_uid: + user_data = verify_token(request.headers.get('Authorization')) + if not user_data: return jsonify({"error": "Unauthorized"}), 401 if not db: @@ -378,7 +402,8 @@ def pin_chat(chat_id): data = request.json is_pinned = data.get('isPinned', False) - chat_ref = db.collection('users').document(user_uid).collection('chats').document(chat_id) + user_ref = get_user_ref(user_data) + chat_ref = user_ref.collection('chats').document(chat_id) chat_ref.update({ 'isPinned': is_pinned, 'updatedAt': datetime.datetime.now(datetime.timezone.utc) @@ -386,7 +411,7 @@ def pin_chat(chat_id): return jsonify({"success": True, "isPinned": is_pinned}) except Exception as e: - print(f"❌ Error pinning chat: {type(e).__name__}: {e}") + print(f"Error pinning chat: {type(e).__name__}: {e}") return jsonify({"error": "Failed to pin chat"}), 500 @app.route('/api/chat', methods=['POST']) @@ -396,9 +421,9 @@ def chat(): Headers: Authorization: Bearer <firebase_id_token> Body: { "message": str, "history": list, "codeContext": str, "fileContext": str, "sessionId": str } """ - # 1. Verify User - user_uid = verify_token(request.headers.get('Authorization')) - if not user_uid: + # 1. Verify User & Get Identity Info + user_data = verify_token(request.headers.get('Authorization')) + if not user_data: return jsonify({ "error": "Unauthorized", "message": "Invalid or missing authentication token. Please sign in again." @@ -440,7 +465,7 @@ def chat(): try: chat_session = model.start_chat(history=chat_history) except Exception as e: - print(f"❌ Error starting chat session: {type(e).__name__}: {e}") + print(f"Error starting chat session: {type(e).__name__}: {e}") return jsonify({ "error": "Internal Server Error", "message": "Failed to initialize chat session" @@ -460,11 +485,13 @@ def chat(): yield chunk.text final_text_acc += chunk.text - # 4. Save Interaction to Firestore + # 4. Save Interaction to Firestore (Specific Path) # We save this asynchronously (conceptually) after streaming is done. if session_id and db: try: - doc_ref = db.collection('users').document(user_uid).collection('chats').document(session_id) + # Get correct reference (users vs temp-users) + user_ref = get_user_ref(user_data) + doc_ref = user_ref.collection('chats').document(session_id) msg_data = { "role": "user", @@ -494,7 +521,8 @@ def chat(): "title": chat_title, "createdAt": datetime.datetime.now(datetime.timezone.utc), "updatedAt": datetime.datetime.now(datetime.timezone.utc), - "userId": user_uid, + "userId": user_data['uid'], + "isGuest": user_data['is_guest'], # Mark for easy identification "isPinned": False, "messages": [msg_data, ai_data] }) @@ -503,12 +531,12 @@ def chat(): "messages": firestore.ArrayUnion([msg_data, ai_data]), "updatedAt": datetime.datetime.now(datetime.timezone.utc) }) - print(f"✅ Chat saved to Firestore for session: {session_id}") + print(f"Chat saved to Firestore for session: {session_id} (Guest: {user_data['is_guest']})") except Exception as db_err: - print(f"❌ Database Save Error: {type(db_err).__name__}: {db_err}") + print(f"Database Save Error: {type(db_err).__name__}: {db_err}") except Exception as e: - error_msg = f"❌ Gemini API Error: {type(e).__name__}: {str(e)}" + error_msg = f"Gemini API Error: {type(e).__name__}: {str(e)}" print(error_msg) yield f"\n\nError: I encountered an issue while processing your request. Please try again."
© notamitgamer • Site Built: 2026-07-21 13:58:23 UTC • git-mirror commit: 1037f62 [view raw info]
Originally created with stagit • modified by notamitgamer
Forked from github.com/notamitgamer/git-mirror