feat: add new features
Build and Push Docker Image / build-and-push (push) Has been skipped Details

This commit is contained in:
itqop 2025-11-12 21:01:39 +03:00
parent fc3e15205f
commit dac619dbdd
13 changed files with 1252 additions and 34 deletions

960
API_ENDPOINTS.md Normal file
View File

@ -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: <your-api-key>
```
API ключ настраивается через переменную окружения `SECURITY__API_KEY`.
## Обработка ошибок
Все endpoints возвращают стандартные HTTP коды ошибок:
- `400 Bad Request` - некорректные данные в запросе
- `401 Unauthorized` - отсутствует или неверный API ключ
- `404 Not Found` - ресурс не найден
- `500 Internal Server Error` - внутренняя ошибка сервера
Тело ответа при ошибке содержит детальное описание проблемы.

215
WHITELIST_API_SCHEMAS.md Normal file
View File

@ -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` - игрок уже в вайтлисте
---

View File

@ -82,7 +82,6 @@ class AppContext(metaclass=Singleton):
**engine_kwargs,
)
# Azuriom использует ту же Base, но с другим engine
Base.metadata.bind = self._azuriom_engine
return self._azuriom_engine

View File

@ -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)

View File

@ -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())

View File

@ -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)

View File

@ -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()

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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(

View File

@ -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

View File

@ -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,