diff --git a/README.md b/README.md index 71fa09e..d8c6bfc 100644 --- a/README.md +++ b/README.md @@ -27,18 +27,37 @@ rothbard/ ├── generate_sample.py # Utility to generate sample Filevine API responses ├── requirements.txt # Python dependencies ├── rothbard-service-account.json # Firebase service account credentials +├── rothbard-staging2-12345-firebase-adminsdk-fbsvc-7f95268383.json # Firebase service account for staging ├── static/ │ └── auth.js # Client-side authentication handling ├── templates/ # Jinja2 HTML templates │ ├── base.html # Base template with navigation │ ├── login.html # Firebase login page │ ├── welcome.html # User welcome/onboarding page -│ └── dashboard.html # Main case dashboard +│ ├── dashboard.html # Main case dashboard +│ ├── admin_users.html # Admin user management interface +│ ├── admin_user_edit.html # Admin user edit interface +│ ├── admin_user_create.html # Admin user creation interface +│ └── _pagination.html # Pagination component ├── examples/ # Sample Filevine API responses -│ ├── project_list.json -│ ├── project_contacts.json -│ └── client.json -└── .env # Environment variables (not tracked) +│ ├── forms__complaintInfo.json +│ ├── forms__newFileReview.json +│ ├── project_tasks.json +│ ├── project_type_pahe_list.json +│ ├── project_team.json +│ └── ... +├── .env # Environment variables (not tracked) +├── .gcloudignore # Files ignored by Google Cloud Build +├── .gitignore # Files ignored by Git +├── cloudbuild.yaml # Google Cloud Build configuration +├── Dockerfile # Containerization configuration +├── deploy.sh # Deployment script +├── main.tf # Terraform infrastructure as code +├── firestore.rules # Firestore security rules +├── column_mapping.json # Column mapping configuration (unused) +├── DEPLOY.md # Deployment instructions +├── CLAUDE.md # Development guidance +└── utils.py # Utility functions for user profile management ``` ## Core Features @@ -48,6 +67,7 @@ rothbard/ - Server-side session management with 8-hour expiration - User profile management in Firestore - Role-based access control (admin-enabled users only) +- Admin interface for user management ### Case Management Dashboard - Real-time fetching of projects from Filevine API @@ -57,6 +77,9 @@ rothbard/ - Project numbers and incident dates - Contact information and project URLs - Responsive design using Tailwind CSS +- Configurable column visibility with local storage +- Pagination for large datasets +- Admin simulation mode to view other users' cases ### API Integration - OAuth 2.0 authentication with Filevine API @@ -66,6 +89,16 @@ rothbard/ - Individual project details - Client information - Project contacts + - Project tasks and forms + +### Admin Interface +- Comprehensive user management dashboard +- Enable/disable user access +- Grant/revoke admin privileges +- Reset user passwords +- Create new users +- View all user profiles +- Simulate case email access for testing ## Configuration @@ -78,11 +111,17 @@ Create a `.env` file with the following variables: FLASK_SECRET_KEY=your-secret-key-here # Firebase Configuration +# Choose ONE of these approaches: +# 1) Path to JSON creds file +GOOGLE_APPLICATION_CREDENTIALS=./rothbard-staging2-12345-firebase-adminsdk-fbsvc-7f95268383.json +# 2) Or inline JSON (escaped as single line) +# FIREBASE_SERVICE_ACCOUNT_JSON="{\"type\":\"service_account\",...}" + +# Front-end Firebase (public — safe to expose) FIREBASE_API_KEY=your-firebase-api-key FIREBASE_AUTH_DOMAIN=your-project.firebaseapp.com FIREBASE_PROJECT_ID=your-firebase-project-id FIREBASE_APP_ID=your-firebase-app-id -FIREBASE_SERVICE_ACCOUNT_JSON='{"type":"service_account",...}' # or set GOOGLE_APPLICATION_CREDENTIALS # Filevine API Configuration FILEVINE_CLIENT_ID=your-filevine-client-id @@ -97,8 +136,9 @@ FILEVINE_USER_ID=your-filevine-user-id 1. Create a Firebase project at https://console.firebase.google.com 2. Enable Authentication with Email/Password provider 3. Create a Firestore database -4. Generate a service account key and save as `rothbard-service-account.json` +4. Generate a service account key and save as `rothbard-staging2-12345-firebase-adminsdk-fbsvc-7f95268383.json` 5. Configure Authentication settings for your web app +6. Set up Firestore security rules (provided in `firestore.rules`) ### Filevine API Setup @@ -133,7 +173,7 @@ FILEVINE_USER_ID=your-filevine-user-id ``` 5. **Initialize Firebase** - - Place your service account JSON file at `rothbard-service-account.json` + - Place your service account JSON file at `rothbard-staging2-12345-firebase-adminsdk-fbsvc-7f95268383.json` - Or set the `GOOGLE_APPLICATION_CREDENTIALS` environment variable 6. **Run the application** @@ -158,6 +198,8 @@ FILEVINE_USER_ID=your-filevine-user-id 1. User profiles are stored in Firestore at `users/{uid}` 2. Enable users by setting `enabled: true` and providing a `caseEmail` 3. The `caseEmail` field determines which projects the user can access +4. Admins can access the admin interface at `/admin/users` to manage all users +5. Admins can simulate other users' case access by entering their email in the simulation field on the dashboard ## Development Tools @@ -169,9 +211,10 @@ python generate_sample.py ``` This will create JSON files in the `examples/` directory containing: -- Sample project lists -- Project contacts -- Client information +- Sample project forms (complaintInfo, newFileReview) +- Project tasks +- Project type phases +- Project team members These samples are useful for development and testing without hitting the live API. @@ -183,6 +226,8 @@ These samples are useful for development and testing without hitting the live AP - User access is controlled through Firestore profiles - Sensitive credentials are stored in environment variables - Filevine API tokens are properly scoped and managed +- Firestore security rules restrict access to user's own profile +- Google Cloud Build and deployment use secure practices ## Future Enhancements @@ -204,6 +249,8 @@ Planned improvements include: - **python-dotenv 1.0.1** - Environment variable management - **requests 2.32.3** - HTTP client for API calls - **itsdangerous 2.2.0** - Security utilities for Flask +- **gunicorn 23.0.0** - WSGI HTTP Server +- **pytz 2024.1** - Timezone library ## API Endpoints @@ -214,13 +261,21 @@ Planned improvements include: - `GET /logout` - Session termination - `GET /welcome` - User onboarding page - `GET /dashboard` - Main case dashboard (authenticated users only) +- `GET /admin/users` - Admin user management interface +- `GET /admin/users/` - Admin user detail view +- `POST /admin/users/update` - Update user information +- `POST /admin/users/create` - Create new user +- `POST /admin/users//reset-password` - Reset user password ### Filevine API Integration - Projects: `/fv-app/v2/Projects` - Project Details: `/fv-app/v2/Projects/{id}` -- Contacts: `/fv-app/v2/projects/{id}/contacts` +- Project Team: `/fv-app/v2/Projects/{id}/team` +- Project Tasks: `/fv-app/v2/Projects/{id}/tasks` +- Project Forms: `/fv-app/v2/Projects/{id}/Forms/{form}` +- Project Contacts: `/fv-app/v2/projects/{id}/contacts` - Client Info: `/fv-app/v2/contacts/{id}` ## License -This project is proprietary software for Rothbard Law Group. \ No newline at end of file +This project is proprietary software for Rothbard Law Group. diff --git a/app.py b/app.py index 1073c9d..a6c1edc 100644 --- a/app.py +++ b/app.py @@ -1,61 +1,20 @@ -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 +import config 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): diff --git a/config.py b/config.py new file mode 100644 index 0000000..4af5475 --- /dev/null +++ b/config.py @@ -0,0 +1,38 @@ +import os + +# --- 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", +} \ No newline at end of file diff --git a/sync.py b/sync.py index 2db433a..62a9497 100644 --- a/sync.py +++ b/sync.py @@ -40,7 +40,7 @@ def convert_to_pacific_time(date_str): pacific_time = utc_time.astimezone(pytz.timezone('America/Los_Angeles')) # Format as YYYY-MM-DD - return pacific_time.strftime('%Y-%m-%d') + return pacific_time.strftime('%d/%m/%Y') except (ValueError, AttributeError) as e: print(f"[WARN] Date conversion failed for '{date_str}': {e}") return '' diff --git a/templates/_expander.html b/templates/_expander.html new file mode 100644 index 0000000..a8c5dea --- /dev/null +++ b/templates/_expander.html @@ -0,0 +1,6 @@ +{% macro expander() %} +
+ {{ caller() }} +
+{% endmacro %} diff --git a/templates/dashboard.html b/templates/dashboard.html index b891772..8aad4ee 100644 --- a/templates/dashboard.html +++ b/templates/dashboard.html @@ -2,34 +2,29 @@ {% block content %}

Projects for {{ case_email }}

- + {% set profile = get_user_profile(session.uid) %} {% if profile.is_admin %}
- +
- {% endif %} - + {% endif %} +
-
-
+

Configure Visible Columns

@@ -39,392 +34,592 @@
- +
- +
- +
+ {% from "_expander.html" import expander %}
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {% for r in rows %} - - - - - - - - - - - -
Matter NumClient / PropertyMatter DescriptionDefendant 1Matter OpenPractice AreaNotice TypeCase NumberPremises AddressPremises CityAssigned AttorneyPrimary ContactSecondary ParalegalDocumentsMatter StageCompleted TasksPending TasksNotice Service DateNotice Expir. DateDate Case FiledDaily Rent DamagesDefault DateDefault Entered OnMotions:Demurrer Hearing DateMotion To Strike Hearing DateMotion to Quash Hearing DateOther Motion Hearing DateMSC DateMSC TimeMSC AddressMSC Div/ Dept/ RoomTrial DateTrial TimeTrial AddressTrial Div/ Dept/ RoomFinal Result of Trial/ MSCDate of SettlementFinal Obligation Under the StipDef's Comply with the Stip?Judgment DateWrit Issued DateScheduled LockoutOppose Stays?Premises Safety or Access IssuesMatter Gate or Entry CodeDate Possession RecoveredAttorney's FeesCosts
{{ r.number }}{{ r.client }}{{ r.matter_description }}{{ r.defendant_1 }}{{ r.matter_open }}{{ r.notice_type }}{{ r.case_number }}{{ r.premises_address }}{{ r.premises_city }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {% for r in rows %} + + + + + + + + + + + + + - + - + - - - +
Matter NumClient / PropertyMatter DescriptionDefendant 1Matter OpenPractice AreaNotice TypeCase NumberPremises AddressPremises CityAssigned AttorneyPrimary ContactSecondary ParalegalDocumentsMatter StageCompleted TasksPending TasksNotice Service DateNotice Expir. DateDate Case FiledDaily Rent DamagesDefault DateDefault Entered OnMotions:Demurrer Hearing Date Motion To Strike Hearing DateMotion to Quash Hearing DateOther Motion Hearing DateMSC DateMSC TimeMSC AddressMSC Div/ Dept/ RoomTrial DateTrial TimeTrial AddressTrial Div/ Dept/ Room Final Result of Trial/ MSCDate of Settlement Final Obligation Under the StipDef's Comply with the Stip?Judgment DateWrit Issued DateScheduled LockoutOppose Stays?Premises Safety or Access IssuesMatter Gate or Entry CodeDate Possession RecoveredAttorney's FeesCosts
+ + + {{ r.number }} + + {% call expander() %} + {{ r.client }} + {% endcall %} + + {% call expander() %} + {{ r.matter_description }} + {% endcall %} + + {% call expander() %} + {{ r.defendant_1 }} + {% endcall %} + + {% call expander() %} + {{ r.matter_open }} + {% endcall %} + + {% call expander() %} + {% endcall %} + + {% call expander() %} + {{ r.notice_type }} + {% endcall %} + + {% call expander() %} + {{ r.case_number }} + {% endcall %} + + {% call expander() %} + {{ r.premises_address }} + {% endcall %} + + {% call expander() %} + {{ r.premises_city }} + {% endcall %} + + + {% call expander() %} {{ r.responsible_attorney }} - + {% endcall %} + + {% call expander() %} {{ r.staff_person }} - + {% endcall %} + + {% call expander() %} {{ r.staff_person_2 }} - - {% if r.documents_url %} - Documents - {% endif %} {{ r.phase_name }} - {% if r.completed_tasks %} -
- - - - - - - - - {% for x in r.completed_tasks[:2] %} - - - - - {% endfor %} - -
TaskCompleted
{{ x.description }}{{ x.completed }}
- {% if r.completed_tasks|length > 2 %} - + {% endcall %} +
+ + {% call expander() %} + {% endcall %} + {% if r.documents_url %} + Documents {% endif %} - - -
-
-
-

Completed Tasks

- -
- + + + - + + +
+ {% call expander() %} + {{ r.phase_name }} + {% endcall %} + + {% call expander() %} + {% if r.completed_tasks %} +
+ - - - + + + - {% for x in r.completed_tasks %} - - - + {% for x in r.completed_tasks[:2] %} + + + {% endfor %}
Task DescriptionCompleted Date
TaskCompleted
{{ x.description }}{{ x.completed }}
{{ x.description }}{{ x.completed }}
+ {% if r.completed_tasks|length > 2 %} + + {% endif %}
- - {% endif %} -
- {% if r.pending_tasks %} -
- - - - - - - - {% for x in r.pending_tasks[:2] %} - - - - {% endfor %} - -
Task
{{ x.description }}
- {% if r.pending_tasks|length > 2 %} - - {% endif %} -
- -
-
-
-

Pending Tasks

- + +
+
+
+

Completed Tasks

+ +
+ + + + + + + + + {% for x in r.completed_tasks %} + + + + + {% endfor %} + +
Task DescriptionCompleted Date
{{ x.description }}{{ x.completed }}
- + + {% endif %} + {% endcall %} + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {% else %} - - - - {% endfor %} - -
+ + {% call expander() %} + {% if r.pending_tasks %} +
+ - - + + - {% for x in r.pending_tasks %} - - + {% for x in r.pending_tasks[:2] %} + + {% endfor %}
Task Description
Task
{{ x.description }}
{{ x.description }}
+ {% if r.pending_tasks|length > 2 %} + + {% endif %}
- - {% endif %} -
{{ r.notice_service_date }}{{ r.notice_expiration_date }}{{ r.case_field_date }}{% if r.daily_rent_damages %}${{ "{:,.2f}".format(r.daily_rent_damages) }}{% endif %}{{ r.default_date }}{{ r.default_entered_on_date }}{{ r.demurrer_hearing_date }}{{ r.motion_to_strike_hearing_date }}{{ r.motion_to_quash_hearing_date }}{{ r.other_motion_hearing_date }}{{ r.msc_date }}{{ r.msc_time }}{{ r.msc_address }}{{ r.msc_div_dept_room }}{{ r.trial_date }}{{ r.trial_time }}{{ r.trial_address }}{{ r.trial_div_dept_room }}{{ r.final_result }}{{ r.date_of_settlement }}{{ r.final_obligation }}{{ r.def_comply_stip }}{{ r.judgment_date }}{{ r.writ_issued_date }}{{ r.scheduled_lockout }}{{ r.oppose_stays }}{{ r.premises_safety }}{{ r.matter_gate_code }}{{ r.date_possession_recovered }}{{ r.attorney_fees }}{{ r.costs }}
No matching projects found.
-
- -{% include '_pagination.html' %} +
+
+
+

Pending Tasks

+ +
+ + + + + + + + {% for x in r.pending_tasks %} + + + + {% endfor %} + +
Task Description
{{ x.description }}
+
+
+ {% endif %} + {% endcall %} +
+ {% call expander() %} + {{ r.notice_service_date }} + {% endcall %} + + {% call expander() %} + {{ r.notice_expiration_date }} + {% endcall %} + - +