implements cache
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -7,5 +7,6 @@ terraform/.terraform/**
|
||||
**/terraform.tfstate
|
||||
**/terraform.tfstate.backup
|
||||
.terraform/*
|
||||
__pycache__/**
|
||||
|
||||
|
||||
|
||||
110
app.py
110
app.py
@@ -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
42
cache.py
Normal 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()
|
||||
Reference in New Issue
Block a user