commit 2383d1ad9a1cb69b94f11cf6e3e5fed4cff19175 Author: Bryce Date: Wed Oct 22 17:34:13 2025 -0700 Initial diff --git a/.env b/.env new file mode 100644 index 0000000..b9d83de --- /dev/null +++ b/.env @@ -0,0 +1,21 @@ +# Flask +FLASK_SECRET_KEY=replace-with-long-random-string + +# Firebase Admin (choose ONE of these approaches) +# 1) Path to JSON creds file +GOOGLE_APPLICATION_CREDENTIALS=./rothbard-service-account.json +# 2) Or inline JSON (escaped as single line) +# FIREBASE_SERVICE_ACCOUNT_JSON={"type":"service_account",...} + +# Filevine auth +FILEVINE_CLIENT_ID=4F18738C-107A-4B82-BFAC-308F1B6A626A +FILEVINE_CLIENT_SECRET=*2{aXWvYN(9!BiYUXC_tXj^n8 +FILEVINE_PERSONAL_ACCESS_TOKEN=B87C144C35A570077F9D7D70CDC2280DD023142D1BFCE2C45B6CE8934600EA6F +FILEVINE_ORG_ID=9227 +FILEVINE_USER_ID=100510 + +# Front-end Firebase (public — safe to expose) +FIREBASE_API_KEY=AIzaSyC7t2D0uSuc1hm6ZEkfUMVPtkaE2TXF1a0 +FIREBASE_AUTH_DOMAIN=rothbard-3f496.firebaseapp.com +FIREBASE_PROJECT_ID=rothbard-3f496 +FIREBASE_APP_ID=1:90016977941:web:da38d57849021115e52a1c \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..eeaae09 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.venv/** +.lsp/** +.clj-kondo/** + + diff --git a/README.md b/README.md new file mode 100644 index 0000000..246361f --- /dev/null +++ b/README.md @@ -0,0 +1,19 @@ +Rothbard Client Portal + +This is a server-side rendered client portal for rothbard law group. Users can log in, and once they've been configured, can see a list of cases from the filevine API. + +Todo list +* Lock down firebase +* Set firebase up with terraform +* Allow users to store their preferences on which columns they see +* Styling +* New user sign up +* Set up users + + +Project Structure: +* static/ - static assets, html, javascript etc +* templates/ jinja templates +* app.py - core login +* examples/ sample filevine api requests/responses for digging into +* generate_sample.py - generates the examples \ No newline at end of file diff --git a/app.py b/app.py new file mode 100644 index 0000000..a52da59 --- /dev/null +++ b/app.py @@ -0,0 +1,270 @@ +import json +import os +from functools import wraps +from datetime import datetime, timedelta + +from flask import Flask, render_template, request, redirect, url_for, session, abort, jsonify +from dotenv import load_dotenv +import firebase_admin +from firebase_admin import credentials, auth as fb_auth, firestore +import requests + +load_dotenv() + +app = Flask(__name__) +app.secret_key = os.environ.get("FLASK_SECRET_KEY", os.urandom(32)) + +# --- Firebase Admin init --- +_creds = None +json_inline = os.environ.get("FIREBASE_SERVICE_ACCOUNT_JSON") +file_path = os.environ.get("GOOGLE_APPLICATION_CREDENTIALS") +if json_inline: + _creds = credentials.Certificate(json.loads(json_inline)) +elif file_path and os.path.exists(file_path): + _creds = credentials.Certificate(file_path) +else: + raise RuntimeError("Firebase credentials not configured. Set GOOGLE_APPLICATION_CREDENTIALS or FIREBASE_SERVICE_ACCOUNT_JSON.") + +firebase_admin.initialize_app(_creds) +db = firestore.client() + +# --- 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.") + +# --- 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 + + +def get_user_profile(uid: str): + """Fetch user's Firestore profile: users/{uid} => { enabled, caseEmail }""" + doc_ref = db.collection("users").document(uid) + snap = doc_ref.get() + if not snap.exists: + # bootstrap a placeholder doc so admins can fill it in + doc_ref.set({"enabled": False}, merge=True) + return {"enabled": False, "caseEmail": None} + data = snap.to_dict() or {} + return {"enabled": bool(data.get("enabled", False)), "caseEmail": data.get("caseEmail")} + + +# --- Routes --- +@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("caseEmail"): + 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 --- + +def get_filevine_bearer(): + url = "https://identity.filevine.com/connect/token" + data = { + "client_id": FV_CLIENT_ID, + "client_secret": FV_CLIENT_SECRET, + "grant_type": "personal_access_token", + "scope": "fv.api.gateway.access tenant filevine.v2.api.* email openid fv.auth.tenant.read", + "token": FV_PAT, + } + headers = {"Accept": "application/json"} + resp = requests.post(url, data=data, headers=headers, timeout=30) + resp.raise_for_status() + js = resp.json() + print(js) + return js.get("access_token") + + +def list_all_projects(bearer: str): + base = "https://api.filevineapp.com/fv-app/v2/Projects" + headers = { + "Accept": "application/json", + "Authorization": f"Bearer {bearer}", + "x-fv-orgid": str(FV_ORG_ID), + "x-fv-userid": str(FV_USER_ID), + } + print(headers) + results = [] + last_id = None + tries = 0 + while True: + tries += 1 + url = base + params = {} + if last_id is not None: + # Some deployments use LastID/Offset pagination; adapt if needed + params["lastID"] = last_id + r = requests.get(url, headers=headers, params=params, timeout=30) + print(r.content) + r.raise_for_status() + page = r.json() + items = page.get("items", []) + results.extend(items) + has_more = page.get("hasMore") + last_id = page.get("lastID") + if not has_more: + break + # Safety valve + if tries > 200: + break + print("RESULTS", results) + return results + + +def fetch_project_detail(bearer: str, project_id_native: int): + url = f"https://api.filevineapp.com/fv-app/v2/Projects/{project_id_native}" + headers = { + "Accept": "application/json", + "Authorization": f"Bearer {bearer}", + "x-fv-orgid": str(FV_ORG_ID), + "x-fv-userid": str(FV_USER_ID), + } + r = requests.get(url, headers=headers, timeout=30) + r.raise_for_status() + return r.json() + +def fetch_client(bearer: str, client_id_native: int): + url = f"https://api.filevineapp.com/fv-app/v2/contacts/{client_id_native}" + headers = { + "Accept": "application/json", + "Authorization": f"Bearer {bearer}", + "x-fv-orgid": str(FV_ORG_ID), + "x-fv-userid": str(FV_USER_ID), + } + r = requests.get(url, headers=headers, timeout=30) + r.raise_for_status() + return r.json() + +def fetch_contacts(bearer: str, project_id_native: int): + url = f"https://api.filevineapp.com/fv-app/v2/projects/{project_id_native}/contacts" + headers = { + "Accept": "application/json", + "Authorization": f"Bearer {bearer}", + "x-fv-orgid": str(FV_ORG_ID), + "x-fv-userid": str(FV_USER_ID), + } + r = requests.get(url, headers=headers, timeout=30) + r.raise_for_status() + return r.json().get("items") + + + + +@app.route("/dashboard") +@login_required +def dashboard(): + uid = session.get("uid") + profile = get_user_profile(uid) + if not profile.get("enabled"): + return redirect(url_for("welcome")) + case_email = profile.get("caseEmail") + 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) + + # 5) Render table + return render_template("dashboard.html", rows=detailed_rows, case_email=case_email) + + +if __name__ == "__main__": + app.run(debug=True, host="0.0.0.0", port=5000) \ No newline at end of file diff --git a/examples/client.json b/examples/client.json new file mode 100644 index 0000000..c7d6e4f --- /dev/null +++ b/examples/client.json @@ -0,0 +1,85 @@ +{ + "sample_request": { + "url": "https://api.filevineapp.com/fv-app/v2/contacts/43125866", + "headers": { + "Accept": "application/json", + "Authorization": "Bearer eyJhbGciOiJSUzUxMiIsImtpZCI6Ijg2NjRFMkY0MDNCQjIxMzk2MzQ4NUFDOEI0MzVGMEJBOTgxNTBFN0RSUzUxMiIsInR5cCI6ImF0K2p3dCIsIng1dCI6ImhtVGk5QU83SVRsalNGckl0RFh3dXBnVkRuMCJ9.eyJuYmYiOjE3NjExNzg4NzUsImV4cCI6MTc2MTE4MDY3NSwiaXNzIjoiaHR0cHM6Ly9pZGVudGl0eS5maWxldmluZS5jb20iLCJhdWQiOlsiZmlsZXZpbmUudjIuYXBpIiwiZnYuYXBpLmdhdGV3YXkiLCJmdi5hdXRoIl0sImNsaWVudF9pZCI6IjRGMTg3MzhDLTEwN0EtNEI4Mi1CRkFDLTMwOEYxQjZBNjI2QSIsInN1YiI6ImY3MDQ4NGZmLTQ5MjItNDliMy05MWFkLTE2YjA5Mjk5MGIzMCIsImF1dGhfdGltZSI6MTc2MTE3ODg3NSwiaWRwIjoibG9jYWwiLCJwYXRfaWQiOiJmUkEwbHRoSnp5aTBBWU9ZS1F4WGVlckczUHc1MXEyMmh4cTlzbk4zL2V3PSIsInBhdF9uYW1lIjoiQnJ5Y2UgQ292ZXJ0IiwicGF0X3ZlcnNpb24iOiIxIiwidGVuYW50X2ZybiI6ImZybjpmaWxldmluZTp1cy1wcm9kOmZpbGV2aW5lLWFwcDo6OnRlbmFudFxcMGJlOGFhOGItZmEyOS00MjQ0LWI1YzItMDE5NzIzMWExNWY5IiwidGVuYW50X2lkIjoiMGJlOGFhOGItZmEyOS00MjQ0LWI1YzItMDE5NzIzMWExNWY5IiwianRpIjoiMkNENzYzNUFGNkU5RjQ0RjY2NzI1RTkyREY3RUI2NDciLCJpYXQiOjE3NjExNzg4NzUsInNjb3BlIjpbImVtYWlsIiwiZmlsZXZpbmUudjIuYXBpLioiLCJmdi5hcGkuZ2F0ZXdheS5hY2Nlc3MiLCJmdi5hdXRoLnRlbmFudC5yZWFkIiwib3BlbmlkIiwidGVuYW50Il0sImFtciI6WyJwZXJzb25hbF9hY2Nlc3NfdG9rZW4iXX0.E-TzpOEg6TO0ab65oq_rIambdugvK435q-yR1miardzwt5mP1Bz4aiLCurnx3FmkxhMSTnIOC8jzX73mG_lZL22FFctsB5EWt7WrUScqVAmTtG4hfPQOp4BM007nbXiKWwEXejBhyV8bsxkRkjv1gj6OlaCnvLkiDCgiD93RWpo6LSMi7nry1bNUXI2eN-0yXXrvDEiwvfY2meO1lvVwEUktLPy45_ehZ6IOc6ZIDw3zMLkqZjj046JRCsT9L8w-oNHq5xerRC4RcssIklsn9KH7M_oDrDtW20gLcTJ0mubk_HcTc1oQh-loe89UHFg-tiB63LHng9Vj3JnyZPvEjg", + "x-fv-orgid": "9227", + "x-fv-userid": "100510" + }, + "params": {}, + "method": "GET" + }, + "sample_response": { + "status_code": 200, + "json": { + "personId": { + "native": 43125866, + "partner": null + }, + "firstName": "Twin Pines Apartments (Twin Pine LLC)", + "middleName": "", + "lastName": "", + "isSingleName": true, + "fullName": "Twin Pines Apartments (Twin Pine LLC)", + "pictureUrl": "/images/Default6eba3d0d-6227-44c3-b907-8311c3d4da82.png", + "pictureKey": "Default6eba3d0d-6227-44c3-b907-8311c3d4da82.png", + "fromCompany": "", + "personTypes": [ + "Client/ Property" + ], + "uniqueId": "65333863-f119-4e10-9473-992b99fbac16", + "searchNames": [ + "twin", + "pines", + "apartments", + "(twin", + "pine", + "llc)", + "twin pines apartments (twin pine llc)" + ], + "phones": [], + "emails": [], + "addresses": [], + "prefix": "", + "hashtags": [], + "suffix": "", + "jobTitle": "", + "department": "", + "modifiedDate": "2025-10-22T15:06:31.993Z", + "links": { + "self": "/contacts/43125866", + "projects": "/contacts/43125866/projects", + "phones": "/contacts/43125866/phones", + "emailAddresses": "/contacts/43125866/emailAddresses", + "addresses": "/contacts/43125866/addresses" + } + }, + "headers": { + "Date": "Thu, 23 Oct 2025 00:21:16 GMT", + "Content-Type": "application/json; charset=utf-8", + "Transfer-Encoding": "chunked", + "Connection": "keep-alive", + "Server": "cloudflare", + "access-control-allow-headers": "Content-Type, x-fv-orgid, x-fv-clientip, x-fv-userid, authorization, x-fv-application", + "access-control-allow-methods": "GET, POST, PUT, PATCH, DELETE, OPTIONS, LOCK, UNLOCK, PROPPATCH, PROPFIND", + "access-control-allow-origin": "*", + "Cache-Control": "no-store, must-revalidate, no-cache, max-age=0, private", + "ratelimit-limit": "10;r=3300;w=60;c=Customer Temp", + "ratelimit-remaining": "9;r=3300;w=60;c=Customer Temp", + "content-security-policy": "default-src 'self';child-src https://fv-prod-us-shard-h-images.s3.amazonaws.com https://fv-prod-us-shard-h-docs.s3.amazonaws.com https://app.vinesign.com https://fv-prod-us-shard-h-report-export.s3.us-west-2.amazonaws.com *.amazonaws.com https://app.pendo.io https://feedback.us.pendo.io docs.google.com https://feedback.filevine.com *.newrelic.com *.filev.io *.flvn.io filev.io flvn.io 'self';connect-src *.filevinedev.com *.filevineapp.com *.filevine.ca *.filevine.com *.filevinegov.com *.fvauth.com https://app.vinesign.com https://fv-prod-us-shard-h-docs.s3.amazonaws.com https://fv-prod-us-shard-h-report-export.s3.us-west-2.amazonaws.com *.amazonaws.com *.nr-data.net *.pendo.io *.pdftron.com *.typeform.com *.newrelic.com https://app.pendo.io https://data.pendo.io https://pendo-static-5683967597215744.storage.googleapis.com https://pendo-io-static.storage.googleapis.com https://localhost:8080 *.filev.io *.flvn.io filev.io flvn.io 'self' blob: wss:;font-src *.bootstrapcdn.com fonts.gstatic.com *.typekit.net *.typeform.com 'self' data: blob:;frame-src *;frame-ancestors https://*.filevineapp.com https://app.pendo.io 'self';img-src *.typekit.net *.typeform.com https://app.pendo.io https://cdn.pendo.io https://data.pendo.io https://pendo-static-5683967597215744.storage.googleapis.com https://pendo-io-static.storage.googleapis.com https://fv-prod-us-shard-h-images.s3.amazonaws.com https://fv-prod-us-shard-h-images.s3.us-west-2.amazonaws.com https://fv-globalproducts-prod-us-logos.s3.us-west-2.amazonaws.com https://us.fv-globalproducts-logos.prod.filevine.com https://fv-prod-us-shard-h-fv-internal-image.s3.amazonaws.com https://fv-prod-us-shard-h-fv-internal-image.s3.us-west-2.amazonaws.com https://fv-prod-us-shard-h-docs.s3.amazonaws.com https://fv-prod-us-shard-h-docs.s3.us-west-2.amazonaws.com *.filev.io *.flvn.io filev.io flvn.io *.kaywa.com www.googletagmanager.com 'self' data: blob: cid:;manifest-src 'self';media-src https://fv-prod-us-shard-h-images.s3.amazonaws.com https://fv-prod-us-shard-h-images.s3.us-west-2.amazonaws.com https://fv-prod-us-shard-h-docs.s3.amazonaws.com https://fv-prod-us-shard-h-docs.s3.us-west-2.amazonaws.com https://fv-prod-us-shard-h-report-export.s3.us-west-2.amazonaws.com https://us-shard-h-discussions.filevineapp.com *.filev.io *.flvn.io filev.io flvn.io 'self';object-src https://fv-prod-us-shard-h-images.s3.amazonaws.com https://fv-prod-us-shard-h-images.s3.us-west-2.amazonaws.com https://fv-prod-us-shard-h-docs.s3.amazonaws.com https://fv-prod-us-shard-h-docs.s3.us-west-2.amazonaws.com https://fv-prod-us-shard-h-report-export.s3.us-west-2.amazonaws.com *.filev.io *.flvn.io filev.io flvn.io 'self';script-src *.bootstrapcdn.com *.typekit.net *.typeform.com *.newrelic.com *.nr-data.net https://app.pendo.io https://cdn.pendo.io https://data.pendo.io https://pendo-static-5683967597215744.storage.googleapis.com https://pendo-io-static.storage.googleapis.com https://duuxdetkhlwyv.cloudfront.net https://code.jquery.com https://localhost:8080 https://www.googletagmanager.com 'unsafe-inline' 'unsafe-eval' 'self' blob:;style-src *.bootstrapcdn.com fonts.googleapis.com *.typekit.net *.typeform.com https://app.pendo.io https://pendo-static-5683967597215744.storage.googleapis.com https://pendo-io-static.storage.googleapis.com https://duuxdetkhlwyv.cloudfront.net https://cdn.pendo.io https://data.pendo.io 'unsafe-inline' 'self';worker-src 'self' blob: 'unsafe-inline'", + "x-filevine-api-version": "3.3426.3.0", + "x-fv-correlation-id": "0a3acd7278c6486796d853ddf232e48f", + "x-aspnet-version": "4.0.30319", + "x-powered-by": "ASP.NET", + "x-content-type-options": "nosniff", + "x-frame-option": "SAMEORIGIN", + "x-xss-protection": "1; mode=block", + "x-fv-gateway-correlation-id": "0a3acd7278c6486796d853ddf232e48f", + "cf-cache-status": "DYNAMIC", + "Content-Encoding": "gzip", + "CF-RAY": "992d12c6c95790db-SEA", + "alt-svc": "h3=\":443\"; ma=86400" + } + } +} \ No newline at end of file diff --git a/examples/project_contacts.json b/examples/project_contacts.json new file mode 100644 index 0000000..2a44400 --- /dev/null +++ b/examples/project_contacts.json @@ -0,0 +1,246 @@ +{ + "sample_request": { + "url": "https://api.filevineapp.com/fv-app/v2/Projects/15905506/contacts", + "headers": { + "Accept": "application/json", + "Authorization": "Bearer eyJhbGciOiJSUzUxMiIsImtpZCI6Ijg2NjRFMkY0MDNCQjIxMzk2MzQ4NUFDOEI0MzVGMEJBOTgxNTBFN0RSUzUxMiIsInR5cCI6ImF0K2p3dCIsIng1dCI6ImhtVGk5QU83SVRsalNGckl0RFh3dXBnVkRuMCJ9.eyJuYmYiOjE3NjExNzg4NzUsImV4cCI6MTc2MTE4MDY3NSwiaXNzIjoiaHR0cHM6Ly9pZGVudGl0eS5maWxldmluZS5jb20iLCJhdWQiOlsiZmlsZXZpbmUudjIuYXBpIiwiZnYuYXBpLmdhdGV3YXkiLCJmdi5hdXRoIl0sImNsaWVudF9pZCI6IjRGMTg3MzhDLTEwN0EtNEI4Mi1CRkFDLTMwOEYxQjZBNjI2QSIsInN1YiI6ImY3MDQ4NGZmLTQ5MjItNDliMy05MWFkLTE2YjA5Mjk5MGIzMCIsImF1dGhfdGltZSI6MTc2MTE3ODg3NSwiaWRwIjoibG9jYWwiLCJwYXRfaWQiOiJmUkEwbHRoSnp5aTBBWU9ZS1F4WGVlckczUHc1MXEyMmh4cTlzbk4zL2V3PSIsInBhdF9uYW1lIjoiQnJ5Y2UgQ292ZXJ0IiwicGF0X3ZlcnNpb24iOiIxIiwidGVuYW50X2ZybiI6ImZybjpmaWxldmluZTp1cy1wcm9kOmZpbGV2aW5lLWFwcDo6OnRlbmFudFxcMGJlOGFhOGItZmEyOS00MjQ0LWI1YzItMDE5NzIzMWExNWY5IiwidGVuYW50X2lkIjoiMGJlOGFhOGItZmEyOS00MjQ0LWI1YzItMDE5NzIzMWExNWY5IiwianRpIjoiRTg1ODdBNDY3NkI1OTg2M0U0OTAxNkNFRTYxNDNFNjciLCJpYXQiOjE3NjExNzg4NzUsInNjb3BlIjpbImVtYWlsIiwiZmlsZXZpbmUudjIuYXBpLioiLCJmdi5hcGkuZ2F0ZXdheS5hY2Nlc3MiLCJmdi5hdXRoLnRlbmFudC5yZWFkIiwib3BlbmlkIiwidGVuYW50Il0sImFtciI6WyJwZXJzb25hbF9hY2Nlc3NfdG9rZW4iXX0.mjeEZUAE466DLME0jmRu_znERUH6_85AitHzgDrQlWPpeIy_18-B5x9hG2-wAp2s_WGdjn2eRc8qmy5wD9vbw4k1-L3HNhfI_9s24MGbanXWeu9eGHenY092D12W4eoJYlYCCpbGhqPNbql021hbU6TZTTGs2IMfwBy9k5H2HVCkPd2NbswuEsTt_M1XUUe-6duqEIGWdYQ097xGcVl3tGYgAxR6nyICyiovxVRZXsuKGK0a5r_7KKSZwM6v1brB9V08oBBsGoTRaLe_uxG-YHrMSPZ0JXvLxvzT8XXLh5RMXd2ijekHQ9nMKehmxojiQKg20r8_4kUtzZlkyeL_WQ", + "x-fv-orgid": "9227", + "x-fv-userid": "100510" + }, + "params": {}, + "method": "GET" + }, + "sample_response": { + "status_code": 200, + "json": { + "count": 3, + "offset": 0, + "limit": 50, + "hasMore": false, + "requestedFields": "*", + "items": [ + { + "projectId": { + "native": 15905506, + "partner": null + }, + "orgContactId": { + "native": 43125848, + "partner": null + }, + "roles": [ + "Property Contacts: Property Manager 1" + ], + "orgContact": { + "personId": { + "native": 43125848, + "partner": null + }, + "firstName": "Brian", + "middleName": "Robert", + "lastName": "Testy", + "isSingleName": false, + "fullName": "Brian Robert Testy", + "primaryEmail": "brianskarbek@gmail.com", + "pictureUrl": "/images/Default64896de9-7ee8-47a4-bcd2-1df186e6230a.png", + "pictureKey": "Default64896de9-7ee8-47a4-bcd2-1df186e6230a.png", + "personTypes": [ + "Property Manager" + ], + "uniqueId": "4daf252f-db81-4ca7-b7e4-0a5fd801170c", + "searchNames": [ + "brian", + "robert", + "testy", + "brian robert testy", + "brian testy" + ], + "phones": [ + { + "phoneId": { + "native": 74619813, + "partner": null + }, + "number": "4084821733", + "rawNumber": "4084821733", + "isSmsable": false, + "isFaxable": false, + "links": {} + } + ], + "emails": [ + { + "emailId": { + "native": 19265473, + "partner": null + }, + "address": "brianskarbek@gmail.com", + "links": {} + } + ], + "addresses": [ + { + "addressId": { + "native": 56313190, + "partner": null + }, + "line1": "2926 Neal Ave", + "line2": "", + "city": "San Jose", + "state": "CA", + "postalCode": "95128", + "fullAddress": "2926 Neal Ave, San Jose, CA 95128", + "links": {} + } + ], + "hashtags": [], + "modifiedDate": "2025-10-18T14:24:54.16Z", + "links": { + "self": "/contacts/43125848", + "projects": "/contacts/43125848/projects", + "phones": "/contacts/43125848/phones", + "emailAddresses": "/contacts/43125848/emailAddresses", + "addresses": "/contacts/43125848/addresses" + } + }, + "links": {} + }, + { + "projectId": { + "native": 15905506, + "partner": null + }, + "orgContactId": { + "native": 43193362, + "partner": null + }, + "roles": [ + "Defendants: Defendant Contact" + ], + "orgContact": { + "personId": { + "native": 43193362, + "partner": null + }, + "firstName": "Bad", + "lastName": "Guy", + "isSingleName": false, + "fullName": "Bad Guy", + "pictureUrl": "/images/Defaultf6812c31-f9e7-4961-b7da-89adcd48dbac.png", + "pictureKey": "Defaultf6812c31-f9e7-4961-b7da-89adcd48dbac.png", + "personTypes": [ + "Defendant" + ], + "uniqueId": "3368bc70-4aed-4349-9fd6-0cd2dca654ae", + "searchNames": [ + "bad", + "guy", + "bad guy" + ], + "phones": [], + "emails": [], + "addresses": [], + "hashtags": [], + "modifiedDate": "2025-10-22T15:29:38.463Z", + "links": { + "self": "/contacts/43193362", + "projects": "/contacts/43193362/projects", + "phones": "/contacts/43193362/phones", + "emailAddresses": "/contacts/43193362/emailAddresses", + "addresses": "/contacts/43193362/addresses" + } + }, + "links": {} + }, + { + "projectId": { + "native": 15905506, + "partner": null + }, + "orgContactId": { + "native": 43182533, + "partner": null + }, + "roles": [ + "Matter Overview: Signing Attorney" + ], + "orgContact": { + "personId": { + "native": 43182533, + "partner": null + }, + "firstName": "Brian", + "lastName": "Skarbek", + "isSingleName": false, + "fullName": "Brian Skarbek", + "primaryEmail": "Brian@ToddRothbardLaw.com", + "pictureUrl": "/images/Default96eecdd8-0702-4099-9ef2-254bb3a788fa.png", + "pictureKey": "Default96eecdd8-0702-4099-9ef2-254bb3a788fa.png", + "personTypes": [ + "Attorney" + ], + "uniqueId": "f0cab457-e837-4341-843d-a8afb4265beb", + "searchNames": [ + "brian", + "skarbek", + "brian skarbek" + ], + "phones": [], + "emails": [ + { + "emailId": { + "native": 19294763, + "partner": null + }, + "address": "Brian@ToddRothbardLaw.com", + "links": {} + } + ], + "addresses": [], + "hashtags": [], + "modifiedDate": "2025-10-20T23:47:10.213Z", + "links": { + "self": "/contacts/43182533", + "projects": "/contacts/43182533/projects", + "phones": "/contacts/43182533/phones", + "emailAddresses": "/contacts/43182533/emailAddresses", + "addresses": "/contacts/43182533/addresses" + } + }, + "links": {} + } + ], + "links": { + "self": "/contacts?firstName=&lastName=&fullName=&nickName=&personType=&phone=&email=&searchTerm=&offset=0&limit=50&requestedFields=*", + "prev": null, + "next": null + } + }, + "headers": { + "Date": "Thu, 23 Oct 2025 00:21:15 GMT", + "Content-Type": "application/json; charset=utf-8", + "Transfer-Encoding": "chunked", + "Connection": "keep-alive", + "Server": "cloudflare", + "access-control-allow-headers": "Content-Type, x-fv-orgid, x-fv-clientip, x-fv-userid, authorization, x-fv-application", + "access-control-allow-methods": "GET, POST, PUT, PATCH, DELETE, OPTIONS, LOCK, UNLOCK, PROPPATCH, PROPFIND", + "access-control-allow-origin": "*", + "Cache-Control": "no-store, must-revalidate, no-cache, max-age=0, private", + "ratelimit-limit": "10;r=3300;w=60;c=Customer Temp", + "ratelimit-remaining": "9;r=3300;w=60;c=Customer Temp", + "content-security-policy": "default-src 'self';child-src https://fv-prod-us-shard-h-images.s3.amazonaws.com https://fv-prod-us-shard-h-docs.s3.amazonaws.com https://app.vinesign.com https://fv-prod-us-shard-h-report-export.s3.us-west-2.amazonaws.com *.amazonaws.com https://app.pendo.io https://feedback.us.pendo.io docs.google.com https://feedback.filevine.com *.newrelic.com *.filev.io *.flvn.io filev.io flvn.io 'self';connect-src *.filevinedev.com *.filevineapp.com *.filevine.ca *.filevine.com *.filevinegov.com *.fvauth.com https://app.vinesign.com https://fv-prod-us-shard-h-docs.s3.amazonaws.com https://fv-prod-us-shard-h-report-export.s3.us-west-2.amazonaws.com *.amazonaws.com *.nr-data.net *.pendo.io *.pdftron.com *.typeform.com *.newrelic.com https://app.pendo.io https://data.pendo.io https://pendo-static-5683967597215744.storage.googleapis.com https://pendo-io-static.storage.googleapis.com https://localhost:8080 *.filev.io *.flvn.io filev.io flvn.io 'self' blob: wss:;font-src *.bootstrapcdn.com fonts.gstatic.com *.typekit.net *.typeform.com 'self' data: blob:;frame-src *;frame-ancestors https://*.filevineapp.com https://app.pendo.io 'self';img-src *.typekit.net *.typeform.com https://app.pendo.io https://cdn.pendo.io https://data.pendo.io https://pendo-static-5683967597215744.storage.googleapis.com https://pendo-io-static.storage.googleapis.com https://fv-prod-us-shard-h-images.s3.amazonaws.com https://fv-prod-us-shard-h-images.s3.us-west-2.amazonaws.com https://fv-globalproducts-prod-us-logos.s3.us-west-2.amazonaws.com https://us.fv-globalproducts-logos.prod.filevine.com https://fv-prod-us-shard-h-fv-internal-image.s3.amazonaws.com https://fv-prod-us-shard-h-fv-internal-image.s3.us-west-2.amazonaws.com https://fv-prod-us-shard-h-docs.s3.amazonaws.com https://fv-prod-us-shard-h-docs.s3.us-west-2.amazonaws.com *.filev.io *.flvn.io filev.io flvn.io *.kaywa.com www.googletagmanager.com 'self' data: blob: cid:;manifest-src 'self';media-src https://fv-prod-us-shard-h-images.s3.amazonaws.com https://fv-prod-us-shard-h-images.s3.us-west-2.amazonaws.com https://fv-prod-us-shard-h-docs.s3.amazonaws.com https://fv-prod-us-shard-h-docs.s3.us-west-2.amazonaws.com https://fv-prod-us-shard-h-report-export.s3.us-west-2.amazonaws.com https://us-shard-h-discussions.filevineapp.com *.filev.io *.flvn.io filev.io flvn.io 'self';object-src https://fv-prod-us-shard-h-images.s3.amazonaws.com https://fv-prod-us-shard-h-images.s3.us-west-2.amazonaws.com https://fv-prod-us-shard-h-docs.s3.amazonaws.com https://fv-prod-us-shard-h-docs.s3.us-west-2.amazonaws.com https://fv-prod-us-shard-h-report-export.s3.us-west-2.amazonaws.com *.filev.io *.flvn.io filev.io flvn.io 'self';script-src *.bootstrapcdn.com *.typekit.net *.typeform.com *.newrelic.com *.nr-data.net https://app.pendo.io https://cdn.pendo.io https://data.pendo.io https://pendo-static-5683967597215744.storage.googleapis.com https://pendo-io-static.storage.googleapis.com https://duuxdetkhlwyv.cloudfront.net https://code.jquery.com https://localhost:8080 https://www.googletagmanager.com 'unsafe-inline' 'unsafe-eval' 'self' blob:;style-src *.bootstrapcdn.com fonts.googleapis.com *.typekit.net *.typeform.com https://app.pendo.io https://pendo-static-5683967597215744.storage.googleapis.com https://pendo-io-static.storage.googleapis.com https://duuxdetkhlwyv.cloudfront.net https://cdn.pendo.io https://data.pendo.io 'unsafe-inline' 'self';worker-src 'self' blob: 'unsafe-inline'", + "x-filevine-api-version": "3.3426.3.0", + "x-fv-correlation-id": "51ff3a6e96c24996b386a4177bef952a", + "x-aspnet-version": "4.0.30319", + "x-powered-by": "ASP.NET", + "x-content-type-options": "nosniff", + "x-frame-option": "SAMEORIGIN", + "x-xss-protection": "1; mode=block", + "x-fv-gateway-correlation-id": "51ff3a6e96c24996b386a4177bef952a", + "cf-cache-status": "DYNAMIC", + "Content-Encoding": "gzip", + "CF-RAY": "992d12c2ec4131ba-SEA", + "alt-svc": "h3=\":443\"; ma=86400" + } + } +} \ No newline at end of file diff --git a/examples/project_list.json b/examples/project_list.json new file mode 100644 index 0000000..8acd918 --- /dev/null +++ b/examples/project_list.json @@ -0,0 +1,442 @@ +{ + "sample_request": { + "url": "https://api.filevineapp.com/fv-app/v2/projects", + "headers": { + "Accept": "application/json", + "Authorization": "Bearer eyJhbGciOiJSUzUxMiIsImtpZCI6Ijg2NjRFMkY0MDNCQjIxMzk2MzQ4NUFDOEI0MzVGMEJBOTgxNTBFN0RSUzUxMiIsInR5cCI6ImF0K2p3dCIsIng1dCI6ImhtVGk5QU83SVRsalNGckl0RFh3dXBnVkRuMCJ9.eyJuYmYiOjE3NjExNzg4NzQsImV4cCI6MTc2MTE4MDY3NCwiaXNzIjoiaHR0cHM6Ly9pZGVudGl0eS5maWxldmluZS5jb20iLCJhdWQiOlsiZmlsZXZpbmUudjIuYXBpIiwiZnYuYXBpLmdhdGV3YXkiLCJmdi5hdXRoIl0sImNsaWVudF9pZCI6IjRGMTg3MzhDLTEwN0EtNEI4Mi1CRkFDLTMwOEYxQjZBNjI2QSIsInN1YiI6ImY3MDQ4NGZmLTQ5MjItNDliMy05MWFkLTE2YjA5Mjk5MGIzMCIsImF1dGhfdGltZSI6MTc2MTE3ODg3NCwiaWRwIjoibG9jYWwiLCJwYXRfaWQiOiJmUkEwbHRoSnp5aTBBWU9ZS1F4WGVlckczUHc1MXEyMmh4cTlzbk4zL2V3PSIsInBhdF9uYW1lIjoiQnJ5Y2UgQ292ZXJ0IiwicGF0X3ZlcnNpb24iOiIxIiwidGVuYW50X2ZybiI6ImZybjpmaWxldmluZTp1cy1wcm9kOmZpbGV2aW5lLWFwcDo6OnRlbmFudFxcMGJlOGFhOGItZmEyOS00MjQ0LWI1YzItMDE5NzIzMWExNWY5IiwidGVuYW50X2lkIjoiMGJlOGFhOGItZmEyOS00MjQ0LWI1YzItMDE5NzIzMWExNWY5IiwianRpIjoiODlCRTQzRDlDRTYwQkNFNzg4Mzc5OEE1M0VBQkYwQTAiLCJpYXQiOjE3NjExNzg4NzQsInNjb3BlIjpbImVtYWlsIiwiZmlsZXZpbmUudjIuYXBpLioiLCJmdi5hcGkuZ2F0ZXdheS5hY2Nlc3MiLCJmdi5hdXRoLnRlbmFudC5yZWFkIiwib3BlbmlkIiwidGVuYW50Il0sImFtciI6WyJwZXJzb25hbF9hY2Nlc3NfdG9rZW4iXX0.P5rPsrBwt0MQ7SmR9l0K0WXgTkqStXXbyYRYCmjPaBnJZjLljMLkpqZsFYDf3pyg6t9oZK_oa86sGa7H13SBIocWqV70hloqAVn1uinwzKxS1gv0xvUdTkd_uPgJZPvQwsu_Py4RrXcHkhtH8dqOGO_VFBFyYvt5RSNUpVtS6ofP0srbxoAOUEsYW-VhmKV8ugcdFzATgTx6k6gXyy1fTKoa_s7tVSFcOe-yPD7xpnixY3aqnYFaCSf5PYzd9l7iBPzfgjn0cFWnOjtxKSl_fxzQacJqOonrSPYdTlNf-F6h5E-yT-CQuVVWRplcbadEDvgMptTQjbprZHTMLfdn-w", + "x-fv-orgid": "9227", + "x-fv-userid": "100510" + }, + "params": {}, + "method": "GET" + }, + "sample_response": { + "status_code": 200, + "json": { + "count": 8, + "offset": 0, + "limit": 50, + "hasMore": false, + "requestedFields": "*", + "items": [ + { + "projectTypeCode": "EVICT", + "rootDocFolderId": { + "native": 138236604, + "partner": null + }, + "phaseName": "Nonpayment File Review", + "phaseDate": "2025-10-22T15:23:59.89Z", + "clientName": "Twin Pines Apartments (Twin Pine LLC)", + "clientPictureURL": "/images/Default6eba3d0d-6227-44c3-b907-8311c3d4da82.png", + "clientPictureKey": "Default6eba3d0d-6227-44c3-b907-8311c3d4da82.png", + "clientPictureS3Url": "", + "firstPrimaryName": "Lani", + "firstPrimaryUsername": "lani2", + "isArchived": false, + "lastActivity": "2025-10-22T15:27:44.177Z", + "uniqueKey": "TwinPinesApartmentsZ15905506", + "projectOrClientName": "Twin Pines Apartments (Twin Pine LLC) v. Guy, #12, NP", + "hashtags": [], + "orgId": 9227, + "projectEmailAddress": "TwinPinesApartmentsZ15905506@rothbardlawgroup.filevineapp.com", + "createdDate": "2025-10-22T15:03:48.41Z", + "projectUrl": "https://rothbardlawgroup.filevineapp.com/#/project/15905506", + "projectId": { + "native": 15905506, + "partner": null + }, + "projectTypeId": { + "native": 34111, + "partner": null + }, + "clientId": { + "native": 43125866, + "partner": null + }, + "projectName": "Twin Pines Apartments (Twin Pine LLC) v. Guy, #12, NP", + "phaseId": { + "native": 209436, + "partner": null + }, + "links": { + "self": "/projects/15905506", + "projectType": "/projecttypes/34111", + "rootFolder": "/folders/138236604", + "client": "/contacts/43125866", + "contacts": "/projects/15905506/contacts" + } + }, + { + "projectTypeCode": "EVICT", + "rootDocFolderId": { + "native": 138206532, + "partner": null + }, + "phaseName": "Nonpayment File Review", + "phaseDate": "2025-10-21T14:42:31.18Z", + "clientName": "3900 Adeline", + "clientPictureURL": "/images/avatar-user-placeholder.png", + "clientPictureKey": "avatar-user-placeholder.png", + "clientPictureS3Url": "", + "firstPrimaryName": "Rebecca Hartzler", + "firstPrimaryUsername": "rebecca63", + "isArchived": false, + "lastActivity": "2025-10-21T15:29:19.773Z", + "uniqueKey": "AdelineZ15902340", + "projectOrClientName": "3900 Adeline (3900 Adeline, LLC) v. Patten, #323, NP", + "hashtags": [], + "orgId": 9227, + "projectEmailAddress": "AdelineZ15902340@rothbardlawgroup.filevineapp.com", + "createdDate": "2025-10-21T14:38:26.957Z", + "projectUrl": "https://rothbardlawgroup.filevineapp.com/#/project/15902340", + "projectId": { + "native": 15902340, + "partner": null + }, + "projectTypeId": { + "native": 34111, + "partner": null + }, + "clientId": { + "native": 43183162, + "partner": null + }, + "projectName": "3900 Adeline (3900 Adeline, LLC) v. Patten, #323, NP", + "phaseId": { + "native": 209436, + "partner": null + }, + "links": { + "self": "/projects/15902340", + "projectType": "/projecttypes/34111", + "rootFolder": "/folders/138206532", + "client": "/contacts/43183162", + "contacts": "/projects/15902340/contacts" + } + }, + { + "projectTypeCode": "EVICT", + "rootDocFolderId": { + "native": 138179643, + "partner": null + }, + "phaseName": "Nonpayment File Review", + "phaseDate": "2025-10-20T15:07:56.023Z", + "clientName": "Bates Motel (Bates Motel Group, LLC)", + "clientPictureURL": "/images/Defaultf3f74f0b-0eee-4a40-98f1-57960d87f12a.png", + "clientPictureKey": "Defaultf3f74f0b-0eee-4a40-98f1-57960d87f12a.png", + "clientPictureS3Url": "", + "firstPrimaryName": "Stephanie Gonzalez", + "firstPrimaryUsername": "stephanie148", + "isArchived": false, + "lastActivity": "2025-10-21T00:07:51.847Z", + "uniqueKey": "vFakeDefendantNPZ15901312", + "projectOrClientName": "v. Fake Defendant, #50, NP", + "hashtags": [], + "orgId": 9227, + "projectEmailAddress": "vFakeDefendantNPZ15901312@rothbardlawgroup.filevineapp.com", + "createdDate": "2025-10-20T14:31:07.29Z", + "projectUrl": "https://rothbardlawgroup.filevineapp.com/#/project/15901312", + "projectId": { + "native": 15901312, + "partner": null + }, + "projectTypeId": { + "native": 34111, + "partner": null + }, + "clientId": { + "native": 40576359, + "partner": null + }, + "projectName": "v. Fake Defendant, #50, NP", + "phaseId": { + "native": 209436, + "partner": null + }, + "links": { + "self": "/projects/15901312", + "projectType": "/projecttypes/34111", + "rootFolder": "/folders/138179643", + "client": "/contacts/40576359", + "contacts": "/projects/15901312/contacts" + } + }, + { + "projectTypeCode": "EVICT", + "rootDocFolderId": { + "native": 138011554, + "partner": null + }, + "phaseName": "Migrated", + "phaseDate": "2025-10-16T20:33:18.8Z", + "clientName": "ABC Property Management", + "clientPictureURL": "/images/Default5888c798-e92d-4c2e-82e9-737a564b5317.png", + "clientPictureKey": "Default5888c798-e92d-4c2e-82e9-737a564b5317.png", + "clientPictureS3Url": "", + "firstPrimaryName": "Brian Skarbek", + "firstPrimaryUsername": "brianskarbek", + "isArchived": false, + "lastActivity": "2025-10-16T20:33:18.803Z", + "uniqueKey": "TBDZ15896299", + "projectOrClientName": "TBD", + "hashtags": [], + "orgId": 9227, + "projectEmailAddress": "TBDZ15896299@rothbardlawgroup.filevineapp.com", + "createdDate": "2025-10-16T20:33:18.8Z", + "projectUrl": "https://rothbardlawgroup.filevineapp.com/#/project/15896299", + "projectId": { + "native": 15896299, + "partner": null + }, + "projectTypeId": { + "native": 34111, + "partner": null + }, + "clientId": { + "native": 43122010, + "partner": null + }, + "projectName": "TBD", + "phaseId": { + "native": 211957, + "partner": null + }, + "links": { + "self": "/projects/15896299", + "projectType": "/projecttypes/34111", + "rootFolder": "/folders/138011554", + "client": "/contacts/43122010", + "contacts": "/projects/15896299/contacts" + } + }, + { + "projectTypeCode": "EVICT", + "rootDocFolderId": { + "native": 137923183, + "partner": null + }, + "phaseName": "Default", + "phaseDate": "2025-10-22T18:03:38.6Z", + "clientName": "Bates Motel (Bates Motel Group, LLC)", + "clientPictureURL": "/images/Defaultf3f74f0b-0eee-4a40-98f1-57960d87f12a.png", + "clientPictureKey": "Defaultf3f74f0b-0eee-4a40-98f1-57960d87f12a.png", + "clientPictureS3Url": "", + "firstPrimaryName": "Sarra McDonald", + "firstPrimaryUsername": "sarra", + "isArchived": false, + "lastActivity": "2025-10-22T22:14:58.907Z", + "uniqueKey": "BatesMotelZ15892080", + "projectOrClientName": "Bates Motel (Bates Motel Group, LLC)", + "hashtags": [], + "orgId": 9227, + "projectEmailAddress": "BatesMotelZ15892080@rothbardlawgroup.filevineapp.com", + "createdDate": "2025-10-13T21:54:29.187Z", + "projectUrl": "https://rothbardlawgroup.filevineapp.com/#/project/15892080", + "projectId": { + "native": 15892080, + "partner": null + }, + "projectTypeId": { + "native": 34111, + "partner": null + }, + "clientId": { + "native": 40576359, + "partner": null + }, + "projectName": "Bates Motel (Bates Motel Group, LLC)", + "phaseId": { + "native": 211435, + "partner": null + }, + "links": { + "self": "/projects/15892080", + "projectType": "/projecttypes/34111", + "rootFolder": "/folders/137923183", + "client": "/contacts/40576359", + "contacts": "/projects/15892080/contacts" + } + }, + { + "projectTypeCode": "EVICT", + "rootDocFolderId": { + "native": 128719223, + "partner": null + }, + "phaseName": "Trial Prep and Trial", + "phaseDate": "2025-10-22T21:40:11.117Z", + "clientName": "1234 Main Street Apartments (Main Street Property Holding LLC)", + "clientPictureURL": "/images/avatar-user-placeholder.png", + "clientPictureKey": "avatar-user-placeholder.png", + "clientPictureS3Url": "", + "firstPrimaryName": "Sarra McDonald", + "firstPrimaryUsername": "sarra", + "isArchived": false, + "lastActivity": "2025-10-22T22:14:26.443Z", + "uniqueKey": "MainStreetApartmentsvSmithNTTZ12685677", + "projectOrClientName": "1234 Main Street Apartments v Smith, #24, NTT", + "hashtags": [ + "#Attorney-to-accept-service" + ], + "orgId": 9227, + "projectEmailAddress": "MainStreetApartmentsvSmithNTTZ12685677@rothbardlawgroup.filevineapp.com", + "createdDate": "2025-06-17T01:07:55.873Z", + "projectUrl": "https://rothbardlawgroup.filevineapp.com/#/project/12685677", + "projectId": { + "native": 12685677, + "partner": null + }, + "projectTypeId": { + "native": 34111, + "partner": null + }, + "clientId": { + "native": 40637221, + "partner": null + }, + "projectName": "1234 Main Street Apartments v Smith, #24, NTT", + "phaseId": { + "native": 211438, + "partner": null + }, + "links": { + "self": "/projects/12685677", + "projectType": "/projecttypes/34111", + "rootFolder": "/folders/128719223", + "client": "/contacts/40637221", + "contacts": "/projects/12685677/contacts" + } + }, + { + "projectTypeCode": "EVICT", + "rootDocFolderId": { + "native": 128547693, + "partner": null + }, + "phaseName": "Notice Pending", + "phaseDate": "2025-10-21T00:21:07.603Z", + "clientName": "Bates Motel (Bates Motel Group, LLC)", + "clientPictureURL": "/images/Defaultf3f74f0b-0eee-4a40-98f1-57960d87f12a.png", + "clientPictureKey": "Defaultf3f74f0b-0eee-4a40-98f1-57960d87f12a.png", + "clientPictureS3Url": "", + "firstPrimaryName": "Jen Rhodes", + "firstPrimaryUsername": "jen60", + "isArchived": false, + "lastActivity": "2025-10-21T00:07:49.373Z", + "uniqueKey": "BatesMotelVsTenantNameZ12662539", + "projectOrClientName": "Bates Motel Vs. Tenant Name 25CV123456", + "hashtags": [], + "orgId": 9227, + "projectEmailAddress": "BatesMotelVsTenantNameZ12662539@rothbardlawgroup.filevineapp.com", + "createdDate": "2025-06-06T02:48:45.96Z", + "projectUrl": "https://rothbardlawgroup.filevineapp.com/#/project/12662539", + "projectId": { + "native": 12662539, + "partner": null + }, + "projectTypeId": { + "native": 34111, + "partner": null + }, + "clientId": { + "native": 40576359, + "partner": null + }, + "projectName": "Bates Motel Vs. Tenant Name 25CV123456", + "phaseId": { + "native": 209439, + "partner": null + }, + "links": { + "self": "/projects/12662539", + "projectType": "/projecttypes/34111", + "rootFolder": "/folders/128547693", + "client": "/contacts/40576359", + "contacts": "/projects/12662539/contacts" + } + }, + { + "projectTypeCode": "ADMIN", + "rootDocFolderId": { + "native": 128547692, + "partner": null + }, + "phaseName": "ADMIN", + "phaseDate": "2025-06-06T02:47:33.87Z", + "clientName": "Admin", + "clientPictureURL": "/images/c8711333-fad7-40f3-81fb-2853b8a7e082.png", + "clientPictureKey": "c8711333-fad7-40f3-81fb-2853b8a7e082.png", + "clientPictureS3Url": "", + "firstPrimaryName": "Jen Rhodes", + "firstPrimaryUsername": "jen60", + "isArchived": false, + "lastActivity": "2025-06-06T02:47:33.873Z", + "uniqueKey": "VineskillsTrainingandResourcesProjectZ12662538", + "projectOrClientName": "Vineskills | Training and Resources Project", + "hashtags": [], + "orgId": 9227, + "projectEmailAddress": "VineskillsTrainingandResourcesProjectZ12662538@rothbardlawgroup.filevineapp.com", + "createdDate": "2025-06-06T02:47:33.87Z", + "projectUrl": "https://rothbardlawgroup.filevineapp.com/#/project/12662538", + "projectId": { + "native": 12662538, + "partner": null + }, + "projectTypeId": { + "native": 34112, + "partner": null + }, + "clientId": { + "native": 40576358, + "partner": null + }, + "projectName": "Vineskills | Training and Resources Project", + "phaseId": { + "native": 209445, + "partner": null + }, + "links": { + "self": "/projects/12662538", + "projectType": "/projecttypes/34112", + "rootFolder": "/folders/128547692", + "client": "/contacts/40576358", + "contacts": "/projects/12662538/contacts" + } + } + ], + "links": { + "self": "/projects?name=&number=&createdSince=&searchterm=&projectId=&incidentDate=&hashtags=&offset=0&limit=50&requestedFields=*", + "prev": null, + "next": null + } + }, + "headers": { + "Date": "Thu, 23 Oct 2025 00:21:15 GMT", + "Content-Type": "application/json; charset=utf-8", + "Transfer-Encoding": "chunked", + "Connection": "keep-alive", + "Server": "cloudflare", + "access-control-allow-headers": "Content-Type, x-fv-orgid, x-fv-clientip, x-fv-userid, authorization, x-fv-application", + "access-control-allow-methods": "GET, POST, PUT, PATCH, DELETE, OPTIONS, LOCK, UNLOCK, PROPPATCH, PROPFIND", + "access-control-allow-origin": "*", + "Cache-Control": "no-store, must-revalidate, no-cache, max-age=0, private", + "ratelimit-limit": "10;r=3300;w=60;c=Customer Temp", + "ratelimit-remaining": "9;r=3300;w=60;c=Customer Temp", + "content-security-policy": "default-src 'self';child-src https://fv-prod-us-shard-h-images.s3.amazonaws.com https://fv-prod-us-shard-h-docs.s3.amazonaws.com https://app.vinesign.com https://fv-prod-us-shard-h-report-export.s3.us-west-2.amazonaws.com *.amazonaws.com https://app.pendo.io https://feedback.us.pendo.io docs.google.com https://feedback.filevine.com *.newrelic.com *.filev.io *.flvn.io filev.io flvn.io 'self';connect-src *.filevinedev.com *.filevineapp.com *.filevine.ca *.filevine.com *.filevinegov.com *.fvauth.com https://app.vinesign.com https://fv-prod-us-shard-h-docs.s3.amazonaws.com https://fv-prod-us-shard-h-report-export.s3.us-west-2.amazonaws.com *.amazonaws.com *.nr-data.net *.pendo.io *.pdftron.com *.typeform.com *.newrelic.com https://app.pendo.io https://data.pendo.io https://pendo-static-5683967597215744.storage.googleapis.com https://pendo-io-static.storage.googleapis.com https://localhost:8080 *.filev.io *.flvn.io filev.io flvn.io 'self' blob: wss:;font-src *.bootstrapcdn.com fonts.gstatic.com *.typekit.net *.typeform.com 'self' data: blob:;frame-src *;frame-ancestors https://*.filevineapp.com https://app.pendo.io 'self';img-src *.typekit.net *.typeform.com https://app.pendo.io https://cdn.pendo.io https://data.pendo.io https://pendo-static-5683967597215744.storage.googleapis.com https://pendo-io-static.storage.googleapis.com https://fv-prod-us-shard-h-images.s3.amazonaws.com https://fv-prod-us-shard-h-images.s3.us-west-2.amazonaws.com https://fv-globalproducts-prod-us-logos.s3.us-west-2.amazonaws.com https://us.fv-globalproducts-logos.prod.filevine.com https://fv-prod-us-shard-h-fv-internal-image.s3.amazonaws.com https://fv-prod-us-shard-h-fv-internal-image.s3.us-west-2.amazonaws.com https://fv-prod-us-shard-h-docs.s3.amazonaws.com https://fv-prod-us-shard-h-docs.s3.us-west-2.amazonaws.com *.filev.io *.flvn.io filev.io flvn.io *.kaywa.com www.googletagmanager.com 'self' data: blob: cid:;manifest-src 'self';media-src https://fv-prod-us-shard-h-images.s3.amazonaws.com https://fv-prod-us-shard-h-images.s3.us-west-2.amazonaws.com https://fv-prod-us-shard-h-docs.s3.amazonaws.com https://fv-prod-us-shard-h-docs.s3.us-west-2.amazonaws.com https://fv-prod-us-shard-h-report-export.s3.us-west-2.amazonaws.com https://us-shard-h-discussions.filevineapp.com *.filev.io *.flvn.io filev.io flvn.io 'self';object-src https://fv-prod-us-shard-h-images.s3.amazonaws.com https://fv-prod-us-shard-h-images.s3.us-west-2.amazonaws.com https://fv-prod-us-shard-h-docs.s3.amazonaws.com https://fv-prod-us-shard-h-docs.s3.us-west-2.amazonaws.com https://fv-prod-us-shard-h-report-export.s3.us-west-2.amazonaws.com *.filev.io *.flvn.io filev.io flvn.io 'self';script-src *.bootstrapcdn.com *.typekit.net *.typeform.com *.newrelic.com *.nr-data.net https://app.pendo.io https://cdn.pendo.io https://data.pendo.io https://pendo-static-5683967597215744.storage.googleapis.com https://pendo-io-static.storage.googleapis.com https://duuxdetkhlwyv.cloudfront.net https://code.jquery.com https://localhost:8080 https://www.googletagmanager.com 'unsafe-inline' 'unsafe-eval' 'self' blob:;style-src *.bootstrapcdn.com fonts.googleapis.com *.typekit.net *.typeform.com https://app.pendo.io https://pendo-static-5683967597215744.storage.googleapis.com https://pendo-io-static.storage.googleapis.com https://duuxdetkhlwyv.cloudfront.net https://cdn.pendo.io https://data.pendo.io 'unsafe-inline' 'self';worker-src 'self' blob: 'unsafe-inline'", + "x-filevine-api-version": "3.3426.3.0", + "x-fv-correlation-id": "4562cb54a936470cb3fb463db5837dab", + "x-aspnet-version": "4.0.30319", + "x-powered-by": "ASP.NET", + "x-content-type-options": "nosniff", + "x-frame-option": "SAMEORIGIN", + "x-xss-protection": "1; mode=block", + "x-fv-gateway-correlation-id": "4562cb54a936470cb3fb463db5837dab", + "cf-cache-status": "DYNAMIC", + "Content-Encoding": "gzip", + "CF-RAY": "992d12bd2939decc-SEA", + "alt-svc": "h3=\":443\"; ma=86400" + } + } +} \ No newline at end of file diff --git a/generate_sample.py b/generate_sample.py new file mode 100644 index 0000000..8bbe69c --- /dev/null +++ b/generate_sample.py @@ -0,0 +1,111 @@ +import os +import json +import requests +from dotenv import load_dotenv + +# Load environment variables from .env file +load_dotenv() + +def get_bearer_token(): + """Request a bearer token using credentials from environment variables.""" + url = "https://identity.filevine.com/connect/token" + data = { + "client_id": os.environ.get("FILEVINE_CLIENT_ID"), + "client_secret": os.environ.get("FILEVINE_CLIENT_SECRET"), + "grant_type": "personal_access_token", + "scope": "fv.api.gateway.access tenant filevine.v2.api.* email openid fv.auth.tenant.read", + "token": os.environ.get("FILEVINE_PERSONAL_ACCESS_TOKEN"), + } + headers = {"Accept": "application/json"} + response = requests.post(url, data=data, headers=headers) + response.raise_for_status() + return response.json().get("access_token") + +# Define the collection of request/response pairs +# User can add more tuples following the same pattern +SAMPLE_PROJECT_ID = 15905506 +REQUESTS = [ + ( + { + "url": "https://api.filevineapp.com/fv-app/v2/projects", + "headers": { + "Accept": "application/json", + "Authorization": f"Bearer {get_bearer_token()}", + "x-fv-orgid": os.environ.get("FILEVINE_ORG_ID"), + "x-fv-userid": os.environ.get("FILEVINE_USER_ID"), + }, + "params": {}, + "method": "GET" + + }, + "examples/project_list.json" + ), +( + { + "url": f"https://api.filevineapp.com/fv-app/v2/Projects/{SAMPLE_PROJECT_ID}/contacts", + "headers": { + "Accept": "application/json", + "Authorization": f"Bearer {get_bearer_token()}", + "x-fv-orgid": os.environ.get("FILEVINE_ORG_ID"), + "x-fv-userid": os.environ.get("FILEVINE_USER_ID"), + }, + "params": {}, + "method": "GET" + + }, + "examples/project_contacts.json" +), +( + { + "url": f"https://api.filevineapp.com/fv-app/v2/contacts/43125866", + "headers": { + "Accept": "application/json", + "Authorization": f"Bearer {get_bearer_token()}", + "x-fv-orgid": os.environ.get("FILEVINE_ORG_ID"), + "x-fv-userid": os.environ.get("FILEVINE_USER_ID"), + }, + "params": {}, + "method": "GET" + + }, + "examples/client.json" +) +] + +def generate_sample_request_response(request_config): + """Generate a sample request/response pair using the provided request configuration.""" + # Make the request + response = requests.request(**request_config) + + # Create structured output + result = { + "sample_request": request_config, + "sample_response": { + "status_code": response.status_code, + "json": response.json() if response.content else None, + "headers": dict(response.headers) + } + } + + return result + +if __name__ == "__main__": + try: + # Ensure examples directory exists + os.makedirs("examples", exist_ok=True) + + # Process each request/response pair + for request_config, output_path in REQUESTS: + # Regenerate bearer token for each request (tokens may expire) + request_config["headers"]["Authorization"] = f"Bearer {get_bearer_token()}" + + result = generate_sample_request_response(request_config) + + # Save pretty printed JSON to file + with open(output_path, 'w') as f: + json.dump(result, f, indent=2) + + print(f"Generated {len(REQUESTS)} sample request/response pairs") + + except Exception as e: + print(f"Error: {e}") \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..2d71f20 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +Flask==3.0.3 +firebase-admin==6.6.0 +python-dotenv==1.0.1 +requests==2.32.3 +itsdangerous==2.2.0 \ No newline at end of file diff --git a/rothbard-service-account.json b/rothbard-service-account.json new file mode 100755 index 0000000..f4ff4fd --- /dev/null +++ b/rothbard-service-account.json @@ -0,0 +1,13 @@ +{ + "type": "service_account", + "project_id": "rothbard-3f496", + "private_key_id": "58d242c0d372e3ae4821e1b32e2d4be8ca718fde", + "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC5OuQyds3Gdvsw\n44ZJLekGrqt0ktj5YjSvfEjzk7jZuMJITy+mCpohfm6JUv6IRl1MHY+P8sxM9xiT\nkLF3EXeAyyWXVWx4KjpGX0KeG4IfSZhnMKljDBtZIyxJhOvy54+QBFcRqjIeeB5s\n5EHG2jJySOzDUA7JMzP/Oo0MmU332ylLU3NIutolccs1XC/ymi2ocHpgcGhENl40\nDLvn6ZMS3hEulkaUDfMBovJrvvjWWxbK4HLb+04m2+XD98uOmD3x3/nUWuiLNMH+\n1mJNzVRgiNFCDyqg3brUWP6eEWeF0W66QWPk7SV72lPsf8Ldq7BicS5p5k4gMxO2\ng+eaAkZxAgMBAAECggEAL+GOTZEyXiQxiJC4DMCmZQjP32F6XvTI47f/7573AKjm\n5+Q4T/abox7YmfzvOPDfeyaFDtPXhem126diiIHmX0+kFvuI/4MC71/+i3pW55mR\noNMOZkEh7KfP8e0/RNog3TyR+UoCjKfGTaWvbyTGN46sTUyrlcz7mvVasrAKXJBE\nLtyvz5BpXpikybC70MDY/bDKUxrl45AaRR/FuBTqDj7wK56O1TMaviRZUks6GHUM\nYphoammseQi1X/OjxwjtUHgclKcZN3r1F+tYdbBMGLbKGu8rRjEr8y8vLJAVxK0M\nSVsNzjlsUFGF/RVljb38tlqKXchZ+DJ3FZ3fyU4YFQKBgQD2FucwWnoQi0E0d9kX\nJWgmoH9fYwDr6NpD7k9bLW4ktrWTEENAzfV9/cfFRlFbHA5Zk35a4xKYY3ZXiO76\n0Xk5nkhWrymNd46yT094SInEugaKfBAw6Xo243Jq6/eR7GD1KSL+9z+4oCtQ+JHv\nepAiTBDGNOUuXQT06ONJrEUXPwKBgQDAsIx/rJdcaH0XZH+0TeNY0fe5U9LAdNZm\nkNeVSaMBY+UuRvZt0Y0C3F54ngXfEtmAwvQ4LVgBmJVv4JAkETRowAJeD2RflhYl\n4s7aaPyh0GJJ4/j8/XP0TZN5gv7D3MNJAh2bJLluONjZmWgckaRlU8E2za2EZSqY\nW2bmoFNmTwKBgAZLF6Z46d46cXRyDC83Wa6DND6wPXnK/qn2Ejl2s/ZkZchZBh9G\nJR0PvGgjIDmAQi2wQ+73F6amBITAj7wCV2NN1PPCjwF7KT8OIC4nTL6nMzufaJqX\nnfSBZI+vcSleLiyW3LpAgHSsQ+9SLAk/zSfTYipvd9zzrAjHW+iqaynpAoGAMjZP\nhn29O7Fm14+yz5N0aRLeEQdM5iYMMNIRu69isNwNPs/zK47txg8S9y+GrCjHUQx8\ng58dTd0rI+pK5XsuQxW2CDjOmTINN3YxHS06mBgrZMHpglOxwbntcj62kOeYZBAP\nEvyw7Y4WxC17ueYiBt2afeN/Ef8i6Gz5FaQ113UCgYBr39cTINpIuwDyfDAqu032\n0zP/8GFE5zb4VlGoKC2Ma/9x+nkOf05eXqv1jOpq+hsYlBe9mrufeF0gcKAycHWn\nW1QgkepPXlrvFXws928TQUGv+Jp63UMdNWXbfh4ZIp3vhAAdh+h8TcvRyVrADZgB\nqZXfdtkfYTQewM7p8vno3Q==\n-----END PRIVATE KEY-----\n", + "client_email": "firebase-adminsdk-fbsvc@rothbard-3f496.iam.gserviceaccount.com", + "client_id": "112093576377036658724", + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": "https://oauth2.googleapis.com/token", + "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", + "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-fbsvc%40rothbard-3f496.iam.gserviceaccount.com", + "universe_domain": "googleapis.com" +} diff --git a/static/auth.js b/static/auth.js new file mode 100644 index 0000000..621f7ea --- /dev/null +++ b/static/auth.js @@ -0,0 +1,2 @@ +(function(){ +})(); \ No newline at end of file diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 0000000..caac9bd --- /dev/null +++ b/templates/base.html @@ -0,0 +1,28 @@ + + + + + + {{ title or 'App' }} + + + + +
+
+ Filevine Demo + +
+
+
+ {% block content %}{% endblock %} +
+ + \ No newline at end of file diff --git a/templates/dashboard.html b/templates/dashboard.html new file mode 100644 index 0000000..5ccc811 --- /dev/null +++ b/templates/dashboard.html @@ -0,0 +1,45 @@ +{% extends 'base.html' %} +{% block content %} +

Projects for {{ case_email }}

+
+ + + + + + + + + + + + + {% for r in rows %} + + + + + + + + + + {% else %} + + + + {% endfor %} + +
Project EmailMatter DescriptionNumberIncident DateNameLink
{{ r.client or '—' }}{{ r.matter_description or '—' }} +
    + {% for c in r.contacts %} +
  • {{ c.orgContact.firstName }}
  • + {% endfor %} +
+
{{ r.Number or '—' }}{{ (r.IncidentDate or '')[:10] }}{{ r.ProjectName or '—' }} + {% if r.projectUrl %} + Open + {% else %}—{% endif %} +
No matching projects found.
+
+{% endblock %} \ No newline at end of file diff --git a/templates/login.html b/templates/login.html new file mode 100644 index 0000000..7cbd821 --- /dev/null +++ b/templates/login.html @@ -0,0 +1,69 @@ +{% extends 'base.html' %} +{% block content %} +
+

Sign in

+
+
+ + +
+
+ + +
+ + +
+
+ + + + + + + + + +{% endblock %} \ No newline at end of file diff --git a/templates/welcome.html b/templates/welcome.html new file mode 100644 index 0000000..b31d9bf --- /dev/null +++ b/templates/welcome.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% block content %} +
+

Welcome!

+

Your account is almost ready. An administrator will finish setup soon.

+ +
+{% endblock %} \ No newline at end of file