feat: folder permissions (ACLs) - share folders with users/groups, inherit to subfolders, permission dialog UI

This commit is contained in:
Agent Zero
2026-07-28 23:58:19 +02:00
parent 784a771039
commit cc021cda99
12 changed files with 1185 additions and 4 deletions
+31 -3
View File
@@ -27,11 +27,24 @@ def _serialize_folder(f: ContactFolder, contact_count: int = 0) -> dict:
async def list_folders(
db: AsyncSession, tenant_id: uuid.UUID, user_id: uuid.UUID
) -> list[dict]:
"""List all folders for a user (flat list, frontend builds tree)."""
"""List all folders visible to a user (owned + shared via ACLs).
Returns a flat list; the frontend builds the tree.
Each folder includes ``access_level`` (owner|admin|write|read) and
``is_shared`` flag.
"""
from app.services.contact_folder_permission_service import get_visible_folder_ids
# Get visible folder IDs and access map
visible_ids, access_map = await get_visible_folder_ids(db, tenant_id, user_id)
if not visible_ids:
return []
result = await db.execute(
select(ContactFolder)
.where(ContactFolder.tenant_id == tenant_id)
.where(ContactFolder.user_id == user_id)
.where(ContactFolder.id.in_(visible_ids))
.where(ContactFolder.deleted_at.is_(None))
.order_by(ContactFolder.parent_id, ContactFolder.sort_order, ContactFolder.name)
)
@@ -50,8 +63,23 @@ async def list_folders(
for fid, cnt in count_result:
counts[fid] = cnt
# Check which folders have permissions (are shared)
from app.models.contact_folder_permission import ContactFolderPermission
shared_q = await db.execute(
select(ContactFolderPermission.folder_id)
.where(ContactFolderPermission.folder_id.in_(folder_ids))
.where(ContactFolderPermission.tenant_id == tenant_id)
.group_by(ContactFolderPermission.folder_id)
)
shared_ids = {row[0] for row in shared_q}
return [
{**_serialize_folder(f), "contact_count": counts.get(f.id, 0)}
{
**_serialize_folder(f),
"contact_count": counts.get(f.id, 0),
"access_level": access_map.get(f.id, "none"),
"is_shared": f.id in shared_ids,
}
for f in folders
]