diff --git a/app/api/v1/agent_card_keys.py b/app/api/v1/agent_card_keys.py new file mode 100644 index 0000000..605db17 --- /dev/null +++ b/app/api/v1/agent_card_keys.py @@ -0,0 +1,109 @@ +"""管理员卡密 CRUD。""" + +from typing import Literal + +from fastapi import APIRouter, Query + +from app.dependencies import AdminUser, DbSession +from app.schemas.admin_card_key import ( + CardKeyBatchCreateResult, + CardKeyCreate, + CardKeyOut, + CardKeyUpdate, +) +from app.schemas.common import ApiResponse, PaginatedData +from app.services import admin_card_key as card_key_service + +router = APIRouter(prefix="/agent/card-keys", tags=["管理-卡密"]) + + +@router.get("", response_model=ApiResponse[PaginatedData[CardKeyOut]]) +async def list_card_keys( + _admin: AdminUser, + db: DbSession, + page: int = Query(1, ge=1), + page_size: int = Query( + card_key_service.DEFAULT_PAGE_SIZE, + ge=1, + le=card_key_service.MAX_PAGE_SIZE, + ), + status: Literal["all", "unused", "used"] | None = Query( + default="all", + description="筛选:all 全部 / unused 未使用 / used 已激活", + ), + username: str | None = Query(default=None, description="按激活用户名模糊筛选"), + oem_id: int | None = Query(default=None, description="按预分配 OEM 筛选"), + agent_id: int | None = Query(default=None, description="按预分配代理筛选"), +) -> ApiResponse[PaginatedData[CardKeyOut]]: + filter_status = None if status == "all" else status + cards, total = await card_key_service.list_card_keys( + db, + page=page, + page_size=page_size, + status=filter_status, + username=username, + oem_id=oem_id, + agent_id=agent_id, + ) + return ApiResponse( + ok=True, + message="", + data=PaginatedData.build( + [CardKeyOut.model_validate(c) for c in cards], + total=total, + page=page, + page_size=page_size, + ), + ) + + +@router.post("", response_model=ApiResponse[CardKeyBatchCreateResult]) +async def create_card_keys( + body: CardKeyCreate, + _admin: AdminUser, + db: DbSession, +) -> ApiResponse[CardKeyBatchCreateResult]: + try: + created = await card_key_service.create_card_keys(db, body) + except card_key_service.AdminCardKeyError as exc: + return ApiResponse(ok=False, message=exc.message) + + items = [CardKeyOut.model_validate(c) for c in created] + return ApiResponse( + ok=True, + message=f"已生成 {len(items)} 张卡密", + data=CardKeyBatchCreateResult(items=items, created_count=len(items)), + ) + + +@router.patch("/{card_id}", response_model=ApiResponse[CardKeyOut]) +async def update_card_key( + card_id: int, + body: CardKeyUpdate, + _admin: AdminUser, + db: DbSession, +) -> ApiResponse[CardKeyOut]: + try: + card = await card_key_service.update_card_key(db, card_id, body) + except card_key_service.AdminCardKeyError as exc: + return ApiResponse(ok=False, message=exc.message) + + return ApiResponse( + ok=True, + message="卡密已更新", + data=CardKeyOut.model_validate(card), + ) + + +@router.delete("/{card_id}", response_model=ApiResponse[None]) +async def delete_card_key( + card_id: int, + _admin: AdminUser, + db: DbSession, +) -> ApiResponse[None]: + try: + await card_key_service.delete_card_key(db, card_id) + except card_key_service.AdminCardKeyError as exc: + return ApiResponse(ok=False, message=exc.message) + + return ApiResponse(ok=True, message="卡密已删除") diff --git a/app/api/v1/agent_users.py b/app/api/v1/agent_users.py new file mode 100644 index 0000000..76a276a --- /dev/null +++ b/app/api/v1/agent_users.py @@ -0,0 +1,103 @@ +"""管理员用户 CRUD。""" + +from fastapi import APIRouter, Query + +from app.dependencies import OemUser, DbSession +from app.schemas.admin_user import AdminUserCreate, AdminUserOut, AdminUserUpdate +from app.schemas.common import ApiResponse, PaginatedData +from app.services import admin_user as oem_user_service + +router = APIRouter(prefix="/agent/users", tags=["管理-用户"]) + + +@router.get("", response_model=ApiResponse[PaginatedData[AdminUserOut]]) +async def list_users( + _oem: OemUser, + db: DbSession, + page: int = Query(1, ge=1, description="页码,从 1 开始"), + page_size: int = Query( + oem_user_service.DEFAULT_PAGE_SIZE, + ge=1, + le=oem_user_service.MAX_PAGE_SIZE, + description="每页条数", + ), + username: str | None = Query(None, description="用户名模糊搜索"), + agent_id: int | None = Query(None, description="按所属代理用户 ID 筛选"), + role_id: int | None = Query(None, ge=0, le=3, description="按角色筛选"), +) -> ApiResponse[PaginatedData[AdminUserOut]]: + oem_id=_oem.id + users, total = await oem_user_service.list_users( + db, + page=page, + page_size=page_size, + username=username, + oem_id=oem_id, + agent_id=agent_id, + role_id=role_id, + ) + return ApiResponse( + ok=True, + message="", + data=PaginatedData.build( + [AdminUserOut.model_validate(u) for u in users], + total=total, + page=page, + page_size=page_size, + ), + ) + + +@router.post("", response_model=ApiResponse[AdminUserOut]) +async def create_user( + body: AdminUserCreate, + oem: OemUser, + db: DbSession, +) -> ApiResponse[AdminUserOut]: + try: + user = await oem_user_service.create_user(db, body,oem) + except oem_user_service.AdminUserError as exc: + return ApiResponse(ok=False, message=exc.message) + + return ApiResponse( + ok=True, + message="用户已创建", + data=AdminUserOut.model_validate(user), + ) + + +@router.patch("/{user_id}", response_model=ApiResponse[AdminUserOut]) +async def update_user( + user_id: int, + body: AdminUserUpdate, + oem: OemUser, + db: DbSession, +) -> ApiResponse[AdminUserOut]: + try: + user = await oem_user_service.update_user( + db, + user_id, + body, + actor_id=oem.id, + ) + except oem_user_service.AdminUserError as exc: + return ApiResponse(ok=False, message=exc.message) + + return ApiResponse( + ok=True, + message="用户已更新", + data=AdminUserOut.model_validate(user), + ) + + +@router.delete("/{user_id}", response_model=ApiResponse[None]) +async def delete_user( + user_id: int, + oem: OemUser, + db: DbSession, +) -> ApiResponse[None]: + try: + await oem_user_service.delete_user(db, user_id, actor_id=admin.id) + except oem_user_service.AdminUserError as exc: + return ApiResponse(ok=False, message=exc.message) + + return ApiResponse(ok=True, message="用户已删除") diff --git a/app/api/v1/oem_card_keys.py b/app/api/v1/oem_card_keys.py new file mode 100644 index 0000000..26ecbd0 --- /dev/null +++ b/app/api/v1/oem_card_keys.py @@ -0,0 +1,109 @@ +"""管理员卡密 CRUD。""" + +from typing import Literal + +from fastapi import APIRouter, Query + +from app.dependencies import OemUser, DbSession +from app.schemas.admin_card_key import ( + CardKeyBatchCreateResult, + CardKeyCreate, + CardKeyOut, + CardKeyUpdate, +) +from app.schemas.common import ApiResponse, PaginatedData +from app.services import admin_card_key as card_key_service + +router = APIRouter(prefix="/oem/card-keys", tags=["管理-卡密"]) + + +@router.get("", response_model=ApiResponse[PaginatedData[CardKeyOut]]) +async def list_card_keys( + _admin: OemUser, + db: DbSession, + page: int = Query(1, ge=1), + page_size: int = Query( + card_key_service.DEFAULT_PAGE_SIZE, + ge=1, + le=card_key_service.MAX_PAGE_SIZE, + ), + status: Literal["all", "unused", "used"] | None = Query( + default="all", + description="筛选:all 全部 / unused 未使用 / used 已激活", + ), + username: str | None = Query(default=None, description="按激活用户名模糊筛选"), + oem_id: int | None = Query(default=None, description="按预分配 OEM 筛选"), + agent_id: int | None = Query(default=None, description="按预分配代理筛选"), +) -> ApiResponse[PaginatedData[CardKeyOut]]: + filter_status = None if status == "all" else status + cards, total = await card_key_service.list_card_keys( + db, + page=page, + page_size=page_size, + status=filter_status, + username=username, + oem_id=oem_id, + agent_id=agent_id, + ) + return ApiResponse( + ok=True, + message="", + data=PaginatedData.build( + [CardKeyOut.model_validate(c) for c in cards], + total=total, + page=page, + page_size=page_size, + ), + ) + + +@router.post("", response_model=ApiResponse[CardKeyBatchCreateResult]) +async def create_card_keys( + body: CardKeyCreate, + _admin: OemUser, + db: DbSession, +) -> ApiResponse[CardKeyBatchCreateResult]: + try: + created = await card_key_service.create_card_keys(db, body) + except card_key_service.AdminCardKeyError as exc: + return ApiResponse(ok=False, message=exc.message) + + items = [CardKeyOut.model_validate(c) for c in created] + return ApiResponse( + ok=True, + message=f"已生成 {len(items)} 张卡密", + data=CardKeyBatchCreateResult(items=items, created_count=len(items)), + ) + + +@router.patch("/{card_id}", response_model=ApiResponse[CardKeyOut]) +async def update_card_key( + card_id: int, + body: CardKeyUpdate, + _admin: OemUser, + db: DbSession, +) -> ApiResponse[CardKeyOut]: + try: + card = await card_key_service.update_card_key(db, card_id, body) + except card_key_service.AdminCardKeyError as exc: + return ApiResponse(ok=False, message=exc.message) + + return ApiResponse( + ok=True, + message="卡密已更新", + data=CardKeyOut.model_validate(card), + ) + + +@router.delete("/{card_id}", response_model=ApiResponse[None]) +async def delete_card_key( + card_id: int, + _admin: OemUser, + db: DbSession, +) -> ApiResponse[None]: + try: + await card_key_service.delete_card_key(db, card_id) + except card_key_service.AdminCardKeyError as exc: + return ApiResponse(ok=False, message=exc.message) + + return ApiResponse(ok=True, message="卡密已删除") diff --git a/app/api/v1/oem_desktop_configs.py b/app/api/v1/oem_desktop_configs.py new file mode 100644 index 0000000..e3139d0 --- /dev/null +++ b/app/api/v1/oem_desktop_configs.py @@ -0,0 +1,95 @@ +"""管理员 desktop_configs 键值 CRUD。""" + +from fastapi import APIRouter, Query + +from app.dependencies import OemUser, DbSession +from app.schemas.admin_desktop_config import ( + DesktopConfigCreate, + DesktopConfigOut, + DesktopConfigUpdate, +) +from app.schemas.common import ApiResponse, PaginatedData +from app.services import admin_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, + db: DbSession, + page: int = Query(1, ge=1), + page_size: int = Query( + config_service.DEFAULT_PAGE_SIZE, + ge=1, + le=config_service.MAX_PAGE_SIZE, + ), + keyword: str | None = Query(default=None, max_length=64, description="按键名模糊搜索"), +) -> ApiResponse[PaginatedData[DesktopConfigOut]]: + rows, total = await config_service.list_configs( + db, + page=page, + page_size=page_size, + keyword=keyword, + ) + return ApiResponse( + ok=True, + message="", + data=PaginatedData.build( + [DesktopConfigOut.model_validate(r) for r in rows], + total=total, + page=page, + page_size=page_size, + ), + ) + + +@router.post("", response_model=ApiResponse[DesktopConfigOut]) +async def create_config( + body: DesktopConfigCreate, + _admin: OemUser, + db: DbSession, +) -> ApiResponse[DesktopConfigOut]: + try: + row = await config_service.create_config(db, body) + except config_service.AdminDesktopConfigError as exc: + return ApiResponse(ok=False, message=exc.message) + + return ApiResponse( + ok=True, + message="配置已创建", + data=DesktopConfigOut.model_validate(row), + ) + + +@router.patch("/{config_id}", response_model=ApiResponse[DesktopConfigOut]) +async def update_config( + config_id: int, + body: DesktopConfigUpdate, + _admin: OemUser, + db: DbSession, +) -> ApiResponse[DesktopConfigOut]: + try: + row = await config_service.update_config(db, config_id, body) + except config_service.AdminDesktopConfigError as exc: + return ApiResponse(ok=False, message=exc.message) + + return ApiResponse( + ok=True, + message="配置已更新", + data=DesktopConfigOut.model_validate(row), + ) + + +@router.delete("/{config_id}", response_model=ApiResponse[None]) +async def delete_config( + config_id: int, + _admin: OemUser, + db: DbSession, +) -> ApiResponse[None]: + try: + await config_service.delete_config(db, config_id) + except config_service.AdminDesktopConfigError as exc: + return ApiResponse(ok=False, message=exc.message) + + return ApiResponse(ok=True, message="配置已删除") diff --git a/app/api/v1/oem_oem_branding.py b/app/api/v1/oem_oem_branding.py new file mode 100644 index 0000000..0a400f4 --- /dev/null +++ b/app/api/v1/oem_oem_branding.py @@ -0,0 +1,49 @@ +"""管理员:默认 OEM(id=1)软件品牌配置。""" + +from fastapi import APIRouter + +from app.dependencies import OemUser, DbSession +from app.schemas.admin_oem_branding import OemBrandingOut, OemBrandingUpdate +from app.schemas.common import ApiResponse +from app.services import admin_oem_branding as branding_service + +router = APIRouter(prefix="/oem/oem-branding", tags=["管理-OEM品牌"]) + + +@router.get("", response_model=ApiResponse[OemBrandingOut]) +async def get_oem_branding( + admin: OemUser, + db: DbSession, +) -> ApiResponse[OemBrandingOut]: + try: + oem = await branding_service.get_default_oem_branding( + db, owner_user_id=admin.id + ) + except branding_service.AdminOemBrandingError as exc: + return ApiResponse(ok=False, message=exc.message) + + return ApiResponse( + ok=True, + message="", + data=OemBrandingOut.model_validate(oem), + ) + + +@router.patch("", response_model=ApiResponse[OemBrandingOut]) +async def update_oem_branding( + body: OemBrandingUpdate, + admin: OemUser, + db: DbSession, +) -> ApiResponse[OemBrandingOut]: + try: + oem = await branding_service.update_default_oem_branding( + db, body, owner_user_id=admin.id + ) + except branding_service.AdminOemBrandingError as exc: + return ApiResponse(ok=False, message=exc.message) + + return ApiResponse( + ok=True, + message="软件配置已保存", + data=OemBrandingOut.model_validate(oem), + ) diff --git a/app/api/v1/router.py b/app/api/v1/router.py index a6790c6..d3d2681 100644 --- a/app/api/v1/router.py +++ b/app/api/v1/router.py @@ -2,9 +2,14 @@ from fastapi import APIRouter from app.api.v1 import ( admin_card_keys, + oem_card_keys, + agent_card_keys, admin_desktop_configs, + oem_desktop_configs, admin_oem_branding, + oem_oem_branding, admin_users, + agent_users, app_config, oem_public, oem_users, @@ -18,9 +23,14 @@ api_router.include_router(oem_users.router) api_router.include_router(oem_public.router) api_router.include_router(auth.router) api_router.include_router(admin_users.router) +api_router.include_router(agent_users.router) api_router.include_router(admin_card_keys.router) +api_router.include_router(oem_card_keys.router) +api_router.include_router(agent_card_keys.router) api_router.include_router(admin_desktop_configs.router) +api_router.include_router(oem_desktop_configs.router) api_router.include_router(admin_oem_branding.router) +api_router.include_router(oem_oem_branding.router) api_router.include_router(app_config.router) api_router.include_router(quickjs_scripts.router) api_router.include_router(nodejs_scripts.router)