Files
opsxsql/backend/app/models/instance_group.py
T
logan 39d9600e82 refactor: 权限组从一组一类型改为一组同时包含readonly+write实例
- 新增 instance_group_members 中间表关联权限组和实例
- InstanceGroup 模型移除旧的 instances relationship,改为通过中间表查询
- Instance 模型保留 group_id FK(兼容)但移除 group relationship
- Schema 新增 readonly_instance_ids / write_instance_ids 字段
- InstanceGroupResponse 改为 readonly_instance_count + write_instance_count
- InstanceGroupDetailResponse 返回 readonly_instances + write_instances
- InstanceAssignRequest 新增 access_type 参数
- API create/update 支持同步写入 instance_group_members
- role_permissions 服务更新为使用中间表查询
- 新增迁移 SQL 并应用到 sqlite3.db
- 测试全部通过 (20/20)
2026-05-24 20:03:55 +08:00

79 lines
3.3 KiB
Python

from __future__ import annotations
import json
from typing import List, Optional
from sqlalchemy import Column, Integer, String, Text, Boolean, DateTime, ForeignKey, Table, func, UniqueConstraint
from sqlalchemy.orm import relationship
from app.core.database import Base
# 用户-权限组关联表
user_groups = Table(
"user_groups",
Base.metadata,
Column("user_id", Integer, ForeignKey("users.id", ondelete="CASCADE"), primary_key=True),
Column("group_id", Integer, ForeignKey("instance_groups.id", ondelete="CASCADE"), primary_key=True),
Column("permissions", Text, nullable=True), # JSON: 覆盖组默认权限 (legacy, kept for compat)
)
# 实例-权限组关联表(一个权限组可同时包含 readonly 和 write 实例)
instance_group_members = Table(
"instance_group_members",
Base.metadata,
Column("id", Integer, primary_key=True, autoincrement=True),
Column("group_id", Integer, ForeignKey("instance_groups.id", ondelete="CASCADE"), nullable=False),
Column("instance_id", Integer, ForeignKey("instances.id", ondelete="CASCADE"), nullable=False),
Column("access_type", String(20), nullable=False, default="readonly"), # readonly / write
UniqueConstraint("group_id", "instance_id", "access_type", name="uq_group_instance_access"),
)
class InstanceGroup(Base):
__tablename__ = "instance_groups"
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String(100), nullable=False, unique=True)
description = Column(Text, nullable=True)
db_type = Column(String(20), nullable=False, default="all") # mysql/redis/pg/mongo/all
permissions = Column(Text, nullable=True) # JSON: ["query:select", ...] (legacy, kept for compat)
access_type = Column(String(20), nullable=True, default="readonly") # legacy, kept for compat
allowed_databases = Column(Text, nullable=True) # JSON array of database names; empty = all databases
is_active = Column(Boolean, default=True)
created_at = Column(DateTime, server_default=func.now())
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
# 关系 — users 保持不变
users = relationship("User", secondary=user_groups, back_populates="groups", lazy="selectin")
# ---- Legacy helpers (kept for backward compatibility) ----
def get_permissions(self) -> list:
if self.permissions:
try:
return json.loads(self.permissions)
except (json.JSONDecodeError, TypeError):
return []
return []
def set_permissions(self, perms: list):
self.permissions = json.dumps(perms, ensure_ascii=False) if perms else None
# ---- New access_type / allowed_databases helpers ----
def get_allowed_databases(self) -> list[str]:
"""返回允许的数据库名列表,空列表表示关联实例的所有库。"""
if self.allowed_databases:
try:
return json.loads(self.allowed_databases)
except (json.JSONDecodeError, TypeError):
return []
return []
def set_allowed_databases(self, databases: list[str] | None):
self.allowed_databases = (
json.dumps(databases, ensure_ascii=False) if databases else None
)
VALID_ACCESS_TYPES = {"readonly", "write"}