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,
|
"uid": doc.id,
|
||||||
"user_email": user_data.get("user_email", ""),
|
"user_email": user_data.get("user_email", ""),
|
||||||
"case_email": user_data.get("case_email", ""),
|
"case_email": user_data.get("case_email", ""),
|
||||||
|
"case_domain_email": user_data.get("case_domain_email", ""),
|
||||||
"enabled": bool(user_data.get("enabled", False)),
|
"enabled": bool(user_data.get("enabled", False)),
|
||||||
"is_admin": bool(user_data.get("is_admin", False))
|
"is_admin": bool(user_data.get("is_admin", False))
|
||||||
})
|
})
|
||||||
@@ -78,6 +79,7 @@ def register_admin_routes(app):
|
|||||||
"uid": uid,
|
"uid": uid,
|
||||||
"user_email": user_data.get("user_email", ""),
|
"user_email": user_data.get("user_email", ""),
|
||||||
"case_email": user_data.get("case_email", ""),
|
"case_email": user_data.get("case_email", ""),
|
||||||
|
"case_domain_email": user_data.get("case_domain_email", ""),
|
||||||
"enabled": bool(user_data.get("enabled", False)),
|
"enabled": bool(user_data.get("enabled", False)),
|
||||||
"is_admin": bool(user_data.get("is_admin", 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
|
# Only update fields that can be changed, excluding is_admin
|
||||||
update_data = {
|
update_data = {
|
||||||
"enabled": data.get("enabled", False),
|
"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
|
# Never allow changing is_admin field during updates - admin status can only be set during creation
|
||||||
user_ref.update(update_data)
|
user_ref.update(update_data)
|
||||||
@@ -172,6 +175,7 @@ def register_admin_routes(app):
|
|||||||
user_ref.set({
|
user_ref.set({
|
||||||
"user_email": user_email,
|
"user_email": user_email,
|
||||||
"case_email": request.form.get("case_email", ""),
|
"case_email": request.form.get("case_email", ""),
|
||||||
|
"case_domain_email": request.form.get("case_domain_email", ""),
|
||||||
"enabled": bool(request.form.get("enabled", False)),
|
"enabled": bool(request.form.get("enabled", False)),
|
||||||
"is_admin": bool(request.form.get("is_admin", False))
|
"is_admin": bool(request.form.get("is_admin", False))
|
||||||
})
|
})
|
||||||
|
|||||||
68
app.py
68
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.
|
Filter projects based on user profile and case_email query string argument.
|
||||||
|
|
||||||
Args:
|
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
|
case_email_match (str): Case email from query string argument, or None
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -50,40 +50,52 @@ def projects_for(profile, case_email_match, per_page, offset):
|
|||||||
cnt = 0
|
cnt = 0
|
||||||
if is_admin:
|
if is_admin:
|
||||||
if case_email_match:
|
if case_email_match:
|
||||||
projects_ref = db.collection("projects").where("viewing_emails", "array_contains", case_email_match.lower())
|
case_email_match_lower = case_email_match.lower().strip()
|
||||||
# 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)
|
|
||||||
|
|
||||||
# Sort by matter_description
|
# Check if case_email_match is a valid email address (contains @)
|
||||||
matching_docs.sort(key=lambda x: x[1].lower())
|
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
|
||||||
# Extract just the IDs after sorting
|
projects_ref = db.collection("projects").where("viewing_emails", "array_contains", case_email_match_lower)
|
||||||
filtered_ids = [doc_id for doc_id, _ in matching_docs]
|
cnt = int(projects_ref.count().get()[0][0].value)
|
||||||
|
projects = []
|
||||||
# Apply client-side pagination
|
for doc in projects_ref.order_by("matter_description").limit(per_page).offset(offset).stream():
|
||||||
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:
|
|
||||||
projects.append(doc.to_dict())
|
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:
|
else:
|
||||||
projects_ref = db.collection("projects")
|
projects_ref = db.collection("projects")
|
||||||
|
|
||||||
else:
|
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)
|
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)
|
cnt = int(projects_ref.count().get()[0][0].value)
|
||||||
projects = []
|
projects = []
|
||||||
for doc in projects_ref.order_by("matter_description").limit(per_page).offset(offset).stream():
|
for doc in projects_ref.order_by("matter_description").limit(per_page).offset(offset).stream():
|
||||||
@@ -127,7 +139,7 @@ def index():
|
|||||||
if not uid:
|
if not uid:
|
||||||
return redirect(url_for("login"))
|
return redirect(url_for("login"))
|
||||||
profile = get_user_profile(uid)
|
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("dashboard"))
|
||||||
return redirect(url_for("welcome"))
|
return redirect(url_for("welcome"))
|
||||||
|
|
||||||
@@ -197,7 +209,7 @@ def dashboard(page=1):
|
|||||||
case_email_match = None
|
case_email_match = None
|
||||||
if is_admin and request.args.get('case_email'):
|
if is_admin and request.args.get('case_email'):
|
||||||
case_email_match = 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"))
|
return redirect(url_for("welcome"))
|
||||||
paginated_rows, total_projects = projects_for(profile, case_email_match, per_page, offset)
|
paginated_rows, total_projects = projects_for(profile, case_email_match, per_page, offset)
|
||||||
|
|
||||||
|
|||||||
@@ -69,7 +69,8 @@ class ProjectModel:
|
|||||||
project_name: str = "",
|
project_name: str = "",
|
||||||
project_url: str = "",
|
project_url: str = "",
|
||||||
property_contacts: Dict[str, Any] = None,
|
property_contacts: Dict[str, Any] = None,
|
||||||
viewing_emails: List[str] = None
|
viewing_emails: List[str] = None,
|
||||||
|
viewing_domains: List[str] = None
|
||||||
):
|
):
|
||||||
|
|
||||||
self.client = client
|
self.client = client
|
||||||
@@ -127,6 +128,7 @@ class ProjectModel:
|
|||||||
self.project_url = project_url
|
self.project_url = project_url
|
||||||
self.property_contacts = property_contacts or {}
|
self.property_contacts = property_contacts or {}
|
||||||
self.viewing_emails = viewing_emails or []
|
self.viewing_emails = viewing_emails or []
|
||||||
|
self.viewing_domains = viewing_domains or []
|
||||||
|
|
||||||
def to_dict(self) -> Dict[str, Any]:
|
def to_dict(self) -> Dict[str, Any]:
|
||||||
"""Convert the ProjectModel to a dictionary for Firestore storage."""
|
"""Convert the ProjectModel to a dictionary for Firestore storage."""
|
||||||
@@ -185,7 +187,8 @@ class ProjectModel:
|
|||||||
"ProjectName": self.project_name,
|
"ProjectName": self.project_name,
|
||||||
"ProjectUrl": self.project_url,
|
"ProjectUrl": self.project_url,
|
||||||
"property_contacts": self.property_contacts,
|
"property_contacts": self.property_contacts,
|
||||||
"viewing_emails": self.viewing_emails
|
"viewing_emails": self.viewing_emails,
|
||||||
|
"viewing_domains": self.viewing_domains
|
||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -246,5 +249,6 @@ class ProjectModel:
|
|||||||
project_name=data.get("ProjectName", ""),
|
project_name=data.get("ProjectName", ""),
|
||||||
project_url=data.get("ProjectUrl", ""),
|
project_url=data.get("ProjectUrl", ""),
|
||||||
property_contacts=data.get("property_contacts", {}),
|
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:
|
if not date_str:
|
||||||
return ''
|
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:
|
try:
|
||||||
# Parse the UTC datetime
|
# Parse the UTC datetime
|
||||||
utc_time = datetime.fromisoformat(date_str.replace('Z', '+00:00'))
|
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_name=p.get("projectName") or detail.get("projectName"),
|
||||||
project_url=p.get("projectUrl") or detail.get("projectUrl"),
|
project_url=p.get("projectUrl") or detail.get("projectUrl"),
|
||||||
#property_contacts=property_contacts
|
#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
|
# Store the results in Firestore
|
||||||
from app import db # Import db from app
|
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>
|
<p class="mt-1 text-sm text-slate-500">The email address used for project access.</p>
|
||||||
</div>
|
</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">
|
<div class="flex justify-end space-x-3 pt-4">
|
||||||
<button type="button" onclick="window.location.href='/admin/users'"
|
<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">
|
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"
|
<input type="email" id="case_email" name="case_email"
|
||||||
value="{{ user.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">
|
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>
|
||||||
|
|
||||||
<div class="flex justify-end space-x-3 pt-4">
|
<div class="flex justify-end space-x-3 pt-4">
|
||||||
@@ -66,7 +74,8 @@ document.getElementById('userForm').addEventListener('submit', function(e) {
|
|||||||
const userData = {
|
const userData = {
|
||||||
uid: '{{ user.uid }}',
|
uid: '{{ user.uid }}',
|
||||||
enabled: formData.get('enabled') === 'on',
|
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', {
|
fetch('/admin/users/update', {
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="h-full flex flex-col" x-data="columnConfig()">
|
<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>
|
<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">
|
<div class="flex justify-between">
|
||||||
|
|
||||||
{% set profile = get_user_profile(session.uid) %}
|
{% 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,
|
"enabled": False,
|
||||||
"is_admin": False,
|
"is_admin": False,
|
||||||
"user_email": user_email,
|
"user_email": user_email,
|
||||||
"case_email": user_email
|
"case_email": user_email,
|
||||||
|
"case_domain_email": ""
|
||||||
}, merge=True)
|
}, merge=True)
|
||||||
return {
|
return {
|
||||||
"enabled": False,
|
"enabled": False,
|
||||||
"is_admin": False,
|
"is_admin": False,
|
||||||
"user_email": user_email,
|
"user_email": user_email,
|
||||||
"case_email": user_email
|
"case_email": user_email,
|
||||||
|
"case_domain_email": ""
|
||||||
}
|
}
|
||||||
data = snap.to_dict() or {}
|
data = snap.to_dict() or {}
|
||||||
return {
|
return {
|
||||||
"enabled": bool(data.get("enabled", False)),
|
"enabled": bool(data.get("enabled", False)),
|
||||||
"is_admin": bool(data.get("is_admin", False)),
|
"is_admin": bool(data.get("is_admin", False)),
|
||||||
"user_email": data.get("user_email"),
|
"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