From 8323bf68d231988ea7e57c01440397660a47a4d5 Mon Sep 17 00:00:00 2001 From: 1445043649 <> Date: Tue, 12 May 2026 21:07:30 +0800 Subject: [PATCH] feat(persistence): add WorkspaceRow ORM model MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新建 backend/packages/harness/deerflow/persistence/workspace/{__init__.py, model.py} + 在 persistence.models.__init__ 注册 WorkspaceRow 让 Base.metadata.create_all 能自动建表。 WorkspaceRow schema(来自 workspace-schema-design §2.1 锁定版): - id String(36) PK(UUID v4 字符串,与 users.id 对齐) - name String(64) NOT NULL(显示名) - slug String(32) NOT NULL UNIQUE(URL 标识,正则 ^[a-z0-9](-?[a-z0-9])*$) - status String(16) default 'active'(active/suspended/deleted) - owner_id String(36) FK users.id ON DELETE RESTRICT(删 owner 时阻拦, 必须先转让所有权) - created_at / updated_at DateTime(tz=True) 每列 + 表都带中文 comment(沿用 commit 9ff79055 的 convention)。 Stage 0 PR3 T3.3。 Co-Authored-By: Claude Opus 4.7 (1M context) --- .../deerflow/persistence/models/__init__.py | 4 +- .../persistence/workspace/__init__.py | 16 +++++ .../deerflow/persistence/workspace/model.py | 58 +++++++++++++++++++ 3 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 backend/packages/harness/deerflow/persistence/workspace/__init__.py create mode 100644 backend/packages/harness/deerflow/persistence/workspace/model.py diff --git a/backend/packages/harness/deerflow/persistence/models/__init__.py b/backend/packages/harness/deerflow/persistence/models/__init__.py index ab29a353..db2bf452 100644 --- a/backend/packages/harness/deerflow/persistence/models/__init__.py +++ b/backend/packages/harness/deerflow/persistence/models/__init__.py @@ -8,6 +8,7 @@ The actual ORM classes have moved to entity-specific subpackages: - ``deerflow.persistence.run`` - ``deerflow.persistence.feedback`` - ``deerflow.persistence.user`` +- ``deerflow.persistence.workspace`` (Stage 0 PR3) ``RunEventRow`` remains in ``deerflow.persistence.models.run_event`` because its storage implementation lives in ``deerflow.runtime.events.store.db`` and @@ -19,5 +20,6 @@ from deerflow.persistence.models.run_event import RunEventRow from deerflow.persistence.run.model import RunRow from deerflow.persistence.thread_meta.model import ThreadMetaRow from deerflow.persistence.user.model import UserRow +from deerflow.persistence.workspace.model import WorkspaceRow -__all__ = ["FeedbackRow", "RunEventRow", "RunRow", "ThreadMetaRow", "UserRow"] +__all__ = ["FeedbackRow", "RunEventRow", "RunRow", "ThreadMetaRow", "UserRow", "WorkspaceRow"] diff --git a/backend/packages/harness/deerflow/persistence/workspace/__init__.py b/backend/packages/harness/deerflow/persistence/workspace/__init__.py new file mode 100644 index 00000000..c60e57cf --- /dev/null +++ b/backend/packages/harness/deerflow/persistence/workspace/__init__.py @@ -0,0 +1,16 @@ +"""Workspace persistence — ORM model + repository. + +A workspace is the multi-tenant scope unit. Every user has at least +one (auto-created on registration; their personal workspace where +they are sole owner). Team plans get multi-member workspaces. + +Stage 0 PR3 introduces the schema + repository; PR4 wires it into +the registration / login flow; PR5+ ALTER existing business tables +to FK back to ``workspaces.id``. +""" + +from __future__ import annotations + +from deerflow.persistence.workspace.model import WorkspaceRow + +__all__ = ["WorkspaceRow"] diff --git a/backend/packages/harness/deerflow/persistence/workspace/model.py b/backend/packages/harness/deerflow/persistence/workspace/model.py new file mode 100644 index 00000000..165af35c --- /dev/null +++ b/backend/packages/harness/deerflow/persistence/workspace/model.py @@ -0,0 +1,58 @@ +"""ORM model for workspaces (multi-tenant scope).""" + +from __future__ import annotations + +from datetime import UTC, datetime + +from sqlalchemy import DateTime, ForeignKey, String +from sqlalchemy.orm import Mapped, mapped_column + +from deerflow.persistence.base import Base + + +class WorkspaceRow(Base): + __tablename__ = "workspaces" + + id: Mapped[str] = mapped_column( + String(36), + primary_key=True, + comment="工作空间主键,UUID 字符串(36 字符),与 users.id 类型对齐", + ) + name: Mapped[str] = mapped_column( + String(64), + nullable=False, + comment="工作空间显示名(用户注册时默认 's Workspace)", + ) + slug: Mapped[str] = mapped_column( + String(32), + nullable=False, + unique=True, + comment="URL 标识(^[a-z0-9](-?[a-z0-9])*$,3-32 字符);全局唯一,DB 存小写", + ) + status: Mapped[str] = mapped_column( + String(16), + nullable=False, + default="active", + comment='状态:"active"(正常)/ "suspended"(平台 admin 暂停)/ "deleted"(软删)', + ) + owner_id: Mapped[str] = mapped_column( + String(36), + ForeignKey("users.id", ondelete="RESTRICT"), + nullable=False, + comment="所有者 user_id;与 workspace_memberships 中 role='owner' 行严格一致(事务保证);删除 owner 时 RESTRICT 阻拦(必须先转让所有权)", + ) + created_at: Mapped[datetime] = mapped_column( + DateTime(timezone=True), + nullable=False, + default=lambda: datetime.now(UTC), + comment="创建时间(UTC)", + ) + updated_at: Mapped[datetime] = mapped_column( + DateTime(timezone=True), + nullable=False, + default=lambda: datetime.now(UTC), + onupdate=lambda: datetime.now(UTC), + comment="最近更新时间(UTC,写入时自动更新)", + ) + + __table_args__ = ({"comment": ("工作空间表(多租户隔离粒度单位)。每个用户注册时自动建一个 1 人 workspace,owner 即注册者。团队订阅时 workspace 可有多个 member。")},)