117 lines
3.5 KiB
Python
117 lines
3.5 KiB
Python
"""Workspace/tenant helpers for MSP mode foundations."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Optional
|
|
|
|
from sqlalchemy.orm import Query, Session
|
|
|
|
from app.models.domain import Domain
|
|
from app.models.mail_source import MailSource
|
|
from app.models.user import User
|
|
from app.models.workspace import Workspace
|
|
|
|
DEFAULT_WORKSPACE_SLUG = "default"
|
|
DEFAULT_WORKSPACE_NAME = "Default Workspace"
|
|
|
|
|
|
def normalize_workspace_slug(value: str) -> str:
|
|
"""Normalize a workspace slug for stable lookups."""
|
|
slug = (value or "").strip().lower()
|
|
cleaned = []
|
|
previous_dash = False
|
|
for char in slug:
|
|
if char.isalnum():
|
|
cleaned.append(char)
|
|
previous_dash = False
|
|
elif not previous_dash:
|
|
cleaned.append("-")
|
|
previous_dash = True
|
|
return "".join(cleaned).strip("-")
|
|
|
|
|
|
def get_or_create_default_workspace(db: Session, *, commit: bool = True) -> Workspace:
|
|
"""Return the single-tenant default workspace, creating it when needed."""
|
|
workspace = db.query(Workspace).filter(Workspace.slug == DEFAULT_WORKSPACE_SLUG).first()
|
|
if workspace:
|
|
return workspace
|
|
|
|
workspace = Workspace(
|
|
slug=DEFAULT_WORKSPACE_SLUG,
|
|
name=DEFAULT_WORKSPACE_NAME,
|
|
description="Automatically created for existing single-tenant installs.",
|
|
active=True,
|
|
)
|
|
db.add(workspace)
|
|
if commit:
|
|
db.commit()
|
|
db.refresh(workspace)
|
|
else:
|
|
db.flush()
|
|
return workspace
|
|
|
|
|
|
def assign_default_workspace_to_unscoped_rows(
|
|
db: Session,
|
|
*,
|
|
commit: bool = True,
|
|
) -> Workspace:
|
|
"""Attach legacy unscoped rows to the default workspace."""
|
|
workspace = get_or_create_default_workspace(db, commit=commit)
|
|
for model in (Domain, MailSource, User):
|
|
db.query(model).filter(model.workspace_id.is_(None)).update(
|
|
{model.workspace_id: workspace.id},
|
|
synchronize_session=False,
|
|
)
|
|
if commit:
|
|
db.commit()
|
|
else:
|
|
db.flush()
|
|
return workspace
|
|
|
|
|
|
def resolve_workspace(
|
|
db: Session,
|
|
*,
|
|
workspace_id: Optional[int] = None,
|
|
slug: Optional[str] = None,
|
|
) -> Workspace:
|
|
"""Resolve a workspace, defaulting to the single-tenant workspace."""
|
|
if workspace_id is not None:
|
|
workspace = (
|
|
db.query(Workspace)
|
|
.filter(Workspace.id == workspace_id, Workspace.active.is_(True))
|
|
.first()
|
|
)
|
|
if workspace:
|
|
return workspace
|
|
raise ValueError("Workspace not found")
|
|
|
|
if slug:
|
|
normalized = normalize_workspace_slug(slug)
|
|
workspace = (
|
|
db.query(Workspace)
|
|
.filter(Workspace.slug == normalized, Workspace.active.is_(True))
|
|
.first()
|
|
)
|
|
if workspace:
|
|
return workspace
|
|
raise ValueError("Workspace not found")
|
|
|
|
return assign_default_workspace_to_unscoped_rows(db)
|
|
|
|
|
|
def workspace_domain_query(db: Session, workspace: Workspace) -> Query:
|
|
"""Return the default scoped domain query for a workspace."""
|
|
return db.query(Domain).filter(Domain.workspace_id == workspace.id)
|
|
|
|
|
|
def workspace_mail_source_query(db: Session, workspace: Workspace) -> Query:
|
|
"""Return the default scoped mail-source query for a workspace."""
|
|
return db.query(MailSource).filter(MailSource.workspace_id == workspace.id)
|
|
|
|
|
|
def workspace_user_query(db: Session, workspace: Workspace) -> Query:
|
|
"""Return the default scoped user query for a workspace."""
|
|
return db.query(User).filter(User.workspace_id == workspace.id)
|