diff --git a/admin.py b/admin.py index b72d615..9c8341c 100644 --- a/admin.py +++ b/admin.py @@ -45,6 +45,7 @@ def register_admin_routes(app): "uid": doc.id, "user_email": user_data.get("user_email", ""), "case_email": user_data.get("case_email", ""), + "case_domain_email": user_data.get("case_domain_email", ""), "enabled": bool(user_data.get("enabled", False)), "is_admin": bool(user_data.get("is_admin", False)) }) @@ -78,6 +79,7 @@ def register_admin_routes(app): "uid": uid, "user_email": user_data.get("user_email", ""), "case_email": user_data.get("case_email", ""), + "case_domain_email": user_data.get("case_domain_email", ""), "enabled": bool(user_data.get("enabled", False)), "is_admin": bool(user_data.get("is_admin", False)) } @@ -129,7 +131,8 @@ def register_admin_routes(app): # Only update fields that can be changed, excluding is_admin update_data = { "enabled": data.get("enabled", False), - "case_email": data.get("case_email", "") + "case_email": data.get("case_email", ""), + "case_domain_email": data.get("case_domain_email", "") } # Never allow changing is_admin field during updates - admin status can only be set during creation user_ref.update(update_data) @@ -172,6 +175,7 @@ def register_admin_routes(app): user_ref.set({ "user_email": user_email, "case_email": request.form.get("case_email", ""), + "case_domain_email": request.form.get("case_domain_email", ""), "enabled": bool(request.form.get("enabled", False)), "is_admin": bool(request.form.get("is_admin", False)) }) diff --git a/app.py b/app.py index 7b256cc..ed4f12a 100644 --- a/app.py +++ b/app.py @@ -34,7 +34,7 @@ def projects_for(profile, case_email_match, per_page, offset): Filter projects based on user profile and case_email query string argument. Args: - profile (dict): User profile containing 'enabled', 'is_admin', and 'case_email' fields + profile (dict): User profile containing 'enabled', 'is_admin', 'case_email', and 'case_domain_email' fields case_email_match (str): Case email from query string argument, or None Returns: @@ -50,40 +50,52 @@ def projects_for(profile, case_email_match, per_page, offset): cnt = 0 if is_admin: if case_email_match: - projects_ref = db.collection("projects").where("viewing_emails", "array_contains", case_email_match.lower()) - # Get filtered document IDs using client-side filtering with partial match - z = db.collection("projects").select(["viewing_emails", "matter_description", "id"]) - # Get all matching documents with their IDs and descriptions - matching_docs = [(x.id, x.to_dict().get('matter_description', '')) for x in z.stream() - if any(case_email_match.lower() in email.lower() for email in x.to_dict().get('viewing_emails', []))] - count = len(matching_docs) + case_email_match_lower = case_email_match.lower().strip() - # Sort by matter_description - matching_docs.sort(key=lambda x: x[1].lower()) - - # Extract just the IDs after sorting - filtered_ids = [doc_id for doc_id, _ in matching_docs] - - # Apply client-side pagination - filtered_ids = filtered_ids[offset:offset + per_page] - - print(f"Filtered document IDs (partial match, sorted, paginated): {filtered_ids}") - projects_ref = db.collection("projects") - projects = [] - for doc_id in filtered_ids: - doc = projects_ref.document(doc_id).get() - if doc.exists: + # Check if case_email_match is a valid email address (contains @) + if '@' in case_email_match_lower and not case_email_match_lower.startswith('@'): + # If it's a complete email address, filter by exact match in viewing_emails + projects_ref = db.collection("projects").where("viewing_emails", "array_contains", case_email_match_lower) + cnt = int(projects_ref.count().get()[0][0].value) + projects = [] + for doc in projects_ref.order_by("matter_description").limit(per_page).offset(offset).stream(): projects.append(doc.to_dict()) - - return (projects, count) + return (projects, cnt) + else: + # If no @ sign, treat as domain search + # Also handle cases like "@gmail.com" by extracting the domain + domain_search = case_email_match_lower + if domain_search.startswith('@'): + domain_search = domain_search[1:] # Remove the @ sign + + # Filter by domain match in viewing_emails + projects_ref = db.collection("projects").where("viewing_domains", "array_contains", domain_search) + print("HERE domain", domain_search) + cnt = int(projects_ref.count().get()[0][0].value) + + projects = [] + for doc in projects_ref.order_by("matter_description").limit(per_page).offset(offset).stream(): + projects.append(doc.to_dict()) + return (projects, cnt) else: projects_ref = db.collection("projects") else: - if not profile.get("case_email"): + # For non-admin users, check if they have domain email or specific case email + case_domain_email = profile.get("case_domain_email", "") + case_email = profile.get("case_email", "") + + if case_domain_email: + # Use exact match on viewing_domains field + domain_lower = case_domain_email.lower() + projects_ref = db.collection("projects").where("viewing_domains", "array_contains", domain_lower) + elif case_email: + # Use the original logic for specific case email match + projects_ref = db.collection("projects").where("viewing_emails", "array_contains", case_email.lower()) + else: return ([], 0) - projects_ref = db.collection("projects").where("viewing_emails", "array_contains", profile.get("case_email").to_lower()) + cnt = int(projects_ref.count().get()[0][0].value) projects = [] for doc in projects_ref.order_by("matter_description").limit(per_page).offset(offset).stream(): @@ -127,7 +139,7 @@ def index(): if not uid: return redirect(url_for("login")) profile = get_user_profile(uid) - if profile.get("enabled") and profile.get("case_email"): + if profile.get("enabled") and (profile.get("case_email") or profile.get("case_domain_email")): return redirect(url_for("dashboard")) return redirect(url_for("welcome")) @@ -197,7 +209,7 @@ def dashboard(page=1): case_email_match = None if is_admin and request.args.get('case_email'): case_email_match = request.args.get('case_email') - if not is_admin and not profile.get('case_email'): + if not is_admin and (not profile.get('case_email') and not profile.get('case_domain_email')): return redirect(url_for("welcome")) paginated_rows, total_projects = projects_for(profile, case_email_match, per_page, offset) diff --git a/models/project_model.py b/models/project_model.py index 768679e..80a47aa 100644 --- a/models/project_model.py +++ b/models/project_model.py @@ -69,7 +69,8 @@ class ProjectModel: project_name: str = "", project_url: str = "", property_contacts: Dict[str, Any] = None, - viewing_emails: List[str] = None + viewing_emails: List[str] = None, + viewing_domains: List[str] = None ): self.client = client @@ -127,6 +128,7 @@ class ProjectModel: self.project_url = project_url self.property_contacts = property_contacts or {} self.viewing_emails = viewing_emails or [] + self.viewing_domains = viewing_domains or [] def to_dict(self) -> Dict[str, Any]: """Convert the ProjectModel to a dictionary for Firestore storage.""" @@ -185,7 +187,8 @@ class ProjectModel: "ProjectName": self.project_name, "ProjectUrl": self.project_url, "property_contacts": self.property_contacts, - "viewing_emails": self.viewing_emails + "viewing_emails": self.viewing_emails, + "viewing_domains": self.viewing_domains } @classmethod @@ -246,5 +249,6 @@ class ProjectModel: project_name=data.get("ProjectName", ""), project_url=data.get("ProjectUrl", ""), property_contacts=data.get("property_contacts", {}), - viewing_emails=data.get("viewing_emails", []) + viewing_emails=data.get("viewing_emails", []), + viewing_domains=data.get("viewing_domains", []) ) diff --git a/sync.py b/sync.py index b40bbbe..402d378 100644 --- a/sync.py +++ b/sync.py @@ -29,6 +29,28 @@ def convert_to_pacific_time(date_str): if not date_str: return '' + +def extract_domains_from_emails(emails: List[str]) -> List[str]: + """Extract unique domains from a list of email addresses. + + Args: + emails (List[str]): List of email addresses + + Returns: + List[str]: List of unique domains extracted from the emails + """ + if not emails: + return [] + + domains = set() + for email in emails: + if email and '@' in email: + # Extract domain part after @ + domain = email.split('@')[1].lower() + domains.add(domain) + + return sorted(list(domains)) + try: # Parse the UTC datetime utc_time = datetime.fromisoformat(date_str.replace('Z', '+00:00')) @@ -241,7 +263,8 @@ def process_project(index: int, total: int, project_data: dict, client: Filevine project_name=p.get("projectName") or detail.get("projectName"), project_url=p.get("projectUrl") or detail.get("projectUrl"), #property_contacts=property_contacts - viewing_emails = valid_property_managers + viewing_emails = valid_property_managers, + viewing_domains = extract_domains_from_emails(valid_property_managers) ) # Store the results in Firestore from app import db # Import db from app diff --git a/templates/admin_user_create.html b/templates/admin_user_create.html index d99b2a5..41fd3a9 100644 --- a/templates/admin_user_create.html +++ b/templates/admin_user_create.html @@ -40,6 +40,14 @@
The email address used for project access.
+All cases with property contacts in this domain will be viewable to the user
+The email address used for project access.
+All cases with this email will be viewable by the user
+ + +All cases with property contacts in this domain will be viewable to the user