Phase 1C: Frontend unified contact UI
This commit is contained in:
@@ -13,9 +13,6 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
# ── Core system permissions ──
|
||||
CORE_PERMISSIONS: list[dict[str, str]] = [
|
||||
{"key": "companies:read", "label": "Companies: Read", "category": "core", "module": "companies"},
|
||||
{"key": "companies:write", "label": "Companies: Write", "category": "core", "module": "companies"},
|
||||
{"key": "companies:delete", "label": "Companies: Delete", "category": "core", "module": "companies"},
|
||||
{"key": "contacts:read", "label": "Contacts: Read", "category": "core", "module": "contacts"},
|
||||
{"key": "contacts:write", "label": "Contacts: Write", "category": "core", "module": "contacts"},
|
||||
{"key": "contacts:delete", "label": "Contacts: Delete", "category": "core", "module": "contacts"},
|
||||
@@ -61,21 +58,12 @@ CORE_PERMISSIONS: list[dict[str, str]] = [
|
||||
|
||||
# ── Core field definitions for field-level permissions ──
|
||||
CORE_FIELD_DEFINITIONS: list[dict[str, str]] = [
|
||||
{"module": "companies", "field": "name", "label": "Name", "sensitivity": "normal"},
|
||||
{"module": "companies", "field": "account_number", "label": "Account Number", "sensitivity": "normal"},
|
||||
{"module": "companies", "field": "industry", "label": "Industry", "sensitivity": "normal"},
|
||||
{"module": "companies", "field": "phone", "label": "Phone", "sensitivity": "normal"},
|
||||
{"module": "companies", "field": "email", "label": "Email", "sensitivity": "normal"},
|
||||
{"module": "companies", "field": "website", "label": "Website", "sensitivity": "normal"},
|
||||
{"module": "companies", "field": "description", "label": "Description", "sensitivity": "normal"},
|
||||
{"module": "contacts", "field": "first_name", "label": "First Name", "sensitivity": "normal"},
|
||||
{"module": "contacts", "field": "last_name", "label": "Last Name", "sensitivity": "normal"},
|
||||
{"module": "contacts", "field": "email", "label": "Email", "sensitivity": "normal"},
|
||||
{"module": "contacts", "field": "phone", "label": "Phone", "sensitivity": "normal"},
|
||||
{"module": "contacts", "field": "mobile", "label": "Mobile", "sensitivity": "sensitive"},
|
||||
{"module": "contacts", "field": "position", "label": "Position", "sensitivity": "normal"},
|
||||
{"module": "contacts", "field": "department", "label": "Department", "sensitivity": "normal"},
|
||||
{"module": "contacts", "field": "linkedin_url", "label": "LinkedIn URL", "sensitivity": "sensitive"},
|
||||
{"module": "contacts", "field": "firstname", "label": "First Name", "sensitivity": "normal"},
|
||||
{"module": "contacts", "field": "surname", "label": "Last Name", "sensitivity": "normal"},
|
||||
{"module": "contacts", "field": "email_1", "label": "Email", "sensitivity": "normal"},
|
||||
{"module": "contacts", "field": "phone_1", "label": "Phone", "sensitivity": "normal"},
|
||||
{"module": "contacts", "field": "mobilephone", "label": "Mobile", "sensitivity": "sensitive"},
|
||||
{"module": "contacts", "field": "function", "label": "Position", "sensitivity": "normal"},
|
||||
{"module": "contacts", "field": "notes", "label": "Notes", "sensitivity": "sensitive"},
|
||||
{"module": "users", "field": "email", "label": "Email", "sensitivity": "normal"},
|
||||
{"module": "users", "field": "name", "label": "Name", "sensitivity": "normal"},
|
||||
|
||||
@@ -5,7 +5,7 @@ Architecture:
|
||||
- Redis cache: resolved:{user_id}:{tenant_id} with 5-min TTL.
|
||||
- Permission-version stamping for immediate invalidation.
|
||||
- Resolution: allowed = (role ∪ groups), denied = (role.denied ∪ groups.denied), resolved = allowed − denied.
|
||||
- Wildcards: companies:*, *:read, *:* (bare * is forbidden).
|
||||
- Wildcards: contacts:*, *:read, *:* (bare * is forbidden).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -35,8 +35,8 @@ def _matches_permission(granted: str, required: str) -> bool:
|
||||
"""Check if a granted permission matches the required permission.
|
||||
|
||||
Supports wildcards:
|
||||
- companies:read → exact match
|
||||
- companies:* → all actions for companies
|
||||
- contacts:read → exact match
|
||||
- contacts:* → all actions for contacts
|
||||
- *:read → all modules, read action
|
||||
- *:* → everything (superadmin)
|
||||
"""
|
||||
@@ -67,9 +67,9 @@ def _normalize_permissions(permissions: Any) -> set[str]:
|
||||
"""Normalize permissions from JSONB to a set of strings.
|
||||
|
||||
Supports formats:
|
||||
- list[str]: ["companies:read", "contacts:write"]
|
||||
- dict[str, bool]: {"companies:read": true, "contacts:write": false}
|
||||
- dict[str, dict]: {"companies": {"read": true, "write": false}}
|
||||
- list[str]: ["contacts:read", "contacts:write"]
|
||||
- dict[str, bool]: {"contacts:read": true, "contacts:write": false}
|
||||
- dict[str, dict]: {"contacts": {"read": true, "write": false}}
|
||||
"""
|
||||
result: set[str] = set()
|
||||
if isinstance(permissions, list):
|
||||
@@ -156,7 +156,7 @@ async def resolve_permissions(
|
||||
if legacy_role == "admin":
|
||||
allowed.add("*:*")
|
||||
elif legacy_role == "editor":
|
||||
allowed |= {"companies:read", "companies:write", "contacts:read", "contacts:write",
|
||||
allowed |= {"contacts:read", "contacts:write", "contacts:read", "contacts:write",
|
||||
"users:read", "roles:read", "audit:read", "attachments:read",
|
||||
"attachments:write", "workflows:read", "workflows:write",
|
||||
"sequences:read", "sequences:write", "addresses:read", "addresses:write",
|
||||
@@ -164,7 +164,7 @@ async def resolve_permissions(
|
||||
"notifications:read", "notifications:write", "import_export:read",
|
||||
"import_export:write"}
|
||||
elif legacy_role == "viewer":
|
||||
allowed |= {"companies:read", "contacts:read", "users:read", "roles:read",
|
||||
allowed |= {"contacts:read", "contacts:read", "users:read", "roles:read",
|
||||
"audit:read", "attachments:read", "workflows:read", "sequences:read",
|
||||
"addresses:read", "taxes:read", "currencies:read",
|
||||
"notifications:read", "import_export:read"}
|
||||
@@ -265,7 +265,7 @@ def check_permission(resolved: dict[str, Any], required: str) -> bool:
|
||||
|
||||
Args:
|
||||
resolved: result from get_cached_permissions or resolve_permissions
|
||||
required: permission string like "companies:read"
|
||||
required: permission string like "contacts:read"
|
||||
"""
|
||||
if resolved.get("is_system_admin"):
|
||||
return True
|
||||
|
||||
@@ -43,7 +43,6 @@ from app.plugins.builtins.unified_search.jobs import (
|
||||
index_mails,
|
||||
index_file,
|
||||
index_contact,
|
||||
index_company,
|
||||
index_event,
|
||||
reindex,
|
||||
embedding_batch,
|
||||
@@ -57,7 +56,6 @@ class WorkerSettings:
|
||||
index_mails,
|
||||
index_file,
|
||||
index_contact,
|
||||
index_company,
|
||||
index_event,
|
||||
reindex,
|
||||
embedding_batch,
|
||||
|
||||
Reference in New Issue
Block a user