Reparaturplan Fixes: Widget workspace_id check, total bug, context is_visible, permissions, fallbacks
Check Cross-Plugin Imports / check (push) Has been cancelled

Backend:
- Widget total: 0 bug fixed (now returns len(widgets))
- Widget update/delete: now verifies workspace_id + tenant_id (was only tenant_id)
- Workspace context: returns all modules with is_visible flag (was only visible modules)
- is_workspace_manager() removed (Plan 4.2: no manager checks)
- seed_default_workspace: removed hardcoded modules (Plan 4.7: no hardcoded tiles)
- Workspace permissions registered in CORE_PERMISSIONS (Plan 2.3)

Frontend:
- Permission fallback removed: Sidebar/TopBar show nothing while loading (Plan 2.4)
- workspaceStore isModuleVisible: fail-closed when isSystemAdmin undefined
- WorkspaceManager: AVAILABLE_MODULES replaced with dynamic core+plugin items (Plan 4.4)

Tests:
- 17 backend tests (removed is_workspace_manager test, adapted widget/context tests)
- 13 frontend tests (added undefined-isSystemAdmin test, adapted visibility tests)
This commit is contained in:
Agent Zero
2026-08-03 12:44:02 +02:00
parent 9f41da3d10
commit 3cbf92191e
9 changed files with 69 additions and 283 deletions
+12 -40
View File
@@ -382,11 +382,10 @@ async def get_workspace_context(
if wu is None:
return None # User not assigned — caller can check is_system_admin
# Get visible modules
# Get all modules (including hidden) — frontend needs is_visible flag
mod_q = select(WorkspaceModule).where(
WorkspaceModule.workspace_id == workspace_id,
WorkspaceModule.tenant_id == tenant_id,
WorkspaceModule.is_visible == True, # noqa: E712
).order_by(WorkspaceModule.menu_order)
mod_result = await db.execute(mod_q)
modules = mod_result.scalars().all()
@@ -407,6 +406,7 @@ async def get_workspace_context(
"modules": [
{
"module_key": m.module_key,
"is_visible": m.is_visible,
"menu_order": m.menu_order,
"config": m.config or {},
}
@@ -485,15 +485,16 @@ async def create_widget(
async def update_widget(
db: AsyncSession, tenant_id: uuid.UUID, widget_id: uuid.UUID,
db: AsyncSession, tenant_id: uuid.UUID, workspace_id: uuid.UUID, widget_id: uuid.UUID,
position_x: int | None = None, position_y: int | None = None,
width: int | None = None, height: int | None = None,
config: dict | None = None,
) -> dict[str, Any] | None:
"""Update a widget."""
"""Update a widget. Verifies workspace_id and tenant_id."""
q = select(WorkspaceWidget).where(
WorkspaceWidget.id == widget_id,
WorkspaceWidget.tenant_id == tenant_id,
WorkspaceWidget.workspace_id == workspace_id,
)
result = await db.execute(q)
w = result.scalar_one_or_none()
@@ -524,12 +525,13 @@ async def update_widget(
async def delete_widget(
db: AsyncSession, tenant_id: uuid.UUID, widget_id: uuid.UUID
db: AsyncSession, tenant_id: uuid.UUID, workspace_id: uuid.UUID, widget_id: uuid.UUID
) -> bool:
"""Delete a widget."""
"""Delete a widget. Verifies workspace_id and tenant_id."""
q = select(WorkspaceWidget).where(
WorkspaceWidget.id == widget_id,
WorkspaceWidget.tenant_id == tenant_id,
WorkspaceWidget.workspace_id == workspace_id,
)
result = await db.execute(q)
w = result.scalar_one_or_none()
@@ -540,22 +542,6 @@ async def delete_widget(
return True
# ─── Manager Role Check ───────────────────────────────────────
async def is_workspace_manager(
db: AsyncSession, tenant_id: uuid.UUID, workspace_id: uuid.UUID, user_id: uuid.UUID
) -> bool:
"""Check if a user is a manager of a workspace (or system admin)."""
q = select(WorkspaceUser).where(
WorkspaceUser.workspace_id == workspace_id,
WorkspaceUser.user_id == user_id,
WorkspaceUser.tenant_id == tenant_id,
WorkspaceUser.role == "manager",
)
result = await db.execute(q)
return result.scalar_one_or_none() is not None
# ─── Cross-Tenant Validation ──────────────────────────────────
async def verify_user_same_tenant(
@@ -606,29 +592,15 @@ async def seed_default_workspace(
tenant_id=tenant_id,
workspace_id=ws.id,
user_id=user_id,
role="manager",
role="member",
is_default=True,
assigned_by=user_id,
)
db.add(wu)
# Add all standard modules visible by default
standard_modules = [
("dashboard", 0), ("contacts", 10), ("calendar", 20),
("mail", 30), ("tasks", 40), ("dms", 50),
("kommunikation", 60), ("reports", 70), ("automation", 80),
("tags", 90), ("settings", 100), ("audit", 110),
]
for key, order in standard_modules:
wm = WorkspaceModule(
tenant_id=tenant_id,
workspace_id=ws.id,
module_key=key,
is_visible=True,
menu_order=order,
config={},
)
db.add(wm)
# No hardcoded modules — workspace starts empty.
# Modules are configured by the admin via the workspace settings UI.
# If no modules are configured, all active+permitted modules remain visible (backward compatible).
await db.flush()
return _workspace_to_dict(ws, user_count=1)