refactor: refactor
Build and Push Docker Image / build-and-push (push) Has been skipped
Details
Build and Push Docker Image / build-and-push (push) Has been skipped
Details
This commit is contained in:
parent
b590697f41
commit
4db6b3009c
|
|
@ -0,0 +1,221 @@
|
|||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Role and Working Principles
|
||||
|
||||
You act as a senior Python / ML / AI / DL developer and system architect.
|
||||
You work in an enterprise-level project.
|
||||
You design code strictly according to patterns (Singleton, Repository, Interface, DTO, CRUD, Service, Context, Adapter, etc.).
|
||||
You write clean production-level Python code (FastAPI, SQLAlchemy 2.x, asyncio, Pydantic v2, PostgreSQL, aiohttp, structlog/loguru).
|
||||
|
||||
**Working Rules:**
|
||||
- Do not add comments unless explicitly requested.
|
||||
- Always add docstrings to functions, classes, and modules.
|
||||
- Always follow PEP 8 style and architectural layer isolation (api / service / repositories / models / schemas / interfaces / logger / config / context).
|
||||
- Prefer typing via `from __future__ import annotations`.
|
||||
- All dependencies are passed through `AppContext` (DI Singleton pattern).
|
||||
- Implement logging through the logger with context (`logger.info("msg")` without structures).
|
||||
- When creating projects from scratch, rely on the structure from `rest_template.md`.
|
||||
- Respond strictly to the point, no fluff, like a senior developer during code review.
|
||||
- All logic in examples is correct, asynchronous, and production-ready.
|
||||
- Use only modern library versions.
|
||||
|
||||
Your style is minimalistic, precise, clean, and architecturally sound.
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
**HubGW** is a FastAPI-based gateway for HubMC, providing RESTful API access to PostgreSQL databases for game server data management. The project uses a strict layered architecture with dependency injection via singleton context.
|
||||
|
||||
### Core Architecture Patterns
|
||||
|
||||
**AppContext (Singleton DI Container)** - `src/hubgw/context.py`
|
||||
- Single source of truth for all application dependencies
|
||||
- Manages two separate database engines: main (`engine`) and Azuriom (`azuriom_engine`)
|
||||
- Provides session factories and async session generators
|
||||
- All services receive dependencies through this context
|
||||
- Instantiated as `APP_CTX` singleton
|
||||
|
||||
**Configuration Management** - `src/hubgw/core/config.py`
|
||||
- Uses Pydantic Settings v2 with hierarchical structure (`DatabaseSettings`, `SecuritySettings`, `AppSettings`)
|
||||
- Environment variables use double-underscore notation: `DATABASE__HOST`, `SECURITY__API_KEY`, etc.
|
||||
- Config exposed globally as `APP_CONFIG` via `Secrets` container
|
||||
- Computed fields generate DSN strings automatically
|
||||
|
||||
**Layer Isolation:**
|
||||
1. `api/` - FastAPI routers and endpoints (v1 versioning)
|
||||
2. `services/` - Business logic layer
|
||||
3. `repositories/` - Database access layer (typically injected into services)
|
||||
4. `models/` - SQLAlchemy ORM models (inherit from `Base`)
|
||||
5. `schemas/` - Pydantic models for request/response validation
|
||||
6. `core/` - Configuration, logging, errors
|
||||
|
||||
### Database Architecture
|
||||
|
||||
**Dual Database Strategy:**
|
||||
- Main database: game server data (homes, warps, kits, punishments, etc.)
|
||||
- Azuriom database: user management system (separate connection)
|
||||
|
||||
**Session Management:**
|
||||
- `get_session()` dependency provides main DB sessions
|
||||
- `get_azuriom_session()` dependency provides Azuriom DB sessions
|
||||
- Services are instantiated per-request with appropriate session injected
|
||||
|
||||
**Base Model:**
|
||||
All SQLAlchemy models inherit from `Base` (src/hubgw/models/base.py) which includes:
|
||||
- `created_at` - auto-populated timestamp
|
||||
- `updated_at` - auto-updated timestamp
|
||||
|
||||
### Dependency Injection Pattern
|
||||
|
||||
FastAPI dependencies defined in `src/hubgw/api/deps.py`:
|
||||
- `get_context()` - returns AppContext singleton
|
||||
- `get_session()` / `get_azuriom_session()` - database sessions
|
||||
- `verify_api_key()` - API key authentication via `X-API-Key` header
|
||||
- Service factories (e.g., `get_homes_service()`, `get_kits_service()`) - instantiate services with session
|
||||
|
||||
Services receive `AsyncSession` in constructor and interact with repositories or ORM directly.
|
||||
|
||||
### API Structure
|
||||
|
||||
All endpoints under `/api/v1/` prefix:
|
||||
- `/health` - health check endpoint
|
||||
- Domain-specific routers: `/homes`, `/kits`, `/cooldowns`, `/warps`, `/whitelist`, `/punishments`, `/audit`, `/luckperms`, `/teleport-history`, `/users`
|
||||
|
||||
Each router module in `src/hubgw/api/v1/` defines its own `router = APIRouter()` and is registered in `src/hubgw/api/v1/router.py`.
|
||||
|
||||
### Application Lifecycle
|
||||
|
||||
Managed via FastAPI lifespan context in `src/hubgw/main.py`:
|
||||
- `startup()` - initialize AppContext
|
||||
- `shutdown()` - dispose database engines
|
||||
- Logging setup via `setup_logging()` from `core/logging.py`
|
||||
|
||||
## Development Commands
|
||||
|
||||
**Install dependencies:**
|
||||
```bash
|
||||
poetry install
|
||||
```
|
||||
|
||||
**Run development server:**
|
||||
```bash
|
||||
poetry run hubgw
|
||||
```
|
||||
|
||||
**Run tests:**
|
||||
```bash
|
||||
poetry run pytest
|
||||
```
|
||||
|
||||
**Run specific test:**
|
||||
```bash
|
||||
poetry run pytest tests/unit/test_specific.py::test_function_name
|
||||
```
|
||||
|
||||
**Run tests with coverage:**
|
||||
```bash
|
||||
poetry run pytest --cov=hubgw --cov-report=html
|
||||
```
|
||||
|
||||
**Lint with Ruff:**
|
||||
```bash
|
||||
poetry run ruff check .
|
||||
```
|
||||
|
||||
**Format with Black:**
|
||||
```bash
|
||||
poetry run black src/ tests/
|
||||
```
|
||||
|
||||
**Sort imports:**
|
||||
```bash
|
||||
poetry run isort src/ tests/
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Environment variables use hierarchical double-underscore notation:
|
||||
|
||||
**Database:**
|
||||
- `DATABASE__HOST` (default: localhost)
|
||||
- `DATABASE__PORT` (default: 5432)
|
||||
- `DATABASE__USER`
|
||||
- `DATABASE__PASSWORD`
|
||||
- `DATABASE__DATABASE` (main database name)
|
||||
- `DATABASE__AZURIOM_DATABASE` (Azuriom database name)
|
||||
- `DATABASE__POOL_SIZE` (default: 10)
|
||||
- `DATABASE__MAX_OVERFLOW` (default: 10)
|
||||
- `DATABASE__ECHO` (default: False)
|
||||
|
||||
**Security:**
|
||||
- `SECURITY__API_KEY` (required for authentication)
|
||||
- `SECURITY__RATE_LIMIT_PER_MIN` (optional rate limiting)
|
||||
|
||||
**Application:**
|
||||
- `APP__ENV` (dev/prod/test)
|
||||
- `APP__HOST` (default: 0.0.0.0)
|
||||
- `APP__PORT` (default: 8080)
|
||||
- `APP__LOG_LEVEL` (default: INFO)
|
||||
|
||||
DSN strings are automatically computed from individual connection parameters.
|
||||
|
||||
## Adding New Features
|
||||
|
||||
**When adding a new domain entity (e.g., "shops"):**
|
||||
|
||||
1. **Model** - Create `src/hubgw/models/shops.py`:
|
||||
- Inherit from `Base`
|
||||
- Define SQLAlchemy columns
|
||||
- Add table name via `__tablename__`
|
||||
|
||||
2. **Schema** - Create `src/hubgw/schemas/shops.py`:
|
||||
- Define Pydantic models for requests/responses
|
||||
- Use `from __future__ import annotations`
|
||||
- Follow DTO pattern (Create/Update/Response schemas)
|
||||
|
||||
3. **Repository** (if needed) - Create `src/hubgw/repositories/shops_repo.py`:
|
||||
- Encapsulate database queries
|
||||
- Accept `AsyncSession` in constructor
|
||||
- Return model instances or query results
|
||||
|
||||
4. **Service** - Create `src/hubgw/services/shops_service.py`:
|
||||
- Accept `AsyncSession` in constructor
|
||||
- Implement business logic
|
||||
- Use repository or ORM directly
|
||||
- Return schemas or raise exceptions
|
||||
|
||||
5. **API Router** - Create `src/hubgw/api/v1/shops.py`:
|
||||
- Define `router = APIRouter()`
|
||||
- Use service via dependency injection
|
||||
- Apply `verify_api_key` dependency for protected endpoints
|
||||
- Return Pydantic schemas
|
||||
|
||||
6. **Dependency** - Add to `src/hubgw/api/deps.py`:
|
||||
```python
|
||||
def get_shops_service(
|
||||
session: Annotated[AsyncSession, Depends(get_session)]
|
||||
) -> ShopsService:
|
||||
return ShopsService(session)
|
||||
```
|
||||
|
||||
7. **Register Router** - Update `src/hubgw/api/v1/router.py`:
|
||||
```python
|
||||
from hubgw.api.v1 import shops
|
||||
api_router.include_router(shops.router, prefix="/shops", tags=["shops"])
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
Test structure mirrors source structure:
|
||||
- `tests/unit/` - unit tests
|
||||
- `tests/integration/` - integration tests
|
||||
- `tests/conftest.py` - shared fixtures
|
||||
|
||||
**Key fixtures:**
|
||||
- `test_db` - test database engine and session factory
|
||||
- `test_session` - database session for tests
|
||||
- `test_client` - FastAPI TestClient
|
||||
- `test_context` - AppContext instance
|
||||
|
||||
Use async test functions with `pytest-asyncio`.
|
||||
276
db.ddl
276
db.ddl
|
|
@ -1,69 +1,73 @@
|
|||
-- ============================================================
|
||||
-- LUCKPERMS TABLES
|
||||
-- SETUP
|
||||
-- ============================================================
|
||||
|
||||
CREATE SCHEMA IF NOT EXISTS hubmc;
|
||||
|
||||
-- gen_random_uuid() из pgcrypto
|
||||
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
||||
|
||||
-- ============================================================
|
||||
-- LUCKPERMS TABLES (схема hubmc, правки из нового DDL)
|
||||
-- ============================================================
|
||||
|
||||
-- Таблица игроков LuckPerms
|
||||
CREATE TABLE IF NOT EXISTS luckperms_players (
|
||||
CREATE TABLE IF NOT EXISTS hubmc.luckperms_players (
|
||||
uuid VARCHAR(36) PRIMARY KEY,
|
||||
username VARCHAR(16) NOT NULL,
|
||||
primary_group VARCHAR(36) NOT NULL,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||
created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Индексы для luckperms_players
|
||||
CREATE INDEX IF NOT EXISTS idx_luckperms_players_username ON luckperms_players(username);
|
||||
CREATE INDEX IF NOT EXISTS idx_luckperms_players_username ON hubmc.luckperms_players(username);
|
||||
|
||||
-- Таблица групп LuckPerms
|
||||
CREATE TABLE IF NOT EXISTS luckperms_groups (
|
||||
CREATE TABLE IF NOT EXISTS hubmc.luckperms_groups (
|
||||
name VARCHAR(36) PRIMARY KEY,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||
created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Таблица прав пользователей LuckPerms
|
||||
CREATE TABLE IF NOT EXISTS luckperms_user_permissions (
|
||||
-- (ужесточены NOT NULL для server/world/expiry/contexts как в новом DDL)
|
||||
CREATE TABLE IF NOT EXISTS hubmc.luckperms_user_permissions (
|
||||
id SERIAL PRIMARY KEY,
|
||||
uuid VARCHAR(36) NOT NULL,
|
||||
permission VARCHAR(200) NOT NULL,
|
||||
value BOOLEAN NOT NULL,
|
||||
server VARCHAR(36),
|
||||
world VARCHAR(64),
|
||||
expiry BIGINT,
|
||||
contexts VARCHAR(200),
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
FOREIGN KEY (uuid) REFERENCES luckperms_players(uuid) ON DELETE CASCADE
|
||||
server VARCHAR(36) NOT NULL,
|
||||
world VARCHAR(64) NOT NULL,
|
||||
expiry BIGINT NOT NULL,
|
||||
contexts VARCHAR(200) NOT NULL,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
FOREIGN KEY (uuid) REFERENCES hubmc.luckperms_players(uuid) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Индексы для luckperms_user_permissions
|
||||
CREATE INDEX IF NOT EXISTS idx_luckperms_user_permissions_uuid ON luckperms_user_permissions(uuid);
|
||||
CREATE INDEX IF NOT EXISTS idx_luckperms_user_permissions_lookup ON luckperms_user_permissions(uuid, permission, server, world);
|
||||
CREATE INDEX IF NOT EXISTS idx_luckperms_user_permissions_uuid ON hubmc.luckperms_user_permissions(uuid);
|
||||
CREATE INDEX IF NOT EXISTS idx_luckperms_user_permissions_lookup ON hubmc.luckperms_user_permissions(uuid, permission, server, world);
|
||||
|
||||
-- ============================================================
|
||||
-- HUB TABLES
|
||||
-- HUB TABLES (схема hubmc, правки из нового DDL)
|
||||
-- ============================================================
|
||||
|
||||
-- Таблица кулдаунов (ИСПРАВЛЕНО)
|
||||
CREATE TABLE IF NOT EXISTS hub_cooldowns (
|
||||
-- Таблица кулдаунов
|
||||
CREATE TABLE IF NOT EXISTS hubmc.hub_cooldowns (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
player_uuid VARCHAR(36) NOT NULL,
|
||||
cooldown_type VARCHAR(50) NOT NULL,
|
||||
expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
expires_at TIMESTAMPTZ NOT NULL,
|
||||
cooldown_seconds INTEGER NOT NULL,
|
||||
metadata JSONB,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
FOREIGN KEY (player_uuid) REFERENCES luckperms_players(uuid) ON DELETE CASCADE
|
||||
created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
FOREIGN KEY (player_uuid) REFERENCES hubmc.luckperms_players(uuid) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Индексы для hub_cooldowns
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_hub_cooldowns_player_type ON hub_cooldowns(player_uuid, cooldown_type);
|
||||
CREATE INDEX IF NOT EXISTS idx_hub_cooldowns_expires ON hub_cooldowns(expires_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_hub_cooldowns_player_uuid ON hub_cooldowns(player_uuid);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_hub_cooldowns_player_type ON hubmc.hub_cooldowns(player_uuid, cooldown_type);
|
||||
CREATE INDEX IF NOT EXISTS idx_hub_cooldowns_expires ON hubmc.hub_cooldowns(expires_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_hub_cooldowns_player_uuid ON hubmc.hub_cooldowns(player_uuid);
|
||||
|
||||
-- Таблица домов игроков
|
||||
CREATE TABLE IF NOT EXISTS hub_homes (
|
||||
CREATE TABLE IF NOT EXISTS hubmc.hub_homes (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
player_uuid VARCHAR(36) NOT NULL,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
|
|
@ -74,47 +78,44 @@ CREATE TABLE IF NOT EXISTS hub_homes (
|
|||
yaw REAL DEFAULT 0.0,
|
||||
pitch REAL DEFAULT 0.0,
|
||||
is_public BOOLEAN DEFAULT false,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
FOREIGN KEY (player_uuid) REFERENCES luckperms_players(uuid) ON DELETE CASCADE
|
||||
created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
FOREIGN KEY (player_uuid) REFERENCES hubmc.luckperms_players(uuid) ON DELETE CASCADE
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_hub_homes_player_uuid ON hubmc.hub_homes(player_uuid);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_hub_homes_player_name ON hubmc.hub_homes(player_uuid, name);
|
||||
|
||||
-- Индексы для hub_homes
|
||||
CREATE INDEX IF NOT EXISTS idx_hub_homes_player_uuid ON hub_homes(player_uuid);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_hub_homes_player_name ON hub_homes(player_uuid, name);
|
||||
|
||||
-- Таблица наказаний (ДОПОЛНЕНО)
|
||||
CREATE TABLE IF NOT EXISTS hub_punishments (
|
||||
-- Таблица наказаний
|
||||
CREATE TABLE IF NOT EXISTS hubmc.hub_punishments (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
player_uuid VARCHAR(36) NOT NULL,
|
||||
player_name VARCHAR(255) NOT NULL,
|
||||
player_ip INET,
|
||||
punishment_type VARCHAR(50) NOT NULL CHECK (punishment_type IN ('BAN', 'MUTE', 'KICK', 'WARN', 'TEMPBAN', 'TEMPMUTE')),
|
||||
punishment_type VARCHAR(50) NOT NULL
|
||||
CHECK (punishment_type IN ('BAN', 'MUTE', 'KICK', 'WARN', 'TEMPBAN', 'TEMPMUTE')),
|
||||
reason TEXT NOT NULL,
|
||||
staff_uuid VARCHAR(36) NOT NULL,
|
||||
staff_name VARCHAR(255) NOT NULL,
|
||||
expires_at TIMESTAMP WITH TIME ZONE,
|
||||
expires_at TIMESTAMPTZ,
|
||||
is_active BOOLEAN DEFAULT true,
|
||||
revoked_at TIMESTAMP WITH TIME ZONE,
|
||||
revoked_at TIMESTAMPTZ,
|
||||
revoked_by VARCHAR(36),
|
||||
revoked_reason TEXT,
|
||||
evidence_url TEXT,
|
||||
notes TEXT,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
FOREIGN KEY (player_uuid) REFERENCES luckperms_players(uuid) ON DELETE CASCADE,
|
||||
FOREIGN KEY (staff_uuid) REFERENCES luckperms_players(uuid) ON DELETE SET NULL,
|
||||
FOREIGN KEY (revoked_by) REFERENCES luckperms_players(uuid) ON DELETE SET NULL
|
||||
created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
FOREIGN KEY (player_uuid) REFERENCES hubmc.luckperms_players(uuid) ON DELETE CASCADE,
|
||||
FOREIGN KEY (staff_uuid) REFERENCES hubmc.luckperms_players(uuid) ON DELETE SET NULL,
|
||||
FOREIGN KEY (revoked_by) REFERENCES hubmc.luckperms_players(uuid) ON DELETE SET NULL
|
||||
);
|
||||
|
||||
-- Индексы для hub_punishments
|
||||
CREATE INDEX IF NOT EXISTS idx_hub_punishments_player_uuid ON hub_punishments(player_uuid);
|
||||
CREATE INDEX IF NOT EXISTS idx_hub_punishments_player_active ON hub_punishments(player_uuid, is_active) WHERE is_active = true;
|
||||
CREATE INDEX IF NOT EXISTS idx_hub_punishments_type_active ON hub_punishments(punishment_type, is_active) WHERE is_active = true;
|
||||
CREATE INDEX IF NOT EXISTS idx_hub_punishments_player_ip ON hub_punishments(player_ip) WHERE player_ip IS NOT NULL;
|
||||
CREATE INDEX IF NOT EXISTS idx_hub_punishments_player_uuid ON hubmc.hub_punishments(player_uuid);
|
||||
CREATE INDEX IF NOT EXISTS idx_hub_punishments_player_active ON hubmc.hub_punishments(player_uuid, is_active) WHERE is_active = true;
|
||||
CREATE INDEX IF NOT EXISTS idx_hub_punishments_type_active ON hubmc.hub_punishments(punishment_type, is_active) WHERE is_active = true;
|
||||
CREATE INDEX IF NOT EXISTS idx_hub_punishments_player_ip ON hubmc.hub_punishments(player_ip) WHERE player_ip IS NOT NULL;
|
||||
|
||||
-- Таблица варпов
|
||||
CREATE TABLE IF NOT EXISTS hub_warps (
|
||||
CREATE TABLE IF NOT EXISTS hubmc.hub_warps (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
name VARCHAR(255) NOT NULL UNIQUE,
|
||||
world TEXT NOT NULL,
|
||||
|
|
@ -125,38 +126,32 @@ CREATE TABLE IF NOT EXISTS hub_warps (
|
|||
pitch REAL DEFAULT 0.0,
|
||||
is_public BOOLEAN DEFAULT true,
|
||||
description VARCHAR(500),
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||
created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_hub_warps_name ON hubmc.hub_warps(name);
|
||||
CREATE INDEX IF NOT EXISTS idx_hub_warps_world ON hubmc.hub_warps(world);
|
||||
CREATE INDEX IF NOT EXISTS idx_hub_warps_public ON hubmc.hub_warps(is_public) WHERE is_public = true;
|
||||
|
||||
-- Индексы для hub_warps
|
||||
CREATE INDEX IF NOT EXISTS idx_hub_warps_name ON hub_warps(name);
|
||||
CREATE INDEX IF NOT EXISTS idx_hub_warps_world ON hub_warps(world);
|
||||
CREATE INDEX IF NOT EXISTS idx_hub_warps_public ON hub_warps(is_public) WHERE is_public = true;
|
||||
|
||||
-- Таблица вайтлиста (ДОПОЛНЕНО)
|
||||
CREATE TABLE IF NOT EXISTS hub_whitelist (
|
||||
-- Таблица вайтлиста
|
||||
-- (как в новом DDL: без player_uuid; уникальность по player_name)
|
||||
CREATE TABLE IF NOT EXISTS hubmc.hub_whitelist (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
player_name VARCHAR(255) NOT NULL UNIQUE,
|
||||
player_uuid VARCHAR(36),
|
||||
added_by VARCHAR(255) NOT NULL,
|
||||
added_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
expires_at TIMESTAMP WITH TIME ZONE,
|
||||
added_at TIMESTAMPTZ NOT NULL,
|
||||
expires_at TIMESTAMPTZ,
|
||||
is_active BOOLEAN DEFAULT true,
|
||||
reason VARCHAR(500),
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
FOREIGN KEY (player_uuid) REFERENCES luckperms_players(uuid) ON DELETE SET NULL
|
||||
created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Индексы для hub_whitelist
|
||||
CREATE INDEX IF NOT EXISTS idx_hub_whitelist_player_name ON hub_whitelist(player_name);
|
||||
CREATE INDEX IF NOT EXISTS idx_hub_whitelist_player_uuid ON hub_whitelist(player_uuid);
|
||||
CREATE INDEX IF NOT EXISTS idx_hub_whitelist_active ON hub_whitelist(is_active) WHERE is_active = true;
|
||||
CREATE INDEX IF NOT EXISTS idx_hub_whitelist_expires ON hub_whitelist(expires_at) WHERE expires_at IS NOT NULL;
|
||||
CREATE INDEX IF NOT EXISTS idx_hub_whitelist_player_name ON hubmc.hub_whitelist(player_name);
|
||||
CREATE INDEX IF NOT EXISTS idx_hub_whitelist_active ON hubmc.hub_whitelist(is_active) WHERE is_active = true;
|
||||
CREATE INDEX IF NOT EXISTS idx_hub_whitelist_expires ON hubmc.hub_whitelist(expires_at) WHERE expires_at IS NOT NULL;
|
||||
|
||||
-- Таблица истории телепортаций
|
||||
CREATE TABLE IF NOT EXISTS hub_teleport_history (
|
||||
CREATE TABLE IF NOT EXISTS hubmc.hub_teleport_history (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
player_uuid VARCHAR(36) NOT NULL,
|
||||
from_world TEXT,
|
||||
|
|
@ -169,21 +164,43 @@ CREATE TABLE IF NOT EXISTS hub_teleport_history (
|
|||
to_z DOUBLE PRECISION NOT NULL,
|
||||
tp_type VARCHAR(50) NOT NULL,
|
||||
target_name VARCHAR(255),
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL,
|
||||
FOREIGN KEY (player_uuid) REFERENCES luckperms_players(uuid) ON DELETE CASCADE
|
||||
created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
|
||||
FOREIGN KEY (player_uuid) REFERENCES hubmc.luckperms_players(uuid) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Индексы для hub_teleport_history
|
||||
CREATE INDEX IF NOT EXISTS idx_hub_teleport_history_player_uuid ON hub_teleport_history(player_uuid);
|
||||
CREATE INDEX IF NOT EXISTS idx_hub_teleport_history_created_at ON hub_teleport_history(created_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_hub_teleport_history_tp_type ON hub_teleport_history(tp_type);
|
||||
CREATE INDEX IF NOT EXISTS idx_hub_teleport_history_player_uuid ON hubmc.hub_teleport_history(player_uuid);
|
||||
CREATE INDEX IF NOT EXISTS idx_hub_teleport_history_created_at ON hubmc.hub_teleport_history(created_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_hub_teleport_history_tp_type ON hubmc.hub_teleport_history(tp_type);
|
||||
|
||||
-- ============================================================
|
||||
-- TRIGGERS
|
||||
-- COMMENTS
|
||||
-- ============================================================
|
||||
|
||||
COMMENT ON TABLE hubmc.luckperms_players IS 'LuckPerms player data';
|
||||
COMMENT ON TABLE hubmc.luckperms_groups IS 'LuckPerms permission groups';
|
||||
COMMENT ON TABLE hubmc.luckperms_user_permissions IS 'Individual player permissions';
|
||||
COMMENT ON TABLE hubmc.hub_cooldowns IS 'Player cooldowns for various actions';
|
||||
COMMENT ON TABLE hubmc.hub_homes IS 'Player home locations';
|
||||
COMMENT ON TABLE hubmc.hub_punishments IS 'Player punishments (bans, mutes, etc.)';
|
||||
COMMENT ON TABLE hubmc.hub_warps IS 'Server warp points';
|
||||
COMMENT ON TABLE hubmc.hub_whitelist IS 'Server whitelist entries';
|
||||
COMMENT ON TABLE hubmc.hub_teleport_history IS 'History of all player teleportations';
|
||||
|
||||
COMMENT ON COLUMN hubmc.hub_cooldowns.cooldown_type IS 'Type of cooldown: TP_DELAY, HOME_SET, COMBAT, etc.';
|
||||
COMMENT ON COLUMN hubmc.hub_cooldowns.metadata IS 'Additional data in JSON format';
|
||||
COMMENT ON COLUMN hubmc.hub_punishments.player_ip IS 'Player IP address for IP bans';
|
||||
COMMENT ON COLUMN hubmc.hub_punishments.evidence_url IS 'URL to evidence (screenshot, video, etc.)';
|
||||
COMMENT ON COLUMN hubmc.hub_punishments.notes IS 'Internal moderator notes';
|
||||
COMMENT ON COLUMN hubmc.hub_whitelist.expires_at IS 'Expiration date for temporary whitelist';
|
||||
COMMENT ON COLUMN hubmc.hub_whitelist.is_active IS 'Whether whitelist entry is currently active';
|
||||
COMMENT ON COLUMN hubmc.hub_teleport_history.tp_type IS 'Type of teleport: HOME, WARP, TPA, TPAHERE, SPAWN, BACK, etc.';
|
||||
COMMENT ON COLUMN hubmc.hub_teleport_history.target_name IS 'Target name for TPA/home/warp teleports';
|
||||
|
||||
-- ============================================================
|
||||
-- FUNCTIONS (все в конце)
|
||||
-- ============================================================
|
||||
|
||||
-- Функция для автоматического обновления updated_at
|
||||
CREATE OR REPLACE FUNCTION update_updated_at_column()
|
||||
CREATE OR REPLACE FUNCTION hubmc.update_updated_at_column()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = NOW();
|
||||
|
|
@ -191,67 +208,48 @@ BEGIN
|
|||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- Применение триггеров ко всем таблицам
|
||||
DROP TRIGGER IF EXISTS update_luckperms_players_updated_at ON luckperms_players;
|
||||
-- ============================================================
|
||||
-- TRIGGERS (после функций)
|
||||
-- ============================================================
|
||||
|
||||
-- LuckPerms
|
||||
DROP TRIGGER IF EXISTS update_luckperms_players_updated_at ON hubmc.luckperms_players;
|
||||
CREATE TRIGGER update_luckperms_players_updated_at
|
||||
BEFORE UPDATE ON luckperms_players
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
BEFORE UPDATE ON hubmc.luckperms_players
|
||||
FOR EACH ROW EXECUTE FUNCTION hubmc.update_updated_at_column();
|
||||
|
||||
DROP TRIGGER IF EXISTS update_luckperms_groups_updated_at ON luckperms_groups;
|
||||
DROP TRIGGER IF EXISTS update_luckperms_groups_updated_at ON hubmc.luckperms_groups;
|
||||
CREATE TRIGGER update_luckperms_groups_updated_at
|
||||
BEFORE UPDATE ON luckperms_groups
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
BEFORE UPDATE ON hubmc.luckperms_groups
|
||||
FOR EACH ROW EXECUTE FUNCTION hubmc.update_updated_at_column();
|
||||
|
||||
DROP TRIGGER IF EXISTS update_luckperms_user_permissions_updated_at ON luckperms_user_permissions;
|
||||
DROP TRIGGER IF EXISTS update_luckperms_user_permissions_updated_at ON hubmc.luckperms_user_permissions;
|
||||
CREATE TRIGGER update_luckperms_user_permissions_updated_at
|
||||
BEFORE UPDATE ON luckperms_user_permissions
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
BEFORE UPDATE ON hubmc.luckperms_user_permissions
|
||||
FOR EACH ROW EXECUTE FUNCTION hubmc.update_updated_at_column();
|
||||
|
||||
DROP TRIGGER IF EXISTS update_hub_cooldowns_updated_at ON hub_cooldowns;
|
||||
-- HUB
|
||||
DROP TRIGGER IF EXISTS update_hub_cooldowns_updated_at ON hubmc.hub_cooldowns;
|
||||
CREATE TRIGGER update_hub_cooldowns_updated_at
|
||||
BEFORE UPDATE ON hub_cooldowns
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
BEFORE UPDATE ON hubmc.hub_cooldowns
|
||||
FOR EACH ROW EXECUTE FUNCTION hubmc.update_updated_at_column();
|
||||
|
||||
DROP TRIGGER IF EXISTS update_hub_homes_updated_at ON hub_homes;
|
||||
DROP TRIGGER IF EXISTS update_hub_homes_updated_at ON hubmc.hub_homes;
|
||||
CREATE TRIGGER update_hub_homes_updated_at
|
||||
BEFORE UPDATE ON hub_homes
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
BEFORE UPDATE ON hubmc.hub_homes
|
||||
FOR EACH ROW EXECUTE FUNCTION hubmc.update_updated_at_column();
|
||||
|
||||
DROP TRIGGER IF EXISTS update_hub_punishments_updated_at ON hub_punishments;
|
||||
DROP TRIGGER IF EXISTS update_hub_punishments_updated_at ON hubmc.hub_punishments;
|
||||
CREATE TRIGGER update_hub_punishments_updated_at
|
||||
BEFORE UPDATE ON hub_punishments
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
BEFORE UPDATE ON hubmc.hub_punishments
|
||||
FOR EACH ROW EXECUTE FUNCTION hubmc.update_updated_at_column();
|
||||
|
||||
DROP TRIGGER IF EXISTS update_hub_warps_updated_at ON hub_warps;
|
||||
DROP TRIGGER IF EXISTS update_hub_warps_updated_at ON hubmc.hub_warps;
|
||||
CREATE TRIGGER update_hub_warps_updated_at
|
||||
BEFORE UPDATE ON hub_warps
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
BEFORE UPDATE ON hubmc.hub_warps
|
||||
FOR EACH ROW EXECUTE FUNCTION hubmc.update_updated_at_column();
|
||||
|
||||
DROP TRIGGER IF EXISTS update_hub_whitelist_updated_at ON hub_whitelist;
|
||||
DROP TRIGGER IF EXISTS update_hub_whitelist_updated_at ON hubmc.hub_whitelist;
|
||||
CREATE TRIGGER update_hub_whitelist_updated_at
|
||||
BEFORE UPDATE ON hub_whitelist
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
-- ============================================================
|
||||
-- COMMENTS
|
||||
-- ============================================================
|
||||
|
||||
COMMENT ON TABLE luckperms_players IS 'LuckPerms player data';
|
||||
COMMENT ON TABLE luckperms_groups IS 'LuckPerms permission groups';
|
||||
COMMENT ON TABLE luckperms_user_permissions IS 'Individual player permissions';
|
||||
COMMENT ON TABLE hub_cooldowns IS 'Player cooldowns for various actions';
|
||||
COMMENT ON TABLE hub_homes IS 'Player home locations';
|
||||
COMMENT ON TABLE hub_punishments IS 'Player punishments (bans, mutes, etc.)';
|
||||
COMMENT ON TABLE hub_warps IS 'Server warp points';
|
||||
COMMENT ON TABLE hub_whitelist IS 'Server whitelist entries';
|
||||
COMMENT ON TABLE hub_teleport_history IS 'History of all player teleportations';
|
||||
|
||||
COMMENT ON COLUMN hub_cooldowns.cooldown_type IS 'Type of cooldown: TP_DELAY, HOME_SET, COMBAT, etc.';
|
||||
COMMENT ON COLUMN hub_cooldowns.metadata IS 'Additional data in JSON format';
|
||||
COMMENT ON COLUMN hub_punishments.player_ip IS 'Player IP address for IP bans';
|
||||
COMMENT ON COLUMN hub_punishments.evidence_url IS 'URL to evidence (screenshot, video, etc.)';
|
||||
COMMENT ON COLUMN hub_punishments.notes IS 'Internal moderator notes';
|
||||
COMMENT ON COLUMN hub_whitelist.expires_at IS 'Expiration date for temporary whitelist';
|
||||
COMMENT ON COLUMN hub_whitelist.is_active IS 'Whether whitelist entry is currently active';
|
||||
COMMENT ON COLUMN hub_teleport_history.tp_type IS 'Type of teleport: HOME, WARP, TPA, TPAHERE, SPAWN, BACK, etc.';
|
||||
COMMENT ON COLUMN hub_teleport_history.target_name IS 'Target name for TPA/home/warp teleports';
|
||||
BEFORE UPDATE ON hubmc.hub_whitelist
|
||||
FOR EACH ROW EXECUTE FUNCTION hubmc.update_updated_at_column();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
# Application settings
|
||||
APP__ENV=prod
|
||||
APP__HOST=0.0.0.0
|
||||
APP__PORT=8080
|
||||
APP__LOG_LEVEL=INFO
|
||||
|
||||
# Database settings
|
||||
DATABASE__HOST=localhost
|
||||
DATABASE__PORT=5432
|
||||
DATABASE__USER=hubgw_user
|
||||
DATABASE__PASSWORD=your_secure_password
|
||||
DATABASE__DATABASE=hubgw
|
||||
DATABASE__AZURIOM_DATABASE=azuriom
|
||||
DATABASE__POOL_SIZE=10
|
||||
DATABASE__MAX_OVERFLOW=10
|
||||
DATABASE__ECHO=false
|
||||
|
||||
# Security settings
|
||||
SECURITY__API_KEY=your_very_secure_api_key_here
|
||||
SECURITY__RATE_LIMIT_PER_MIN=100
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
"""Whitelist model."""
|
||||
|
||||
from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Index, String
|
||||
from sqlalchemy import Boolean, Column, DateTime, Index, String
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
|
||||
from hubgw.models.base import Base
|
||||
|
|
|
|||
|
|
@ -8,8 +8,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
|||
|
||||
from hubgw.models.whitelist import WhitelistEntry
|
||||
from hubgw.schemas.whitelist import (WhitelistAddRequest,
|
||||
WhitelistCheckRequest, WhitelistQuery,
|
||||
WhitelistRemoveRequest)
|
||||
WhitelistCheckRequest, WhitelistRemoveRequest)
|
||||
|
||||
|
||||
class WhitelistRepository:
|
||||
|
|
@ -58,47 +57,6 @@ class WhitelistRepository:
|
|||
await self.session.commit()
|
||||
return result.rowcount > 0
|
||||
|
||||
async def query(self, query: WhitelistQuery) -> tuple[List[WhitelistEntry], int]:
|
||||
"""Query whitelist entries with filters and pagination."""
|
||||
stmt = select(WhitelistEntry)
|
||||
count_stmt = select(func.count(WhitelistEntry.id))
|
||||
|
||||
if query.player_name:
|
||||
stmt = stmt.where(
|
||||
WhitelistEntry.player_name.ilike(f"%{query.player_name}%")
|
||||
)
|
||||
count_stmt = count_stmt.where(
|
||||
WhitelistEntry.player_name.ilike(f"%{query.player_name}%")
|
||||
)
|
||||
if query.player_uuid:
|
||||
stmt = stmt.where(WhitelistEntry.player_uuid == query.player_uuid)
|
||||
count_stmt = count_stmt.where(
|
||||
WhitelistEntry.player_uuid == query.player_uuid
|
||||
)
|
||||
if query.added_by:
|
||||
stmt = stmt.where(WhitelistEntry.added_by.ilike(f"%{query.added_by}%"))
|
||||
count_stmt = count_stmt.where(
|
||||
WhitelistEntry.added_by.ilike(f"%{query.added_by}%")
|
||||
)
|
||||
if query.is_active is not None:
|
||||
stmt = stmt.where(WhitelistEntry.is_active == query.is_active)
|
||||
count_stmt = count_stmt.where(WhitelistEntry.is_active == query.is_active)
|
||||
|
||||
count_result = await self.session.execute(count_stmt)
|
||||
total = count_result.scalar_one()
|
||||
|
||||
offset = (query.page - 1) * query.size
|
||||
stmt = (
|
||||
stmt.offset(offset)
|
||||
.limit(query.size)
|
||||
.order_by(WhitelistEntry.added_at.desc())
|
||||
)
|
||||
|
||||
result = await self.session.execute(stmt)
|
||||
entries = list(result.scalars().all())
|
||||
|
||||
return entries, total
|
||||
|
||||
async def check(self, request: WhitelistCheckRequest) -> Optional[WhitelistEntry]:
|
||||
"""Check if player is whitelisted."""
|
||||
stmt = select(WhitelistEntry).where(
|
||||
|
|
|
|||
|
|
@ -1,33 +1,120 @@
|
|||
"""Whitelist schemas."""
|
||||
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Optional
|
||||
from uuid import UUID
|
||||
import re
|
||||
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
||||
|
||||
|
||||
class WhitelistAddRequest(BaseModel):
|
||||
"""Whitelist add request schema."""
|
||||
|
||||
player_name: str
|
||||
added_by: str
|
||||
player_name: str = Field(min_length=3, max_length=16)
|
||||
added_by: str = Field(min_length=1, max_length=100)
|
||||
added_at: datetime
|
||||
expires_at: Optional[datetime] = None
|
||||
is_active: bool = True
|
||||
reason: Optional[str] = None
|
||||
reason: Optional[str] = Field(None, max_length=500)
|
||||
|
||||
@field_validator('player_name')
|
||||
@classmethod
|
||||
def validate_minecraft_username(cls, v: str) -> str:
|
||||
"""Validate Minecraft username format (alphanumeric and underscore only)."""
|
||||
if not re.match(r'^[a-zA-Z0-9_]{3,16}$', v):
|
||||
raise ValueError(
|
||||
'Invalid Minecraft username: must be 3-16 characters, '
|
||||
'only letters, numbers, and underscores allowed'
|
||||
)
|
||||
return v
|
||||
|
||||
@field_validator('added_by')
|
||||
@classmethod
|
||||
def validate_added_by(cls, v: str) -> str:
|
||||
"""Validate added_by field is not empty or whitespace."""
|
||||
if not v or not v.strip():
|
||||
raise ValueError('added_by cannot be empty or whitespace')
|
||||
return v.strip()
|
||||
|
||||
@field_validator('added_at')
|
||||
@classmethod
|
||||
def validate_added_at(cls, v: datetime) -> datetime:
|
||||
"""Validate added_at is within reasonable time range."""
|
||||
now = datetime.now(v.tzinfo) if v.tzinfo else datetime.now()
|
||||
|
||||
if v < now - timedelta(hours=1):
|
||||
raise ValueError('added_at is too far in the past')
|
||||
|
||||
if v > now + timedelta(minutes=5):
|
||||
raise ValueError('added_at cannot be in the future')
|
||||
|
||||
return v
|
||||
|
||||
@field_validator('expires_at')
|
||||
@classmethod
|
||||
def validate_expires_at(cls, v: Optional[datetime]) -> Optional[datetime]:
|
||||
"""Validate expires_at is within reasonable future range."""
|
||||
if v is None:
|
||||
return v
|
||||
|
||||
now = datetime.now(v.tzinfo) if v.tzinfo else datetime.now()
|
||||
|
||||
if v > now + timedelta(days=730):
|
||||
raise ValueError('expires_at cannot be more than 2 years in the future')
|
||||
|
||||
if v < now:
|
||||
raise ValueError('expires_at cannot be in the past')
|
||||
|
||||
return v
|
||||
|
||||
@field_validator('reason')
|
||||
@classmethod
|
||||
def validate_reason(cls, v: Optional[str]) -> Optional[str]:
|
||||
"""Validate and sanitize reason field."""
|
||||
if v is None:
|
||||
return v
|
||||
|
||||
v = v.strip()
|
||||
|
||||
if not v:
|
||||
return None
|
||||
|
||||
return v
|
||||
|
||||
|
||||
class WhitelistRemoveRequest(BaseModel):
|
||||
"""Whitelist remove request schema."""
|
||||
|
||||
player_name: str
|
||||
player_name: str = Field(min_length=3, max_length=16)
|
||||
|
||||
@field_validator('player_name')
|
||||
@classmethod
|
||||
def validate_minecraft_username(cls, v: str) -> str:
|
||||
"""Validate Minecraft username format."""
|
||||
if not re.match(r'^[a-zA-Z0-9_]{3,16}$', v):
|
||||
raise ValueError(
|
||||
'Invalid Minecraft username: must be 3-16 characters, '
|
||||
'only letters, numbers, and underscores allowed'
|
||||
)
|
||||
return v
|
||||
|
||||
|
||||
class WhitelistCheckRequest(BaseModel):
|
||||
"""Whitelist check request schema."""
|
||||
|
||||
player_name: str
|
||||
player_name: str = Field(min_length=3, max_length=16)
|
||||
|
||||
@field_validator('player_name')
|
||||
@classmethod
|
||||
def validate_minecraft_username(cls, v: str) -> str:
|
||||
"""Validate Minecraft username format."""
|
||||
if not re.match(r'^[a-zA-Z0-9_]{3,16}$', v):
|
||||
raise ValueError(
|
||||
'Invalid Minecraft username: must be 3-16 characters, '
|
||||
'only letters, numbers, and underscores allowed'
|
||||
)
|
||||
return v
|
||||
|
||||
|
||||
class WhitelistCheckResponse(BaseModel):
|
||||
|
|
@ -55,14 +142,3 @@ class WhitelistListResponse(BaseModel):
|
|||
|
||||
entries: list[WhitelistEntry]
|
||||
total: int
|
||||
|
||||
|
||||
class WhitelistQuery(BaseModel):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
page: int = 1
|
||||
size: int = 10
|
||||
|
||||
player_name: Optional[str] = None
|
||||
player_uuid: Optional[str] = None
|
||||
added_by: Optional[str] = None
|
||||
is_active: Optional[bool] = None
|
||||
|
|
|
|||
|
|
@ -10,8 +10,7 @@ from hubgw.schemas.whitelist import (WhitelistAddRequest,
|
|||
WhitelistCheckRequest,
|
||||
WhitelistCheckResponse)
|
||||
from hubgw.schemas.whitelist import WhitelistEntry as SchemaWhitelistEntry
|
||||
from hubgw.schemas.whitelist import (WhitelistListResponse, WhitelistQuery,
|
||||
WhitelistRemoveRequest)
|
||||
from hubgw.schemas.whitelist import (WhitelistListResponse, WhitelistRemoveRequest)
|
||||
from hubgw.services.luckperms_service import LuckPermsService
|
||||
from hubgw.services.users_service import UserService
|
||||
|
||||
|
|
@ -79,10 +78,3 @@ class WhitelistService:
|
|||
entry_list = [SchemaWhitelistEntry.model_validate(entry) for entry in entries]
|
||||
|
||||
return WhitelistListResponse(entries=entry_list, total=total)
|
||||
|
||||
async def query_players(self, query: WhitelistQuery) -> WhitelistListResponse:
|
||||
entries, total = await self.repo.query(query)
|
||||
|
||||
entry_list = [SchemaWhitelistEntry.model_validate(entry) for entry in entries]
|
||||
|
||||
return WhitelistListResponse(entries=entry_list, total=total)
|
||||
|
|
|
|||
Loading…
Reference in New Issue