import json import os from functools import wraps from datetime import datetime, timedelta import pytz from flask import Flask, render_template, request, redirect, url_for, session, abort, jsonify from dotenv import load_dotenv load_dotenv() import requests from filevine_client import FilevineClient from utils import get_user_profile from firebase_init import db from firebase_admin import auth as fb_auth app = Flask(__name__) app.secret_key = os.environ.get("FLASK_SECRET_KEY", os.urandom(32)) # --- Firebase Admin init --- (moved to firebase_init.py) # --- Filevine env --- FV_CLIENT_ID = os.environ.get("FILEVINE_CLIENT_ID") FV_CLIENT_SECRET = os.environ.get("FILEVINE_CLIENT_SECRET") FV_PAT = os.environ.get("FILEVINE_PERSONAL_ACCESS_TOKEN") FV_ORG_ID = os.environ.get("FILEVINE_ORG_ID") FV_USER_ID = os.environ.get("FILEVINE_USER_ID") if not all([FV_CLIENT_ID, FV_CLIENT_SECRET, FV_PAT, FV_ORG_ID, FV_USER_ID]): print("[WARN] Missing one or more Filevine env vars — dashboard will fail until set.") PHASES = { 209436: "Nonpayment File Review", 209437: "Attorney File Review", 209438: "Notice Preparation", 209439: "Notice Pending", 209440: "Notice Expired", 209442: "Preparing and Filing UD", 209443: "Waiting for Answer", 209444: "Archived", 210761: "Service of Process", 211435: "Default", 211436: "Pre-Answer Motion", 211437: "Request for Trial", 211438: "Trial Prep and Trial", 211439: "Writ and Sheriff", 211440: "Lockout Pending", 211441: "Stipulation Preparation", 211442: "Stipulation Pending", 211443: "Stipulation Expired", 211446: "On Hold", 211466: "Request for Monetary Judgment", 211467: "Appeals and Post-Poss. Motions", 211957: "Migrated", 213691: "Close Out/ Invoicing", 213774: "Judgment After Stip & Order", } # --- Helpers --- def login_required(view): @wraps(view) def wrapped(*args, **kwargs): uid = session.get("uid") if not uid: return redirect(url_for("login")) return view(*args, **kwargs) return wrapped @app.context_processor def inject_user_profile(): """Make user profile available in all templates""" if 'uid' in session: profile = get_user_profile(session['uid']) return {'get_user_profile': lambda uid: profile if uid == session['uid'] else get_user_profile(uid)} return {'get_user_profile': lambda uid: {}} def get_firestore_document(collection_name: str, document_id: str): """ Retrieve a specific document from Firestore. Args: collection_name (str): Name of the Firestore collection document_id (str): ID of the document to retrieve Returns: dict: Document data as dictionary, or None if document doesn't exist """ doc_ref = db.collection(collection_name).document(document_id) doc = doc_ref.get() if doc.exists: return doc.to_dict() else: return None @app.route("/") def index(): uid = session.get("uid") if not uid: return redirect(url_for("login")) profile = get_user_profile(uid) if profile.get("enabled") and profile.get("case_email"): return redirect(url_for("dashboard")) return redirect(url_for("welcome")) @app.route("/login") def login(): # Pass public Firebase config to template fb_public = { "apiKey": os.environ.get("FIREBASE_API_KEY", ""), "authDomain": os.environ.get("FIREBASE_AUTH_DOMAIN", ""), "projectId": os.environ.get("FIREBASE_PROJECT_ID", ""), "appId": os.environ.get("FIREBASE_APP_ID", ""), } return render_template("login.html", firebase_config=fb_public) @app.route("/session_login", methods=["POST"]) def session_login(): """Exchanges Firebase ID token for a server session.""" id_token = request.json.get("idToken") if request.is_json else request.form.get("idToken") if not id_token: abort(400, "Missing idToken") try: decoded = fb_auth.verify_id_token(id_token) uid = decoded["uid"] session.clear() session["uid"] = uid # Optional: short session session["expires_at"] = (datetime.utcnow() + timedelta(hours=8)).isoformat() return jsonify({"ok": True}) except Exception as e: print("[ERR] session_login:", e) abort(401, "Invalid ID token") @app.route("/logout") def logout(): session.clear() return redirect(url_for("login")) @app.route("/welcome") @login_required def welcome(): uid = session.get("uid") profile = get_user_profile(uid) return render_template("welcome.html", profile=profile) # --- Filevine API --- # Filevine client is now in filevine_client.py @app.route("/dashboard") @app.route("/dashboard/") @login_required def dashboard(page=1): uid = session.get("uid") profile = get_user_profile(uid) if not profile.get("enabled"): return redirect(url_for("welcome")) is_admin = profile.get("is_admin") case_email = None if not is_admin: case_email = profile.get("case_email") if not case_email: return redirect(url_for("welcome")) if is_admin and request.args.get('case_email'): case_email = request.args.get('case_email').lower() # Validate email format if '@' not in case_email: return abort(400, "Invalid email format") # Pagination settings per_page = 25 offset = (page - 1) * per_page query = None # Get total count efficiently using a count aggregation query try: # Firestore doesn't have a direct count() method, so we need to count documents import time start_time = time.time() projects_ref = db.collection("projects") # Filter projects where case_email is in viewing_emails array if case_email: query = projects_ref.where("viewing_emails", "array_contains", case_email.lower()) else: query = projects_ref total_projects = int(query.count().get()[0][0].value) end_time = time.time() print(f"Filtered projects count: {total_projects} (took {end_time - start_time:.2f}s)") except Exception as e: print(f"[WARN] Failed to get filtered count: {e}") total_projects = 0 # Calculate pagination total_pages = (total_projects + per_page - 1) // per_page # Ceiling division # Read only the current page from Firestore using limit() and offset() import time start_time = time.time() # Filter projects where case_email is in viewing_emails array if case_email: projects_ref = db.collection("projects").where("viewing_emails", "array_contains", case_email.lower()).order_by("matter_description").limit(per_page).offset(offset) else: projects_ref = db.collection("projects").order_by("matter_description").limit(per_page).offset(offset) docs = projects_ref.stream() paginated_rows = [] for doc in docs: paginated_rows.append(doc.to_dict()) end_time = time.time() print(f"Retrieved {len(paginated_rows)} projects from Firestore (page {page} of {total_pages}) in {end_time - start_time:.2f}s") from pprint import pprint pprint([p['property_contacts'] for p in paginated_rows if p['property_contacts'].get('propertyManager1', None)]) pprint([p['ProjectId'] for p in paginated_rows ]) # Render table with pagination data return render_template("dashboard.html", rows=paginated_rows, case_email=case_email, current_page=page, total_pages=total_pages, total_projects=total_projects, per_page=per_page) import admin # Register admin routes admin.register_admin_routes(app) # GAE compatibility if __name__ == "__main__": app.run(debug=True, host="0.0.0.0", port=int(os.environ.get("PORT", "5004")))