"""
Concept: Flask + HTML Integration - Spiritual Path Assessment Tool
This app helps users discover which religious or spiritual path aligns with their
beliefs, values, lifestyle, and background through an interactive questionnaire.
"""
# cSpell:ignore jsonify werkzeug dotenv puja moksha sikhism jainism shintoism paganism wicca
from flask import Flask, render_template, request, jsonify, session, redirect, url_for
from werkzeug.security import generate_password_hash, check_password_hash
import json
import os
import re
import secrets
from dotenv import load_dotenv
from together import Together
from rag_utils import load_religions_from_csv, prepare_religion_rag_context
from openai import OpenAI
import firebase_admin
from firebase_admin import credentials, auth, firestore
load_dotenv()
app = Flask(__name__)
app.secret_key = os.getenv('SECRET_KEY', secrets.token_hex(32))
# Session configuration for production deployment
app.config['SESSION_COOKIE_SECURE'] = os.getenv('FLASK_ENV') == 'production'
app.config['SESSION_COOKIE_HTTPONLY'] = True
app.config['SESSION_COOKIE_SAMESITE'] = 'Lax'
app.config['PERMANENT_SESSION_LIFETIME'] = 3600 # 1 hour
# Initialize Firebase Admin SDK
try:
firebase_cred_path = os.getenv('FIREBASE_CREDENTIALS_PATH', 'serviceAccountKey.json')
if os.path.exists(firebase_cred_path):
cred = credentials.Certificate(firebase_cred_path)
firebase_admin.initialize_app(cred)
db = firestore.client()
print("✅ Firebase initialized successfully")
else:
print(f"⚠️ Firebase credentials not found at {firebase_cred_path}")
db = None
except Exception as e:
print(f"⚠️ Firebase initialization failed: {e}")
db = None
# Firebase Web Config (for frontend)
FIREBASE_CONFIG = {
'apiKey': os.getenv('FIREBASE_WEB_API_KEY'),
'authDomain': os.getenv('FIREBASE_AUTH_DOMAIN'),
'projectId': os.getenv('FIREBASE_PROJECT_ID'),
'storageBucket': os.getenv('FIREBASE_STORAGE_BUCKET', f"{os.getenv('FIREBASE_PROJECT_ID')}.appspot.com"),
'messagingSenderId': os.getenv('FIREBASE_MESSAGING_SENDER_ID'),
'appId': os.getenv('FIREBASE_APP_ID')
}
# File to store user data - defaults to current directory (writable in Docker)
# Keep for backward compatibility during transition
USERS_FILE = os.getenv("USERS_FILE", "users_data.json")
# Together API for chatbot
TOGETHER_API_KEY = os.getenv("TOGETHER_API_KEY")
client = Together(api_key=TOGETHER_API_KEY) if TOGETHER_API_KEY else None
# OpenAI for Whisper transcription
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
openai_client = OpenAI(api_key=OPENAI_API_KEY) if OPENAI_API_KEY else None
# Load detailed religion data at startup
RELIGIONS_CSV = load_religions_from_csv('religions.csv')
# Aliases map inconsistent keys to canonical ones
TRADITION_ALIASES = {
"secular": "humanism",
"secularism": "humanism",
"spiritualism": "spiritual_not_religious",
"wicca": "paganism",
"zen": "buddhism",
"pantheism": "paganism",
"unitarian": "spiritual_not_religious",
"deism": "agnosticism"
}
def canon(key):
"""Normalize tradition key to canonical form"""
return TRADITION_ALIASES.get(key, key)
# Question importance weights (higher = more significant)
QUESTION_WEIGHTS = {
1: 5, # Nature of the divine (most fundamental)
2: 3, # Spiritual connection style
3: 4, # Afterlife beliefs
4: 4, # Moral/ethical foundation
5: 3, # Prayer practices
6: 3, # Ritual/practice style
7: 3, # Human-nature relationship
8: 3, # View on suffering
9: 2 # Community importance
}
# Assessment Questions ----------------------------
QUESTIONS = [
{
"id": 1,
"question": "What is your view on the nature of the divine?",
"options": {
"One supreme God who created everything": {
"christianity": 5, "islam": 5, "judaism": 5, "sikhism": 4
},
"Multiple gods and goddesses": {
"hinduism": 5, "paganism": 5, "shintoism": 3
},
"A universal energy or force": {
"taoism": 5, "buddhism": 4, "new_age": 4, "spiritual_not_religious": 3
},
"No divine being, focus on human potential": {
"humanism": 5, "atheism": 5, "stoicism": 3, "confucianism": 2
},
"Uncertain or unknowable": {
"agnosticism": 5, "spiritual_not_religious": 2
}
}
},
{
"id": 2,
"question": "How do you prefer to connect with spirituality?",
"options": {
"Through organized worship and community": {
"christianity": 4, "islam": 4, "judaism": 4, "sikhism": 3
},
"Through personal meditation and reflection": {
"buddhism": 5, "hinduism": 4, "taoism": 4, "jainism": 4
},
"Through nature and natural cycles": {
"paganism": 5, "indigenous": 5, "shintoism": 4
},
"Through reason and philosophy": {
"stoicism": 5, "humanism": 4, "confucianism": 3
},
"I don't feel the need for spiritual connection": {
"atheism": 5, "humanism": 2
}
}
},
{
"id": 3,
"question": "What is your belief about the afterlife?",
"options": {
"Heaven or Hell based on faith/deeds": {
"christianity": 5, "islam": 5, "judaism": 3
},
"Reincarnation until enlightenment": {
"hinduism": 5, "buddhism": 5, "jainism": 4, "sikhism": 3
},
"Ancestral realm or spiritual world": {
"indigenous": 4, "paganism": 3, "shintoism": 3, "confucianism": 2
},
"No afterlife, this life is all there is": {
"atheism": 5, "humanism": 4, "stoicism": 2
},
"Unsure or open to possibilities": {
"agnosticism": 4, "new_age": 3, "spiritual_not_religious": 3
}
}
},
{
"id": 4,
"question": "What guides your moral and ethical decisions?",
"options": {
"Sacred texts and religious teachings": {
"islam": 5, "christianity": 5, "judaism": 5, "sikhism": 3
},
"Universal principles of compassion and mindfulness": {
"buddhism": 5, "jainism": 5, "hinduism": 3
},
"Harmony with nature and balance": {
"taoism": 5, "indigenous": 4, "shintoism": 3, "paganism": 3
},
"Reason, empathy, and human rights": {
"humanism": 5, "atheism": 3, "stoicism": 3
},
"Personal intuition and inner wisdom": {
"spiritual_not_religious": 5, "new_age": 4
},
"Virtue, duty, and social harmony": {
"confucianism": 5, "stoicism": 4
}
}
},
{
"id": 5,
"question": "How do you approach prayer or meditation?",
"options": {
"Structured daily prayers at set times (e.g., 5 times daily)": {
"islam": 5, "sikhism": 3
},
"Regular personal prayer and church attendance": {
"christianity": 5, "judaism": 4
},
"Daily meditation or mindfulness practice": {
"buddhism": 5, "hinduism": 4, "jainism": 4
},
"Occasional prayer or reflection when needed": {
"christianity": 2, "judaism": 2, "spiritual_not_religious": 3, "new_age": 2
},
"Meditation for inner peace, not religious practice": {
"stoicism": 3, "humanism": 2, "buddhism": 2
},
"I don't pray or meditate": {
"atheism": 4, "humanism": 3
}
}
},
{
"id": 6,
"question": "What role do rituals and ceremonies play in your life?",
"options": {
"Essential - weekly services and holy day observances": {
"christianity": 4, "islam": 4, "judaism": 5, "hinduism": 3
},
"Important - seasonal celebrations and nature rituals": {
"paganism": 5, "indigenous": 5, "shintoism": 4
},
"Helpful - personal rituals that feel meaningful": {
"new_age": 4, "spiritual_not_religious": 4, "hinduism": 2
},
"Minimal - prefer contemplation and philosophy": {
"stoicism": 4, "humanism": 3, "confucianism": 2, "agnosticism": 2
},
"None - I don't practice rituals": {
"atheism": 4, "humanism": 2
}
}
},
{
"id": 7,
"question": "How do you view the relationship between humans and nature?",
"options": {
"Humans are stewards of God's creation": {
"christianity": 4, "islam": 4, "judaism": 4
},
"All life is interconnected and sacred": {
"buddhism": 5, "hinduism": 4, "jainism": 5, "indigenous": 4
},
"Nature itself is divine and should be revered": {
"paganism": 5, "indigenous": 4, "shintoism": 5, "taoism": 3
},
"Nature follows natural laws we can study": {
"atheism": 4, "humanism": 4, "stoicism": 2
},
"We should live in harmony with natural flow": {
"taoism": 5, "buddhism": 3, "shintoism": 3, "indigenous": 3
}
}
},
{
"id": 8,
"question": "What is your view on suffering and its purpose?",
"options": {
"A test of faith or part of God's plan": {
"christianity": 4, "islam": 4, "judaism": 3
},
"Result of attachment, desire, and ignorance": {
"buddhism": 5, "stoicism": 3, "jainism": 3
},
"Karma from past actions and choices": {
"hinduism": 5, "sikhism": 4, "buddhism": 3, "jainism": 3
},
"Natural part of life without cosmic meaning": {
"atheism": 5, "humanism": 3, "stoicism": 2
},
"An opportunity for spiritual growth": {
"new_age": 4, "spiritual_not_religious": 3, "christianity": 2
},
"To be accepted with virtue and equanimity": {
"stoicism": 5, "buddhism": 2, "confucianism": 2
}
}
},
{
"id": 9,
"question": "How important is community in your spiritual life?",
"options": {
"Very important - prefer group worship and fellowship": {
"christianity": 4, "islam": 4, "sikhism": 5, "judaism": 4
},
"Somewhat important - personal practice matters more": {
"buddhism": 3, "hinduism": 3, "taoism": 2
},
"Community of like-minded spiritual seekers": {
"paganism": 4, "spiritual_not_religious": 4, "new_age": 3
},
"Not important - spirituality is deeply personal": {
"spiritual_not_religious": 3, "agnosticism": 3, "taoism": 2
},
"Prefer secular community over religious": {
"humanism": 4, "atheism": 4, "stoicism": 2
}
}
}
]
# Religion Descriptions
RELIGIONS = {
"christianity": {"name": "Christianity", "description": "Faith in Jesus Christ emphasizing love, forgiveness, and salvation through grace.", "practices": "Prayer, Bible study, church, communion", "core_beliefs": "Trinity, salvation through Christ, eternal life"},
"islam": {"name": "Islam", "description": "Submission to Allah through Prophet Muhammad's teachings and the Quran.", "practices": "Five daily prayers, Ramadan fasting, charity, Mecca pilgrimage", "core_beliefs": "One God (Allah), Muhammad as prophet, Day of Judgment"},
"buddhism": {"name": "Buddhism", "description": "Path to enlightenment through mindfulness and compassion.", "practices": "Meditation, mindfulness, Eightfold Path", "core_beliefs": "Four Noble Truths, impermanence, ending suffering"},
"hinduism": {"name": "Hinduism", "description": "Ancient tradition embracing diverse paths to spiritual realization.", "practices": "Yoga, meditation, puja, festivals", "core_beliefs": "Dharma, karma, reincarnation, moksha, multiple paths"},
"judaism": {"name": "Judaism", "description": "Covenant with God through Torah and Jewish community.", "practices": "Shabbat, Torah study, prayer, kosher", "core_beliefs": "One God, Torah as divine law, ethical monotheism"},
"taoism": {"name": "Taoism", "description": "Living in harmony with the Tao - the natural order.", "practices": "Meditation, tai chi, wu wei, simplicity", "core_beliefs": "Yin-yang balance, harmony with nature"},
"paganism": {"name": "Modern Paganism", "description": "Nature-based spirituality honoring seasonal cycles.", "practices": "Seasonal celebrations, rituals, nature work", "core_beliefs": "Nature as sacred, multiple deities"},
"humanism": {"name": "Secular Humanism", "description": "Ethics emphasizing human values and reason without supernatural beliefs.", "practices": "Critical thinking, ethical living, community service", "core_beliefs": "Human dignity, reason, science, secular ethics"},
"atheism": {"name": "Atheism", "description": "Lack of belief in deities with naturalistic worldview.", "practices": "Evidence-based thinking, secular community", "core_beliefs": "No gods, natural explanations, this-life focus"},
"agnosticism": {"name": "Agnosticism", "description": "Divine existence is unknown or unknowable.", "practices": "Philosophical inquiry, ethical living", "core_beliefs": "Uncertainty about divine, questions over answers"},
"new_age": {"name": "New Age Spirituality", "description": "Eclectic approach emphasizing personal growth.", "practices": "Meditation, energy work, crystals, yoga", "core_beliefs": "Personal transformation, universal consciousness"},
"spiritual_not_religious": {"name": "Spiritual But Not Religious", "description": "Personal spirituality without organized religion.", "practices": "Personal practices, meditation, self-reflection", "core_beliefs": "Individual journey, authenticity, diverse wisdom"},
"sikhism": {"name": "Sikhism", "description": "One God emphasizing service, equality, and meditation.", "practices": "Prayer, meditation, community service, 5 Ks", "core_beliefs": "One God, equality, honest living, sharing"},
"indigenous": {"name": "Indigenous Spirituality", "description": "Traditional practices honoring ancestors and land.", "practices": "Ceremonies, storytelling, seasonal rituals", "core_beliefs": "Land connection, ancestor veneration, reciprocity"},
"jainism": {"name": "Jainism", "description": "Ancient path emphasizing non-violence and spiritual purification.", "practices": "Meditation, fasting, non-violence, self-discipline", "core_beliefs": "Ahimsa (non-violence), karma liberation, asceticism"},
"shintoism": {"name": "Shinto", "description": "Japanese tradition honoring kami spirits and natural harmony.", "practices": "Shrine visits, purification rituals, festivals", "core_beliefs": "Kami reverence, purity, harmony with nature"},
"stoicism": {"name": "Stoicism", "description": "Philosophy emphasizing virtue, reason, and acceptance of fate.", "practices": "Contemplation, virtue practice, rational thinking", "core_beliefs": "Virtue, reason, acceptance, inner peace"},
"confucianism": {"name": "Confucianism", "description": "Philosophy emphasizing moral cultivation and social harmony.", "practices": "Ritual propriety, study, self-cultivation", "core_beliefs": "Filial piety, benevolence, social harmony"}
}
# ============================================================================
# FIRESTORE HELPER FUNCTIONS
# ============================================================================
def get_user_by_uid(uid):
"""Get user data from Firestore by Firebase UID"""
if not db:
return None
try:
user_ref = db.collection('users').document(uid)
user_doc = user_ref.get()
if user_doc.exists:
return user_doc.to_dict()
return None
except Exception as e:
print(f"Error getting user {uid}: {e}")
return None
def create_or_update_user(uid, user_data):
"""Create or update user in Firestore"""
if not db:
return False
try:
user_ref = db.collection('users').document(uid)
user_ref.set(user_data, merge=True)
return True
except Exception as e:
print(f"Error saving user {uid}: {e}")
return False
def verify_firebase_token(id_token):
"""Verify Firebase ID token and return decoded token"""
try:
decoded_token = auth.verify_id_token(id_token)
return decoded_token
except Exception as e:
print(f"Token verification error: {e}")
return None
# ============================================================================
# LEGACY JSON FILE FUNCTIONS (for backward compatibility)
# ============================================================================
def load_users():
try:
if os.path.exists(USERS_FILE):
with open(USERS_FILE, 'r') as f:
return json.load(f)
except Exception as e:
print(f"Error loading users: {e}")
return {}
def save_users(users):
"""Save users to JSON file"""
try:
# Ensure parent directory exists
os.makedirs(os.path.dirname(USERS_FILE) if os.path.dirname(USERS_FILE) else '.', exist_ok=True)
with open(USERS_FILE, 'w') as f:
json.dump(users, f, indent=2)
return True
except Exception as e:
print(f"Error saving users: {e}")
return False
def initialize_default_user():
"""Create default test user if no users exist"""
users = load_users()
if not users: # Only create if no users exist
users['test'] = {
'password': generate_password_hash('test'),
'email': 'test@example.com',
'verified': True,
'answers': [],
'results': []
}
save_users(users)
print("✅ Default test user created (username: test, password: test)")
def calculate_results(answers):
"""
Calculate which spiritual paths align with user's answers
Uses weighted scoring with canonical tradition keys and tie-breakers
"""
scores = {}
coverage = {} # Track number of questions contributing to each tradition (for tie-breaking)
# Calculate weighted scores
for answer in answers:
question = next((q for q in QUESTIONS if q["id"] == answer["question_id"]), None)
if question and answer["answer"] in question["options"]:
points_map = question["options"][answer["answer"]]
question_weight = QUESTION_WEIGHTS.get(answer["question_id"], 1)
# Track which traditions are scored in this question (for tie-breaker)
touched_this_question = set()
for tradition_key, base_points in points_map.items():
# Normalize tradition key to canonical form
canonical_key = canon(tradition_key)
# Calculate weighted score
weighted_score = base_points * question_weight
scores[canonical_key] = scores.get(canonical_key, 0) + weighted_score
touched_this_question.add(canonical_key)
# Update coverage count for tie-breaking
for key in touched_this_question:
coverage[key] = coverage.get(key, 0) + 1
# Calculate maximum possible score for percentage calculation
max_possible_score = 0
for answer in answers:
question = next((q for q in QUESTIONS if q["id"] == answer["question_id"]), None)
if question:
# Find the highest point value among all options for this question
max_option_points = 0
for option_scores in question["options"].values():
if option_scores:
max_option_points = max(max_option_points, max(option_scores.values()))
question_weight = QUESTION_WEIGHTS.get(answer["question_id"], 1)
max_possible_score += max_option_points * question_weight
# Sort by score (primary) and coverage (tie-breaker)
# Higher coverage means the tradition was scored across more questions
sorted_scores = sorted(
scores.items(),
key=lambda x: (x[1], coverage.get(x[0], 0)),
reverse=True
)
# Build top 3 recommendations
recommendations = []
for tradition_key, score in sorted_scores:
if tradition_key in RELIGIONS:
tradition_info = RELIGIONS[tradition_key].copy()
tradition_info["score"] = score
# Calculate percentage based on actual max possible
if max_possible_score > 0:
tradition_info["percentage"] = round((score / max_possible_score) * 100)
else:
tradition_info["percentage"] = 0
recommendations.append(tradition_info)
# Stop after top 3
if len(recommendations) == 3:
break
return recommendations
@app.route("/")
def landing():
return render_template('landing.html')
@app.route("/assessment")
def assessment():
# Check for Firebase user first, then legacy username
user_id = session.get('user_id')
username = session.get('username')
if not user_id and not username:
return redirect(url_for('login'))
# Get user data from appropriate source
if user_id:
# Firebase user
user_data = get_user_by_uid(user_id) or {}
display_name = session.get('email', 'User')
has_results = 'results' in user_data and user_data['results']
else:
# Legacy user
users = load_users()
user_data = users.get(username, {})
display_name = username
has_results = 'results' in user_data and user_data['results']
return render_template(
"index.html",
title="Spiritual Path Finder",
message=f"Welcome, {display_name}!",
username=display_name,
logged_in=True,
questions=QUESTIONS,
has_results=has_results,
results=user_data.get('results', []) if has_results else [],
firebase_config=FIREBASE_CONFIG
)
@app.route("/login", methods=["GET", "POST"])
def login():
if request.method == "POST":
try:
data = request.get_json()
if not data:
return jsonify({"success": False, "message": "Invalid request"}), 400
# Firebase authentication - verify ID token from frontend
id_token = data.get('idToken')
if id_token:
# Firebase authentication flow
decoded_token = verify_firebase_token(id_token)
if not decoded_token:
return jsonify({"success": False, "message": "Invalid authentication token"}), 401
uid = decoded_token['uid']
email = decoded_token.get('email', '')
# Check if user exists in Firestore, create if not
user_data = get_user_by_uid(uid)
if not user_data:
# Create new user document
create_or_update_user(uid, {
'email': email,
'answers': [],
'results': [],
'created_at': firestore.SERVER_TIMESTAMP
})
# Store UID in session
session['user_id'] = uid
session['email'] = email
session.permanent = True
return jsonify({"success": True})
else:
# Legacy username/password flow (backward compatibility)
username = data.get('username', '').strip()
password = data.get('password', '')
if not username or not password:
return jsonify({"success": False, "message": "Username and password required"}), 400
users = load_users()
if username not in users:
return jsonify({"success": False, "message": "Invalid credentials"}), 401
user_data = users[username]
# Check if email is verified
if not user_data.get('verified', True):
return jsonify({"success": False, "message": "Please verify your email first. Check your inbox."}), 403
stored = user_data['password']
# Try hash-based verification first
if stored.startswith(('scrypt:', 'pbkdf2:')):
if check_password_hash(stored, password):
session['username'] = username
session.permanent = True
return jsonify({"success": True})
# Legacy plaintext fallback
elif stored == password:
users[username]['password'] = generate_password_hash(password)
if not save_users(users):
return jsonify({"success": False, "message": "Error saving data"}), 500
session['username'] = username
session.permanent = True
return jsonify({"success": True})
return jsonify({"success": False, "message": "Invalid credentials"}), 401
except Exception as e:
print(f"Login error: {e}")
return jsonify({"success": False, "message": "Server error"}), 500
# Pass Firebase config to template
return render_template("index.html", logged_in=False, is_signup=False, firebase_config=FIREBASE_CONFIG)
@app.route("/signup", methods=["GET", "POST"])
def signup():
if request.method == "POST":
try:
data = request.get_json()
if not data:
return jsonify({"success": False, "message": "Invalid request"}), 400
# Firebase authentication - verify ID token from frontend
id_token = data.get('idToken')
if id_token:
# Firebase signup flow (user already created by Firebase Auth on frontend)
decoded_token = verify_firebase_token(id_token)
if not decoded_token:
return jsonify({"success": False, "message": "Invalid authentication token"}), 401
uid = decoded_token['uid']
email = decoded_token.get('email', '')
# Create user document in Firestore
user_data = {
'email': email,
'answers': [],
'results': [],
'created_at': firestore.SERVER_TIMESTAMP
}
if create_or_update_user(uid, user_data):
session['user_id'] = uid
session['email'] = email
session.permanent = True
return jsonify({
"success": True,
"message": "Account created successfully!"
})
else:
return jsonify({"success": False, "message": "Error creating user profile"}), 500
else:
# Legacy username/password flow (backward compatibility)
username = data.get('username', '').strip()
password = data.get('password', '')
email = data.get('email', '').strip().lower()
if not username or not password:
return jsonify({"success": False, "message": "Username and password required"}), 400
if not email:
return jsonify({"success": False, "message": "Email is required"}), 400
if not validate_email(email):
return jsonify({"success": False, "message": "Invalid email format"}), 400
users = load_users()
if username in users:
return jsonify({"success": False, "message": "Username already exists"}), 409
# Check if email already exists
for user_data in users.values():
if user_data.get('email') == email:
return jsonify({"success": False, "message": "Email already registered"}), 409
# Generate verification token
token = secrets.token_urlsafe(32)
VERIFICATION_TOKENS[token] = {
'username': username,
'email': email,
'password': password,
'timestamp': os.path.getmtime(USERS_FILE) if os.path.exists(USERS_FILE) else 0
}
# Send verification email
send_verification_email(email, token)
# Create user with verified status (auto-verify in dev mode)
users[username] = {
'password': generate_password_hash(password),
'email': email,
'verified': True, # Auto-verify in dev mode
'answers': [],
'results': []
}
if not save_users(users):
return jsonify({"success": False, "message": "Error saving user data"}), 500
return jsonify({
"success": True,
"message": "Account created! Please check your email to verify your account.",
"verification_sent": True
})
except Exception as e:
print(f"Signup error: {e}")
return jsonify({"success": False, "message": "Server error"}), 500
# Pass Firebase config to template
return render_template("index.html", logged_in=False, is_signup=True, firebase_config=FIREBASE_CONFIG)
@app.route("/forgot-password", methods=["GET", "POST"])
def forgot_password():
if request.method == "POST":
try:
data = request.get_json()
if not data:
return jsonify({"success": False, "message": "Invalid request"}), 400
email = data.get('email', '').strip().lower()
if not email:
return jsonify({"success": False, "message": "Email is required"}), 400
if not validate_email(email):
return jsonify({"success": False, "message": "Invalid email format"}), 400
users = load_users()
# Find user by email
user_found = None
for username, user_data in users.items():
if user_data.get('email') == email:
user_found = username
break
if not user_found:
# Don't reveal that email doesn't exist (security best practice)
return jsonify({
"success": True,
"message": "If that email exists, a reset link has been sent."
})
# Generate reset token
token = secrets.token_urlsafe(32)
VERIFICATION_TOKENS[token] = {
'username': user_found,
'email': email,
'type': 'password_reset',
'timestamp': os.path.getmtime(USERS_FILE) if os.path.exists(USERS_FILE) else 0
}
# Send reset email
send_password_reset_email(email, token)
return jsonify({
"success": True,
"message": "Password reset link sent to your email. Please check your inbox."
})
except Exception as e:
print(f"Password reset error: {e}")
return jsonify({"success": False, "message": "Server error"}), 500
return render_template("index.html", logged_in=False, is_signup=False, is_forgot_password=True)
@app.route("/reset-password")
def reset_password_page():
"""Handle password reset via token - show form to enter new password"""
token = request.args.get('token')
if not token or token not in VERIFICATION_TOKENS:
return render_template("index.html", logged_in=False, is_signup=False,
reset_error="Invalid or expired reset token"), 400
token_data = VERIFICATION_TOKENS[token]
if token_data.get('type') != 'password_reset':
return render_template("index.html", logged_in=False, is_signup=False,
reset_error="Invalid token type"), 400
# Store token in session temporarily for password reset form
session['reset_token'] = token
return render_template("index.html", logged_in=False, is_signup=False,
show_reset_form=True, reset_token=token)
@app.route("/reset-password-submit", methods=["POST"])
def reset_password_submit():
"""Handle password reset submission"""
try:
data = request.get_json()
token = data.get('token')
new_password = data.get('password', '')
if not token or token not in VERIFICATION_TOKENS:
return jsonify({"success": False, "message": "Invalid or expired token"}), 400
if not new_password:
return jsonify({"success": False, "message": "Password is required"}), 400
token_data = VERIFICATION_TOKENS[token]
if token_data.get('type') != 'password_reset':
return jsonify({"success": False, "message": "Invalid token type"}), 400
# Reset password
users = load_users()
username = token_data['username']
if username in users:
users[username]['password'] = generate_password_hash(new_password)
save_users(users)
del VERIFICATION_TOKENS[token]
session.pop('reset_token', None)
return jsonify({"success": True, "message": "Password reset successfully"})
return jsonify({"success": False, "message": "User not found"}), 404
except Exception as e:
print(f"Password reset submit error: {e}")
return jsonify({"success": False, "message": "Server error"}), 500
@app.route("/verify-email")
def verify_email():
"""Handle email verification"""
token = request.args.get('token')
if not token or token not in VERIFICATION_TOKENS:
return render_template("index.html", logged_in=False, is_signup=False,
verify_error="Invalid or expired verification token"), 400
token_data = VERIFICATION_TOKENS[token]
users = load_users()
username = token_data['username']
if username in users:
users[username]['verified'] = True
save_users(users)
del VERIFICATION_TOKENS[token]
return render_template("index.html", logged_in=False, is_signup=False,
verify_success=True)
return render_template("index.html", logged_in=False, is_signup=False,
verify_error="User not found"), 404
@app.route("/logout")
def logout():
# Clear both Firebase and legacy session data
session.pop('user_id', None)
session.pop('email', None)
session.pop('username', None)
return redirect(url_for('landing'))
@app.route("/submit_assessment", methods=["POST"])
def submit_assessment():
user_id = session.get('user_id')
username = session.get('username')
if not user_id and not username:
return jsonify({"success": False, "message": "Not logged in"})
data = request.json
answers = data.get('answers', [])
if len(answers) != len(QUESTIONS):
return jsonify({"success": False, "message": "Please answer all questions!"})
# Calculate results
results = calculate_results(answers)
# Save to appropriate storage
if user_id:
# Firebase user - save to Firestore
user_ref = db.collection('users').document(user_id)
user_ref.update({
'answers': answers,
'results': results,
'updated_at': firestore.SERVER_TIMESTAMP
})
return jsonify({"success": True, "results": results})
else:
# Legacy user - save to JSON
users = load_users()
if username in users:
users[username]['answers'] = answers
users[username]['results'] = results
save_users(users)
return jsonify({"success": True, "results": results})
return jsonify({"success": False, "message": "User not found"})
@app.route("/reset_assessment", methods=["POST"])
def reset_assessment():
user_id = session.get('user_id')
username = session.get('username')
if not user_id and not username:
return jsonify({"success": False, "message": "Not logged in"})
# Reset in appropriate storage
if user_id:
# Firebase user - reset in Firestore
user_ref = db.collection('users').document(user_id)
user_ref.update({
'answers': [],
'results': [],
'updated_at': firestore.SERVER_TIMESTAMP
})
return jsonify({"success": True})
else:
# Legacy user - reset in JSON
users = load_users()
if username in users:
users[username]['answers'] = []
users[username]['results'] = []
save_users(users)
return jsonify({"success": True})
return jsonify({"success": False, "message": "User not found"})
@app.route("/chat", methods=["POST"])
def chat():
"""
RAG-enhanced chat endpoint for spiritual guidance
Uses retrieval-augmented generation with religion-specific context
"""
if 'user_id' not in session and 'username' not in session:
return jsonify({"success": False, "message": "Not logged in"})
if not client:
return jsonify({"success": False, "message": "Chat service not configured. Please set TOGETHER_API_KEY."})
data = request.json
user_message = data.get('message', '').strip()
religion_name = data.get('religion', '')
chat_history = data.get('history', [])
if not user_message or not religion_name:
return jsonify({"success": False, "message": "Message and religion required"})
# Find religion in CSV data first, fallback to basic RELIGIONS
religion_data = None
religion_key = None
for key, value in RELIGIONS.items():
if value['name'] == religion_name:
religion_key = key
# Use CSV data if available
if key in RELIGIONS_CSV:
religion_data = RELIGIONS_CSV[key]
else:
religion_data = value
break
if not religion_data:
return jsonify({"success": False, "message": "Religion not found"})
# Build RAG context using rag_utils
if religion_key in RELIGIONS_CSV:
# Rich context from CSV using RAG utilities
csv_data = RELIGIONS_CSV[religion_key]
context_chunks = prepare_religion_rag_context(csv_data)
context = f"""REFERENCE DATA FOR {csv_data['name']}:
{context_chunks[0]}"""
else:
# Fallback to basic data
basic_context = prepare_religion_rag_context(religion_data)
context = f"""REFERENCE DATA FOR {religion_data['name']}:
{basic_context[0]}"""
system_prompt = f"""You're a knowledgeable spiritual guide. Use the reference data below to answer questions.
{context}
INSTRUCTIONS:
- Keep responses concise, minimal. 30-60 words, depending on the context
- ALWAYS complete your sentences - never cut off mid-sentence
- Be respectful and accurate
- If unsure, say so
- Use * for bullet points if listing
- End responses with complete thoughts, not incomplete phrases
- If you need to cut information, end with "..." but complete the current sentence"""
# Build conversation
messages = [{"role": "system", "content": system_prompt}]
# Add recent chat history
for msg in chat_history[-4:]:
messages.append({"role": msg["role"], "content": msg["content"]})
messages.append({"role": "user", "content": user_message})
try:
response = client.chat.completions.create(
model="meta-llama/Meta-Llama-3-8B-Instruct-Lite",
messages=messages,
max_tokens=400,
temperature=0.7,
)
bot_response = response.choices[0].message.content
return jsonify({
"success": True,
"response": bot_response
})
except Exception as e:
return jsonify({
"success": False,
"message": f"Chat error: {str(e)}"
})
@app.route("/transcribe", methods=["POST"])
def transcribe():
"""Convert audio to text using Whisper"""
if 'username' not in session:
return jsonify({"success": False, "message": "Not logged in"})
if not openai_client:
return jsonify({"success": False, "message": "Whisper not configured"})
audio_file = request.files.get('audio')
if not audio_file:
return jsonify({"success": False, "message": "No audio file"})
try:
# Convert FileStorage to bytes
audio_bytes = audio_file.read()
transcript = openai_client.audio.transcriptions.create(
model="whisper-1",
file=("recording.webm", audio_bytes, "audio/webm")
)
return jsonify({"success": True, "text": transcript.text})
except Exception as e:
return jsonify({"success": False, "message": str(e)})
@app.route("/debug")
def debug():
"""
Debug endpoint to check API configuration and environment
"""
return jsonify({
"api_key_set": bool(TOGETHER_API_KEY),
"client_available": client is not None,
"environment": os.environ.get("ENVIRONMENT", "unknown"),
"together_api_key_length": len(TOGETHER_API_KEY) if TOGETHER_API_KEY else 0,
"flask_debug": app.debug,
"users_file": USERS_FILE
})
@app.route("/session-debug")
def session_debug():
"""
Debug endpoint to check session and user data
"""
users = load_users()
return jsonify({
"session_data": dict(session),
"username_in_session": 'username' in session,
"current_username": session.get('username', 'None'),
"users_file_exists": os.path.exists(USERS_FILE),
"users_file_path": os.path.abspath(USERS_FILE),
"users_count": len(users),
"user_list": list(users.keys()),
"session_cookie_config": {
"secure": app.config.get('SESSION_COOKIE_SECURE'),
"httponly": app.config.get('SESSION_COOKIE_HTTPONLY'),
"samesite": app.config.get('SESSION_COOKIE_SAMESITE')
}
})
# Initialize default test user on startup
initialize_default_user()
if __name__ == "__main__":
app.run(debug=True, port=5003)