22
This commit is contained in:
128
app/services/admin_card_key.py
Normal file
128
app/services/admin_card_key.py
Normal file
@@ -0,0 +1,128 @@
|
||||
import secrets
|
||||
import string
|
||||
|
||||
from sqlalchemy import func, select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.models.card_key import CardKey
|
||||
from app.schemas.admin_card_key import CardKeyCreate, CardKeyUpdate
|
||||
|
||||
DEFAULT_PAGE_SIZE = 20
|
||||
MAX_PAGE_SIZE = 100
|
||||
|
||||
_SERIAL_ALPHABET = string.ascii_uppercase + string.digits
|
||||
_SERIAL_LENGTH = 16
|
||||
|
||||
|
||||
class AdminCardKeyError(Exception):
|
||||
def __init__(self, message: str) -> None:
|
||||
self.message = message
|
||||
super().__init__(message)
|
||||
|
||||
|
||||
def _generate_serial() -> str:
|
||||
return "".join(secrets.choice(_SERIAL_ALPHABET) for _ in range(_SERIAL_LENGTH))
|
||||
|
||||
|
||||
async def _unique_serial(db: AsyncSession, preferred: str | None = None) -> str:
|
||||
if preferred:
|
||||
existing = await db.scalar(
|
||||
select(CardKey.id).where(CardKey.serial_number == preferred)
|
||||
)
|
||||
if existing is not None:
|
||||
raise AdminCardKeyError("序列号已存在")
|
||||
return preferred
|
||||
|
||||
for _ in range(20):
|
||||
candidate = _generate_serial()
|
||||
existing = await db.scalar(
|
||||
select(CardKey.id).where(CardKey.serial_number == candidate)
|
||||
)
|
||||
if existing is None:
|
||||
return candidate
|
||||
raise AdminCardKeyError("生成序列号失败,请重试")
|
||||
|
||||
|
||||
async def list_card_keys(
|
||||
db: AsyncSession,
|
||||
*,
|
||||
page: int = 1,
|
||||
page_size: int = DEFAULT_PAGE_SIZE,
|
||||
status: str | None = None,
|
||||
) -> tuple[list[CardKey], int]:
|
||||
page_size = min(max(page_size, 1), MAX_PAGE_SIZE)
|
||||
page = max(page, 1)
|
||||
offset = (page - 1) * page_size
|
||||
|
||||
filters = []
|
||||
if status == "unused":
|
||||
filters.append(CardKey.activated_at.is_(None))
|
||||
elif status == "used":
|
||||
filters.append(CardKey.activated_at.is_not(None))
|
||||
|
||||
count_stmt = select(func.count()).select_from(CardKey)
|
||||
list_stmt = select(CardKey)
|
||||
for clause in filters:
|
||||
count_stmt = count_stmt.where(clause)
|
||||
list_stmt = list_stmt.where(clause)
|
||||
|
||||
total = await db.scalar(count_stmt) or 0
|
||||
result = await db.scalars(
|
||||
list_stmt.order_by(CardKey.id.desc()).offset(offset).limit(page_size)
|
||||
)
|
||||
return list(result.all()), total
|
||||
|
||||
|
||||
async def create_card_keys(db: AsyncSession, body: CardKeyCreate) -> list[CardKey]:
|
||||
created: list[CardKey] = []
|
||||
|
||||
for index in range(body.count):
|
||||
serial = await _unique_serial(
|
||||
db,
|
||||
body.serial_number if index == 0 else None,
|
||||
)
|
||||
card = CardKey(
|
||||
serial_number=serial,
|
||||
duration_days=body.duration_days,
|
||||
remark=body.remark,
|
||||
)
|
||||
db.add(card)
|
||||
created.append(card)
|
||||
|
||||
await db.flush()
|
||||
for card in created:
|
||||
await db.refresh(card)
|
||||
return created
|
||||
|
||||
|
||||
async def update_card_key(
|
||||
db: AsyncSession,
|
||||
card_id: int,
|
||||
body: CardKeyUpdate,
|
||||
) -> CardKey:
|
||||
card = await db.get(CardKey, card_id)
|
||||
if card is None:
|
||||
raise AdminCardKeyError("卡密不存在")
|
||||
|
||||
if card.activated_at is not None:
|
||||
if body.duration_days is not None:
|
||||
raise AdminCardKeyError("已激活卡密不能修改时长")
|
||||
elif body.duration_days is not None:
|
||||
card.duration_days = body.duration_days
|
||||
|
||||
if body.remark is not None:
|
||||
card.remark = body.remark
|
||||
|
||||
await db.flush()
|
||||
await db.refresh(card)
|
||||
return card
|
||||
|
||||
|
||||
async def delete_card_key(db: AsyncSession, card_id: int) -> None:
|
||||
card = await db.get(CardKey, card_id)
|
||||
if card is None:
|
||||
raise AdminCardKeyError("卡密不存在")
|
||||
if card.activated_at is not None:
|
||||
raise AdminCardKeyError("已激活卡密不能删除")
|
||||
|
||||
await db.delete(card)
|
||||
88
app/services/admin_desktop_config.py
Normal file
88
app/services/admin_desktop_config.py
Normal file
@@ -0,0 +1,88 @@
|
||||
from sqlalchemy import func, select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.models.desktopConfig import DesktopConfig
|
||||
from app.schemas.admin_desktop_config import DesktopConfigCreate, DesktopConfigUpdate
|
||||
|
||||
DEFAULT_PAGE_SIZE = 20
|
||||
MAX_PAGE_SIZE = 100
|
||||
|
||||
|
||||
class AdminDesktopConfigError(Exception):
|
||||
def __init__(self, message: str) -> None:
|
||||
self.message = message
|
||||
super().__init__(message)
|
||||
|
||||
|
||||
async def list_configs(
|
||||
db: AsyncSession,
|
||||
*,
|
||||
page: int = 1,
|
||||
page_size: int = DEFAULT_PAGE_SIZE,
|
||||
keyword: str | None = None,
|
||||
) -> tuple[list[DesktopConfig], int]:
|
||||
page_size = min(max(page_size, 1), MAX_PAGE_SIZE)
|
||||
page = max(page, 1)
|
||||
offset = (page - 1) * page_size
|
||||
|
||||
filters = []
|
||||
if keyword:
|
||||
like = f"%{keyword.strip()}%"
|
||||
filters.append(DesktopConfig.name.ilike(like))
|
||||
|
||||
count_stmt = select(func.count()).select_from(DesktopConfig)
|
||||
list_stmt = select(DesktopConfig)
|
||||
for clause in filters:
|
||||
count_stmt = count_stmt.where(clause)
|
||||
list_stmt = list_stmt.where(clause)
|
||||
|
||||
total = await db.scalar(count_stmt) or 0
|
||||
result = await db.scalars(
|
||||
list_stmt.order_by(DesktopConfig.name.asc()).offset(offset).limit(page_size)
|
||||
)
|
||||
return list(result.all()), total
|
||||
|
||||
|
||||
async def create_config(db: AsyncSession, body: DesktopConfigCreate) -> DesktopConfig:
|
||||
existing = await db.scalar(select(DesktopConfig).where(DesktopConfig.name == body.name))
|
||||
if existing is not None:
|
||||
raise AdminDesktopConfigError("配置键名已存在")
|
||||
|
||||
row = DesktopConfig(name=body.name, value=body.value, mark=body.mark)
|
||||
db.add(row)
|
||||
await db.flush()
|
||||
await db.refresh(row)
|
||||
return row
|
||||
|
||||
|
||||
async def update_config(
|
||||
db: AsyncSession,
|
||||
config_id: int,
|
||||
body: DesktopConfigUpdate,
|
||||
) -> DesktopConfig:
|
||||
row = await db.get(DesktopConfig, config_id)
|
||||
if row is None:
|
||||
raise AdminDesktopConfigError("配置不存在")
|
||||
|
||||
if body.name is not None and body.name != row.name:
|
||||
taken = await db.scalar(select(DesktopConfig).where(DesktopConfig.name == body.name))
|
||||
if taken is not None:
|
||||
raise AdminDesktopConfigError("配置键名已存在")
|
||||
row.name = body.name
|
||||
|
||||
if body.value is not None:
|
||||
row.value = body.value
|
||||
|
||||
if body.mark is not None:
|
||||
row.mark = body.mark
|
||||
|
||||
await db.flush()
|
||||
await db.refresh(row)
|
||||
return row
|
||||
|
||||
|
||||
async def delete_config(db: AsyncSession, config_id: int) -> None:
|
||||
row = await db.get(DesktopConfig, config_id)
|
||||
if row is None:
|
||||
raise AdminDesktopConfigError("配置不存在")
|
||||
await db.delete(row)
|
||||
109
app/services/admin_user.py
Normal file
109
app/services/admin_user.py
Normal file
@@ -0,0 +1,109 @@
|
||||
from sqlalchemy import func, select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.roles import ROLE_ADMIN
|
||||
from app.core.security import hash_password
|
||||
from app.models.user import User
|
||||
from app.schemas.admin_user import AdminUserCreate, AdminUserUpdate
|
||||
|
||||
DEFAULT_PAGE_SIZE = 20
|
||||
MAX_PAGE_SIZE = 100
|
||||
|
||||
|
||||
class AdminUserError(Exception):
|
||||
def __init__(self, message: str) -> None:
|
||||
self.message = message
|
||||
super().__init__(message)
|
||||
|
||||
|
||||
async def list_users(
|
||||
db: AsyncSession,
|
||||
*,
|
||||
page: int = 1,
|
||||
page_size: int = DEFAULT_PAGE_SIZE,
|
||||
) -> tuple[list[User], int]:
|
||||
page_size = min(max(page_size, 1), MAX_PAGE_SIZE)
|
||||
page = max(page, 1)
|
||||
offset = (page - 1) * page_size
|
||||
|
||||
total = await db.scalar(select(func.count()).select_from(User)) or 0
|
||||
result = await db.scalars(
|
||||
select(User).order_by(User.id.desc()).offset(offset).limit(page_size)
|
||||
)
|
||||
return list(result.all()), total
|
||||
|
||||
|
||||
async def create_user(db: AsyncSession, body: AdminUserCreate) -> User:
|
||||
existing = await db.scalar(select(User).where(User.username == body.username))
|
||||
if existing is not None:
|
||||
raise AdminUserError("用户名已存在")
|
||||
|
||||
if body.phone:
|
||||
phone_taken = await db.scalar(select(User).where(User.phone == body.phone))
|
||||
if phone_taken is not None:
|
||||
raise AdminUserError("手机号已被使用")
|
||||
|
||||
user = User(
|
||||
username=body.username,
|
||||
password_hash=hash_password(body.password),
|
||||
phone=body.phone,
|
||||
role_id=body.role_id,
|
||||
vip_end_time=body.vip_end_time,
|
||||
)
|
||||
db.add(user)
|
||||
await db.flush()
|
||||
await db.refresh(user)
|
||||
return user
|
||||
|
||||
|
||||
async def update_user(
|
||||
db: AsyncSession,
|
||||
user_id: int,
|
||||
body: AdminUserUpdate,
|
||||
*,
|
||||
actor_id: int,
|
||||
) -> User:
|
||||
user = await db.get(User, user_id)
|
||||
if user is None:
|
||||
raise AdminUserError("用户不存在")
|
||||
|
||||
if body.username is not None and body.username != user.username:
|
||||
taken = await db.scalar(select(User).where(User.username == body.username))
|
||||
if taken is not None:
|
||||
raise AdminUserError("用户名已存在")
|
||||
user.username = body.username
|
||||
|
||||
if body.phone is not None:
|
||||
if body.phone != user.phone:
|
||||
taken = await db.scalar(select(User).where(User.phone == body.phone))
|
||||
if taken is not None:
|
||||
raise AdminUserError("手机号已被使用")
|
||||
user.phone = body.phone
|
||||
|
||||
if body.role_id is not None:
|
||||
if user_id == actor_id and body.role_id != ROLE_ADMIN:
|
||||
raise AdminUserError("不能修改自己的管理员角色")
|
||||
user.role_id = body.role_id
|
||||
|
||||
if body.password:
|
||||
user.password_hash = hash_password(body.password)
|
||||
|
||||
if body.clear_vip_end_time:
|
||||
user.vip_end_time = None
|
||||
elif body.vip_end_time is not None:
|
||||
user.vip_end_time = body.vip_end_time
|
||||
|
||||
await db.flush()
|
||||
await db.refresh(user)
|
||||
return user
|
||||
|
||||
|
||||
async def delete_user(db: AsyncSession, user_id: int, *, actor_id: int) -> None:
|
||||
if user_id == actor_id:
|
||||
raise AdminUserError("不能删除当前登录账号")
|
||||
|
||||
user = await db.get(User, user_id)
|
||||
if user is None:
|
||||
raise AdminUserError("用户不存在")
|
||||
|
||||
await db.delete(user)
|
||||
@@ -30,7 +30,12 @@ async def register_user(
|
||||
if existing is not None:
|
||||
raise AuthError("用户名已存在")
|
||||
|
||||
user = User(username=username, password_hash=hash_password(password))
|
||||
user = User(
|
||||
username=username,
|
||||
password_hash=hash_password(password),
|
||||
role_id=0,
|
||||
phone=None,
|
||||
)
|
||||
db.add(user)
|
||||
await db.flush()
|
||||
await db.refresh(user)
|
||||
|
||||
12
app/services/desktop_config.py
Normal file
12
app/services/desktop_config.py
Normal file
@@ -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) -> dict[str, str]:
|
||||
result = await db.execute(select(DesktopConfig))
|
||||
rows = result.scalars().all()
|
||||
return {row.name: row.value for row in rows}
|
||||
Reference in New Issue
Block a user