mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-07-14 22:52:15 +02:00
refactor(db): rename SearchSpace ORM core to Workspace (Phase 2 Wave A)
Flip the symbolic name in db.py: classes SearchSpace/Role/Membership/Invite ->
Workspace*, attributes search_space_id -> workspace_id (and owner_*), relationship
attrs + back_populates pairs, and drop the Phase 1 Column("workspace_id", ...) shim
now that attribute name == column name. Enum values 'SEARCH_SPACE' and the
SearchSourceConnector class are intentionally untouched (carve-outs).
Part of the atomic SearchSpace -> Workspace rename; the suite goes green only once
Waves B-F land (no half-renamed steady state).
This commit is contained in:
parent
49d3001fbf
commit
df03565940
1 changed files with 97 additions and 117 deletions
|
|
@ -292,7 +292,7 @@ INCENTIVE_TASKS_CONFIG = {
|
||||||
|
|
||||||
class Permission(StrEnum):
|
class Permission(StrEnum):
|
||||||
"""
|
"""
|
||||||
Granular permissions for search space resources.
|
Granular permissions for workspace resources.
|
||||||
Use '*' (FULL_ACCESS) to grant all permissions.
|
Use '*' (FULL_ACCESS) to grant all permissions.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
@ -363,10 +363,10 @@ class Permission(StrEnum):
|
||||||
ROLES_UPDATE = "roles:update"
|
ROLES_UPDATE = "roles:update"
|
||||||
ROLES_DELETE = "roles:delete"
|
ROLES_DELETE = "roles:delete"
|
||||||
|
|
||||||
# Search Space Settings
|
# Workspace Settings
|
||||||
SETTINGS_VIEW = "settings:view"
|
SETTINGS_VIEW = "settings:view"
|
||||||
SETTINGS_UPDATE = "settings:update"
|
SETTINGS_UPDATE = "settings:update"
|
||||||
SETTINGS_DELETE = "settings:delete" # Delete the entire search space
|
SETTINGS_DELETE = "settings:delete" # Delete the entire workspace
|
||||||
|
|
||||||
# API Access
|
# API Access
|
||||||
API_ACCESS_MANAGE = "api_access:manage"
|
API_ACCESS_MANAGE = "api_access:manage"
|
||||||
|
|
@ -515,7 +515,7 @@ class ChatVisibility(StrEnum):
|
||||||
Visibility/sharing level for chat threads.
|
Visibility/sharing level for chat threads.
|
||||||
|
|
||||||
PRIVATE: Only the creator can see/access the chat (default)
|
PRIVATE: Only the creator can see/access the chat (default)
|
||||||
SEARCH_SPACE: All members of the search space can see/access the chat
|
SEARCH_SPACE: All members of the workspace can see/access the chat
|
||||||
PUBLIC: (Future) Anyone with the link can access the chat
|
PUBLIC: (Future) Anyone with the link can access the chat
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
@ -605,8 +605,7 @@ class NewChatThread(BaseModel, TimestampMixin):
|
||||||
)
|
)
|
||||||
|
|
||||||
# Foreign keys
|
# Foreign keys
|
||||||
search_space_id = Column(
|
workspace_id = Column(
|
||||||
"workspace_id",
|
|
||||||
Integer,
|
Integer,
|
||||||
ForeignKey("workspaces.id", ondelete="CASCADE"),
|
ForeignKey("workspaces.id", ondelete="CASCADE"),
|
||||||
nullable=False,
|
nullable=False,
|
||||||
|
|
@ -647,7 +646,7 @@ class NewChatThread(BaseModel, TimestampMixin):
|
||||||
# Auto model pin for this thread: concrete resolved global LLM
|
# Auto model pin for this thread: concrete resolved global LLM
|
||||||
# config id. NULL means no pin; Auto will resolve on the next turn.
|
# config id. NULL means no pin; Auto will resolve on the next turn.
|
||||||
# Single-writer invariant: only app.services.auto_model_pin_service sets
|
# Single-writer invariant: only app.services.auto_model_pin_service sets
|
||||||
# or clears this column (plus bulk clears when a search space's
|
# or clears this column (plus bulk clears when a workspace's
|
||||||
# chat_model_id changes). Unindexed: all reads are by primary key.
|
# chat_model_id changes). Unindexed: all reads are by primary key.
|
||||||
pinned_llm_config_id = Column(Integer, nullable=True)
|
pinned_llm_config_id = Column(Integer, nullable=True)
|
||||||
|
|
||||||
|
|
@ -664,7 +663,7 @@ class NewChatThread(BaseModel, TimestampMixin):
|
||||||
)
|
)
|
||||||
|
|
||||||
# Relationships
|
# Relationships
|
||||||
search_space = relationship("SearchSpace", back_populates="new_chat_threads")
|
workspace = relationship("Workspace", back_populates="new_chat_threads")
|
||||||
created_by = relationship("User", back_populates="new_chat_threads")
|
created_by = relationship("User", back_populates="new_chat_threads")
|
||||||
messages = relationship(
|
messages = relationship(
|
||||||
"NewChatMessage",
|
"NewChatMessage",
|
||||||
|
|
@ -788,8 +787,7 @@ class ExternalChatAccount(Base, TimestampMixin):
|
||||||
owner_user_id = Column(
|
owner_user_id = Column(
|
||||||
UUID(as_uuid=True), ForeignKey("user.id", ondelete="CASCADE"), nullable=True
|
UUID(as_uuid=True), ForeignKey("user.id", ondelete="CASCADE"), nullable=True
|
||||||
)
|
)
|
||||||
owner_search_space_id = Column(
|
owner_workspace_id = Column(
|
||||||
"owner_workspace_id",
|
|
||||||
Integer,
|
Integer,
|
||||||
ForeignKey("workspaces.id", ondelete="CASCADE"),
|
ForeignKey("workspaces.id", ondelete="CASCADE"),
|
||||||
nullable=True,
|
nullable=True,
|
||||||
|
|
@ -825,8 +823,8 @@ class ExternalChatAccount(Base, TimestampMixin):
|
||||||
)
|
)
|
||||||
|
|
||||||
owner = relationship("User", foreign_keys=[owner_user_id])
|
owner = relationship("User", foreign_keys=[owner_user_id])
|
||||||
owner_search_space = relationship(
|
owner_workspace = relationship(
|
||||||
"SearchSpace", foreign_keys=[owner_search_space_id]
|
"Workspace", foreign_keys=[owner_workspace_id]
|
||||||
)
|
)
|
||||||
bindings = relationship(
|
bindings = relationship(
|
||||||
"ExternalChatBinding",
|
"ExternalChatBinding",
|
||||||
|
|
@ -903,8 +901,7 @@ class ExternalChatBinding(Base, TimestampMixin):
|
||||||
user_id = Column(
|
user_id = Column(
|
||||||
UUID(as_uuid=True), ForeignKey("user.id", ondelete="CASCADE"), nullable=False
|
UUID(as_uuid=True), ForeignKey("user.id", ondelete="CASCADE"), nullable=False
|
||||||
)
|
)
|
||||||
search_space_id = Column(
|
workspace_id = Column(
|
||||||
"workspace_id",
|
|
||||||
Integer,
|
Integer,
|
||||||
ForeignKey("workspaces.id", ondelete="CASCADE"),
|
ForeignKey("workspaces.id", ondelete="CASCADE"),
|
||||||
nullable=False,
|
nullable=False,
|
||||||
|
|
@ -957,7 +954,7 @@ class ExternalChatBinding(Base, TimestampMixin):
|
||||||
|
|
||||||
account = relationship("ExternalChatAccount", back_populates="bindings")
|
account = relationship("ExternalChatAccount", back_populates="bindings")
|
||||||
user = relationship("User", foreign_keys=[user_id])
|
user = relationship("User", foreign_keys=[user_id])
|
||||||
search_space = relationship("SearchSpace", foreign_keys=[search_space_id])
|
workspace = relationship("Workspace", foreign_keys=[workspace_id])
|
||||||
new_chat_thread = relationship("NewChatThread", foreign_keys=[new_chat_thread_id])
|
new_chat_thread = relationship("NewChatThread", foreign_keys=[new_chat_thread_id])
|
||||||
threads = relationship(
|
threads = relationship(
|
||||||
"NewChatThread",
|
"NewChatThread",
|
||||||
|
|
@ -1122,8 +1119,7 @@ class TokenUsage(BaseModel, TimestampMixin):
|
||||||
nullable=True,
|
nullable=True,
|
||||||
index=True,
|
index=True,
|
||||||
)
|
)
|
||||||
search_space_id = Column(
|
workspace_id = Column(
|
||||||
"workspace_id",
|
|
||||||
Integer,
|
Integer,
|
||||||
ForeignKey("workspaces.id", ondelete="CASCADE"),
|
ForeignKey("workspaces.id", ondelete="CASCADE"),
|
||||||
nullable=False,
|
nullable=False,
|
||||||
|
|
@ -1139,7 +1135,7 @@ class TokenUsage(BaseModel, TimestampMixin):
|
||||||
# Relationships
|
# Relationships
|
||||||
thread = relationship("NewChatThread", back_populates="token_usages")
|
thread = relationship("NewChatThread", back_populates="token_usages")
|
||||||
message = relationship("NewChatMessage", back_populates="token_usage")
|
message = relationship("NewChatMessage", back_populates="token_usage")
|
||||||
search_space = relationship("SearchSpace")
|
workspace = relationship("Workspace")
|
||||||
user = relationship("User")
|
user = relationship("User")
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1328,8 +1324,7 @@ class Folder(BaseModel, TimestampMixin):
|
||||||
nullable=True,
|
nullable=True,
|
||||||
index=True,
|
index=True,
|
||||||
)
|
)
|
||||||
search_space_id = Column(
|
workspace_id = Column(
|
||||||
"workspace_id",
|
|
||||||
Integer,
|
Integer,
|
||||||
ForeignKey("workspaces.id", ondelete="CASCADE"),
|
ForeignKey("workspaces.id", ondelete="CASCADE"),
|
||||||
nullable=False,
|
nullable=False,
|
||||||
|
|
@ -1351,7 +1346,7 @@ class Folder(BaseModel, TimestampMixin):
|
||||||
folder_metadata = Column("metadata", JSONB, nullable=True)
|
folder_metadata = Column("metadata", JSONB, nullable=True)
|
||||||
|
|
||||||
parent = relationship("Folder", remote_side="Folder.id", backref="children")
|
parent = relationship("Folder", remote_side="Folder.id", backref="children")
|
||||||
search_space = relationship("SearchSpace", back_populates="folders")
|
workspace = relationship("Workspace", back_populates="folders")
|
||||||
created_by = relationship("User", back_populates="folders")
|
created_by = relationship("User", back_populates="folders")
|
||||||
documents = relationship("Document", back_populates="folder", passive_deletes=True)
|
documents = relationship("Document", back_populates="folder", passive_deletes=True)
|
||||||
|
|
||||||
|
|
@ -1368,7 +1363,7 @@ class Document(BaseModel, TimestampMixin):
|
||||||
# filesystem two files at different paths can hold identical bytes,
|
# filesystem two files at different paths can hold identical bytes,
|
||||||
# and the agent's ``write_file`` flow needs that semantic to support
|
# and the agent's ``write_file`` flow needs that semantic to support
|
||||||
# copy / duplicate operations. Path uniqueness lives on
|
# copy / duplicate operations. Path uniqueness lives on
|
||||||
# ``unique_identifier_hash`` (per search space). The hash remains
|
# ``unique_identifier_hash`` (per workspace). The hash remains
|
||||||
# indexed because connector indexers consult it as a change-detection
|
# indexed because connector indexers consult it as a change-detection
|
||||||
# / cross-source dedup hint via :func:`check_duplicate_document`.
|
# / cross-source dedup hint via :func:`check_duplicate_document`.
|
||||||
# See migration 133.
|
# See migration 133.
|
||||||
|
|
@ -1393,8 +1388,7 @@ class Document(BaseModel, TimestampMixin):
|
||||||
# Track when document was last updated by indexers, processors, or editor
|
# Track when document was last updated by indexers, processors, or editor
|
||||||
updated_at = Column(TIMESTAMP(timezone=True), nullable=True, index=True)
|
updated_at = Column(TIMESTAMP(timezone=True), nullable=True, index=True)
|
||||||
|
|
||||||
search_space_id = Column(
|
workspace_id = Column(
|
||||||
"workspace_id",
|
|
||||||
Integer,
|
Integer,
|
||||||
ForeignKey("workspaces.id", ondelete="CASCADE"),
|
ForeignKey("workspaces.id", ondelete="CASCADE"),
|
||||||
nullable=False,
|
nullable=False,
|
||||||
|
|
@ -1435,7 +1429,7 @@ class Document(BaseModel, TimestampMixin):
|
||||||
)
|
)
|
||||||
|
|
||||||
# Relationships
|
# Relationships
|
||||||
search_space = relationship("SearchSpace", back_populates="documents")
|
workspace = relationship("Workspace", back_populates="documents")
|
||||||
folder = relationship("Folder", back_populates="documents")
|
folder = relationship("Folder", back_populates="documents")
|
||||||
created_by = relationship("User", back_populates="documents")
|
created_by = relationship("User", back_populates="documents")
|
||||||
connector = relationship("SearchSourceConnector", back_populates="documents")
|
connector = relationship("SearchSourceConnector", back_populates="documents")
|
||||||
|
|
@ -1520,13 +1514,12 @@ class VideoPresentation(BaseModel, TimestampMixin):
|
||||||
index=True,
|
index=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
search_space_id = Column(
|
workspace_id = Column(
|
||||||
"workspace_id",
|
|
||||||
Integer,
|
Integer,
|
||||||
ForeignKey("workspaces.id", ondelete="CASCADE"),
|
ForeignKey("workspaces.id", ondelete="CASCADE"),
|
||||||
nullable=False,
|
nullable=False,
|
||||||
)
|
)
|
||||||
search_space = relationship("SearchSpace", back_populates="video_presentations")
|
workspace = relationship("Workspace", back_populates="video_presentations")
|
||||||
|
|
||||||
thread_id = Column(
|
thread_id = Column(
|
||||||
Integer,
|
Integer,
|
||||||
|
|
@ -1550,13 +1543,12 @@ class Report(BaseModel, TimestampMixin):
|
||||||
String(100), nullable=True
|
String(100), nullable=True
|
||||||
) # e.g. "executive_summary", "deep_research"
|
) # e.g. "executive_summary", "deep_research"
|
||||||
|
|
||||||
search_space_id = Column(
|
workspace_id = Column(
|
||||||
"workspace_id",
|
|
||||||
Integer,
|
Integer,
|
||||||
ForeignKey("workspaces.id", ondelete="CASCADE"),
|
ForeignKey("workspaces.id", ondelete="CASCADE"),
|
||||||
nullable=False,
|
nullable=False,
|
||||||
)
|
)
|
||||||
search_space = relationship("SearchSpace", back_populates="reports")
|
workspace = relationship("Workspace", back_populates="reports")
|
||||||
|
|
||||||
# Versioning: reports sharing the same report_group_id are versions of the same report.
|
# Versioning: reports sharing the same report_group_id are versions of the same report.
|
||||||
# For v1, report_group_id = the report's own id (set after insert).
|
# For v1, report_group_id = the report's own id (set after insert).
|
||||||
|
|
@ -1581,8 +1573,7 @@ class Connection(BaseModel, TimestampMixin):
|
||||||
scope = Column(SQLAlchemyEnum(ConnectionScope), nullable=False, index=True)
|
scope = Column(SQLAlchemyEnum(ConnectionScope), nullable=False, index=True)
|
||||||
enabled = Column(Boolean, nullable=False, default=True, server_default="true")
|
enabled = Column(Boolean, nullable=False, default=True, server_default="true")
|
||||||
|
|
||||||
search_space_id = Column(
|
workspace_id = Column(
|
||||||
"workspace_id",
|
|
||||||
Integer,
|
Integer,
|
||||||
ForeignKey("workspaces.id", ondelete="CASCADE"),
|
ForeignKey("workspaces.id", ondelete="CASCADE"),
|
||||||
nullable=True,
|
nullable=True,
|
||||||
|
|
@ -1591,7 +1582,7 @@ class Connection(BaseModel, TimestampMixin):
|
||||||
UUID(as_uuid=True), ForeignKey("user.id", ondelete="CASCADE"), nullable=True
|
UUID(as_uuid=True), ForeignKey("user.id", ondelete="CASCADE"), nullable=True
|
||||||
)
|
)
|
||||||
|
|
||||||
search_space = relationship("SearchSpace", back_populates="connections")
|
workspace = relationship("Workspace", back_populates="connections")
|
||||||
user = relationship("User", back_populates="connections")
|
user = relationship("User", back_populates="connections")
|
||||||
models = relationship(
|
models = relationship(
|
||||||
"Model",
|
"Model",
|
||||||
|
|
@ -1695,8 +1686,7 @@ class ImageGeneration(BaseModel, TimestampMixin):
|
||||||
access_token = Column(String(64), nullable=True, index=True)
|
access_token = Column(String(64), nullable=True, index=True)
|
||||||
|
|
||||||
# Foreign keys
|
# Foreign keys
|
||||||
search_space_id = Column(
|
workspace_id = Column(
|
||||||
"workspace_id",
|
|
||||||
Integer,
|
Integer,
|
||||||
ForeignKey("workspaces.id", ondelete="CASCADE"),
|
ForeignKey("workspaces.id", ondelete="CASCADE"),
|
||||||
nullable=False,
|
nullable=False,
|
||||||
|
|
@ -1709,11 +1699,11 @@ class ImageGeneration(BaseModel, TimestampMixin):
|
||||||
)
|
)
|
||||||
|
|
||||||
# Relationships
|
# Relationships
|
||||||
search_space = relationship("SearchSpace", back_populates="image_generations")
|
workspace = relationship("Workspace", back_populates="image_generations")
|
||||||
created_by = relationship("User", back_populates="image_generations")
|
created_by = relationship("User", back_populates="image_generations")
|
||||||
|
|
||||||
|
|
||||||
class SearchSpace(BaseModel, TimestampMixin):
|
class Workspace(BaseModel, TimestampMixin):
|
||||||
__tablename__ = "workspaces"
|
__tablename__ = "workspaces"
|
||||||
|
|
||||||
name = Column(String(100), nullable=False, index=True)
|
name = Column(String(100), nullable=False, index=True)
|
||||||
|
|
@ -1735,7 +1725,7 @@ class SearchSpace(BaseModel, TimestampMixin):
|
||||||
# Note: ID values preserve the existing convention:
|
# Note: ID values preserve the existing convention:
|
||||||
# - 0: Auto mode
|
# - 0: Auto mode
|
||||||
# - Negative IDs: Global virtual models from global_llm_config.yaml
|
# - Negative IDs: Global virtual models from global_llm_config.yaml
|
||||||
# - Positive IDs: User/search-space models from the models table
|
# - Positive IDs: User/workspace models from the models table
|
||||||
chat_model_id = Column(
|
chat_model_id = Column(
|
||||||
Integer, nullable=True, default=0, server_default="0"
|
Integer, nullable=True, default=0, server_default="0"
|
||||||
) # For agent/chat operations, defaults to Auto mode
|
) # For agent/chat operations, defaults to Auto mode
|
||||||
|
|
@ -1753,71 +1743,71 @@ class SearchSpace(BaseModel, TimestampMixin):
|
||||||
user_id = Column(
|
user_id = Column(
|
||||||
UUID(as_uuid=True), ForeignKey("user.id", ondelete="CASCADE"), nullable=False
|
UUID(as_uuid=True), ForeignKey("user.id", ondelete="CASCADE"), nullable=False
|
||||||
)
|
)
|
||||||
user = relationship("User", back_populates="search_spaces")
|
user = relationship("User", back_populates="workspaces")
|
||||||
|
|
||||||
folders = relationship(
|
folders = relationship(
|
||||||
"Folder",
|
"Folder",
|
||||||
back_populates="search_space",
|
back_populates="workspace",
|
||||||
order_by="Folder.position",
|
order_by="Folder.position",
|
||||||
cascade="all, delete-orphan",
|
cascade="all, delete-orphan",
|
||||||
)
|
)
|
||||||
documents = relationship(
|
documents = relationship(
|
||||||
"Document",
|
"Document",
|
||||||
back_populates="search_space",
|
back_populates="workspace",
|
||||||
order_by="Document.id",
|
order_by="Document.id",
|
||||||
cascade="all, delete-orphan",
|
cascade="all, delete-orphan",
|
||||||
)
|
)
|
||||||
new_chat_threads = relationship(
|
new_chat_threads = relationship(
|
||||||
"NewChatThread",
|
"NewChatThread",
|
||||||
back_populates="search_space",
|
back_populates="workspace",
|
||||||
order_by="NewChatThread.updated_at.desc()",
|
order_by="NewChatThread.updated_at.desc()",
|
||||||
cascade="all, delete-orphan",
|
cascade="all, delete-orphan",
|
||||||
)
|
)
|
||||||
podcasts = relationship(
|
podcasts = relationship(
|
||||||
"Podcast",
|
"Podcast",
|
||||||
back_populates="search_space",
|
back_populates="workspace",
|
||||||
order_by="Podcast.id.desc()",
|
order_by="Podcast.id.desc()",
|
||||||
cascade="all, delete-orphan",
|
cascade="all, delete-orphan",
|
||||||
)
|
)
|
||||||
video_presentations = relationship(
|
video_presentations = relationship(
|
||||||
"VideoPresentation",
|
"VideoPresentation",
|
||||||
back_populates="search_space",
|
back_populates="workspace",
|
||||||
order_by="VideoPresentation.id.desc()",
|
order_by="VideoPresentation.id.desc()",
|
||||||
cascade="all, delete-orphan",
|
cascade="all, delete-orphan",
|
||||||
)
|
)
|
||||||
reports = relationship(
|
reports = relationship(
|
||||||
"Report",
|
"Report",
|
||||||
back_populates="search_space",
|
back_populates="workspace",
|
||||||
order_by="Report.id.desc()",
|
order_by="Report.id.desc()",
|
||||||
cascade="all, delete-orphan",
|
cascade="all, delete-orphan",
|
||||||
)
|
)
|
||||||
image_generations = relationship(
|
image_generations = relationship(
|
||||||
"ImageGeneration",
|
"ImageGeneration",
|
||||||
back_populates="search_space",
|
back_populates="workspace",
|
||||||
order_by="ImageGeneration.id.desc()",
|
order_by="ImageGeneration.id.desc()",
|
||||||
cascade="all, delete-orphan",
|
cascade="all, delete-orphan",
|
||||||
)
|
)
|
||||||
logs = relationship(
|
logs = relationship(
|
||||||
"Log",
|
"Log",
|
||||||
back_populates="search_space",
|
back_populates="workspace",
|
||||||
order_by="Log.id",
|
order_by="Log.id",
|
||||||
cascade="all, delete-orphan",
|
cascade="all, delete-orphan",
|
||||||
)
|
)
|
||||||
notifications = relationship(
|
notifications = relationship(
|
||||||
"Notification",
|
"Notification",
|
||||||
back_populates="search_space",
|
back_populates="workspace",
|
||||||
order_by="Notification.created_at.desc()",
|
order_by="Notification.created_at.desc()",
|
||||||
cascade="all, delete-orphan",
|
cascade="all, delete-orphan",
|
||||||
)
|
)
|
||||||
search_source_connectors = relationship(
|
search_source_connectors = relationship(
|
||||||
"SearchSourceConnector",
|
"SearchSourceConnector",
|
||||||
back_populates="search_space",
|
back_populates="workspace",
|
||||||
order_by="SearchSourceConnector.id",
|
order_by="SearchSourceConnector.id",
|
||||||
cascade="all, delete-orphan",
|
cascade="all, delete-orphan",
|
||||||
)
|
)
|
||||||
connections = relationship(
|
connections = relationship(
|
||||||
"Connection",
|
"Connection",
|
||||||
back_populates="search_space",
|
back_populates="workspace",
|
||||||
order_by="Connection.id",
|
order_by="Connection.id",
|
||||||
cascade="all, delete-orphan",
|
cascade="all, delete-orphan",
|
||||||
passive_deletes=True,
|
passive_deletes=True,
|
||||||
|
|
@ -1825,7 +1815,7 @@ class SearchSpace(BaseModel, TimestampMixin):
|
||||||
|
|
||||||
automations = relationship(
|
automations = relationship(
|
||||||
"Automation",
|
"Automation",
|
||||||
back_populates="search_space",
|
back_populates="workspace",
|
||||||
order_by="Automation.id",
|
order_by="Automation.id",
|
||||||
cascade="all, delete-orphan",
|
cascade="all, delete-orphan",
|
||||||
passive_deletes=True,
|
passive_deletes=True,
|
||||||
|
|
@ -1833,21 +1823,21 @@ class SearchSpace(BaseModel, TimestampMixin):
|
||||||
|
|
||||||
# RBAC relationships
|
# RBAC relationships
|
||||||
roles = relationship(
|
roles = relationship(
|
||||||
"SearchSpaceRole",
|
"WorkspaceRole",
|
||||||
back_populates="search_space",
|
back_populates="workspace",
|
||||||
order_by="SearchSpaceRole.id",
|
order_by="WorkspaceRole.id",
|
||||||
cascade="all, delete-orphan",
|
cascade="all, delete-orphan",
|
||||||
)
|
)
|
||||||
memberships = relationship(
|
memberships = relationship(
|
||||||
"SearchSpaceMembership",
|
"WorkspaceMembership",
|
||||||
back_populates="search_space",
|
back_populates="workspace",
|
||||||
order_by="SearchSpaceMembership.id",
|
order_by="WorkspaceMembership.id",
|
||||||
cascade="all, delete-orphan",
|
cascade="all, delete-orphan",
|
||||||
)
|
)
|
||||||
invites = relationship(
|
invites = relationship(
|
||||||
"SearchSpaceInvite",
|
"WorkspaceInvite",
|
||||||
back_populates="search_space",
|
back_populates="workspace",
|
||||||
order_by="SearchSpaceInvite.id",
|
order_by="WorkspaceInvite.id",
|
||||||
cascade="all, delete-orphan",
|
cascade="all, delete-orphan",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -1907,14 +1897,13 @@ class SearchSourceConnector(BaseModel, TimestampMixin):
|
||||||
indexing_frequency_minutes = Column(Integer, nullable=True)
|
indexing_frequency_minutes = Column(Integer, nullable=True)
|
||||||
next_scheduled_at = Column(TIMESTAMP(timezone=True), nullable=True)
|
next_scheduled_at = Column(TIMESTAMP(timezone=True), nullable=True)
|
||||||
|
|
||||||
search_space_id = Column(
|
workspace_id = Column(
|
||||||
"workspace_id",
|
|
||||||
Integer,
|
Integer,
|
||||||
ForeignKey("workspaces.id", ondelete="CASCADE"),
|
ForeignKey("workspaces.id", ondelete="CASCADE"),
|
||||||
nullable=False,
|
nullable=False,
|
||||||
)
|
)
|
||||||
search_space = relationship(
|
workspace = relationship(
|
||||||
"SearchSpace", back_populates="search_source_connectors"
|
"Workspace", back_populates="search_source_connectors"
|
||||||
)
|
)
|
||||||
|
|
||||||
user_id = Column(
|
user_id = Column(
|
||||||
|
|
@ -1937,13 +1926,12 @@ class Log(BaseModel, TimestampMixin):
|
||||||
) # Service/component that generated the log
|
) # Service/component that generated the log
|
||||||
log_metadata = Column(JSON, nullable=True, default={}) # Additional context data
|
log_metadata = Column(JSON, nullable=True, default={}) # Additional context data
|
||||||
|
|
||||||
search_space_id = Column(
|
workspace_id = Column(
|
||||||
"workspace_id",
|
|
||||||
Integer,
|
Integer,
|
||||||
ForeignKey("workspaces.id", ondelete="CASCADE"),
|
ForeignKey("workspaces.id", ondelete="CASCADE"),
|
||||||
nullable=False,
|
nullable=False,
|
||||||
)
|
)
|
||||||
search_space = relationship("SearchSpace", back_populates="logs")
|
workspace = relationship("Workspace", back_populates="logs")
|
||||||
|
|
||||||
|
|
||||||
class UserIncentiveTask(BaseModel, TimestampMixin):
|
class UserIncentiveTask(BaseModel, TimestampMixin):
|
||||||
|
|
@ -2056,10 +2044,10 @@ class CreditPurchase(Base, TimestampMixin):
|
||||||
user = relationship("User", back_populates="credit_purchases")
|
user = relationship("User", back_populates="credit_purchases")
|
||||||
|
|
||||||
|
|
||||||
class SearchSpaceRole(BaseModel, TimestampMixin):
|
class WorkspaceRole(BaseModel, TimestampMixin):
|
||||||
"""
|
"""
|
||||||
Custom roles that can be defined per search space.
|
Custom roles that can be defined per workspace.
|
||||||
Each search space can have multiple roles with different permission sets.
|
Each workspace can have multiple roles with different permission sets.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__tablename__ = "workspace_roles"
|
__tablename__ = "workspace_roles"
|
||||||
|
|
@ -2080,26 +2068,25 @@ class SearchSpaceRole(BaseModel, TimestampMixin):
|
||||||
# System roles (Owner, Editor, Viewer) cannot be deleted
|
# System roles (Owner, Editor, Viewer) cannot be deleted
|
||||||
is_system_role = Column(Boolean, nullable=False, default=False)
|
is_system_role = Column(Boolean, nullable=False, default=False)
|
||||||
|
|
||||||
search_space_id = Column(
|
workspace_id = Column(
|
||||||
"workspace_id",
|
|
||||||
Integer,
|
Integer,
|
||||||
ForeignKey("workspaces.id", ondelete="CASCADE"),
|
ForeignKey("workspaces.id", ondelete="CASCADE"),
|
||||||
nullable=False,
|
nullable=False,
|
||||||
)
|
)
|
||||||
search_space = relationship("SearchSpace", back_populates="roles")
|
workspace = relationship("Workspace", back_populates="roles")
|
||||||
|
|
||||||
memberships = relationship(
|
memberships = relationship(
|
||||||
"SearchSpaceMembership", back_populates="role", passive_deletes=True
|
"WorkspaceMembership", back_populates="role", passive_deletes=True
|
||||||
)
|
)
|
||||||
invites = relationship(
|
invites = relationship(
|
||||||
"SearchSpaceInvite", back_populates="role", passive_deletes=True
|
"WorkspaceInvite", back_populates="role", passive_deletes=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class SearchSpaceMembership(BaseModel, TimestampMixin):
|
class WorkspaceMembership(BaseModel, TimestampMixin):
|
||||||
"""
|
"""
|
||||||
Tracks user membership in search spaces with their assigned role.
|
Tracks user membership in workspaces with their assigned role.
|
||||||
Each user can be a member of multiple search spaces with different roles.
|
Each user can be a member of multiple workspaces with different roles.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__tablename__ = "workspace_memberships"
|
__tablename__ = "workspace_memberships"
|
||||||
|
|
@ -2114,8 +2101,7 @@ class SearchSpaceMembership(BaseModel, TimestampMixin):
|
||||||
user_id = Column(
|
user_id = Column(
|
||||||
UUID(as_uuid=True), ForeignKey("user.id", ondelete="CASCADE"), nullable=False
|
UUID(as_uuid=True), ForeignKey("user.id", ondelete="CASCADE"), nullable=False
|
||||||
)
|
)
|
||||||
search_space_id = Column(
|
workspace_id = Column(
|
||||||
"workspace_id",
|
|
||||||
Integer,
|
Integer,
|
||||||
ForeignKey("workspaces.id", ondelete="CASCADE"),
|
ForeignKey("workspaces.id", ondelete="CASCADE"),
|
||||||
nullable=False,
|
nullable=False,
|
||||||
|
|
@ -2125,7 +2111,7 @@ class SearchSpaceMembership(BaseModel, TimestampMixin):
|
||||||
ForeignKey("workspace_roles.id", ondelete="SET NULL"),
|
ForeignKey("workspace_roles.id", ondelete="SET NULL"),
|
||||||
nullable=True,
|
nullable=True,
|
||||||
)
|
)
|
||||||
# Indicates if this user is the original creator/owner of the search space
|
# Indicates if this user is the original creator/owner of the workspace
|
||||||
is_owner = Column(Boolean, nullable=False, default=False)
|
is_owner = Column(Boolean, nullable=False, default=False)
|
||||||
# Timestamp when the user joined (via invite or as creator)
|
# Timestamp when the user joined (via invite or as creator)
|
||||||
joined_at = Column(
|
joined_at = Column(
|
||||||
|
|
@ -2140,17 +2126,17 @@ class SearchSpaceMembership(BaseModel, TimestampMixin):
|
||||||
nullable=True,
|
nullable=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
user = relationship("User", back_populates="search_space_memberships")
|
user = relationship("User", back_populates="workspace_memberships")
|
||||||
search_space = relationship("SearchSpace", back_populates="memberships")
|
workspace = relationship("Workspace", back_populates="memberships")
|
||||||
role = relationship("SearchSpaceRole", back_populates="memberships")
|
role = relationship("WorkspaceRole", back_populates="memberships")
|
||||||
invited_by_invite = relationship(
|
invited_by_invite = relationship(
|
||||||
"SearchSpaceInvite", back_populates="used_by_memberships"
|
"WorkspaceInvite", back_populates="used_by_memberships"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class SearchSpaceInvite(BaseModel, TimestampMixin):
|
class WorkspaceInvite(BaseModel, TimestampMixin):
|
||||||
"""
|
"""
|
||||||
Invite links for search spaces.
|
Invite links for workspaces.
|
||||||
Users can create invite links with specific roles that others can use to join.
|
Users can create invite links with specific roles that others can use to join.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
@ -2159,8 +2145,7 @@ class SearchSpaceInvite(BaseModel, TimestampMixin):
|
||||||
# Unique invite code (used in invite URLs)
|
# Unique invite code (used in invite URLs)
|
||||||
invite_code = Column(String(64), nullable=False, unique=True, index=True)
|
invite_code = Column(String(64), nullable=False, unique=True, index=True)
|
||||||
|
|
||||||
search_space_id = Column(
|
workspace_id = Column(
|
||||||
"workspace_id",
|
|
||||||
Integer,
|
Integer,
|
||||||
ForeignKey("workspaces.id", ondelete="CASCADE"),
|
ForeignKey("workspaces.id", ondelete="CASCADE"),
|
||||||
nullable=False,
|
nullable=False,
|
||||||
|
|
@ -2189,11 +2174,11 @@ class SearchSpaceInvite(BaseModel, TimestampMixin):
|
||||||
# Optional custom name/label for the invite
|
# Optional custom name/label for the invite
|
||||||
name = Column(String(100), nullable=True)
|
name = Column(String(100), nullable=True)
|
||||||
|
|
||||||
search_space = relationship("SearchSpace", back_populates="invites")
|
workspace = relationship("Workspace", back_populates="invites")
|
||||||
role = relationship("SearchSpaceRole", back_populates="invites")
|
role = relationship("WorkspaceRole", back_populates="invites")
|
||||||
created_by = relationship("User", back_populates="created_invites")
|
created_by = relationship("User", back_populates="created_invites")
|
||||||
used_by_memberships = relationship(
|
used_by_memberships = relationship(
|
||||||
"SearchSpaceMembership",
|
"WorkspaceMembership",
|
||||||
back_populates="invited_by_invite",
|
back_populates="invited_by_invite",
|
||||||
passive_deletes=True,
|
passive_deletes=True,
|
||||||
)
|
)
|
||||||
|
|
@ -2220,8 +2205,7 @@ class Prompt(BaseModel, TimestampMixin):
|
||||||
nullable=False,
|
nullable=False,
|
||||||
index=True,
|
index=True,
|
||||||
)
|
)
|
||||||
search_space_id = Column(
|
workspace_id = Column(
|
||||||
"workspace_id",
|
|
||||||
Integer,
|
Integer,
|
||||||
ForeignKey("workspaces.id", ondelete="CASCADE"),
|
ForeignKey("workspaces.id", ondelete="CASCADE"),
|
||||||
nullable=True,
|
nullable=True,
|
||||||
|
|
@ -2238,7 +2222,7 @@ class Prompt(BaseModel, TimestampMixin):
|
||||||
is_public = Column(Boolean, nullable=False, default=False)
|
is_public = Column(Boolean, nullable=False, default=False)
|
||||||
|
|
||||||
user = relationship("User")
|
user = relationship("User")
|
||||||
search_space = relationship("SearchSpace")
|
workspace = relationship("Workspace")
|
||||||
|
|
||||||
|
|
||||||
if config.AUTH_TYPE == "GOOGLE":
|
if config.AUTH_TYPE == "GOOGLE":
|
||||||
|
|
@ -2250,7 +2234,7 @@ if config.AUTH_TYPE == "GOOGLE":
|
||||||
oauth_accounts: Mapped[list[OAuthAccount]] = relationship(
|
oauth_accounts: Mapped[list[OAuthAccount]] = relationship(
|
||||||
"OAuthAccount", lazy="joined"
|
"OAuthAccount", lazy="joined"
|
||||||
)
|
)
|
||||||
search_spaces = relationship("SearchSpace", back_populates="user")
|
workspaces = relationship("Workspace", back_populates="user")
|
||||||
notifications = relationship(
|
notifications = relationship(
|
||||||
"Notification",
|
"Notification",
|
||||||
back_populates="user",
|
back_populates="user",
|
||||||
|
|
@ -2259,13 +2243,13 @@ if config.AUTH_TYPE == "GOOGLE":
|
||||||
)
|
)
|
||||||
|
|
||||||
# RBAC relationships
|
# RBAC relationships
|
||||||
search_space_memberships = relationship(
|
workspace_memberships = relationship(
|
||||||
"SearchSpaceMembership",
|
"WorkspaceMembership",
|
||||||
back_populates="user",
|
back_populates="user",
|
||||||
cascade="all, delete-orphan",
|
cascade="all, delete-orphan",
|
||||||
)
|
)
|
||||||
created_invites = relationship(
|
created_invites = relationship(
|
||||||
"SearchSpaceInvite",
|
"WorkspaceInvite",
|
||||||
back_populates="created_by",
|
back_populates="created_by",
|
||||||
passive_deletes=True,
|
passive_deletes=True,
|
||||||
)
|
)
|
||||||
|
|
@ -2387,7 +2371,7 @@ if config.AUTH_TYPE == "GOOGLE":
|
||||||
else:
|
else:
|
||||||
|
|
||||||
class User(SQLAlchemyBaseUserTableUUID, Base):
|
class User(SQLAlchemyBaseUserTableUUID, Base):
|
||||||
search_spaces = relationship("SearchSpace", back_populates="user")
|
workspaces = relationship("Workspace", back_populates="user")
|
||||||
notifications = relationship(
|
notifications = relationship(
|
||||||
"Notification",
|
"Notification",
|
||||||
back_populates="user",
|
back_populates="user",
|
||||||
|
|
@ -2396,13 +2380,13 @@ else:
|
||||||
)
|
)
|
||||||
|
|
||||||
# RBAC relationships
|
# RBAC relationships
|
||||||
search_space_memberships = relationship(
|
workspace_memberships = relationship(
|
||||||
"SearchSpaceMembership",
|
"WorkspaceMembership",
|
||||||
back_populates="user",
|
back_populates="user",
|
||||||
cascade="all, delete-orphan",
|
cascade="all, delete-orphan",
|
||||||
)
|
)
|
||||||
created_invites = relationship(
|
created_invites = relationship(
|
||||||
"SearchSpaceInvite",
|
"WorkspaceInvite",
|
||||||
back_populates="created_by",
|
back_populates="created_by",
|
||||||
passive_deletes=True,
|
passive_deletes=True,
|
||||||
)
|
)
|
||||||
|
|
@ -2550,8 +2534,7 @@ class AgentActionLog(BaseModel):
|
||||||
nullable=True,
|
nullable=True,
|
||||||
index=True,
|
index=True,
|
||||||
)
|
)
|
||||||
search_space_id = Column(
|
workspace_id = Column(
|
||||||
"workspace_id",
|
|
||||||
Integer,
|
Integer,
|
||||||
ForeignKey("workspaces.id", ondelete="CASCADE"),
|
ForeignKey("workspaces.id", ondelete="CASCADE"),
|
||||||
nullable=False,
|
nullable=False,
|
||||||
|
|
@ -2622,8 +2605,7 @@ class DocumentRevision(BaseModel):
|
||||||
nullable=True,
|
nullable=True,
|
||||||
index=True,
|
index=True,
|
||||||
)
|
)
|
||||||
search_space_id = Column(
|
workspace_id = Column(
|
||||||
"workspace_id",
|
|
||||||
Integer,
|
Integer,
|
||||||
ForeignKey("workspaces.id", ondelete="CASCADE"),
|
ForeignKey("workspaces.id", ondelete="CASCADE"),
|
||||||
nullable=False,
|
nullable=False,
|
||||||
|
|
@ -2664,8 +2646,7 @@ class FolderRevision(BaseModel):
|
||||||
nullable=True,
|
nullable=True,
|
||||||
index=True,
|
index=True,
|
||||||
)
|
)
|
||||||
search_space_id = Column(
|
workspace_id = Column(
|
||||||
"workspace_id",
|
|
||||||
Integer,
|
Integer,
|
||||||
ForeignKey("workspaces.id", ondelete="CASCADE"),
|
ForeignKey("workspaces.id", ondelete="CASCADE"),
|
||||||
nullable=False,
|
nullable=False,
|
||||||
|
|
@ -2693,7 +2674,7 @@ class FolderRevision(BaseModel):
|
||||||
class AgentPermissionRule(BaseModel):
|
class AgentPermissionRule(BaseModel):
|
||||||
"""Persistent permission rule consumed by :class:`PermissionMiddleware`.
|
"""Persistent permission rule consumed by :class:`PermissionMiddleware`.
|
||||||
|
|
||||||
Scoped at one of: search-space-wide (``user_id`` and ``thread_id`` NULL),
|
Scoped at one of: workspace-wide (``user_id`` and ``thread_id`` NULL),
|
||||||
user-wide (``user_id`` set, ``thread_id`` NULL), or per-thread
|
user-wide (``user_id`` set, ``thread_id`` NULL), or per-thread
|
||||||
(``thread_id`` set). Loaded at agent build time and converted to
|
(``thread_id`` set). Loaded at agent build time and converted to
|
||||||
:class:`Rule` instances inside the agent factory.
|
:class:`Rule` instances inside the agent factory.
|
||||||
|
|
@ -2701,8 +2682,7 @@ class AgentPermissionRule(BaseModel):
|
||||||
|
|
||||||
__tablename__ = "agent_permission_rules"
|
__tablename__ = "agent_permission_rules"
|
||||||
|
|
||||||
search_space_id = Column(
|
workspace_id = Column(
|
||||||
"workspace_id",
|
|
||||||
Integer,
|
Integer,
|
||||||
ForeignKey("workspaces.id", ondelete="CASCADE"),
|
ForeignKey("workspaces.id", ondelete="CASCADE"),
|
||||||
nullable=False,
|
nullable=False,
|
||||||
|
|
@ -3080,10 +3060,10 @@ def has_all_permissions(
|
||||||
def get_default_roles_config() -> list[dict]:
|
def get_default_roles_config() -> list[dict]:
|
||||||
"""
|
"""
|
||||||
Get the configuration for default system roles.
|
Get the configuration for default system roles.
|
||||||
These roles are created automatically when a search space is created.
|
These roles are created automatically when a workspace is created.
|
||||||
|
|
||||||
Only 3 roles are supported:
|
Only 3 roles are supported:
|
||||||
- Owner: Full access to everything (assigned to search space creator)
|
- Owner: Full access to everything (assigned to workspace creator)
|
||||||
- Editor: Can create/update content but cannot delete, manage roles, or change settings
|
- Editor: Can create/update content but cannot delete, manage roles, or change settings
|
||||||
- Viewer: Read-only access to resources (can add comments)
|
- Viewer: Read-only access to resources (can add comments)
|
||||||
|
|
||||||
|
|
@ -3093,7 +3073,7 @@ def get_default_roles_config() -> list[dict]:
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
"name": "Owner",
|
"name": "Owner",
|
||||||
"description": "Full access to all search space resources and settings",
|
"description": "Full access to all workspace resources and settings",
|
||||||
"permissions": DEFAULT_ROLE_PERMISSIONS["Owner"],
|
"permissions": DEFAULT_ROLE_PERMISSIONS["Owner"],
|
||||||
"is_default": False,
|
"is_default": False,
|
||||||
"is_system_role": True,
|
"is_system_role": True,
|
||||||
|
|
@ -3107,7 +3087,7 @@ def get_default_roles_config() -> list[dict]:
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Viewer",
|
"name": "Viewer",
|
||||||
"description": "Read-only access to search space resources",
|
"description": "Read-only access to workspace resources",
|
||||||
"permissions": DEFAULT_ROLE_PERMISSIONS["Viewer"],
|
"permissions": DEFAULT_ROLE_PERMISSIONS["Viewer"],
|
||||||
"is_default": False,
|
"is_default": False,
|
||||||
"is_system_role": True,
|
"is_system_role": True,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue