baseline: Clover 独立仓库首次基线提交
将 Clover 从上层产品包旧仓库中独立出来,建立专属版本控制。 当前状态=纵切片端到端已打通(登录→选品→出文出图→审核→下载包), M1文案质量去套路化已验收。此提交作为后续按核销清单逐条修复的基线。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
88
backend/app/models/workspace.py
Normal file
88
backend/app/models/workspace.py
Normal file
@@ -0,0 +1,88 @@
|
||||
"""
|
||||
app/models/workspace.py — workspaces / workspace_members / user_api_keys
|
||||
Alembic 002:多租户基础,全新建。
|
||||
matrix_accounts 二期预留,一期不建。
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import (
|
||||
BigInteger, DateTime, Enum, ForeignKey,
|
||||
Index, String, UniqueConstraint, func,
|
||||
)
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from app.constants.enums import UserRole
|
||||
from app.core.database import Base
|
||||
|
||||
|
||||
class Workspace(Base):
|
||||
__tablename__ = "workspaces"
|
||||
|
||||
id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True)
|
||||
name: Mapped[str] = mapped_column(String(128), nullable=False)
|
||||
slug: Mapped[str] = mapped_column(String(64), unique=True, nullable=False)
|
||||
is_active: Mapped[bool] = mapped_column(default=True, nullable=False)
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime, server_default=func.now(), nullable=False
|
||||
)
|
||||
|
||||
members: Mapped[list["WorkspaceMember"]] = relationship(
|
||||
back_populates="workspace", lazy="noload"
|
||||
)
|
||||
|
||||
|
||||
class WorkspaceMember(Base):
|
||||
__tablename__ = "workspace_members"
|
||||
|
||||
id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True)
|
||||
workspace_id: Mapped[int] = mapped_column(
|
||||
BigInteger, ForeignKey("workspaces.id", ondelete="CASCADE"), nullable=False
|
||||
)
|
||||
user_id: Mapped[int] = mapped_column(
|
||||
BigInteger, ForeignKey("users.id", ondelete="CASCADE"), nullable=False
|
||||
)
|
||||
role: Mapped[str] = mapped_column(
|
||||
Enum(UserRole, values_callable=lambda x: [e.value for e in x]),
|
||||
nullable=False,
|
||||
)
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime, server_default=func.now(), nullable=False
|
||||
)
|
||||
|
||||
workspace: Mapped["Workspace"] = relationship(back_populates="members")
|
||||
|
||||
__table_args__ = (
|
||||
UniqueConstraint("workspace_id", "user_id", name="uq_workspace_member"),
|
||||
)
|
||||
|
||||
|
||||
class UserApiKey(Base):
|
||||
"""
|
||||
个人 API Key(Fernet 加密)。
|
||||
encrypted_key 只存密文,不存 url(token站固定自家站)。
|
||||
UNIQUE(user_id, workspace_id, provider)。
|
||||
"""
|
||||
__tablename__ = "user_api_keys"
|
||||
|
||||
id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True)
|
||||
user_id: Mapped[int] = mapped_column(
|
||||
BigInteger, ForeignKey("users.id", ondelete="CASCADE"), nullable=False
|
||||
)
|
||||
workspace_id: Mapped[int] = mapped_column(
|
||||
BigInteger, ForeignKey("workspaces.id", ondelete="CASCADE"), nullable=False
|
||||
)
|
||||
provider: Mapped[str] = mapped_column(String(32), nullable=False)
|
||||
encrypted_key: Mapped[str] = mapped_column(String(512), nullable=False)
|
||||
key_last4: Mapped[str] = mapped_column(String(4), nullable=False)
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime, server_default=func.now(), nullable=False
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
UniqueConstraint(
|
||||
"user_id", "workspace_id", "provider",
|
||||
name="uq_user_workspace_provider",
|
||||
),
|
||||
Index("idx_user_api_keys_workspace_id", "workspace_id"),
|
||||
)
|
||||
Reference in New Issue
Block a user