From dac619dbddbb39f18f1fdbc8ca36cf7b5a7a6e8d Mon Sep 17 00:00:00 2001 From: itqop Date: Wed, 12 Nov 2025 21:01:39 +0300 Subject: [PATCH] feat: add new features --- API_ENDPOINTS.md | 960 +++++++++++++++++++++ WHITELIST_API_SCHEMAS.md | 215 +++++ src/hubgw/context.py | 1 - src/hubgw/repositories/cooldowns_repo.py | 11 +- src/hubgw/repositories/homes_repo.py | 17 +- src/hubgw/repositories/kits_repo.py | 10 +- src/hubgw/repositories/punishments_repo.py | 12 +- src/hubgw/repositories/warps_repo.py | 15 +- src/hubgw/repositories/whitelist_repo.py | 6 +- src/hubgw/services/cooldowns_service.py | 11 +- src/hubgw/services/kits_service.py | 4 +- src/hubgw/services/luckperms_service.py | 13 +- src/hubgw/services/punishments_service.py | 11 +- 13 files changed, 1252 insertions(+), 34 deletions(-) create mode 100644 API_ENDPOINTS.md create mode 100644 WHITELIST_API_SCHEMAS.md diff --git a/API_ENDPOINTS.md b/API_ENDPOINTS.md new file mode 100644 index 0000000..fa15dec --- /dev/null +++ b/API_ENDPOINTS.md @@ -0,0 +1,960 @@ +# HubGW API Endpoints Documentation + +Полное описание всех API endpoints в HubGW с телами запросов и ответов. + +Базовый URL: `/api/v1` + +Все endpoints требуют аутентификации через заголовок `X-API-Key`. + +--- + +## Health + +**Префикс:** `/health` + +### GET `/health/` + +Проверка работоспособности сервиса. + +**Ответ:** +```json +{ + "status": "ok" +} +``` + +--- + +## Homes + +**Префикс:** `/homes` + +### PUT `/homes/` + +Создает или обновляет дом игрока. + +**Тело запроса:** +```json +{ + "player_uuid": "string", + "name": "string", + "world": "string", + "x": 0.0, + "y": 0.0, + "z": 0.0, + "yaw": 0.0, + "pitch": 0.0, + "is_public": false +} +``` + +**Ответ:** +```json +{ + "id": "uuid", + "player_uuid": "string", + "name": "string", + "world": "string", + "x": 0.0, + "y": 0.0, + "z": 0.0, + "yaw": 0.0, + "pitch": 0.0, + "is_public": false, + "created_at": "2024-01-01T00:00:00Z", + "updated_at": "2024-01-01T00:00:00Z" +} +``` + +### POST `/homes/get` + +Получает конкретный дом игрока по имени. + +**Тело запроса:** +```json +{ + "player_uuid": "string", + "name": "string" +} +``` + +**Ответ:** +```json +{ + "id": "uuid", + "player_uuid": "string", + "name": "string", + "world": "string", + "x": 0.0, + "y": 0.0, + "z": 0.0, + "yaw": 0.0, + "pitch": 0.0, + "is_public": false, + "created_at": "2024-01-01T00:00:00Z", + "updated_at": "2024-01-01T00:00:00Z" +} +``` + +### GET `/homes/{player_uuid}` + +Получает список всех домов игрока. + +**Параметры:** +- `player_uuid` (path) - UUID игрока + +**Ответ:** +```json +{ + "homes": [ + { + "id": "uuid", + "player_uuid": "string", + "name": "string", + "world": "string", + "x": 0.0, + "y": 0.0, + "z": 0.0, + "yaw": 0.0, + "pitch": 0.0, + "is_public": false, + "created_at": "2024-01-01T00:00:00Z", + "updated_at": "2024-01-01T00:00:00Z" + } + ], + "total": 1 +} +``` + +--- + +## Kits + +**Префикс:** `/kits` + +### POST `/kits/claim` + +Выдает набор (kit) игроку. + +**Тело запроса:** +```json +{ + "player_uuid": "uuid", + "kit_name": "string" +} +``` + +**Ответ:** +```json +{ + "success": true, + "message": "string", + "cooldown_remaining": 3600 +} +``` + +--- + +## Cooldowns + +**Префикс:** `/cooldowns` + +### POST `/cooldowns/check` + +Проверяет статус кулдауна для игрока. + +**Тело запроса:** +```json +{ + "player_uuid": "string", + "cooldown_type": "string" +} +``` + +**Ответ:** +```json +{ + "is_active": true, + "expires_at": "2024-01-01T00:00:00Z", + "remaining_seconds": 3600 +} +``` + +### POST `/cooldowns/` + +Создает новый кулдаун для игрока. + +**Тело запроса:** +```json +{ + "player_uuid": "string", + "cooldown_type": "string", + "expires_at": "2024-01-01T00:00:00Z", + "cooldown_seconds": 3600, + "data": {} +} +``` + +**Статус:** `201 Created` + +**Ответ:** +```json +{ + "message": "Cooldown created successfully" +} +``` + +--- + +## Warps + +**Префикс:** `/warps` + +### POST `/warps/` + +Создает новую точку варпа. + +**Тело запроса:** +```json +{ + "name": "string", + "world": "string", + "x": 0.0, + "y": 0.0, + "z": 0.0, + "yaw": 0.0, + "pitch": 0.0, + "is_public": true, + "description": "string" +} +``` + +**Статус:** `201 Created` + +**Ответ:** +```json +{ + "id": "uuid", + "name": "string", + "world": "string", + "x": 0.0, + "y": 0.0, + "z": 0.0, + "yaw": 0.0, + "pitch": 0.0, + "is_public": true, + "description": "string", + "created_at": "2024-01-01T00:00:00Z", + "updated_at": "2024-01-01T00:00:00Z" +} +``` + +### PATCH `/warps/` + +Обновляет существующую точку варпа. + +**Тело запроса:** +```json +{ + "name": "string", + "world": "string", + "x": 0.0, + "y": 0.0, + "z": 0.0, + "yaw": 0.0, + "pitch": 0.0, + "is_public": true, + "description": "string" +} +``` + +**Ответ:** +```json +{ + "id": "uuid", + "name": "string", + "world": "string", + "x": 0.0, + "y": 0.0, + "z": 0.0, + "yaw": 0.0, + "pitch": 0.0, + "is_public": true, + "description": "string", + "created_at": "2024-01-01T00:00:00Z", + "updated_at": "2024-01-01T00:00:00Z" +} +``` + +### DELETE `/warps/` + +Удаляет точку варпа. + +**Тело запроса:** +```json +{ + "name": "string" +} +``` + +**Статус:** `204 No Content` + +### POST `/warps/get` + +Получает конкретную точку варпа по имени. + +**Тело запроса:** +```json +{ + "name": "string" +} +``` + +**Ответ:** +```json +{ + "id": "uuid", + "name": "string", + "world": "string", + "x": 0.0, + "y": 0.0, + "z": 0.0, + "yaw": 0.0, + "pitch": 0.0, + "is_public": true, + "description": "string", + "created_at": "2024-01-01T00:00:00Z", + "updated_at": "2024-01-01T00:00:00Z" +} +``` + +### POST `/warps/list` + +Получает список точек варпа с возможностью фильтрации. + +**Тело запроса:** +```json +{ + "name": "string", + "world": "string", + "is_public": true, + "page": 1, + "size": 20 +} +``` + +**Ответ:** +```json +{ + "warps": [ + { + "id": "uuid", + "name": "string", + "world": "string", + "x": 0.0, + "y": 0.0, + "z": 0.0, + "yaw": 0.0, + "pitch": 0.0, + "is_public": true, + "description": "string", + "created_at": "2024-01-01T00:00:00Z", + "updated_at": "2024-01-01T00:00:00Z" + } + ], + "total": 1, + "page": 1, + "size": 20, + "pages": 1 +} +``` + +--- + +## Whitelist + +**Префикс:** `/whitelist` + +### POST `/whitelist/add` + +Добавляет игрока в белый список. + +**Тело запроса:** +```json +{ + "player_name": "string", + "added_by": "string", + "added_at": "2024-01-01T00:00:00Z", + "expires_at": "2024-01-01T00:00:00Z", + "is_active": true, + "reason": "string" +} +``` + +**Статус:** `201 Created` + +**Ответ:** +```json +{ + "id": "uuid", + "player_name": "string", + "added_by": "string", + "added_at": "2024-01-01T00:00:00Z", + "expires_at": "2024-01-01T00:00:00Z", + "is_active": true, + "reason": "string" +} +``` + +### POST `/whitelist/remove` + +Удаляет игрока из белого списка. + +**Тело запроса:** +```json +{ + "player_name": "string" +} +``` + +**Статус:** `204 No Content` + +### POST `/whitelist/check` + +Проверяет, находится ли игрок в белом списке. + +**Тело запроса:** +```json +{ + "player_name": "string" +} +``` + +**Ответ:** +```json +{ + "is_whitelisted": true +} +``` + +### GET `/whitelist/` + +Получает список всех игроков в белом списке. + +**Ответ:** +```json +{ + "entries": [ + { + "id": "uuid", + "player_name": "string", + "added_by": "string", + "added_at": "2024-01-01T00:00:00Z", + "expires_at": "2024-01-01T00:00:00Z", + "is_active": true, + "reason": "string" + } + ], + "total": 1 +} +``` + +### GET `/whitelist/count` + +Получает общее количество игроков в белом списке. + +**Ответ:** +```json +{ + "total": 42 +} +``` + +--- + +## Punishments + +**Префикс:** `/punishments` + +### POST `/punishments/` + +Создает новое наказание (бан/мут). + +**Тело запроса:** +```json +{ + "player_uuid": "string", + "player_name": "string", + "player_ip": "192.168.1.1", + "punishment_type": "string", + "reason": "string", + "staff_uuid": "string", + "staff_name": "string", + "expires_at": "2024-01-01T00:00:00Z", + "evidence_url": "string", + "notes": "string" +} +``` + +**Статус:** `201 Created` + +**Ответ:** +```json +{ + "id": "uuid", + "player_uuid": "string", + "player_name": "string", + "player_ip": "192.168.1.1", + "punishment_type": "string", + "reason": "string", + "staff_uuid": "string", + "staff_name": "string", + "expires_at": "2024-01-01T00:00:00Z", + "is_active": true, + "revoked_at": null, + "revoked_by": null, + "revoked_reason": null, + "evidence_url": "string", + "notes": "string", + "created_at": "2024-01-01T00:00:00Z", + "updated_at": "2024-01-01T00:00:00Z" +} +``` + +### POST `/punishments/revoke` + +Отменяет наказание. + +**Тело запроса:** +```json +{ + "punishment_id": "uuid", + "revoked_by": "string", + "revoked_reason": "string" +} +``` + +**Ответ:** +```json +{ + "id": "uuid", + "player_uuid": "string", + "player_name": "string", + "player_ip": "192.168.1.1", + "punishment_type": "string", + "reason": "string", + "staff_uuid": "string", + "staff_name": "string", + "expires_at": "2024-01-01T00:00:00Z", + "is_active": false, + "revoked_at": "2024-01-01T00:00:00Z", + "revoked_by": "string", + "revoked_reason": "string", + "evidence_url": "string", + "notes": "string", + "created_at": "2024-01-01T00:00:00Z", + "updated_at": "2024-01-01T00:00:00Z" +} +``` + +### POST `/punishments/query` + +Выполняет поиск наказаний по фильтрам. + +**Тело запроса:** +```json +{ + "player_uuid": "string", + "punishment_type": "string", + "is_active": true, + "page": 1, + "size": 20 +} +``` + +**Ответ:** +```json +{ + "punishments": [ + { + "id": "uuid", + "player_uuid": "string", + "player_name": "string", + "player_ip": "192.168.1.1", + "punishment_type": "string", + "reason": "string", + "staff_uuid": "string", + "staff_name": "string", + "expires_at": "2024-01-01T00:00:00Z", + "is_active": true, + "revoked_at": null, + "revoked_by": null, + "revoked_reason": null, + "evidence_url": "string", + "notes": "string", + "created_at": "2024-01-01T00:00:00Z", + "updated_at": "2024-01-01T00:00:00Z" + } + ], + "total": 1, + "page": 1, + "size": 20, + "pages": 1 +} +``` + +### GET `/punishments/ban/{player_uuid}` + +Получает статус активного бана игрока. + +**Параметры:** +- `player_uuid` (path) - UUID игрока + +**Ответ:** +```json +{ + "is_banned": true, + "punishment": { + "id": "uuid", + "player_uuid": "string", + "player_name": "string", + "player_ip": "192.168.1.1", + "punishment_type": "ban", + "reason": "string", + "staff_uuid": "string", + "staff_name": "string", + "expires_at": "2024-01-01T00:00:00Z", + "is_active": true, + "revoked_at": null, + "revoked_by": null, + "revoked_reason": null, + "evidence_url": "string", + "notes": "string", + "created_at": "2024-01-01T00:00:00Z", + "updated_at": "2024-01-01T00:00:00Z" + } +} +``` + +### GET `/punishments/mute/{player_uuid}` + +Получает статус активного мута игрока. + +**Параметры:** +- `player_uuid` (path) - UUID игрока + +**Ответ:** +```json +{ + "is_muted": true, + "punishment": { + "id": "uuid", + "player_uuid": "string", + "player_name": "string", + "player_ip": "192.168.1.1", + "punishment_type": "mute", + "reason": "string", + "staff_uuid": "string", + "staff_name": "string", + "expires_at": "2024-01-01T00:00:00Z", + "is_active": true, + "revoked_at": null, + "revoked_by": null, + "revoked_reason": null, + "evidence_url": "string", + "notes": "string", + "created_at": "2024-01-01T00:00:00Z", + "updated_at": "2024-01-01T00:00:00Z" + } +} +``` + +--- + +## Audit + +**Префикс:** `/audit` + +### POST `/audit/commands` + +Логирует выполнение команды для аудита. + +**Тело запроса:** +```json +{ + "player_uuid": "uuid", + "player_name": "string", + "command": "string", + "arguments": ["arg1", "arg2"], + "server": "string", + "timestamp": "2024-01-01T00:00:00Z" +} +``` + +**Статус:** `202 Accepted` + +**Ответ:** +```json +{ + "accepted": 1 +} +``` + +--- + +## LuckPerms + +**Префикс:** `/luckperms` + +### GET `/luckperms/players/{uuid}` + +Получает информацию об игроке по UUID. + +**Параметры:** +- `uuid` (path) - UUID игрока + +**Ответ:** +```json +{ + "uuid": "string", + "username": "string", + "primary_group": "string", + "created_at": "2024-01-01T00:00:00Z", + "updated_at": "2024-01-01T00:00:00Z" +} +``` + +### GET `/luckperms/players/username/{username}` + +Получает информацию об игроке по имени. + +**Параметры:** +- `username` (path) - Имя игрока + +**Ответ:** +```json +{ + "uuid": "string", + "username": "string", + "primary_group": "string", + "created_at": "2024-01-01T00:00:00Z", + "updated_at": "2024-01-01T00:00:00Z" +} +``` + +### GET `/luckperms/groups/{name}` + +Получает информацию о группе по имени. + +**Параметры:** +- `name` (path) - Название группы + +**Ответ:** +```json +{ + "name": "string", + "created_at": "2024-01-01T00:00:00Z", + "updated_at": "2024-01-01T00:00:00Z" +} +``` + +### GET `/luckperms/players/{uuid}/permissions` + +Получает список разрешений игрока. + +**Параметры:** +- `uuid` (path) - UUID игрока + +**Ответ:** +```json +[ + { + "id": 1, + "uuid": "string", + "permission": "string", + "value": true, + "server": "string", + "world": "string", + "expiry": 1234567890, + "contexts": "string", + "created_at": "2024-01-01T00:00:00Z", + "updated_at": "2024-01-01T00:00:00Z" + } +] +``` + +### GET `/luckperms/players/{uuid}/with-permissions` + +Получает информацию об игроке вместе со всеми разрешениями. + +**Параметры:** +- `uuid` (path) - UUID игрока + +**Ответ:** +```json +{ + "uuid": "string", + "username": "string", + "primary_group": "string", + "permissions": [ + { + "id": 1, + "uuid": "string", + "permission": "string", + "value": true, + "server": "string", + "world": "string", + "expiry": 1234567890, + "contexts": "string", + "created_at": "2024-01-01T00:00:00Z", + "updated_at": "2024-01-01T00:00:00Z" + } + ], + "created_at": "2024-01-01T00:00:00Z", + "updated_at": "2024-01-01T00:00:00Z" +} +``` + +### POST `/luckperms/players` + +Создает нового игрока в LuckPerms. + +**Тело запроса:** +```json +{ + "username": "string", + "primary_group": "default" +} +``` + +**Статус:** `201 Created` + +**Ответ:** +```json +{ + "uuid": "string", + "username": "string", + "primary_group": "default", + "created_at": "2024-01-01T00:00:00Z", + "updated_at": "2024-01-01T00:00:00Z" +} +``` + +--- + +## Teleport History + +**Префикс:** `/teleport-history` + +### POST `/teleport-history/` + +Создает запись о телепортации в истории. + +**Тело запроса:** +```json +{ + "player_uuid": "string", + "from_world": "string", + "from_x": 0.0, + "from_y": 0.0, + "from_z": 0.0, + "to_world": "string", + "to_x": 0.0, + "to_y": 0.0, + "to_z": 0.0, + "tp_type": "string", + "target_name": "string" +} +``` + +**Статус:** `201 Created` + +**Ответ:** +```json +{ + "id": "uuid", + "player_uuid": "string", + "from_world": "string", + "from_x": 0.0, + "from_y": 0.0, + "from_z": 0.0, + "to_world": "string", + "to_x": 0.0, + "to_y": 0.0, + "to_z": 0.0, + "tp_type": "string", + "target_name": "string", + "created_at": "2024-01-01T00:00:00Z" +} +``` + +### GET `/teleport-history/players/{player_uuid}` + +Получает историю телепортаций игрока. + +**Параметры:** +- `player_uuid` (path) - UUID игрока +- `limit` (query) - Максимальное количество записей (по умолчанию: 100, мин: 1, макс: 1000) + +**Ответ:** +```json +[ + { + "id": "uuid", + "player_uuid": "string", + "from_world": "string", + "from_x": 0.0, + "from_y": 0.0, + "from_z": 0.0, + "to_world": "string", + "to_x": 0.0, + "to_y": 0.0, + "to_z": 0.0, + "tp_type": "string", + "target_name": "string", + "created_at": "2024-01-01T00:00:00Z" + } +] +``` + +--- + +## Users + +**Префикс:** `/users` + +### GET `/users/users/{name}/game-id` + +Получает игровой ID пользователя по имени. + +**Параметры:** +- `name` (path) - Имя пользователя + +**Ответ:** +```json +{ + "game_id": "string" +} +``` + +--- + +## Аутентификация + +Все endpoints (кроме `/health/`) требуют наличия заголовка: + +``` +X-API-Key: +``` + +API ключ настраивается через переменную окружения `SECURITY__API_KEY`. + +## Обработка ошибок + +Все endpoints возвращают стандартные HTTP коды ошибок: +- `400 Bad Request` - некорректные данные в запросе +- `401 Unauthorized` - отсутствует или неверный API ключ +- `404 Not Found` - ресурс не найден +- `500 Internal Server Error` - внутренняя ошибка сервера + +Тело ответа при ошибке содержит детальное описание проблемы. diff --git a/WHITELIST_API_SCHEMAS.md b/WHITELIST_API_SCHEMAS.md new file mode 100644 index 0000000..66ecf32 --- /dev/null +++ b/WHITELIST_API_SCHEMAS.md @@ -0,0 +1,215 @@ +# Whitelist API Схемы запросов + +## Базовая информация + +Все запросы требуют заголовок `X-API-Key` с правильным API ключом: +```bash +-H "X-API-Key: your-secret-api-key" +``` + +Base URL: `http://localhost:8080/api/v1/whitelist` + +--- + +## 1. Добавить игрока в вайтлист + +**Endpoint:** `POST /add` + +**Request Body:** +```json +{ + "player_name": "PlayerName", + "player_uuid": "123e4567-e89b-12d3-a456-426614174000", + "added_by": "admin", + "added_at": "2024-01-01T12:00:00Z", + "expires_at": "2024-12-31T23:59:59Z", + "is_active": true, + "reason": "VIP player" +} +``` + +**Обязательные поля:** +- `player_name` (string) - имя игрока +- `player_uuid` (string) - UUID игрока +- `added_by` (string) - кто добавил +- `added_at` (datetime) - время добавления в формате ISO 8601 + +**Опциональные поля:** +- `expires_at` (datetime, null) - время истечения (null = бессрочно) +- `is_active` (boolean, default: true) - активна ли запись +- `reason` (string, null) - причина добавления + +**Response (201):** +```json +{ + "id": "456e7890-e89b-12d3-a456-426614174000", + "player_name": "PlayerName", + "player_uuid": "123e4567-e89b-12d3-a456-426614174000", + "added_by": "admin", + "added_at": "2024-01-01T12:00:00Z", + "expires_at": "2024-12-31T23:59:59Z", + "is_active": true, + "reason": "VIP player" +} +``` + +**Example:** +```bash +curl -X POST \ + -H "X-API-Key: your-secret-api-key" \ + -H "Content-Type: application/json" \ + -d '{ + "player_name": "PlayerName", + "player_uuid": "123e4567-e89b-12d3-a456-426614174000", + "added_by": "admin", + "added_at": "2024-01-01T12:00:00Z", + "reason": "VIP player" + }' \ + http://localhost:8080/api/v1/whitelist/add +``` + +--- + +## 2. Удалить игрока из вайтлиста + +**Endpoint:** `POST /remove` + +**Request Body:** +```json +{ + "player_name": "PlayerName" +} +``` + +**Response (204):** No content + +**Example:** +```bash +curl -X POST \ + -H "X-API-Key: your-secret-api-key" \ + -H "Content-Type: application/json" \ + -d '{ + "player_name": "PlayerName" + }' \ + http://localhost:8080/api/v1/whitelist/remove +``` + +--- + +## 3. Проверить статус игрока в вайтлисте + +**Endpoint:** `POST /check` + +**Request Body:** +```json +{ + "player_name": "PlayerName" +} +``` + +**Response:** +```json +{ + "is_whitelisted": true, + "player_uuid": "123e4567-e89b-12d3-a456-426614174000" +} +``` + +**Example:** +```bash +curl -X POST \ + -H "X-API-Key: your-secret-api-key" \ + -H "Content-Type: application/json" \ + -d '{ + "player_name": "PlayerName" + }' \ + http://localhost:8080/api/v1/whitelist/check +``` + +--- + +## 4. Получить полный список вайтлиста + +**Endpoint:** `GET /` + +**Response:** +```json +{ + "entries": [ + { + "id": "456e7890-e89b-12d3-a456-426614174000", + "player_name": "PlayerName", + "player_uuid": "123e4567-e89b-12d3-a456-426614174000", + "added_by": "admin", + "added_at": "2024-01-01T12:00:00Z", + "expires_at": null, + "is_active": true, + "reason": "VIP player" + } + ], + "total": 1 +} +``` + +**Example:** +```bash +curl -H "X-API-Key: your-secret-api-key" \ + http://localhost:8080/api/v1/whitelist/ +``` + +--- + +## 5. Получить количество игроков в вайтлисте + +**Endpoint:** `GET /count` + +**Response:** +```json +{ + "total": 42 +} +``` + +**Example:** +```bash +curl -H "X-API-Key: your-secret-api-key" \ + http://localhost:8080/api/v1/whitelist/count +``` +--- + +## Структура данных + +### WhitelistEntry +```json +{ + "id": "uuid", + "player_name": "string", + "player_uuid": "string|null", + "added_by": "string", + "added_at": "datetime", + "expires_at": "datetime|null", + "is_active": "boolean", + "reason": "string|null" +} +``` + +### WhitelistListResponse +```json +{ + "entries": [WhitelistEntry], + "total": "number" +} +``` + +--- + +## Коды ошибок + +- `201` - игрок успешно добавлен +- `204` - игрок успешно удален +- `400` - неверный формат запроса +- `401` - неверный API ключ +- `404` - игрок не найден +- `409` - игрок уже в вайтлисте + +--- \ No newline at end of file diff --git a/src/hubgw/context.py b/src/hubgw/context.py index b00a994..b7f0f80 100644 --- a/src/hubgw/context.py +++ b/src/hubgw/context.py @@ -82,7 +82,6 @@ class AppContext(metaclass=Singleton): **engine_kwargs, ) - # Azuriom использует ту же Base, но с другим engine Base.metadata.bind = self._azuriom_engine return self._azuriom_engine diff --git a/src/hubgw/repositories/cooldowns_repo.py b/src/hubgw/repositories/cooldowns_repo.py index 4fa9133..7fa6f31 100644 --- a/src/hubgw/repositories/cooldowns_repo.py +++ b/src/hubgw/repositories/cooldowns_repo.py @@ -1,6 +1,5 @@ """Cooldowns repository.""" -from datetime import datetime, timedelta from typing import List, Optional from uuid import UUID @@ -19,20 +18,22 @@ class CooldownsRepository: async def create(self, request: CooldownCreate) -> Cooldown: """Create new cooldown.""" - expires_at = datetime.utcnow() + timedelta(seconds=request.cooldown_seconds) - cooldown = Cooldown( player_uuid=request.player_uuid, cooldown_type=request.cooldown_type, - expires_at=expires_at, + expires_at=request.expires_at, cooldown_seconds=request.cooldown_seconds, - metadata=request.metadata, + data=request.data, ) self.session.add(cooldown) await self.session.commit() await self.session.refresh(cooldown) return cooldown + async def check(self, player_uuid: str, cooldown_type: str) -> Optional[Cooldown]: + """Check cooldown (alias for get_active_by_type).""" + return await self.get_active_by_type(player_uuid, cooldown_type) + async def get_by_id(self, entry_id: UUID) -> Optional[Cooldown]: """Get cooldown by id.""" stmt = select(Cooldown).where(Cooldown.id == entry_id) diff --git a/src/hubgw/repositories/homes_repo.py b/src/hubgw/repositories/homes_repo.py index 5f7620e..af4209e 100644 --- a/src/hubgw/repositories/homes_repo.py +++ b/src/hubgw/repositories/homes_repo.py @@ -8,7 +8,8 @@ from sqlalchemy.dialects.postgresql import insert from sqlalchemy.ext.asyncio import AsyncSession from hubgw.models.home import Home -from hubgw.schemas.homes import HomeCreate, HomeQuery +from hubgw.schemas.homes import (HomeCreate, HomeGetRequest, HomeQuery, + HomeUpsertRequest) class HomesRepository: @@ -104,3 +105,17 @@ class HomesRepository: homes = list(result.scalars().all()) return homes, total + + async def upsert(self, request: HomeUpsertRequest) -> Home: + """Upsert home (alias for create which handles upsert).""" + return await self.create(request) + + async def get_by_request(self, request: HomeGetRequest) -> Optional[Home]: + """Get home by request (extracts player_uuid and name from request).""" + return await self.get_by_player_and_name(request.player_uuid, request.name) + + async def list_by_player(self, player_uuid: str) -> List[Home]: + """List all homes for a player.""" + stmt = select(Home).where(Home.player_uuid == player_uuid).order_by(Home.name) + result = await self.session.execute(stmt) + return list(result.scalars().all()) diff --git a/src/hubgw/repositories/kits_repo.py b/src/hubgw/repositories/kits_repo.py index 9851e62..77d0a5f 100644 --- a/src/hubgw/repositories/kits_repo.py +++ b/src/hubgw/repositories/kits_repo.py @@ -1,6 +1,6 @@ """Kits repository.""" -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from typing import List, Optional from uuid import UUID @@ -38,7 +38,7 @@ class KitsRepository: self, player_uuid: UUID, kit_name: str, cooldown_seconds: int ) -> Cooldown: """Create cooldown for kit.""" - expires_at = datetime.utcnow() + timedelta(seconds=cooldown_seconds) + expires_at = datetime.now(timezone.utc) + timedelta(seconds=cooldown_seconds) cooldown = Cooldown( player_uuid=str(player_uuid), cooldown_type=f"kit_{kit_name}", @@ -109,3 +109,9 @@ class KitsRepository: cooldowns = list(result.scalars().all()) return cooldowns, total + + async def check_cooldown( + self, player_uuid: UUID, kit_name: str + ) -> Optional[Cooldown]: + """Check cooldown (alias for get_active_cooldown).""" + return await self.get_active_cooldown(player_uuid, kit_name) diff --git a/src/hubgw/repositories/punishments_repo.py b/src/hubgw/repositories/punishments_repo.py index 5cae222..7d3c01c 100644 --- a/src/hubgw/repositories/punishments_repo.py +++ b/src/hubgw/repositories/punishments_repo.py @@ -1,6 +1,6 @@ """Punishments repository.""" -from datetime import datetime +from datetime import datetime, timezone from typing import List, Optional from sqlalchemy import func, select @@ -47,7 +47,7 @@ class PunishmentsRepository: return None punishment.is_active = False - punishment.revoked_at = datetime.utcnow() + punishment.revoked_at = datetime.now(timezone.utc) punishment.revoked_by = request.revoked_by punishment.revoked_reason = request.revoked_reason @@ -95,9 +95,9 @@ class PunishmentsRepository: stmt = select(Punishment).where( Punishment.player_uuid == player_uuid, Punishment.punishment_type.in_(["BAN", "TEMPBAN"]), - Punishment.is_active == True, + Punishment.is_active, (Punishment.expires_at.is_(None)) - | (Punishment.expires_at > datetime.utcnow()), + | (Punishment.expires_at > datetime.now(timezone.utc)), ) result = await self.session.execute(stmt) return result.scalar_one_or_none() @@ -107,9 +107,9 @@ class PunishmentsRepository: stmt = select(Punishment).where( Punishment.player_uuid == player_uuid, Punishment.punishment_type.in_(["MUTE", "TEMPMUTE"]), - Punishment.is_active == True, + Punishment.is_active, (Punishment.expires_at.is_(None)) - | (Punishment.expires_at > datetime.utcnow()), + | (Punishment.expires_at > datetime.now(timezone.utc)), ) result = await self.session.execute(stmt) return result.scalar_one_or_none() diff --git a/src/hubgw/repositories/warps_repo.py b/src/hubgw/repositories/warps_repo.py index c27004a..ac887f7 100644 --- a/src/hubgw/repositories/warps_repo.py +++ b/src/hubgw/repositories/warps_repo.py @@ -7,7 +7,8 @@ from sqlalchemy import delete, func, select from sqlalchemy.ext.asyncio import AsyncSession from hubgw.models.warp import Warp -from hubgw.schemas.warps import WarpCreateRequest, WarpQuery, WarpUpdateRequest +from hubgw.schemas.warps import (WarpCreateRequest, WarpDeleteRequest, + WarpGetRequest, WarpQuery, WarpUpdateRequest) class WarpsRepository: @@ -105,3 +106,15 @@ class WarpsRepository: warps = list(result.scalars().all()) return warps, total + + async def get_by_request(self, request: WarpGetRequest) -> Optional[Warp]: + """Get warp by request (extracts name from request).""" + return await self.get_by_name(request.name) + + async def delete(self, request: WarpDeleteRequest) -> bool: + """Delete warp by request (extracts name from request).""" + return await self.delete_by_name(request.name) + + async def list(self, query: WarpQuery) -> tuple[List[Warp], int]: + """List warps (alias for query).""" + return await self.query(query) diff --git a/src/hubgw/repositories/whitelist_repo.py b/src/hubgw/repositories/whitelist_repo.py index 36dd180..cbf1c0c 100644 --- a/src/hubgw/repositories/whitelist_repo.py +++ b/src/hubgw/repositories/whitelist_repo.py @@ -1,5 +1,6 @@ """Whitelist repository.""" +from datetime import datetime, timezone from typing import List, Optional from uuid import UUID, uuid4 @@ -59,11 +60,14 @@ class WhitelistRepository: return result.rowcount > 0 async def check(self, request: WhitelistCheckRequest) -> Optional[WhitelistEntry]: - """Check if player is whitelisted.""" + """Check if player is whitelisted and not expired.""" + now = datetime.now(timezone.utc) stmt = select(WhitelistEntry).where( and_( WhitelistEntry.player_name == request.player_name, WhitelistEntry.is_active.is_(True), + (WhitelistEntry.expires_at.is_(None)) + | (WhitelistEntry.expires_at > now), ) ) result = await self.session.execute(stmt) diff --git a/src/hubgw/services/cooldowns_service.py b/src/hubgw/services/cooldowns_service.py index 89f2e25..ead3a82 100644 --- a/src/hubgw/services/cooldowns_service.py +++ b/src/hubgw/services/cooldowns_service.py @@ -1,6 +1,6 @@ """Cooldowns service.""" -from datetime import datetime +from datetime import datetime, timezone from sqlalchemy.ext.asyncio import AsyncSession @@ -25,7 +25,7 @@ class CooldownsService: return CooldownCheckResponse(is_active=False) remaining_seconds = int( - (cooldown.expires_at - datetime.utcnow()).total_seconds() + (cooldown.expires_at - datetime.now(timezone.utc)).total_seconds() ) return CooldownCheckResponse( @@ -36,9 +36,4 @@ class CooldownsService: async def create_cooldown(self, request: CooldownCreate) -> None: """Create new cooldown.""" - await self.repo.create( - player_uuid=request.player_uuid, - cooldown_type=request.cooldown_type, - cooldown_seconds=request.cooldown_seconds, - metadata=request.metadata, - ) + await self.repo.create(request) diff --git a/src/hubgw/services/kits_service.py b/src/hubgw/services/kits_service.py index 6887e3f..0bb21ae 100644 --- a/src/hubgw/services/kits_service.py +++ b/src/hubgw/services/kits_service.py @@ -1,6 +1,6 @@ """Kits service.""" -from datetime import datetime +from datetime import datetime, timezone from sqlalchemy.ext.asyncio import AsyncSession @@ -22,7 +22,7 @@ class KitsService: if cooldown: remaining_seconds = int( - (cooldown.expires_at - datetime.utcnow()).total_seconds() + (cooldown.expires_at - datetime.now(timezone.utc)).total_seconds() ) if remaining_seconds > 0: raise CooldownActiveError( diff --git a/src/hubgw/services/luckperms_service.py b/src/hubgw/services/luckperms_service.py index 9805c28..86dda4e 100644 --- a/src/hubgw/services/luckperms_service.py +++ b/src/hubgw/services/luckperms_service.py @@ -59,9 +59,16 @@ class LuckPermsService: LuckPermsUserPermission.model_validate(perm) for perm in permissions ] - return LuckPermsPlayerWithPermissions.model_validate( - player, permissions=permission_schemas - ) + player_dict = { + "uuid": player.uuid, + "username": player.username, + "primary_group": player.primary_group, + "created_at": player.created_at, + "updated_at": player.updated_at, + "permissions": permission_schemas, + } + + return LuckPermsPlayerWithPermissions(**player_dict) async def create_player( self, request: LuckPermsPlayerCreateRequest, user_service: UserService diff --git a/src/hubgw/services/punishments_service.py b/src/hubgw/services/punishments_service.py index 27d5c22..07c7d92 100644 --- a/src/hubgw/services/punishments_service.py +++ b/src/hubgw/services/punishments_service.py @@ -1,7 +1,5 @@ """Punishments service.""" -from uuid import UUID - from sqlalchemy.ext.asyncio import AsyncSession from hubgw.core.errors import NotFoundError @@ -35,6 +33,7 @@ class PunishmentsService: staff_uuid=punishment.staff_uuid, staff_name=punishment.staff_name, created_at=punishment.created_at, + updated_at=punishment.updated_at, expires_at=punishment.expires_at, is_active=punishment.is_active, revoked_at=punishment.revoked_at, @@ -62,6 +61,7 @@ class PunishmentsService: staff_uuid=punishment.staff_uuid, staff_name=punishment.staff_name, created_at=punishment.created_at, + updated_at=punishment.updated_at, expires_at=punishment.expires_at, is_active=punishment.is_active, revoked_at=punishment.revoked_at, @@ -86,6 +86,7 @@ class PunishmentsService: staff_uuid=p.staff_uuid, staff_name=p.staff_name, created_at=p.created_at, + updated_at=p.updated_at, expires_at=p.expires_at, is_active=p.is_active, revoked_at=p.revoked_at, @@ -107,7 +108,7 @@ class PunishmentsService: pages=pages, ) - async def get_active_ban_status(self, player_uuid: UUID) -> ActiveBanStatusResponse: + async def get_active_ban_status(self, player_uuid: str) -> ActiveBanStatusResponse: """Get active ban status for player.""" ban = await self.repo.get_active_ban(player_uuid) @@ -124,6 +125,7 @@ class PunishmentsService: staff_uuid=ban.staff_uuid, staff_name=ban.staff_name, created_at=ban.created_at, + updated_at=ban.updated_at, expires_at=ban.expires_at, is_active=ban.is_active, revoked_at=ban.revoked_at, @@ -136,7 +138,7 @@ class PunishmentsService: return ActiveBanStatusResponse(is_banned=True, punishment=punishment) async def get_active_mute_status( - self, player_uuid: UUID + self, player_uuid: str ) -> ActiveMuteStatusResponse: """Get active mute status for player.""" mute = await self.repo.get_active_mute(player_uuid) @@ -154,6 +156,7 @@ class PunishmentsService: staff_uuid=mute.staff_uuid, staff_name=mute.staff_name, created_at=mute.created_at, + updated_at=mute.updated_at, expires_at=mute.expires_at, is_active=mute.is_active, revoked_at=mute.revoked_at,