fix: resolve all workspace removal breakage - stub permissions, quota, ports, adapters
CI/CD Pipeline / Validate Code Quality And Tests (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Lint (pull_request) Has been cancelled

- Remove Workspace from domain/__init__.py exports
- Stub domain/permissions.py (all checks pass)
- Stub domain/quota.py (all checks pass)
- Create missing port stubs: workspace_member_repository, workspace_repository, workspace_invitation_repository
- Create missing adapter stubs: workspace_repository, workspace_invitation_repository
- Create routes/permissions.py stub (require_workspace_member no-op)
- Fix .env Redis URL encoding on production server
This commit is contained in:
CI Bot
2026-06-27 09:16:53 +08:00
parent 019cb195a7
commit 16043717b8
13 changed files with 92 additions and 420 deletions
+8
View File
@@ -0,0 +1,8 @@
# Compatibility module - workspace concept has been removed
# This is a stub to maintain backward compatibility
def require_workspace_member(workspace_id, authenticated_user, workspace_member_repository):
"""Stub function for backward compatibility.
Workspace concept has been removed.
"""
pass
+1 -2
View File
@@ -68,8 +68,7 @@ const Header: React.FC = () => {
<nav className="xx-nav-links">
{navItems.map((item) => {
const active = item.path === '/' ? location.pathname === '/' : false
: location.pathname.startsWith(item.path);
const active = (item.path === '/' && location.pathname === '/') || location.pathname.startsWith(item.path);
return (
<button
key={item.key}
+3 -2
View File
@@ -1,10 +1,11 @@
import os
from celery import Celery
def create_celery_app() -> Celery:
app = Celery("xiaoxia_saas_worker")
app.conf.broker_url = "redis://redis:6379/0"
app.conf.result_backend = "redis://redis:6379/1"
app.conf.broker_url = os.getenv("CELERY_BROKER_URL", "redis://redis:6379/0")
app.conf.result_backend = os.getenv("CELERY_RESULT_BACKEND", "redis://redis:6379/1")
return app
+1 -1
View File
@@ -22,7 +22,7 @@ WORKDIR /app
COPY requirements.txt /tmp/requirements.txt
# 安装 Python 包
RUN pip install --no-cache-dir -r /tmp/requirements.txt
RUN pip install --no-cache-dir -i https://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com -r /tmp/requirements.txt
# 复制应用代码
COPY apps/worker/ /app/apps/worker/
@@ -0,0 +1,5 @@
"""Stub adapter for workspace invitation repository - workspace concept removed."""
class SQLAlchemyWorkspaceInvitationRepository:
def __init__(self, session):
self.session = session
@@ -0,0 +1,5 @@
"""Stub adapter for workspace repository - workspace concept removed."""
class SQLAlchemyWorkspaceRepository:
def __init__(self, session):
self.session = session
-2
View File
@@ -15,7 +15,6 @@ from .entities import (
IngestJobStatus,
Project,
User,
Workspace,
)
from .generated_video import GeneratedVideo
from .generation_task import GenerationTask, GenerationTaskStatus
@@ -42,5 +41,4 @@ __all__ = [
"TaskPriority",
"TaskStatus",
"User",
"Workspace",
]
+32 -261
View File
@@ -1,303 +1,74 @@
"""
权限验证辅助函数
用于检查用户在工作空间中的权限
Permissions module - stub implementation.
Workspace concept has been removed. All permission checks pass by default.
"""
from typing import Optional
from packages.domain.entities import WorkspaceMemberRole
class WorkspaceMemberRole:
"""Stub enum - workspace concept removed."""
OWNER = "owner"
ADMIN = "admin"
MEMBER = "member"
VIEWER = "viewer"
class PermissionChecker:
"""权限检查器"""
"""Stub permission checker - all checks pass since workspace is removed."""
def __init__(self, workspace_member_repository):
def __init__(self, workspace_member_repository=None):
self.workspace_member_repository = workspace_member_repository
def check_workspace_access(
self,
workspace_id: str,
user_id: str,
) -> tuple[bool, Optional[str]]:
"""
检查用户是否可以访问工作空间
def check_workspace_access(self, workspace_id, user_id):
return True, "owner"
Args:
workspace_id: 工作空间 ID
user_id: 用户 ID
def check_is_owner(self, workspace_id, user_id):
return True
Returns:
(是否有权限, 用户角色)
"""
member = self.workspace_member_repository.find_by_workspace_and_user(
workspace_id,
user_id,
)
def check_is_admin_or_owner(self, workspace_id, user_id):
return True
if not member:
return False, None
def check_can_manage_members(self, workspace_id, user_id):
return True
return True, member.role
def check_can_edit_workspace(self, workspace_id, user_id):
return True
def check_is_owner(
self,
workspace_id: str,
user_id: str,
) -> bool:
"""
检查用户是否是工作空间 Owner
def check_can_create_project(self, workspace_id, user_id):
return True
Args:
workspace_id: 工作空间 ID
user_id: 用户 ID
def check_can_edit_project(self, workspace_id, user_id):
return True
Returns:
是否是 Owner
"""
member = self.workspace_member_repository.find_by_workspace_and_user(
workspace_id,
user_id,
)
def check_can_delete_project(self, workspace_id, user_id):
return True
return member is not None and member.role == WorkspaceMemberRole.OWNER
def check_is_admin_or_owner(
self,
workspace_id: str,
user_id: str,
) -> bool:
"""
检查用户是否是工作空间 Admin 或 Owner
Args:
workspace_id: 工作空间 ID
user_id: 用户 ID
Returns:
是否是 Admin 或 Owner
"""
member = self.workspace_member_repository.find_by_workspace_and_user(
workspace_id,
user_id,
)
return member is not None and member.role in [
WorkspaceMemberRole.OWNER,
WorkspaceMemberRole.ADMIN,
]
def check_can_manage_members(
self,
workspace_id: str,
user_id: str,
) -> bool:
"""
检查用户是否可以管理成员(邀请、移除、修改角色)
Args:
workspace_id: 工作空间 ID
user_id: 用户 ID
Returns:
是否可以管理成员
"""
return self.check_is_admin_or_owner(workspace_id, user_id)
def check_can_edit_workspace(
self,
workspace_id: str,
user_id: str,
) -> bool:
"""
检查用户是否可以编辑工作空间(修改名称、设置等)
Args:
workspace_id: 工作空间 ID
user_id: 用户 ID
Returns:
是否可以编辑工作空间
"""
return self.check_is_admin_or_owner(workspace_id, user_id)
def check_can_create_project(
self,
workspace_id: str,
user_id: str,
) -> bool:
"""
检查用户是否可以创建项目
Args:
workspace_id: 工作空间 ID
user_id: 用户 ID
Returns:
是否可以创建项目
"""
member = self.workspace_member_repository.find_by_workspace_and_user(
workspace_id,
user_id,
)
# Owner, Admin, Member 可以创建项目,Viewer 不可以
return member is not None and member.role in [
WorkspaceMemberRole.OWNER,
WorkspaceMemberRole.ADMIN,
WorkspaceMemberRole.MEMBER,
]
def check_can_edit_project(
self,
workspace_id: str,
user_id: str,
) -> bool:
"""
检查用户是否可以编辑项目
Args:
workspace_id: 工作空间 ID
user_id: 用户 ID
Returns:
是否可以编辑项目
"""
# 与创建项目权限相同
return self.check_can_create_project(workspace_id, user_id)
def check_can_delete_project(
self,
workspace_id: str,
user_id: str,
) -> bool:
"""
检查用户是否可以删除项目
Args:
workspace_id: 工作空间 ID
user_id: 用户 ID
Returns:
是否可以删除项目
"""
# 只有 Owner 和 Admin 可以删除项目
return self.check_is_admin_or_owner(workspace_id, user_id)
def check_can_view_workspace(
self,
workspace_id: str,
user_id: str,
) -> bool:
"""
检查用户是否可以查看工作空间
Args:
workspace_id: 工作空间 ID
user_id: 用户 ID
Returns:
是否可以查看工作空间
"""
has_access, _ = self.check_workspace_access(workspace_id, user_id)
return has_access
def check_can_view_workspace(self, workspace_id, user_id):
return True
# 权限级别定义
class Permission:
"""权限常量"""
# 工作空间权限
WORKSPACE_VIEW = "workspace:view"
WORKSPACE_EDIT = "workspace:edit"
WORKSPACE_DELETE = "workspace:delete"
WORKSPACE_MANAGE_SUBSCRIPTION = "workspace:manage_subscription"
# 成员权限
MEMBER_VIEW = "member:view"
MEMBER_INVITE = "member:invite"
MEMBER_REMOVE = "member:remove"
MEMBER_UPDATE_ROLE = "member:update_role"
# 项目权限
PROJECT_VIEW = "project:view"
PROJECT_CREATE = "project:create"
PROJECT_EDIT = "project:edit"
PROJECT_DELETE = "project:delete"
# 资产权限
ASSET_VIEW = "asset:view"
ASSET_UPLOAD = "asset:upload"
ASSET_EDIT = "asset:edit"
ASSET_DELETE = "asset:delete"
# 角色权限映射
ROLE_PERMISSIONS = {
WorkspaceMemberRole.OWNER: [
# 所有权限
Permission.WORKSPACE_VIEW,
Permission.WORKSPACE_EDIT,
Permission.WORKSPACE_DELETE,
Permission.WORKSPACE_MANAGE_SUBSCRIPTION,
Permission.MEMBER_VIEW,
Permission.MEMBER_INVITE,
Permission.MEMBER_REMOVE,
Permission.MEMBER_UPDATE_ROLE,
Permission.PROJECT_VIEW,
Permission.PROJECT_CREATE,
Permission.PROJECT_EDIT,
Permission.PROJECT_DELETE,
Permission.ASSET_VIEW,
Permission.ASSET_UPLOAD,
Permission.ASSET_EDIT,
Permission.ASSET_DELETE,
],
WorkspaceMemberRole.ADMIN: [
Permission.WORKSPACE_VIEW,
Permission.WORKSPACE_EDIT,
Permission.MEMBER_VIEW,
Permission.MEMBER_INVITE,
Permission.MEMBER_REMOVE,
Permission.MEMBER_UPDATE_ROLE,
Permission.PROJECT_VIEW,
Permission.PROJECT_CREATE,
Permission.PROJECT_EDIT,
Permission.PROJECT_DELETE,
Permission.ASSET_VIEW,
Permission.ASSET_UPLOAD,
Permission.ASSET_EDIT,
Permission.ASSET_DELETE,
],
WorkspaceMemberRole.MEMBER: [
Permission.WORKSPACE_VIEW,
Permission.MEMBER_VIEW,
Permission.PROJECT_VIEW,
Permission.PROJECT_CREATE,
Permission.PROJECT_EDIT,
Permission.ASSET_VIEW,
Permission.ASSET_UPLOAD,
Permission.ASSET_EDIT,
Permission.ASSET_DELETE,
],
WorkspaceMemberRole.VIEWER: [
Permission.WORKSPACE_VIEW,
Permission.MEMBER_VIEW,
Permission.PROJECT_VIEW,
Permission.ASSET_VIEW,
],
}
ROLE_PERMISSIONS = {}
def has_permission(role: str, permission: str) -> bool:
"""
检查角色是否有指定权限
Args:
role: 用户角色
permission: 权限标识
Returns:
是否有权限
"""
permissions = ROLE_PERMISSIONS.get(role, [])
return permission in permissions
def has_permission(role, permission):
return True
+18 -151
View File
@@ -1,181 +1,48 @@
"""
配额检查服务
用于检查工作空间是否超出配额限制
Quota checker - stub implementation.
Workspace concept removed. All quota checks pass by default.
"""
from typing import Optional
class QuotaChecker:
"""配额检查器"""
"""Stub quota checker - all checks pass since workspace is removed."""
def __init__(
self,
workspace_repository,
project_repository,
):
def __init__(self, workspace_repository=None, project_repository=None):
self.workspace_repository = workspace_repository
self.project_repository = project_repository
def check_can_create_project(
self,
workspace_id: str,
) -> tuple[bool, Optional[str]]:
"""
检查是否可以创建项目
Args:
workspace_id: 工作空间 ID
Returns:
(是否可以, 错误信息)
"""
workspace = self.workspace_repository.find_by_id(workspace_id)
if not workspace:
return False, "Workspace not found"
# 获取当前项目数量
current_count = self.project_repository.count_by_workspace(workspace_id)
# 检查是否超出配额(999999 表示无限)
if workspace.max_projects != 999999 and current_count >= workspace.max_projects:
return (
False,
f"Project limit reached ({workspace.max_projects}). Upgrade your plan to create more projects.",
)
def check_can_create_project(self, workspace_id=None):
return True, None
def check_storage_available(
self,
workspace_id: str,
additional_gb: float,
) -> tuple[bool, Optional[str]]:
"""
检查存储空间是否足够
Args:
workspace_id: 工作空间 ID
additional_gb: 需要的额外存储空间(GB)
Returns:
(是否可以, 错误信息)
"""
workspace = self.workspace_repository.find_by_id(workspace_id)
if not workspace:
return False, "Workspace not found"
# 检查存储空间
new_usage = workspace.used_storage_gb + additional_gb
if new_usage > workspace.max_storage_gb:
remaining = workspace.max_storage_gb - workspace.used_storage_gb
return (
False,
f"Storage limit exceeded. Available: {remaining:.2f}GB, Required: {additional_gb:.2f}GB. Upgrade your plan for more storage.",
)
def check_storage_available(self, workspace_id=None, additional_gb=0):
return True, None
def get_quota_status(self, workspace_id: str) -> dict:
"""
获取配额使用状态
Args:
workspace_id: 工作空间 ID
Returns:
配额状态信息
"""
workspace = self.workspace_repository.find_by_id(workspace_id)
if not workspace:
return None
# 获取项目数量
project_count = self.project_repository.count_by_workspace(workspace_id)
# 计算使用率
project_usage_percent = (
(project_count / workspace.max_projects * 100) if workspace.max_projects != 999999 else 0 # 无限制
)
storage_usage_percent = (
(workspace.used_storage_gb / workspace.max_storage_gb * 100) if workspace.max_storage_gb > 0 else 0
)
def get_quota_status(self, workspace_id=None):
return {
"workspace_id": workspace.id,
"subscription_plan": workspace.subscription_plan,
"projects": {
"used": project_count,
"limit": workspace.max_projects,
"unlimited": workspace.max_projects == 999999,
"usage_percent": project_usage_percent,
},
"storage": {
"used_gb": workspace.used_storage_gb,
"limit_gb": workspace.max_storage_gb,
"remaining_gb": workspace.max_storage_gb - workspace.used_storage_gb,
"usage_percent": storage_usage_percent,
},
"workspace_id": workspace_id or "",
"subscription_plan": "unlimited",
"projects": {"used": 0, "limit": 999999, "unlimited": True, "usage_percent": 0},
"storage": {"used_gb": 0, "limit_gb": 999999, "remaining_gb": 999999, "usage_percent": 0},
}
def update_storage_usage(
self,
workspace_id: str,
delta_gb: float,
) -> tuple[bool, Optional[str]]:
"""
更新存储使用量
Args:
workspace_id: 工作空间 ID
delta_gb: 变化量(正数为增加,负数为减少)
Returns:
(是否成功, 错误信息)
"""
workspace = self.workspace_repository.find_by_id(workspace_id)
if not workspace:
return False, "Workspace not found"
# 更新使用量
new_usage = workspace.used_storage_gb + delta_gb
# 不能为负数
if new_usage < 0:
new_usage = 0
workspace.used_storage_gb = new_usage
self.workspace_repository.save(workspace)
def update_storage_usage(self, workspace_id=None, delta_gb=0):
return True, None
class QuotaWarningLevel:
"""配额警告级别"""
NORMAL = "normal" # <80%
WARNING = "warning" # 80-90%
CRITICAL = "critical" # 90-100%
EXCEEDED = "exceeded" # >100%
NORMAL = "normal"
WARNING = "warning"
CRITICAL = "critical"
EXCEEDED = "exceeded"
def get_warning_level(usage_percent: float) -> str:
"""
根据使用率获取警告级别
Args:
usage_percent: 使用率(0-100
Returns:
警告级别
"""
def get_warning_level(usage_percent):
if usage_percent >= 100:
return QuotaWarningLevel.EXCEEDED
elif usage_percent >= 90:
return QuotaWarningLevel.CRITICAL
elif usage_percent >= 80:
return QuotaWarningLevel.WARNING
else:
return QuotaWarningLevel.NORMAL
return QuotaWarningLevel.NORMAL
@@ -0,0 +1,6 @@
"""Stub port for workspace invitation repository - workspace concept removed."""
from __future__ import annotations
from typing import Protocol
class WorkspaceInvitationRepository(Protocol):
pass
@@ -0,0 +1,6 @@
"""Stub port for workspace member repository - workspace concept removed."""
from __future__ import annotations
from typing import Protocol
class WorkspaceMemberRepository(Protocol):
pass
+6
View File
@@ -0,0 +1,6 @@
"""Stub port for workspace repository - workspace concept removed."""
from __future__ import annotations
from typing import Protocol
class WorkspaceRepository(Protocol):
pass
+1 -1
View File
@@ -8,7 +8,7 @@ email-validator==2.2.0
pydantic-settings==2.6.0
# 数据库
psycopg2-binary==2.9.9
psycopg2-binary==2.9.10
psycopg[binary]==3.1.18
sqlalchemy==2.0.35
alembic==1.13.3