implements cache

This commit is contained in:
2025-10-31 10:19:43 -07:00
parent 3de2a97d5c
commit 9e2e0bec36
4 changed files with 115 additions and 40 deletions

1
.gitignore vendored
View File

@@ -7,5 +7,6 @@ terraform/.terraform/**
**/terraform.tfstate
**/terraform.tfstate.backup
.terraform/*
__pycache__/**

110
app.py
View File

@@ -38,6 +38,9 @@ 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.")
# --- Cache ---
from cache import project_cache
# --- Helpers ---
def login_required(view):
@@ -62,6 +65,55 @@ def get_user_profile(uid: str):
return {"enabled": bool(data.get("enabled", False)), "caseEmail": data.get("caseEmail")}
def fetch_all_projects_for_user(uid: str):
"""Fetch all projects for a user and cache them"""
# Get bearer token
bearer = get_filevine_bearer()
# List projects (all pages)
projects = list_all_projects(bearer)
# Filter to ProjectEmailAddress == caseEmail
profile = get_user_profile(uid)
case_email = profile.get("caseEmail")
# if case_email:
# filtered = [p for p in projects if str(p.get("ProjectEmailAddress", "")).lower() == str(case_email).lower()]
# else:
# filtered = []
filtered = projects
# Fetch details for each
detailed_rows = []
for p in filtered:
pid = (p.get("projectId") or {}).get("native")
c = fetch_client(bearer, (p.get("clientId") or {}).get("native"))
cs = fetch_contacts(bearer, pid)
print("CS IS", cs)
if pid is None:
continue
try:
detail = fetch_project_detail(bearer, pid)
except Exception as e:
print(f"[WARN] detail fetch failed for {pid}: {e}")
detail = {}
row = {
"client": c.get("firstName"),
"matter_description": p.get("projectName"),
"contacts": cs,
"ProjectEmailAddress": p.get("projectEmailAddress"),
"Number": p.get("number"),
"IncidentDate": (p.get("incidentDate") or detail.get("incidentDate")),
"ProjectId": pid,
"ProjectName": p.get("projectName") or detail.get("projectName"),
"ProjectUrl": p.get("projectUrl") or detail.get("projectUrl"),
}
detailed_rows.append(row)
# Cache the results
project_cache.set_projects(detailed_rows)
return detailed_rows
# --- Routes ---
@app.route("/")
def index():
@@ -99,6 +151,13 @@ def session_login():
session["uid"] = uid
# Optional: short session
session["expires_at"] = (datetime.utcnow() + timedelta(hours=8)).isoformat()
# Async update cache after login
from threading import Thread
thread = Thread(target=fetch_all_projects_for_user, args=(uid,))
thread.daemon = True
thread.start()
return jsonify({"ok": True})
except Exception as e:
print("[ERR] session_login:", e)
@@ -224,48 +283,21 @@ def dashboard():
if not case_email:
return redirect(url_for("welcome"))
# 1) Bearer token
bearer = get_filevine_bearer()
# 2) List projects (all pages)
projects = list_all_projects(bearer)
# 3) Filter to ProjectEmailAddress == caseEmail
#filtered = [p for p in projects if str(p.get("ProjectEmailAddress", "")).lower() == str(case_email).lower()]
filtered = projects
# 4) Fetch details for each
detailed_rows = []
for p in filtered:
pid = (p.get("projectId") or {}).get("native")
c = fetch_client(bearer, (p.get("clientId") or {}).get("native"))
cs = fetch_contacts(bearer, pid)
print("CS IS", cs)
if pid is None:
continue
try:
detail = fetch_project_detail(bearer, pid)
except Exception as e:
print(f"[WARN] detail fetch failed for {pid}: {e}")
detail = {}
row = {
"client": c.get("firstName"),
"matter_description": p.get("projectName"),
"contacts": cs,
"ProjectEmailAddress": p.get("projectEmailAddress"),
"Number": p.get("number"),
"IncidentDate": (p.get("incidentDate") or detail.get("incidentDate")),
"ProjectId": pid,
"ProjectName": p.get("projectName") or detail.get("projectName"),
"ProjectUrl": p.get("projectUrl") or detail.get("projectUrl"),
}
detailed_rows.append(row)
# Check cache first
cached_projects = project_cache.get_projects()
if cached_projects is not None:
detailed_rows = cached_projects
print("USING CACHE")
else:
# Fetch and cache projects
detailed_rows = fetch_all_projects_for_user(uid)
print("FETCHING")
print("HI", len(detailed_rows))
# 5) Render table
return render_template("dashboard.html", rows=detailed_rows, case_email=case_email)
# GAE compatibility
if __name__ == "__main__"
app.run(debug=True, host="0.0.0.0", port=8080)
if __name__ == "__main__":
app.run(debug=True, host="0.0.0.0", port=int(os.environ.get("PORT", "5004")))

42
cache.py Normal file
View File

@@ -0,0 +1,42 @@
# In-memory cache for Filevine projects
import threading
import time
from datetime import datetime, timedelta
class ProjectCache:
def __init__(self):
self._cache = {}
self._lock = threading.Lock()
self._last_updated = None
self._is_updating = False
def get_projects(self):
"""Get cached projects if they exist and are not expired (15 minutes)"""
with self._lock:
if not self._cache or not self._last_updated:
return None
# Check if cache is older than 15 minutes
if datetime.now() - self._last_updated > timedelta(minutes=15):
return None
return self._cache.copy()
def set_projects(self, projects):
"""Set projects in cache with current timestamp"""
with self._lock:
self._cache = projects.copy() if projects else {}
self._last_updated = datetime.now()
def is_updating(self):
"""Check if cache is currently being updated"""
with self._lock:
return self._is_updating
def set_updating(self, updating):
"""Set the updating status"""
with self._lock:
self._is_updating = updating
# Global cache instance
project_cache = ProjectCache()