diff --git a/alembic/versions/010_desktop_config_name_oem_unique.py b/alembic/versions/010_desktop_config_name_oem_unique.py new file mode 100644 index 0000000..b3e1143 --- /dev/null +++ b/alembic/versions/010_desktop_config_name_oem_unique.py @@ -0,0 +1,67 @@ +"""desktop_configs: oem_id + unique (name, oem_id) + +Revision ID: 010 +Revises: 009 +Create Date: 2026-05-21 + +""" + +from typing import Sequence, Union + +import sqlalchemy as sa +from alembic import op + +revision: str = "010" +down_revision: Union[str, None] = "009" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def _drop_name_unique(conn) -> None: + inspector = sa.inspect(conn) + for uc in inspector.get_unique_constraints("desktop_configs"): + cols = uc.get("column_names") or [] + if cols == ["name"]: + op.drop_constraint(uc["name"], "desktop_configs", type_="unique") + return + + for idx in inspector.get_indexes("desktop_configs"): + if idx.get("column_names") == ["name"] and idx.get("unique"): + op.drop_index(idx["name"], table_name="desktop_configs") + return + + +def upgrade() -> None: + conn = op.get_bind() + inspector = sa.inspect(conn) + columns = {c["name"] for c in inspector.get_columns("desktop_configs")} + + if "oem_id" not in columns: + op.add_column( + "desktop_configs", + sa.Column("oem_id", sa.Integer(), nullable=False, server_default="1"), + ) + + unique_names = {uc["name"] for uc in inspector.get_unique_constraints("desktop_configs")} + if "uq_desktop_configs_name_oem_id" not in unique_names: + _drop_name_unique(conn) + op.create_unique_constraint( + "uq_desktop_configs_name_oem_id", + "desktop_configs", + ["name", "oem_id"], + ) + + inspector = sa.inspect(conn) + index_names = {idx["name"] for idx in inspector.get_indexes("desktop_configs")} + if "ix_desktop_configs_name" not in index_names: + op.create_index("ix_desktop_configs_name", "desktop_configs", ["name"], unique=False) + if "ix_desktop_configs_oem_id" not in index_names: + op.create_index("ix_desktop_configs_oem_id", "desktop_configs", ["oem_id"], unique=False) + + +def downgrade() -> None: + op.drop_index("ix_desktop_configs_oem_id", table_name="desktop_configs") + op.drop_index("ix_desktop_configs_name", table_name="desktop_configs") + op.drop_constraint("uq_desktop_configs_name_oem_id", "desktop_configs", type_="unique") + op.create_unique_constraint("name", "desktop_configs", ["name"]) + op.drop_column("desktop_configs", "oem_id") diff --git a/app/api/v1/oem_desktop_configs.py b/app/api/v1/oem_desktop_configs.py index e3139d0..390c07a 100644 --- a/app/api/v1/oem_desktop_configs.py +++ b/app/api/v1/oem_desktop_configs.py @@ -9,14 +9,14 @@ from app.schemas.admin_desktop_config import ( DesktopConfigUpdate, ) from app.schemas.common import ApiResponse, PaginatedData -from app.services import admin_desktop_config as config_service +from app.services import oem_desktop_config as config_service router = APIRouter(prefix="/oem/desktop-configs", tags=["管理-桌面配置"]) @router.get("", response_model=ApiResponse[PaginatedData[DesktopConfigOut]]) async def list_configs( - _admin: OemUser, + _oem: OemUser, db: DbSession, page: int = Query(1, ge=1), page_size: int = Query( @@ -47,7 +47,7 @@ async def list_configs( @router.post("", response_model=ApiResponse[DesktopConfigOut]) async def create_config( body: DesktopConfigCreate, - _admin: OemUser, + _oem: OemUser, db: DbSession, ) -> ApiResponse[DesktopConfigOut]: try: @@ -66,7 +66,7 @@ async def create_config( async def update_config( config_id: int, body: DesktopConfigUpdate, - _admin: OemUser, + _oem: OemUser, db: DbSession, ) -> ApiResponse[DesktopConfigOut]: try: @@ -84,7 +84,7 @@ async def update_config( @router.delete("/{config_id}", response_model=ApiResponse[None]) async def delete_config( config_id: int, - _admin: OemUser, + _oem: OemUser, db: DbSession, ) -> ApiResponse[None]: try: diff --git a/app/api/v1/router.py b/app/api/v1/router.py index d3d2681..8da0a63 100644 --- a/app/api/v1/router.py +++ b/app/api/v1/router.py @@ -5,6 +5,7 @@ from app.api.v1 import ( oem_card_keys, agent_card_keys, admin_desktop_configs, + oem_desktop_configs, admin_oem_branding, oem_oem_branding, diff --git a/app/models/desktopConfig.py b/app/models/desktopConfig.py index d70a852..b5ec9da 100644 --- a/app/models/desktopConfig.py +++ b/app/models/desktopConfig.py @@ -1,6 +1,6 @@ from datetime import datetime -from sqlalchemy import DateTime, String, Text, func +from sqlalchemy import DateTime, Integer, String, Text, UniqueConstraint, func from sqlalchemy.orm import Mapped, mapped_column from app.database import Base @@ -8,10 +8,14 @@ from app.database import Base class DesktopConfig(Base): __tablename__ = "desktop_configs" + __table_args__ = ( + UniqueConstraint("name", "oem_id", name="uq_desktop_configs_name_oem_id"), + ) id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) - name: Mapped[str] = mapped_column(String(64), unique=True) + name: Mapped[str] = mapped_column(String(64), index=True) value: Mapped[str] = mapped_column(Text) mark: Mapped[str | None] = mapped_column(String(255), nullable=True, default=None) + oem_id: Mapped[int] = mapped_column(Integer, default=1, server_default="1", index=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now()) diff --git a/app/schemas/admin_desktop_config.py b/app/schemas/admin_desktop_config.py index 3306cbe..c71f1e0 100644 --- a/app/schemas/admin_desktop_config.py +++ b/app/schemas/admin_desktop_config.py @@ -10,6 +10,7 @@ class DesktopConfigOut(BaseModel): name: str value: str mark: str | None = None + oem_id: int = 1 created_at: datetime updated_at: datetime @@ -18,6 +19,7 @@ class DesktopConfigCreate(BaseModel): name: str = Field(min_length=1, max_length=64) value: str = Field(min_length=0) mark: str | None = Field(default=None, max_length=255) + oem_id: int = Field(default=1, ge=1) @field_validator("name") @classmethod diff --git a/app/services/admin_desktop_config.py b/app/services/admin_desktop_config.py index 8ebb796..68ed80a 100644 --- a/app/services/admin_desktop_config.py +++ b/app/services/admin_desktop_config.py @@ -44,11 +44,21 @@ async def list_configs( async def create_config(db: AsyncSession, body: DesktopConfigCreate) -> DesktopConfig: - existing = await db.scalar(select(DesktopConfig).where(DesktopConfig.name == body.name)) + existing = await db.scalar( + select(DesktopConfig).where( + DesktopConfig.name == body.name, + DesktopConfig.oem_id == body.oem_id, + ) + ) if existing is not None: - raise AdminDesktopConfigError("配置键名已存在") + raise AdminDesktopConfigError("该 OEM 下配置键名已存在") - row = DesktopConfig(name=body.name, value=body.value, mark=body.mark) + row = DesktopConfig( + name=body.name, + value=body.value, + mark=body.mark, + oem_id=body.oem_id, + ) db.add(row) await db.flush() await db.refresh(row) @@ -65,9 +75,15 @@ async def update_config( raise AdminDesktopConfigError("配置不存在") if body.name is not None and body.name != row.name: - taken = await db.scalar(select(DesktopConfig).where(DesktopConfig.name == body.name)) + taken = await db.scalar( + select(DesktopConfig).where( + DesktopConfig.name == body.name, + DesktopConfig.oem_id == row.oem_id, + DesktopConfig.id != config_id, + ) + ) if taken is not None: - raise AdminDesktopConfigError("配置键名已存在") + raise AdminDesktopConfigError("该 OEM 下配置键名已存在") row.name = body.name if body.value is not None: diff --git a/app/services/desktop_config.py b/app/services/desktop_config.py index 16675b0..6c7f5cb 100644 --- a/app/services/desktop_config.py +++ b/app/services/desktop_config.py @@ -6,7 +6,7 @@ from sqlalchemy.ext.asyncio import AsyncSession from app.models.desktopConfig import DesktopConfig -async def list_all_as_map(db: AsyncSession) -> dict[str, str]: - result = await db.execute(select(DesktopConfig)) +async def list_all_as_map(db: AsyncSession, oem_id: int = 1) -> dict[str, str]: + result = await db.execute(select(DesktopConfig).where(DesktopConfig.oem_id == oem_id)) rows = result.scalars().all() return {row.name: row.value for row in rows} diff --git a/app/services/oem_desktop_config.py b/app/services/oem_desktop_config.py new file mode 100644 index 0000000..3d9b5bb --- /dev/null +++ b/app/services/oem_desktop_config.py @@ -0,0 +1,12 @@ +"""desktop_configs 表:键值配置。""" + +from sqlalchemy import select +from sqlalchemy.ext.asyncio import AsyncSession + +from app.models.desktopConfig import DesktopConfig + + +async def list_all_as_map(db: AsyncSession,oem_id: int) -> dict[str, str]: + result = await db.execute(select(DesktopConfig).where(DesktopConfig.oem_id == oem_id)) + rows = result.scalars().all() + return {row.name: row.value for row in rows}