You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
gcgj-dify-1.7.0/api/models/organization.py

187 lines
7.6 KiB
Python

import enum
import json
from datetime import datetime
from sqlalchemy import func
from sqlalchemy.orm import Mapped, mapped_column
from .engine import db
from .types import StringUUID
class OrganizationType(enum.StrEnum):
SCHOOL = "school"
UNIVERSITY = "university"
COMPANY = "company"
ORGANIZATION = "organization"
class Organization(db.Model): # type: ignore[name-defined]
"""
Organization model to represent schools or companies under a single tenant.
This allows a single app provider (tenant) to serve multiple organizations
with separate data and configurations.
"""
__tablename__ = "organizations"
__table_args__ = (
db.PrimaryKeyConstraint("id", name="organization_pkey"),
db.Index("organization_tenant_id_idx", "tenant_id"),
db.Index("organization_code_idx", "code"),
)
id: Mapped[str] = mapped_column(StringUUID, server_default=db.text("uuid_generate_v4()"))
tenant_id: Mapped[str] = mapped_column(StringUUID, nullable=False) # The owning tenant (app provider)
name: Mapped[str] = mapped_column(db.String(255), nullable=False)
code: Mapped[str] = mapped_column(db.String(64), nullable=False, unique=True) # Unique code for the organization
description: Mapped[str] = mapped_column(db.Text, nullable=True)
type: Mapped[str] = mapped_column(db.String(64), nullable=False, default="school")
logo: Mapped[str] = mapped_column(db.String(255), nullable=True)
settings: Mapped[str] = mapped_column(db.Text, nullable=True) # JSON settings
status: Mapped[str] = mapped_column(
db.String(16), nullable=False, server_default=db.text("'active'::character varying")
)
created_by: Mapped[str] = mapped_column(StringUUID, nullable=False)
created_at: Mapped[datetime] = mapped_column(db.DateTime, nullable=False, server_default=func.current_timestamp())
updated_at: Mapped[datetime] = mapped_column(db.DateTime, nullable=False, server_default=func.current_timestamp())
@property
def settings_dict(self) -> dict:
"""Get organization settings as a dictionary"""
return json.loads(self.settings) if self.settings else {}
@settings_dict.setter
def settings_dict(self, value: dict):
"""Set organization settings from a dictionary"""
self.settings = json.dumps(value)
@property
def allowed_email_domains(self) -> list[str]:
"""Get list of allowed email domains for this organization"""
settings = self.settings_dict
return settings.get("allowed_email_domains", [])
@allowed_email_domains.setter
def allowed_email_domains(self, domains: list[str]):
"""Set allowed email domains for this organization"""
settings = self.settings_dict
settings["allowed_email_domains"] = domains
self.settings_dict = settings
@property
def is_email_restricted(self) -> bool:
"""Check if organization restricts registration by email domain"""
return len(self.allowed_email_domains) > 0
def validate_email(self, email: str) -> bool:
"""Validate if an email is allowed for this organization"""
if not self.is_email_restricted:
return True
email_domain = email.split("@")[-1].lower()
return email_domain in self.allowed_email_domains
@property
def available_apps(self):
"""Get apps available for this organization"""
app_access = (
db.session.query(AppOrganizationAccess).filter(AppOrganizationAccess.organization_id == self.id).all()
)
if not app_access:
return []
from .model import App
app_ids = [access.app_id for access in app_access]
return db.session.query(App).filter(App.id.in_(app_ids)).all()
class OrganizationRole(enum.StrEnum):
"""Roles within an organization (school/company)"""
ADMIN = "admin" # Can manage the organization
TEACHER = "teacher" # For educational orgs
STUDENT = "student" # For educational orgs
STAFF = "staff" # General staff
MANAGER = "manager" # Department manager
EMPLOYEE = "employee" # Regular employee
GUEST = "guest" # Guest access
@property
def is_admin(self) -> bool:
return self == OrganizationRole.ADMIN
@property
def is_staff(self) -> bool:
return self in {
OrganizationRole.ADMIN,
OrganizationRole.TEACHER,
OrganizationRole.STAFF,
OrganizationRole.MANAGER,
}
class OrganizationMember(db.Model): # type: ignore[name-defined]
"""Represents membership of an account in an organization"""
__tablename__ = "organization_members"
__table_args__ = (
db.PrimaryKeyConstraint("id", name="organization_member_pkey"),
db.Index("org_member_org_idx", "organization_id"),
db.Index("org_member_account_idx", "account_id"),
db.UniqueConstraint("organization_id", "account_id", name="unique_org_account"),
)
id: Mapped[str] = mapped_column(StringUUID, server_default=db.text("uuid_generate_v4()"))
organization_id: Mapped[str] = mapped_column(StringUUID, nullable=False)
account_id: Mapped[str] = mapped_column(StringUUID, nullable=False)
role: Mapped[str] = mapped_column(db.String(64), nullable=False)
department: Mapped[str] = mapped_column(db.String(255), nullable=True)
title: Mapped[str] = mapped_column(db.String(255), nullable=True)
is_default: Mapped[bool] = mapped_column(db.Boolean, nullable=False, server_default=db.text("false"))
meta_data: Mapped[str] = mapped_column(db.Text, nullable=True) # Additional metadata as JSON
created_by: Mapped[str] = mapped_column(StringUUID, nullable=False)
created_at: Mapped[datetime] = mapped_column(db.DateTime, nullable=False, server_default=func.current_timestamp())
updated_at: Mapped[datetime] = mapped_column(db.DateTime, nullable=False, server_default=func.current_timestamp())
@property
def metadata_dict(self) -> dict:
"""Get member metadata as a dictionary"""
return json.loads(self.meta_data) if self.meta_data else {}
@metadata_dict.setter
def metadata_dict(self, value: dict):
"""Set member metadata from a dictionary"""
self.meta_data = json.dumps(value)
class AppOrganizationAccess(db.Model): # type: ignore[name-defined]
"""Controls which apps are accessible to which organizations"""
__tablename__ = "app_organization_access"
__table_args__ = (
db.PrimaryKeyConstraint("id", name="app_organization_access_pkey"),
db.Index("app_org_access_app_idx", "app_id"),
db.Index("app_org_access_org_idx", "organization_id"),
db.UniqueConstraint("app_id", "organization_id", name="unique_app_organization"),
)
id: Mapped[str] = mapped_column(StringUUID, server_default=db.text("uuid_generate_v4()"))
app_id: Mapped[str] = mapped_column(StringUUID, nullable=False)
organization_id: Mapped[str] = mapped_column(StringUUID, nullable=False)
permissions: Mapped[str] = mapped_column(db.Text, nullable=True) # JSON permissions
created_by: Mapped[str] = mapped_column(StringUUID, nullable=False)
created_at: Mapped[datetime] = mapped_column(db.DateTime, nullable=False, server_default=func.current_timestamp())
updated_at: Mapped[datetime] = mapped_column(db.DateTime, nullable=False, server_default=func.current_timestamp())
@property
def permissions_dict(self) -> dict:
"""Get permissions as a dictionary"""
return json.loads(self.permissions) if self.permissions else {}
@permissions_dict.setter
def permissions_dict(self, value: dict):
"""Set permissions from a dictionary"""
self.permissions = json.dumps(value)