Files
languard-servers-manager/backend/core/dal/player_repository.py
Tran G. (Revernomad) Khoa 6511353b55 feat: implement full backend + frontend server detail, settings, and create server pages
Backend:
- Complete FastAPI backend with 42+ REST endpoints (auth, servers, config,
  players, bans, missions, mods, games, system)
- Game adapter architecture with Arma 3 as first-class adapter
- WebSocket real-time events for status, metrics, logs, players
- Background thread system (process monitor, metrics, log tail, RCon poller)
- Fernet encryption for sensitive config fields at rest
- JWT auth with admin/viewer roles, bcrypt password hashing
- SQLite with WAL mode, parameterized queries, migration system
- APScheduler cleanup jobs for logs, metrics, events

Frontend:
- Server Detail page with 7 tabs (overview, config, players, bans,
  missions, mods, logs)
- Settings page with password change and admin user management
- Create Server wizard (4-step; known bug: silent validation failure)
- New hooks: useServerDetail, useAuth, useGames
- New components: ServerHeader, ConfigEditor, PlayerTable, BanTable,
  MissionList, ModList, LogViewer, PasswordChange, UserManager
- WebSocket onEvent callback for real-time log accumulation
- 120 unit tests passing (Vitest + React Testing Library)

Docs:
- Added .gitignore, CLAUDE.md, README.md
- Updated FRONTEND.md, ARCHITECTURE.md with current implementation state
- Added .env.example for backend configuration

Known issues:
- Create Server form: "Next" buttons don't validate before advancing,
  causing silent submit failure when fields are invalid
- Config sub-tabs need UX redesign for non-technical users
2026-04-17 11:58:34 +07:00

70 lines
2.5 KiB
Python

import json
from datetime import datetime, timezone
from core.dal.base_repository import BaseRepository
class PlayerRepository(BaseRepository):
def get_all(self, server_id: int) -> list[dict]:
return self._fetchall(
"SELECT * FROM players WHERE server_id = :sid ORDER BY slot_id",
{"sid": server_id},
)
def count(self, server_id: int) -> int:
row = self._fetchone(
"SELECT COUNT(*) as cnt FROM players WHERE server_id = :sid",
{"sid": server_id},
)
return row["cnt"] if row else 0
def upsert(self, server_id: int, player: dict) -> None:
now = datetime.now(timezone.utc).isoformat()
self._execute(
"""
INSERT INTO players (server_id, slot_id, name, guid, ip, ping, game_data, joined_at, updated_at)
VALUES (:sid, :slot, :name, :guid, :ip, :ping, :gd, :now, :now)
ON CONFLICT(server_id, slot_id) DO UPDATE SET
name = excluded.name,
guid = excluded.guid,
ping = excluded.ping,
game_data = excluded.game_data,
updated_at = excluded.updated_at
""",
{
"sid": server_id,
"slot": str(player.get("slot_id", "")),
"name": player.get("name", ""),
"guid": player.get("guid"),
"ip": player.get("ip"),
"ping": player.get("ping"),
"gd": json.dumps(player.get("game_data", {})),
"now": now,
},
)
def clear(self, server_id: int) -> None:
self._execute("DELETE FROM players WHERE server_id = :sid", {"sid": server_id})
def get_history(
self,
server_id: int,
limit: int = 50,
offset: int = 0,
search: str | None = None,
) -> tuple[int, list[dict]]:
conditions = ["server_id = :sid"]
params: dict = {"sid": server_id, "limit": limit, "offset": offset}
if search:
conditions.append("name LIKE :search")
params["search"] = f"%{search}%"
where = " AND ".join(conditions)
total_row = self._fetchone(
f"SELECT COUNT(*) as cnt FROM player_history WHERE {where}", params
)
total = total_row["cnt"] if total_row else 0
rows = self._fetchall(
f"SELECT * FROM player_history WHERE {where} ORDER BY left_at DESC LIMIT :limit OFFSET :offset",
params,
)
return total, rows