supports lookup by domains
This commit is contained in:
6
admin.py
6
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))
|
||||
})
|
||||
|
||||
66
app.py
66
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")
|
||||
# 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_id in filtered_ids:
|
||||
doc = projects_ref.document(doc_id).get()
|
||||
if doc.exists:
|
||||
for doc in projects_ref.order_by("matter_description").limit(per_page).offset(offset).stream():
|
||||
projects.append(doc.to_dict())
|
||||
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
|
||||
|
||||
return (projects, count)
|
||||
# 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)
|
||||
|
||||
|
||||
@@ -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", [])
|
||||
)
|
||||
|
||||
25
sync.py
25
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
|
||||
|
||||
@@ -40,6 +40,14 @@
|
||||
<p class="mt-1 text-sm text-slate-500">The email address used for project access.</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="case_domain_email" class="block text-sm font-medium text-slate-700">Case Domain Email</label>
|
||||
<input type="text" id="case_domain_email" name="case_domain_email"
|
||||
value=""
|
||||
class="mt-1 block w-full px-3 py-2 border border-slate-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500">
|
||||
<p class="mt-1 text-sm text-slate-500">All cases with property contacts in this domain will be viewable to the user</p>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end space-x-3 pt-4">
|
||||
<button type="button" onclick="window.location.href='/admin/users'"
|
||||
class="px-4 py-2 text-sm font-medium text-slate-700 bg-gray-100 hover:bg-gray-200 rounded-md transition-colors">
|
||||
|
||||
@@ -42,7 +42,15 @@
|
||||
<input type="email" id="case_email" name="case_email"
|
||||
value="{{ user.case_email }}"
|
||||
class="mt-1 block w-full px-3 py-2 border border-slate-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500">
|
||||
<p class="mt-1 text-sm text-slate-500">The email address used for project access.</p>
|
||||
<p class="mt-1 text-sm text-slate-500">All cases with this email will be viewable by the user</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="case_domain_email" class="block text-sm font-medium text-slate-700">Case Domain Email</label>
|
||||
<input type="text" id="case_domain_email" name="case_domain_email"
|
||||
value="{{ user.case_domain_email }}"
|
||||
class="mt-1 block w-full px-3 py-2 border border-slate-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500">
|
||||
<p class="mt-1 text-sm text-slate-500">All cases with property contacts in this domain will be viewable to the user</p>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end space-x-3 pt-4">
|
||||
@@ -66,7 +74,8 @@ document.getElementById('userForm').addEventListener('submit', function(e) {
|
||||
const userData = {
|
||||
uid: '{{ user.uid }}',
|
||||
enabled: formData.get('enabled') === 'on',
|
||||
case_email: formData.get('case_email')
|
||||
case_email: formData.get('case_email'),
|
||||
case_domain_email: formData.get('case_domain_email')
|
||||
};
|
||||
|
||||
fetch('/admin/users/update', {
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block content %}
|
||||
<div class="h-full flex flex-col" x-data="columnConfig()">
|
||||
{% if case_email %}
|
||||
<h1 class="text-xl font-semibold mb-4">Projects for {{ case_email }}</h1>
|
||||
{% else %}
|
||||
<h1 class="text-xl font-semibold mb-4">All projects</h1>
|
||||
|
||||
{% endif %}
|
||||
<div class="flex justify-between">
|
||||
|
||||
{% set profile = get_user_profile(session.uid) %}
|
||||
|
||||
9
utils.py
9
utils.py
@@ -19,18 +19,21 @@ def get_user_profile(uid: str):
|
||||
"enabled": False,
|
||||
"is_admin": False,
|
||||
"user_email": user_email,
|
||||
"case_email": user_email
|
||||
"case_email": user_email,
|
||||
"case_domain_email": ""
|
||||
}, merge=True)
|
||||
return {
|
||||
"enabled": False,
|
||||
"is_admin": False,
|
||||
"user_email": user_email,
|
||||
"case_email": user_email
|
||||
"case_email": user_email,
|
||||
"case_domain_email": ""
|
||||
}
|
||||
data = snap.to_dict() or {}
|
||||
return {
|
||||
"enabled": bool(data.get("enabled", False)),
|
||||
"is_admin": bool(data.get("is_admin", False)),
|
||||
"user_email": data.get("user_email"),
|
||||
"case_email": data.get("case_email")
|
||||
"case_email": data.get("case_email"),
|
||||
"case_domain_email": data.get("case_domain_email", "")
|
||||
}
|
||||
Reference in New Issue
Block a user