feat(workspace): restore core routes on sqlalchemy adapters
Deploy / Deploy Production (push) Failing after 2094h6m31s
CI/CD Pipeline / Validate Code Quality And Tests (push) Failing after 2094h8m39s
Deploy / Deploy Staging (push) Has been cancelled

This commit is contained in:
Xiaoxia AI
2026-06-21 09:22:09 +08:00
parent 2e863c8b43
commit 38b5b60160
11 changed files with 806 additions and 361 deletions
+43 -1
View File
@@ -1,6 +1,6 @@
from datetime import datetime, timezone
from sqlalchemy import Boolean, Column, DateTime, Float, String, Text, create_engine
from sqlalchemy import Boolean, Column, DateTime, Float, String, Text, UniqueConstraint, create_engine
from sqlalchemy.orm import declarative_base
Base = declarative_base()
@@ -23,6 +23,48 @@ class UserModel(Base):
created_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc))
class WorkspaceModel(Base):
__tablename__ = "workspaces"
id = Column(String(36), primary_key=True)
name = Column(String(100), nullable=False)
owner_user_id = Column(String(36), nullable=False, index=True)
subscription_plan = Column(String(20), nullable=False, default="free")
subscription_status = Column(String(20), nullable=False, default="active")
subscription_expires_at = Column(DateTime, nullable=True)
max_projects = Column(Float, nullable=False, default=3)
max_storage_gb = Column(Float, nullable=False, default=10)
used_storage_gb = Column(Float, nullable=False, default=0.0)
created_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc))
class WorkspaceMemberModel(Base):
__tablename__ = "workspace_members"
__table_args__ = (UniqueConstraint("workspace_id", "user_id", name="uq_workspace_members_workspace_user"),)
id = Column(String(36), primary_key=True)
workspace_id = Column(String(36), nullable=False, index=True)
user_id = Column(String(36), nullable=False, index=True)
role = Column(String(20), nullable=False)
invited_by = Column(String(36), nullable=True)
joined_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc))
class WorkspaceInvitationModel(Base):
__tablename__ = "workspace_invitations"
id = Column(String(36), primary_key=True)
workspace_id = Column(String(36), nullable=False, index=True)
inviter_user_id = Column(String(36), nullable=False)
invitee_email = Column(String(255), nullable=False, index=True)
role = Column(String(20), nullable=False)
invitation_token = Column(String(255), nullable=False, unique=True, index=True)
status = Column(String(20), nullable=False, default="pending", index=True)
expires_at = Column(DateTime, nullable=True)
accepted_at = Column(DateTime, nullable=True)
created_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc))
class ProjectModel(Base):
__tablename__ = "projects"
@@ -0,0 +1,91 @@
from __future__ import annotations
from sqlalchemy.orm import Session
from packages.adapters.sqlalchemy_impl.models import WorkspaceMemberModel
from packages.domain.entities import WorkspaceMember
from packages.ports.workspace_member_repository import WorkspaceMemberRepository
class SQLAlchemyWorkspaceMemberRepository(WorkspaceMemberRepository):
def __init__(self, session: Session):
self.session = session
def save(self, member: WorkspaceMember) -> None:
model = self.session.get(WorkspaceMemberModel, member.id)
if model is None:
model = (
self.session.query(WorkspaceMemberModel)
.filter(
WorkspaceMemberModel.workspace_id == member.workspace_id,
WorkspaceMemberModel.user_id == member.user_id,
)
.first()
)
if model is None:
model = WorkspaceMemberModel(id=member.id)
self.session.add(model)
model.workspace_id = member.workspace_id
model.user_id = member.user_id
model.role = str(member.role)
model.invited_by = member.invited_by
model.joined_at = member.joined_at
self.session.commit()
self.session.refresh(model)
def find_by_id(self, member_id: str) -> WorkspaceMember | None:
return self._to_entity(self.session.get(WorkspaceMemberModel, member_id))
def find_by_workspace_and_user(self, workspace_id: str, user_id: str) -> WorkspaceMember | None:
model = (
self.session.query(WorkspaceMemberModel)
.filter(WorkspaceMemberModel.workspace_id == workspace_id, WorkspaceMemberModel.user_id == user_id)
.first()
)
return self._to_entity(model)
def find_by_user(self, user_id: str) -> list[WorkspaceMember]:
models = (
self.session.query(WorkspaceMemberModel)
.filter(WorkspaceMemberModel.user_id == user_id)
.order_by(WorkspaceMemberModel.joined_at.desc())
.all()
)
return [self._to_entity(model) for model in models if model is not None]
def find_by_workspace(self, workspace_id: str) -> list[WorkspaceMember]:
models = (
self.session.query(WorkspaceMemberModel)
.filter(WorkspaceMemberModel.workspace_id == workspace_id)
.order_by(WorkspaceMemberModel.joined_at.asc())
.all()
)
return [self._to_entity(model) for model in models if model is not None]
def count_by_workspace(self, workspace_id: str) -> int:
return (
self.session.query(WorkspaceMemberModel).filter(WorkspaceMemberModel.workspace_id == workspace_id).count()
)
def delete(self, member_id: str) -> bool:
model = self.session.get(WorkspaceMemberModel, member_id)
if model is None:
return False
self.session.delete(model)
self.session.commit()
return True
@staticmethod
def _to_entity(model: WorkspaceMemberModel | None) -> WorkspaceMember | None:
if model is None:
return None
return WorkspaceMember(
id=model.id,
workspace_id=model.workspace_id,
user_id=model.user_id,
role=model.role,
invited_by=model.invited_by,
joined_at=model.joined_at,
)
@@ -0,0 +1,59 @@
from __future__ import annotations
from sqlalchemy.orm import Session
from packages.adapters.sqlalchemy_impl.models import WorkspaceModel
from packages.domain.entities import Workspace
from packages.ports.workspace_repository import WorkspaceRepository
class SQLAlchemyWorkspaceRepository(WorkspaceRepository):
def __init__(self, session: Session):
self.session = session
def save(self, workspace: Workspace) -> None:
model = self.session.get(WorkspaceModel, workspace.id)
if model is None:
model = WorkspaceModel(id=workspace.id)
self.session.add(model)
model.name = workspace.name
model.owner_user_id = workspace.owner_user_id
model.subscription_plan = workspace.subscription_plan
model.subscription_status = workspace.subscription_status
model.subscription_expires_at = workspace.subscription_expires_at
model.max_projects = workspace.max_projects
model.max_storage_gb = workspace.max_storage_gb
model.used_storage_gb = workspace.used_storage_gb
model.created_at = workspace.created_at
self.session.commit()
self.session.refresh(model)
def find_by_id(self, workspace_id: str) -> Workspace | None:
return self._to_entity(self.session.get(WorkspaceModel, workspace_id))
def delete(self, workspace_id: str) -> bool:
model = self.session.get(WorkspaceModel, workspace_id)
if model is None:
return False
self.session.delete(model)
self.session.commit()
return True
@staticmethod
def _to_entity(model: WorkspaceModel | None) -> Workspace | None:
if model is None:
return None
return Workspace(
id=model.id,
name=model.name,
owner_user_id=model.owner_user_id,
subscription_plan=model.subscription_plan,
subscription_status=model.subscription_status,
subscription_expires_at=model.subscription_expires_at,
max_projects=int(model.max_projects),
max_storage_gb=int(model.max_storage_gb),
used_storage_gb=float(model.used_storage_gb),
created_at=model.created_at,
)