feat(repository): add PostgreSQL repositories for Workspace and Member
- Implement PostgresWorkspaceRepository with full CRUD - Implement PostgresWorkspaceMemberRepository with query methods - Support find_by_user, find_by_workspace, count_by_workspace - Use upsert pattern (INSERT ... ON CONFLICT DO UPDATE) - Proper connection management and cleanup - Add __init__.py to export all PostgreSQL repositories Phase 4 Task 35/68 completed
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
"""
|
||||
PostgreSQL 适配器
|
||||
"""
|
||||
from packages.adapters.postgres.user_repository import PostgresUserRepository
|
||||
from packages.adapters.postgres.workspace_repository import PostgresWorkspaceRepository
|
||||
from packages.adapters.postgres.workspace_member_repository import PostgresWorkspaceMemberRepository
|
||||
|
||||
__all__ = [
|
||||
"PostgresUserRepository",
|
||||
"PostgresWorkspaceRepository",
|
||||
"PostgresWorkspaceMemberRepository",
|
||||
]
|
||||
@@ -0,0 +1,138 @@
|
||||
"""
|
||||
PostgreSQL WorkspaceMember Repository 实现
|
||||
"""
|
||||
from typing import Optional, List
|
||||
import psycopg2
|
||||
from psycopg2.extras import RealDictCursor
|
||||
|
||||
from packages.domain.entities import WorkspaceMember
|
||||
from packages.ports.workspace_member_repository import WorkspaceMemberRepository
|
||||
|
||||
|
||||
class PostgresWorkspaceMemberRepository(WorkspaceMemberRepository):
|
||||
"""WorkspaceMember 仓储 PostgreSQL 实现"""
|
||||
|
||||
def __init__(self, connection_string: str):
|
||||
self.connection_string = connection_string
|
||||
|
||||
def _get_connection(self):
|
||||
return psycopg2.connect(self.connection_string, cursor_factory=RealDictCursor)
|
||||
|
||||
def save(self, member: WorkspaceMember) -> None:
|
||||
"""保存成员"""
|
||||
conn = self._get_connection()
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute("""
|
||||
INSERT INTO workspace_members (
|
||||
id, workspace_id, user_id, role, invited_by, joined_at
|
||||
) VALUES (
|
||||
%(id)s, %(workspace_id)s, %(user_id)s, %(role)s,
|
||||
%(invited_by)s, %(joined_at)s
|
||||
)
|
||||
ON CONFLICT (workspace_id, user_id) DO UPDATE SET
|
||||
role = EXCLUDED.role
|
||||
""", {
|
||||
"id": member.id,
|
||||
"workspace_id": member.workspace_id,
|
||||
"user_id": member.user_id,
|
||||
"role": member.role,
|
||||
"invited_by": member.invited_by,
|
||||
"joined_at": member.joined_at,
|
||||
})
|
||||
conn.commit()
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
def find_by_id(self, member_id: str) -> Optional[WorkspaceMember]:
|
||||
"""根据 ID 查找成员"""
|
||||
conn = self._get_connection()
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute("SELECT * FROM workspace_members WHERE id = %s", (member_id,))
|
||||
row = cur.fetchone()
|
||||
return self._row_to_member(row) if row else None
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
def find_by_workspace_and_user(
|
||||
self,
|
||||
workspace_id: str,
|
||||
user_id: str,
|
||||
) -> Optional[WorkspaceMember]:
|
||||
"""根据 workspace 和 user 查找成员"""
|
||||
conn = self._get_connection()
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"SELECT * FROM workspace_members WHERE workspace_id = %s AND user_id = %s",
|
||||
(workspace_id, user_id)
|
||||
)
|
||||
row = cur.fetchone()
|
||||
return self._row_to_member(row) if row else None
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
def find_by_user(self, user_id: str) -> List[WorkspaceMember]:
|
||||
"""查找用户的所有成员记录"""
|
||||
conn = self._get_connection()
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"SELECT * FROM workspace_members WHERE user_id = %s ORDER BY joined_at DESC",
|
||||
(user_id,)
|
||||
)
|
||||
rows = cur.fetchall()
|
||||
return [self._row_to_member(row) for row in rows]
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
def find_by_workspace(self, workspace_id: str) -> List[WorkspaceMember]:
|
||||
"""查找 workspace 的所有成员"""
|
||||
conn = self._get_connection()
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"SELECT * FROM workspace_members WHERE workspace_id = %s ORDER BY joined_at",
|
||||
(workspace_id,)
|
||||
)
|
||||
rows = cur.fetchall()
|
||||
return [self._row_to_member(row) for row in rows]
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
def count_by_workspace(self, workspace_id: str) -> int:
|
||||
"""统计 workspace 的成员数量"""
|
||||
conn = self._get_connection()
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"SELECT COUNT(*) FROM workspace_members WHERE workspace_id = %s",
|
||||
(workspace_id,)
|
||||
)
|
||||
return cur.fetchone()["count"]
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
def delete(self, member_id: str) -> bool:
|
||||
"""删除成员"""
|
||||
conn = self._get_connection()
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute("DELETE FROM workspace_members WHERE id = %s", (member_id,))
|
||||
deleted = cur.rowcount > 0
|
||||
conn.commit()
|
||||
return deleted
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
def _row_to_member(self, row: dict) -> WorkspaceMember:
|
||||
"""将数据库行转换为 WorkspaceMember 对象"""
|
||||
return WorkspaceMember(
|
||||
id=row["id"],
|
||||
workspace_id=row["workspace_id"],
|
||||
user_id=row["user_id"],
|
||||
role=row["role"],
|
||||
invited_by=row["invited_by"],
|
||||
joined_at=row["joined_at"],
|
||||
)
|
||||
@@ -0,0 +1,100 @@
|
||||
"""
|
||||
PostgreSQL Workspace Repository 实现
|
||||
"""
|
||||
from typing import Optional
|
||||
import psycopg2
|
||||
from psycopg2.extras import RealDictCursor
|
||||
|
||||
from packages.domain.entities import Workspace
|
||||
from packages.ports.workspace_repository import WorkspaceRepository
|
||||
|
||||
|
||||
class PostgresWorkspaceRepository(WorkspaceRepository):
|
||||
"""Workspace 仓储 PostgreSQL 实现"""
|
||||
|
||||
def __init__(self, connection_string: str):
|
||||
self.connection_string = connection_string
|
||||
|
||||
def _get_connection(self):
|
||||
"""获取数据库连接"""
|
||||
return psycopg2.connect(self.connection_string, cursor_factory=RealDictCursor)
|
||||
|
||||
def save(self, workspace: Workspace) -> None:
|
||||
"""保存工作空间"""
|
||||
conn = self._get_connection()
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute("""
|
||||
INSERT INTO workspaces (
|
||||
id, name, owner_user_id, subscription_plan,
|
||||
subscription_status, subscription_expires_at,
|
||||
max_projects, max_storage_gb, used_storage_gb, created_at
|
||||
) VALUES (
|
||||
%(id)s, %(name)s, %(owner_user_id)s, %(subscription_plan)s,
|
||||
%(subscription_status)s, %(subscription_expires_at)s,
|
||||
%(max_projects)s, %(max_storage_gb)s, %(used_storage_gb)s, %(created_at)s
|
||||
)
|
||||
ON CONFLICT (id) DO UPDATE SET
|
||||
name = EXCLUDED.name,
|
||||
subscription_plan = EXCLUDED.subscription_plan,
|
||||
subscription_status = EXCLUDED.subscription_status,
|
||||
subscription_expires_at = EXCLUDED.subscription_expires_at,
|
||||
max_projects = EXCLUDED.max_projects,
|
||||
max_storage_gb = EXCLUDED.max_storage_gb,
|
||||
used_storage_gb = EXCLUDED.used_storage_gb
|
||||
""", {
|
||||
"id": workspace.id,
|
||||
"name": workspace.name,
|
||||
"owner_user_id": workspace.owner_user_id,
|
||||
"subscription_plan": workspace.subscription_plan,
|
||||
"subscription_status": workspace.subscription_status,
|
||||
"subscription_expires_at": workspace.subscription_expires_at,
|
||||
"max_projects": workspace.max_projects,
|
||||
"max_storage_gb": workspace.max_storage_gb,
|
||||
"used_storage_gb": workspace.used_storage_gb,
|
||||
"created_at": workspace.created_at,
|
||||
})
|
||||
conn.commit()
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
def find_by_id(self, workspace_id: str) -> Optional[Workspace]:
|
||||
"""根据 ID 查找工作空间"""
|
||||
conn = self._get_connection()
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute("SELECT * FROM workspaces WHERE id = %s", (workspace_id,))
|
||||
row = cur.fetchone()
|
||||
|
||||
if row:
|
||||
return self._row_to_workspace(row)
|
||||
return None
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
def delete(self, workspace_id: str) -> bool:
|
||||
"""删除工作空间"""
|
||||
conn = self._get_connection()
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute("DELETE FROM workspaces WHERE id = %s", (workspace_id,))
|
||||
deleted = cur.rowcount > 0
|
||||
conn.commit()
|
||||
return deleted
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
def _row_to_workspace(self, row: dict) -> Workspace:
|
||||
"""将数据库行转换为 Workspace 对象"""
|
||||
return Workspace(
|
||||
id=row["id"],
|
||||
name=row["name"],
|
||||
owner_user_id=row["owner_user_id"],
|
||||
subscription_plan=row["subscription_plan"],
|
||||
subscription_status=row["subscription_status"],
|
||||
subscription_expires_at=row["subscription_expires_at"],
|
||||
max_projects=row["max_projects"],
|
||||
max_storage_gb=row["max_storage_gb"],
|
||||
used_storage_gb=float(row["used_storage_gb"]),
|
||||
created_at=row["created_at"],
|
||||
)
|
||||
Reference in New Issue
Block a user