feat(persistence): add WorkspaceRow ORM model

新建 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) <noreply@anthropic.com>
This commit is contained in:
1445043649
2026-05-12 21:07:30 +08:00
parent f63089aea8
commit 8323bf68d2
3 changed files with 77 additions and 1 deletions
@@ -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"]
@@ -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"]
@@ -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="工作空间显示名(用户注册时默认 <email 前缀>'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。")},)