feat: new tables

This commit is contained in:
itqop 2025-10-16 00:02:52 +03:00
parent eb96c98878
commit 19e62d87d4
33 changed files with 698 additions and 150 deletions

View File

@ -13,6 +13,8 @@ from hubgw.services.warps_service import WarpsService
from hubgw.services.whitelist_service import WhitelistService
from hubgw.services.punishments_service import PunishmentsService
from hubgw.services.audit_service import AuditService
from hubgw.services.luckperms_service import LuckPermsService
from hubgw.services.teleport_history_service import TeleportHistoryService
async def get_context() -> AppContext:
@ -71,3 +73,13 @@ def get_punishments_service(session: Annotated[AsyncSession, Depends(get_session
def get_audit_service(session: Annotated[AsyncSession, Depends(get_session)]) -> AuditService:
"""Get audit service."""
return AuditService(session)
def get_luckperms_service(session: Annotated[AsyncSession, Depends(get_session)]) -> LuckPermsService:
"""Get luckperms service."""
return LuckPermsService(session)
def get_teleport_history_service(session: Annotated[AsyncSession, Depends(get_session)]) -> TeleportHistoryService:
"""Get teleport history service."""
return TeleportHistoryService(session)

View File

@ -6,7 +6,7 @@ from uuid import UUID
from hubgw.api.deps import get_cooldowns_service, verify_api_key
from hubgw.services.cooldowns_service import CooldownsService
from hubgw.schemas.cooldowns import CooldownCheckRequest, CooldownCheckResponse, CooldownKey
from hubgw.schemas.cooldowns import CooldownCheckRequest, CooldownCheckResponse, CooldownCreate
from hubgw.core.errors import AppError, create_http_exception
router = APIRouter()
@ -25,17 +25,15 @@ async def check_cooldown(
raise create_http_exception(e)
@router.put("/touch")
async def touch_cooldown(
key: CooldownKey,
@router.post("/", response_model=dict, status_code=201)
async def create_cooldown(
request: CooldownCreate,
service: Annotated[CooldownsService, Depends(get_cooldowns_service)],
_: Annotated[str, Depends(verify_api_key)],
seconds: int = Query(..., description="Cooldown duration in seconds"),
player_uuid: UUID = Query(..., description="Player UUID")
_: Annotated[str, Depends(verify_api_key)]
):
"""Touch cooldown."""
"""Create cooldown."""
try:
await service.touch_cooldown(player_uuid, key.key, seconds)
return {"message": "Cooldown touched successfully"}
await service.create_cooldown(request)
return {"message": "Cooldown created successfully"}
except AppError as e:
raise create_http_exception(e)

View File

@ -40,7 +40,7 @@ async def get_home(
@router.get("/{player_uuid}", response_model=HomeListResponse)
async def list_homes(
player_uuid: UUID,
player_uuid: str,
service: Annotated[HomesService, Depends(get_homes_service)],
_: Annotated[str, Depends(verify_api_key)]
):

View File

@ -0,0 +1,76 @@
"""LuckPerms endpoints."""
from fastapi import APIRouter, Depends, HTTPException
from typing import Annotated
from hubgw.api.deps import get_luckperms_service, verify_api_key
from hubgw.services.luckperms_service import LuckPermsService
from hubgw.schemas.luckperms import LuckPermsPlayer, LuckPermsGroup, LuckPermsUserPermission, LuckPermsPlayerWithPermissions
from hubgw.core.errors import AppError, create_http_exception
router = APIRouter()
@router.get("/players/{uuid}", response_model=LuckPermsPlayer)
async def get_player(
uuid: str,
service: Annotated[LuckPermsService, Depends(get_luckperms_service)],
_: Annotated[str, Depends(verify_api_key)]
):
"""Get player by UUID."""
try:
return await service.get_player(uuid)
except AppError as e:
raise create_http_exception(e)
@router.get("/players/username/{username}", response_model=LuckPermsPlayer)
async def get_player_by_username(
username: str,
service: Annotated[LuckPermsService, Depends(get_luckperms_service)],
_: Annotated[str, Depends(verify_api_key)]
):
"""Get player by username."""
try:
return await service.get_player_by_username(username)
except AppError as e:
raise create_http_exception(e)
@router.get("/groups/{name}", response_model=LuckPermsGroup)
async def get_group(
name: str,
service: Annotated[LuckPermsService, Depends(get_luckperms_service)],
_: Annotated[str, Depends(verify_api_key)]
):
"""Get group by name."""
try:
return await service.get_group(name)
except AppError as e:
raise create_http_exception(e)
@router.get("/players/{uuid}/permissions", response_model=list[LuckPermsUserPermission])
async def get_user_permissions(
uuid: str,
service: Annotated[LuckPermsService, Depends(get_luckperms_service)],
_: Annotated[str, Depends(verify_api_key)]
):
"""Get user permissions."""
try:
return await service.get_user_permissions(uuid)
except AppError as e:
raise create_http_exception(e)
@router.get("/players/{uuid}/with-permissions", response_model=LuckPermsPlayerWithPermissions)
async def get_player_with_permissions(
uuid: str,
service: Annotated[LuckPermsService, Depends(get_luckperms_service)],
_: Annotated[str, Depends(verify_api_key)]
):
"""Get player with permissions."""
try:
return await service.get_player_with_permissions(uuid)
except AppError as e:
raise create_http_exception(e)

View File

@ -56,7 +56,7 @@ async def query_punishments(
@router.get("/ban/{player_uuid}", response_model=ActiveBanStatusResponse)
async def get_ban_status(
player_uuid: UUID,
player_uuid: str,
service: Annotated[PunishmentsService, Depends(get_punishments_service)],
_: Annotated[str, Depends(verify_api_key)]
):
@ -69,7 +69,7 @@ async def get_ban_status(
@router.get("/mute/{player_uuid}", response_model=ActiveMuteStatusResponse)
async def get_mute_status(
player_uuid: UUID,
player_uuid: str,
service: Annotated[PunishmentsService, Depends(get_punishments_service)],
_: Annotated[str, Depends(verify_api_key)]
):

View File

@ -1,7 +1,7 @@
"""Main API v1 router."""
from fastapi import APIRouter
from hubgw.api.v1 import health, homes, kits, cooldowns, warps, whitelist, punishments, audit
from hubgw.api.v1 import health, homes, kits, cooldowns, warps, whitelist, punishments, audit, luckperms, teleport_history
api_router = APIRouter()
@ -14,3 +14,5 @@ api_router.include_router(warps.router, prefix="/warps", tags=["warps"])
api_router.include_router(whitelist.router, prefix="/whitelist", tags=["whitelist"])
api_router.include_router(punishments.router, prefix="/punishments", tags=["punishments"])
api_router.include_router(audit.router, prefix="/audit", tags=["audit"])
api_router.include_router(luckperms.router, prefix="/luckperms", tags=["luckperms"])
api_router.include_router(teleport_history.router, prefix="/teleport-history", tags=["teleport-history"])

View File

@ -0,0 +1,38 @@
"""Teleport History endpoints."""
from fastapi import APIRouter, Depends, HTTPException, Query
from typing import Annotated
from hubgw.api.deps import get_teleport_history_service, verify_api_key
from hubgw.services.teleport_history_service import TeleportHistoryService
from hubgw.schemas.teleport_history import TeleportHistoryCreate, TeleportHistory
from hubgw.core.errors import AppError, create_http_exception
router = APIRouter()
@router.post("/", response_model=TeleportHistory, status_code=201)
async def create_teleport(
request: TeleportHistoryCreate,
service: Annotated[TeleportHistoryService, Depends(get_teleport_history_service)],
_: Annotated[str, Depends(verify_api_key)]
):
"""Create teleport history entry."""
try:
return await service.create_teleport(request)
except AppError as e:
raise create_http_exception(e)
@router.get("/players/{player_uuid}", response_model=list[TeleportHistory])
async def list_player_teleports(
player_uuid: str,
service: Annotated[TeleportHistoryService, Depends(get_teleport_history_service)],
_: Annotated[str, Depends(verify_api_key)],
limit: int = Query(100, ge=1, le=1000, description="Maximum number of entries to return")
):
"""List teleport history for player."""
try:
return await service.list_player_teleports(player_uuid, limit)
except AppError as e:
raise create_http_exception(e)

View File

@ -1,17 +1,18 @@
"""Cooldown model."""
from sqlalchemy import Column, String, DateTime, Integer
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy import Column, String, DateTime, Integer, ForeignKey
from sqlalchemy.dialects.postgresql import UUID, JSONB
from hubgw.models.base import Base
class Cooldown(Base):
"""Cooldown model."""
__tablename__ = "hub_cooldowns"
id = Column(UUID(as_uuid=True), primary_key=True)
key = Column(String(255), nullable=False, unique=True, index=True)
player_uuid = Column(UUID(as_uuid=True), nullable=False, index=True)
expires_at = Column(DateTime(timezone=True), nullable=False)
id = Column(UUID(as_uuid=True), primary_key=True, server_default="gen_random_uuid()")
player_uuid = Column(String(36), ForeignKey("luckperms_players.uuid", ondelete="CASCADE"), nullable=False, index=True)
cooldown_type = Column(String(50), nullable=False)
expires_at = Column(DateTime(timezone=True), nullable=False, index=True)
cooldown_seconds = Column(Integer, nullable=False)
metadata = Column(JSONB)

View File

@ -1,22 +1,22 @@
"""Home model."""
from sqlalchemy import Column, String, Float, Integer, ForeignKey
from sqlalchemy import Column, Float, String, Boolean, ForeignKey, REAL
from sqlalchemy.dialects.postgresql import UUID
from hubgw.models.base import Base
class Home(Base):
"""Home model."""
__tablename__ = "hub_homes"
id = Column(UUID(as_uuid=True), primary_key=True)
player_uuid = Column(UUID(as_uuid=True), nullable=False, index=True)
id = Column(UUID(as_uuid=True), primary_key=True, server_default="gen_random_uuid()")
player_uuid = Column(String(36), ForeignKey("luckperms_players.uuid", ondelete="CASCADE"), nullable=False, index=True)
name = Column(String(255), nullable=False)
world = Column(String(255), nullable=False)
x = Column(Float, nullable=False)
y = Column(Float, nullable=False)
z = Column(Float, nullable=False)
yaw = Column(Float, default=0.0)
pitch = Column(Float, default=0.0)
is_public = Column(Integer, default=0) # 0 = private, 1 = public
world = Column(String, nullable=False) # TEXT type
x = Column(Float, nullable=False) # DOUBLE PRECISION
y = Column(Float, nullable=False) # DOUBLE PRECISION
z = Column(Float, nullable=False) # DOUBLE PRECISION
yaw = Column(REAL, default=0.0)
pitch = Column(REAL, default=0.0)
is_public = Column(Boolean, default=False)

View File

@ -0,0 +1,50 @@
"""LuckPerms models."""
from sqlalchemy import (
Column,
String,
Boolean,
BigInteger,
ForeignKey,
Integer,
)
from sqlalchemy.orm import relationship
from hubgw.models.base import Base
class LuckPermsPlayer(Base):
"""LuckPerms Player model."""
__tablename__ = "luckperms_players"
uuid = Column(String(36), primary_key=True)
username = Column(String(16), nullable=False, index=True)
primary_group = Column(String(36), nullable=False)
permissions = relationship("LuckPermsUserPermission", back_populates="player", cascade="all, delete-orphan")
class LuckPermsGroup(Base):
"""LuckPerms Group model."""
__tablename__ = "luckperms_groups"
name = Column(String(36), primary_key=True)
class LuckPermsUserPermission(Base):
"""LuckPerms User Permission model."""
__tablename__ = "luckperms_user_permissions"
id = Column(Integer, primary_key=True, autoincrement=True)
uuid = Column(String(36), ForeignKey("luckperms_players.uuid", ondelete="CASCADE"), nullable=False, index=True)
permission = Column(String(200), nullable=False)
value = Column(Boolean, nullable=False)
server = Column(String(36))
world = Column(String(64))
expiry = Column(BigInteger)
contexts = Column(String(200))
player = relationship("LuckPermsPlayer", back_populates="permissions")

View File

@ -1,25 +1,27 @@
"""Punishment model."""
from sqlalchemy import Column, String, DateTime, Integer, Text
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy import Column, String, DateTime, Boolean, Text, ForeignKey
from sqlalchemy.dialects.postgresql import UUID, INET
from hubgw.models.base import Base
class Punishment(Base):
"""Punishment model."""
__tablename__ = "hub_punishments"
id = Column(UUID(as_uuid=True), primary_key=True)
player_uuid = Column(UUID(as_uuid=True), nullable=False, index=True)
id = Column(UUID(as_uuid=True), primary_key=True, server_default="gen_random_uuid()")
player_uuid = Column(String(36), ForeignKey("luckperms_players.uuid", ondelete="CASCADE"), nullable=False, index=True)
player_name = Column(String(255), nullable=False)
punishment_type = Column(String(50), nullable=False) # ban, warn, mute
player_ip = Column(INET)
punishment_type = Column(String(50), nullable=False) # BAN, MUTE, KICK, WARN, TEMPBAN, TEMPMUTE
reason = Column(Text, nullable=False)
staff_uuid = Column(UUID(as_uuid=True), nullable=False)
staff_uuid = Column(String(36), ForeignKey("luckperms_players.uuid", ondelete="SET NULL"), nullable=False)
staff_name = Column(String(255), nullable=False)
created_at = Column(DateTime(timezone=True), nullable=False)
expires_at = Column(DateTime(timezone=True), nullable=True) # None for permanent
is_active = Column(Integer, default=1) # 0 = inactive, 1 = active
revoked_at = Column(DateTime(timezone=True), nullable=True)
revoked_by = Column(UUID(as_uuid=True), nullable=True)
revoked_reason = Column(Text, nullable=True)
expires_at = Column(DateTime(timezone=True))
is_active = Column(Boolean, default=True)
revoked_at = Column(DateTime(timezone=True))
revoked_by = Column(String(36), ForeignKey("luckperms_players.uuid", ondelete="SET NULL"))
revoked_reason = Column(Text)
evidence_url = Column(Text)
notes = Column(Text)

View File

@ -0,0 +1,24 @@
"""Teleport History model."""
from sqlalchemy import Column, String, Float, ForeignKey
from sqlalchemy.dialects.postgresql import UUID
from hubgw.models.base import Base
class TeleportHistory(Base):
"""Teleport History model."""
__tablename__ = "hub_teleport_history"
id = Column(UUID(as_uuid=True), primary_key=True, server_default="gen_random_uuid()")
player_uuid = Column(String(36), ForeignKey("luckperms_players.uuid", ondelete="CASCADE"), nullable=False, index=True)
from_world = Column(String)
from_x = Column(Float)
from_y = Column(Float)
from_z = Column(Float)
to_world = Column(String, nullable=False)
to_x = Column(Float, nullable=False)
to_y = Column(Float, nullable=False)
to_z = Column(Float, nullable=False)
tp_type = Column(String(50), nullable=False)
target_name = Column(String(255))

View File

@ -1,22 +1,22 @@
"""Warp model."""
from sqlalchemy import Column, String, Float, Integer
from sqlalchemy import Column, String, Float, Boolean, REAL
from sqlalchemy.dialects.postgresql import UUID
from hubgw.models.base import Base
class Warp(Base):
"""Warp model."""
__tablename__ = "hub_warps"
id = Column(UUID(as_uuid=True), primary_key=True)
id = Column(UUID(as_uuid=True), primary_key=True, server_default="gen_random_uuid()")
name = Column(String(255), nullable=False, unique=True, index=True)
world = Column(String(255), nullable=False)
x = Column(Float, nullable=False)
y = Column(Float, nullable=False)
z = Column(Float, nullable=False)
yaw = Column(Float, default=0.0)
pitch = Column(Float, default=0.0)
is_public = Column(Integer, default=1) # 0 = private, 1 = public
description = Column(String(500), nullable=True)
world = Column(String, nullable=False) # TEXT type
x = Column(Float, nullable=False) # DOUBLE PRECISION
y = Column(Float, nullable=False) # DOUBLE PRECISION
z = Column(Float, nullable=False) # DOUBLE PRECISION
yaw = Column(REAL, default=0.0)
pitch = Column(REAL, default=0.0)
is_public = Column(Boolean, default=True)
description = Column(String(500))

View File

@ -1,18 +1,20 @@
"""Whitelist model."""
from sqlalchemy import Column, String, DateTime
from sqlalchemy import Column, String, DateTime, Boolean, ForeignKey
from sqlalchemy.dialects.postgresql import UUID
from hubgw.models.base import Base
class WhitelistEntry(Base):
"""Whitelist entry model."""
__tablename__ = "hub_whitelist"
id = Column(UUID(as_uuid=True), primary_key=True)
id = Column(UUID(as_uuid=True), primary_key=True, server_default="gen_random_uuid()")
player_name = Column(String(255), nullable=False, unique=True, index=True)
player_uuid = Column(UUID(as_uuid=True), nullable=True, index=True)
player_uuid = Column(String(36), ForeignKey("luckperms_players.uuid", ondelete="SET NULL"), index=True)
added_by = Column(String(255), nullable=False)
added_at = Column(DateTime(timezone=True), nullable=False)
reason = Column(String(500), nullable=True)
expires_at = Column(DateTime(timezone=True))
is_active = Column(Boolean, default=True)
reason = Column(String(500))

View File

@ -15,45 +15,33 @@ class CooldownsRepository:
def __init__(self, session: AsyncSession):
self.session = session
async def check(self, player_uuid: UUID, key: str) -> Optional[Cooldown]:
async def check(self, player_uuid: str, cooldown_type: str) -> Optional[Cooldown]:
"""Check if cooldown is active."""
stmt = select(Cooldown).where(
Cooldown.player_uuid == player_uuid,
Cooldown.key == key,
Cooldown.cooldown_type == cooldown_type,
Cooldown.expires_at > func.now()
)
result = await self.session.execute(stmt)
return result.scalar_one_or_none()
async def touch(self, player_uuid: UUID, key: str, cooldown_seconds: int) -> Cooldown:
"""Touch cooldown (create or update)."""
async def create(self, player_uuid: str, cooldown_type: str, cooldown_seconds: int, metadata: Optional[dict] = None) -> Cooldown:
"""Create new cooldown."""
expires_at = datetime.utcnow() + timedelta(seconds=cooldown_seconds)
stmt = insert(Cooldown).values(
key=key,
cooldown = Cooldown(
player_uuid=player_uuid,
cooldown_type=cooldown_type,
expires_at=expires_at,
cooldown_seconds=cooldown_seconds,
expires_at=expires_at
metadata=metadata
)
stmt = stmt.on_conflict_do_update(
index_elements=['key', 'player_uuid'],
set_=dict(
cooldown_seconds=stmt.excluded.cooldown_seconds,
expires_at=stmt.excluded.expires_at
)
)
result = await self.session.execute(stmt)
self.session.add(cooldown)
await self.session.commit()
# Get the touched cooldown
stmt = select(Cooldown).where(
Cooldown.player_uuid == player_uuid,
Cooldown.key == key
)
result = await self.session.execute(stmt)
return result.scalar_one()
await self.session.refresh(cooldown)
return cooldown
async def list_by_player(self, player_uuid: UUID) -> List[Cooldown]:
async def list_by_player(self, player_uuid: str) -> List[Cooldown]:
"""List active cooldowns by player."""
stmt = select(Cooldown).where(
Cooldown.player_uuid == player_uuid,

View File

@ -61,7 +61,7 @@ class HomesRepository:
result = await self.session.execute(stmt)
return result.scalar_one_or_none()
async def list_by_player(self, player_uuid: UUID) -> List[Home]:
async def list_by_player(self, player_uuid: str) -> List[Home]:
"""List homes by player UUID."""
stmt = select(Home).where(Home.player_uuid == player_uuid)
result = await self.session.execute(stmt)

View File

@ -0,0 +1,38 @@
"""LuckPerms repository."""
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
from typing import List, Optional
from hubgw.models.luckperms import LuckPermsPlayer, LuckPermsGroup, LuckPermsUserPermission
class LuckPermsRepository:
"""LuckPerms repository for database operations."""
def __init__(self, session: AsyncSession):
self.session = session
async def get_player(self, uuid: str) -> Optional[LuckPermsPlayer]:
"""Get player by UUID."""
stmt = select(LuckPermsPlayer).where(LuckPermsPlayer.uuid == uuid)
result = await self.session.execute(stmt)
return result.scalar_one_or_none()
async def get_player_by_username(self, username: str) -> Optional[LuckPermsPlayer]:
"""Get player by username."""
stmt = select(LuckPermsPlayer).where(LuckPermsPlayer.username == username)
result = await self.session.execute(stmt)
return result.scalar_one_or_none()
async def get_group(self, name: str) -> Optional[LuckPermsGroup]:
"""Get group by name."""
stmt = select(LuckPermsGroup).where(LuckPermsGroup.name == name)
result = await self.session.execute(stmt)
return result.scalar_one_or_none()
async def get_user_permissions(self, uuid: str) -> List[LuckPermsUserPermission]:
"""Get user permissions."""
stmt = select(LuckPermsUserPermission).where(LuckPermsUserPermission.uuid == uuid)
result = await self.session.execute(stmt)
return list(result.scalars().all())

View File

@ -21,12 +21,14 @@ class PunishmentsRepository:
punishment = Punishment(
player_uuid=request.player_uuid,
player_name=request.player_name,
player_ip=request.player_ip,
punishment_type=request.punishment_type,
reason=request.reason,
staff_uuid=request.staff_uuid,
staff_name=request.staff_name,
created_at=datetime.utcnow(),
expires_at=request.expires_at
expires_at=request.expires_at,
evidence_url=request.evidence_url,
notes=request.notes
)
self.session.add(punishment)
await self.session.commit()
@ -42,7 +44,7 @@ class PunishmentsRepository:
if not punishment:
return None
punishment.is_active = 0
punishment.is_active = False
punishment.revoked_at = datetime.utcnow()
punishment.revoked_by = request.revoked_by
punishment.revoked_reason = request.revoked_reason
@ -66,8 +68,8 @@ class PunishmentsRepository:
count_stmt = count_stmt.where(Punishment.punishment_type == query.punishment_type)
if query.is_active is not None:
stmt = stmt.where(Punishment.is_active == (1 if query.is_active else 0))
count_stmt = count_stmt.where(Punishment.is_active == (1 if query.is_active else 0))
stmt = stmt.where(Punishment.is_active == query.is_active)
count_stmt = count_stmt.where(Punishment.is_active == query.is_active)
# Get total count
count_result = await self.session.execute(count_stmt)
@ -82,23 +84,23 @@ class PunishmentsRepository:
return punishments, total
async def get_active_ban(self, player_uuid: UUID) -> Optional[Punishment]:
async def get_active_ban(self, player_uuid: str) -> Optional[Punishment]:
"""Get active ban for player."""
stmt = select(Punishment).where(
Punishment.player_uuid == player_uuid,
Punishment.punishment_type == "ban",
Punishment.is_active == 1,
Punishment.punishment_type.in_(['BAN', 'TEMPBAN']),
Punishment.is_active == True,
(Punishment.expires_at.is_(None)) | (Punishment.expires_at > datetime.utcnow())
)
result = await self.session.execute(stmt)
return result.scalar_one_or_none()
async def get_active_mute(self, player_uuid: UUID) -> Optional[Punishment]:
async def get_active_mute(self, player_uuid: str) -> Optional[Punishment]:
"""Get active mute for player."""
stmt = select(Punishment).where(
Punishment.player_uuid == player_uuid,
Punishment.punishment_type == "mute",
Punishment.is_active == 1,
Punishment.punishment_type.in_(['MUTE', 'TEMPMUTE']),
Punishment.is_active == True,
(Punishment.expires_at.is_(None)) | (Punishment.expires_at > datetime.utcnow())
)
result = await self.session.execute(stmt)

View File

@ -0,0 +1,31 @@
"""Teleport History repository."""
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
from typing import List
from hubgw.models.teleport_history import TeleportHistory
from hubgw.schemas.teleport_history import TeleportHistoryCreate
class TeleportHistoryRepository:
"""Teleport History repository for database operations."""
def __init__(self, session: AsyncSession):
self.session = session
async def create(self, request: TeleportHistoryCreate) -> TeleportHistory:
"""Create teleport history entry."""
entry = TeleportHistory(**request.model_dump())
self.session.add(entry)
await self.session.commit()
await self.session.refresh(entry)
return entry
async def list_by_player(self, player_uuid: str, limit: int = 100) -> List[TeleportHistory]:
"""List teleport history by player."""
stmt = select(TeleportHistory).where(
TeleportHistory.player_uuid == player_uuid
).order_by(TeleportHistory.created_at.desc()).limit(limit)
result = await self.session.execute(stmt)
return list(result.scalars().all())

View File

@ -42,7 +42,7 @@ class WarpsRepository:
if not warp:
return None
update_data = request.dict(exclude_unset=True, exclude={'name'})
update_data = request.model_dump(exclude_unset=True, exclude={'name'})
for field, value in update_data.items():
setattr(warp, field, value)

View File

@ -22,7 +22,9 @@ class WhitelistRepository:
player_name=request.player_name,
player_uuid=request.player_uuid,
added_by=request.added_by,
added_at=datetime.utcnow(),
added_at=request.added_at,
expires_at=request.expires_at,
is_active=request.is_active,
reason=request.reason
)
self.session.add(entry)

View File

@ -7,17 +7,11 @@ from typing import Optional
from hubgw.schemas.common import BaseSchema
class CooldownKey(BaseModel):
"""Cooldown key schema."""
key: str
class CooldownCheckRequest(BaseModel):
"""Cooldown check request schema."""
player_uuid: UUID
key: str
player_uuid: str
cooldown_type: str
class CooldownCheckResponse(BaseModel):
@ -28,11 +22,23 @@ class CooldownCheckResponse(BaseModel):
remaining_seconds: Optional[int] = None
class Cooldown(BaseSchema):
class CooldownBase(BaseModel):
"""Base cooldown schema."""
cooldown_type: str
expires_at: datetime
cooldown_seconds: int
metadata: Optional[dict] = None
class CooldownCreate(CooldownBase):
"""Cooldown creation schema."""
player_uuid: str
class Cooldown(CooldownBase, BaseSchema):
"""Cooldown schema."""
id: UUID
key: str
player_uuid: UUID
expires_at: datetime
cooldown_seconds: int
player_uuid: str

View File

@ -17,38 +17,45 @@ class HomeBase(BaseModel):
z: float
yaw: float = 0.0
pitch: float = 0.0
is_public: int = 0
is_public: bool = False
class HomeCreate(HomeBase):
"""Home creation schema."""
player_uuid: UUID
player_uuid: str
class HomeUpdate(HomeBase):
class HomeUpdate(BaseModel):
"""Home update schema."""
pass
name: Optional[str] = None
world: Optional[str] = None
x: Optional[float] = None
y: Optional[float] = None
z: Optional[float] = None
yaw: Optional[float] = None
pitch: Optional[float] = None
is_public: Optional[bool] = None
class HomeUpsertRequest(HomeBase):
"""Home upsert request schema."""
player_uuid: UUID
player_uuid: str
class Home(HomeBase, BaseSchema):
"""Home response schema."""
id: UUID
player_uuid: UUID
player_uuid: str
class HomeGetRequest(BaseModel):
"""Home get request schema."""
player_uuid: UUID
player_uuid: str
name: str

View File

@ -0,0 +1,40 @@
"""LuckPerms schemas."""
from pydantic import BaseModel
from typing import Optional
from datetime import datetime
from uuid import UUID
from hubgw.schemas.common import BaseSchema
class LuckPermsPlayer(BaseSchema):
"""LuckPerms Player schema."""
uuid: str
username: str
primary_group: str
class LuckPermsGroup(BaseSchema):
"""LuckPerms Group schema."""
name: str
class LuckPermsUserPermission(BaseSchema):
"""LuckPerms User Permission schema."""
id: int
uuid: str
permission: str
value: bool
server: Optional[str] = None
world: Optional[str] = None
expiry: Optional[int] = None
contexts: Optional[str] = None
class LuckPermsPlayerWithPermissions(LuckPermsPlayer):
"""LuckPerms Player with permissions schema."""
permissions: list[LuckPermsUserPermission] = []

View File

@ -7,30 +7,35 @@ from uuid import UUID
from hubgw.schemas.common import BaseSchema
from ipaddress import IPv4Address
class PunishmentCreateRequest(BaseModel):
"""Punishment create request schema."""
player_uuid: UUID
player_uuid: str
player_name: str
punishment_type: str # ban, warn, mute
player_ip: Optional[IPv4Address] = None
punishment_type: str
reason: str
staff_uuid: UUID
staff_uuid: str
staff_name: str
expires_at: Optional[datetime] = None # None for permanent
expires_at: Optional[datetime] = None
evidence_url: Optional[str] = None
notes: Optional[str] = None
class PunishmentRevokeRequest(BaseModel):
"""Punishment revoke request schema."""
punishment_id: UUID
revoked_by: UUID
revoked_by: str
revoked_reason: str
class PunishmentQuery(BaseModel):
"""Punishment query schema."""
player_uuid: Optional[UUID] = None
player_uuid: Optional[str] = None
punishment_type: Optional[str] = None
is_active: Optional[bool] = None
page: int = 1
@ -41,17 +46,20 @@ class PunishmentBase(BaseSchema):
"""Base punishment schema."""
id: UUID
player_uuid: UUID
player_uuid: str
player_name: str
player_ip: Optional[IPv4Address] = None
punishment_type: str
reason: str
staff_uuid: UUID
staff_uuid: str
staff_name: str
expires_at: Optional[datetime] = None
is_active: int
is_active: bool
revoked_at: Optional[datetime] = None
revoked_by: Optional[UUID] = None
revoked_by: Optional[str] = None
revoked_reason: Optional[str] = None
evidence_url: Optional[str] = None
notes: Optional[str] = None
class PunishmentListResponse(BaseModel):

View File

@ -0,0 +1,35 @@
"""Teleport History schemas."""
from pydantic import BaseModel
from typing import Optional
from datetime import datetime
from uuid import UUID
from hubgw.schemas.common import BaseSchema
class TeleportHistoryBase(BaseModel):
"""Base teleport history schema."""
from_world: Optional[str] = None
from_x: Optional[float] = None
from_y: Optional[float] = None
from_z: Optional[float] = None
to_world: str
to_x: float
to_y: float
to_z: float
tp_type: str
target_name: Optional[str] = None
class TeleportHistoryCreate(TeleportHistoryBase):
"""Teleport history creation schema."""
player_uuid: str
class TeleportHistory(TeleportHistoryBase, BaseSchema):
"""Teleport history schema."""
id: UUID
player_uuid: str

View File

@ -17,7 +17,7 @@ class WarpBase(BaseModel):
z: float
yaw: float = 0.0
pitch: float = 0.0
is_public: int = 1
is_public: bool = True
description: Optional[str] = None
@ -37,7 +37,7 @@ class WarpUpdateRequest(BaseModel):
z: Optional[float] = None
yaw: Optional[float] = None
pitch: Optional[float] = None
is_public: Optional[int] = None
is_public: Optional[bool] = None
description: Optional[str] = None

View File

@ -11,8 +11,11 @@ class WhitelistAddRequest(BaseModel):
"""Whitelist add request schema."""
player_name: str
player_uuid: Optional[UUID] = None
player_uuid: Optional[str] = None
added_by: str
added_at: datetime
expires_at: Optional[datetime] = None
is_active: bool = True
reason: Optional[str] = None
@ -32,7 +35,7 @@ class WhitelistCheckResponse(BaseModel):
"""Whitelist check response schema."""
is_whitelisted: bool
player_uuid: Optional[UUID] = None
player_uuid: Optional[str] = None
class WhitelistEntry(BaseSchema):
@ -40,9 +43,11 @@ class WhitelistEntry(BaseSchema):
id: UUID
player_name: str
player_uuid: Optional[UUID] = None
player_uuid: Optional[str] = None
added_by: str
added_at: datetime
expires_at: Optional[datetime] = None
is_active: bool = True
reason: Optional[str] = None

View File

@ -5,7 +5,7 @@ from uuid import UUID
from datetime import datetime
from hubgw.repositories.cooldowns_repo import CooldownsRepository
from hubgw.schemas.cooldowns import CooldownCheckRequest, CooldownCheckResponse, CooldownKey
from hubgw.schemas.cooldowns import CooldownCheckRequest, CooldownCheckResponse, CooldownCreate
class CooldownsService:
@ -16,7 +16,7 @@ class CooldownsService:
async def check_cooldown(self, request: CooldownCheckRequest) -> CooldownCheckResponse:
"""Check cooldown status."""
cooldown = await self.repo.check(request.player_uuid, request.key)
cooldown = await self.repo.check(request.player_uuid, request.cooldown_type)
if not cooldown:
return CooldownCheckResponse(is_active=False)
@ -29,6 +29,11 @@ class CooldownsService:
remaining_seconds=max(0, remaining_seconds)
)
async def touch_cooldown(self, player_uuid: UUID, key: str, cooldown_seconds: int) -> None:
"""Touch cooldown (create or update)."""
await self.repo.touch(player_uuid, key, cooldown_seconds)
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
)

View File

@ -40,7 +40,7 @@ class HomesService:
updated_at=home.updated_at
)
async def list_homes(self, player_uuid: UUID) -> HomeListResponse:
async def list_homes(self, player_uuid: str) -> HomeListResponse:
"""List homes with business logic."""
homes = await self.repo.list_by_player(player_uuid)

View File

@ -0,0 +1,104 @@
"""LuckPerms service."""
from sqlalchemy.ext.asyncio import AsyncSession
from typing import List, Optional
from hubgw.repositories.luckperms_repo import LuckPermsRepository
from hubgw.schemas.luckperms import LuckPermsPlayer, LuckPermsGroup, LuckPermsUserPermission, LuckPermsPlayerWithPermissions
from hubgw.core.errors import NotFoundError
class LuckPermsService:
"""LuckPerms service for business logic."""
def __init__(self, session: AsyncSession):
self.repo = LuckPermsRepository(session)
async def get_player(self, uuid: str) -> LuckPermsPlayer:
"""Get player by UUID."""
player = await self.repo.get_player(uuid)
if not player:
raise NotFoundError(f"Player {uuid} not found")
return LuckPermsPlayer(
uuid=player.uuid,
username=player.username,
primary_group=player.primary_group,
created_at=player.created_at,
updated_at=player.updated_at
)
async def get_player_by_username(self, username: str) -> LuckPermsPlayer:
"""Get player by username."""
player = await self.repo.get_player_by_username(username)
if not player:
raise NotFoundError(f"Player {username} not found")
return LuckPermsPlayer(
uuid=player.uuid,
username=player.username,
primary_group=player.primary_group,
created_at=player.created_at,
updated_at=player.updated_at
)
async def get_group(self, name: str) -> LuckPermsGroup:
"""Get group by name."""
group = await self.repo.get_group(name)
if not group:
raise NotFoundError(f"Group {name} not found")
return LuckPermsGroup(
name=group.name,
created_at=group.created_at,
updated_at=group.updated_at
)
async def get_user_permissions(self, uuid: str) -> List[LuckPermsUserPermission]:
"""Get user permissions."""
permissions = await self.repo.get_user_permissions(uuid)
return [
LuckPermsUserPermission(
id=perm.id,
uuid=perm.uuid,
permission=perm.permission,
value=perm.value,
server=perm.server,
world=perm.world,
expiry=perm.expiry,
contexts=perm.contexts,
created_at=perm.created_at,
updated_at=perm.updated_at
)
for perm in permissions
]
async def get_player_with_permissions(self, uuid: str) -> LuckPermsPlayerWithPermissions:
"""Get player with permissions."""
player = await self.repo.get_player(uuid)
if not player:
raise NotFoundError(f"Player {uuid} not found")
permissions = await self.repo.get_user_permissions(uuid)
permission_schemas = [
LuckPermsUserPermission(
id=perm.id,
uuid=perm.uuid,
permission=perm.permission,
value=perm.value,
server=perm.server,
world=perm.world,
expiry=perm.expiry,
contexts=perm.contexts,
created_at=perm.created_at,
updated_at=perm.updated_at
)
for perm in permissions
]
return LuckPermsPlayerWithPermissions(
uuid=player.uuid,
username=player.username,
primary_group=player.primary_group,
created_at=player.created_at,
updated_at=player.updated_at,
permissions=permission_schemas
)

View File

@ -26,6 +26,7 @@ class PunishmentsService:
id=punishment.id,
player_uuid=punishment.player_uuid,
player_name=punishment.player_name,
player_ip=punishment.player_ip,
punishment_type=punishment.punishment_type,
reason=punishment.reason,
staff_uuid=punishment.staff_uuid,
@ -35,7 +36,9 @@ class PunishmentsService:
is_active=punishment.is_active,
revoked_at=punishment.revoked_at,
revoked_by=punishment.revoked_by,
revoked_reason=punishment.revoked_reason
revoked_reason=punishment.revoked_reason,
evidence_url=punishment.evidence_url,
notes=punishment.notes
)
async def revoke_punishment(self, request: PunishmentRevokeRequest) -> PunishmentBase:
@ -48,6 +51,7 @@ class PunishmentsService:
id=punishment.id,
player_uuid=punishment.player_uuid,
player_name=punishment.player_name,
player_ip=punishment.player_ip,
punishment_type=punishment.punishment_type,
reason=punishment.reason,
staff_uuid=punishment.staff_uuid,
@ -57,7 +61,9 @@ class PunishmentsService:
is_active=punishment.is_active,
revoked_at=punishment.revoked_at,
revoked_by=punishment.revoked_by,
revoked_reason=punishment.revoked_reason
revoked_reason=punishment.revoked_reason,
evidence_url=punishment.evidence_url,
notes=punishment.notes
)
async def query_punishments(self, query: PunishmentQuery) -> PunishmentListResponse:
@ -69,6 +75,7 @@ class PunishmentsService:
id=p.id,
player_uuid=p.player_uuid,
player_name=p.player_name,
player_ip=p.player_ip,
punishment_type=p.punishment_type,
reason=p.reason,
staff_uuid=p.staff_uuid,
@ -78,7 +85,9 @@ class PunishmentsService:
is_active=p.is_active,
revoked_at=p.revoked_at,
revoked_by=p.revoked_by,
revoked_reason=p.revoked_reason
revoked_reason=p.revoked_reason,
evidence_url=p.evidence_url,
notes=p.notes
)
for p in punishments
]
@ -104,6 +113,7 @@ class PunishmentsService:
id=ban.id,
player_uuid=ban.player_uuid,
player_name=ban.player_name,
player_ip=ban.player_ip,
punishment_type=ban.punishment_type,
reason=ban.reason,
staff_uuid=ban.staff_uuid,
@ -113,7 +123,9 @@ class PunishmentsService:
is_active=ban.is_active,
revoked_at=ban.revoked_at,
revoked_by=ban.revoked_by,
revoked_reason=ban.revoked_reason
revoked_reason=ban.revoked_reason,
evidence_url=ban.evidence_url,
notes=ban.notes
)
return ActiveBanStatusResponse(is_banned=True, punishment=punishment)
@ -129,6 +141,7 @@ class PunishmentsService:
id=mute.id,
player_uuid=mute.player_uuid,
player_name=mute.player_name,
player_ip=mute.player_ip,
punishment_type=mute.punishment_type,
reason=mute.reason,
staff_uuid=mute.staff_uuid,
@ -138,7 +151,9 @@ class PunishmentsService:
is_active=mute.is_active,
revoked_at=mute.revoked_at,
revoked_by=mute.revoked_by,
revoked_reason=mute.revoked_reason
revoked_reason=mute.revoked_reason,
evidence_url=mute.evidence_url,
notes=mute.notes
)
return ActiveMuteStatusResponse(is_muted=True, punishment=punishment)

View File

@ -0,0 +1,57 @@
"""Teleport History service."""
from sqlalchemy.ext.asyncio import AsyncSession
from typing import List
from hubgw.repositories.teleport_history_repo import TeleportHistoryRepository
from hubgw.schemas.teleport_history import TeleportHistoryCreate, TeleportHistory
class TeleportHistoryService:
"""Teleport History service for business logic."""
def __init__(self, session: AsyncSession):
self.repo = TeleportHistoryRepository(session)
async def create_teleport(self, request: TeleportHistoryCreate) -> TeleportHistory:
"""Create teleport history entry."""
entry = await self.repo.create(request)
return TeleportHistory(
id=entry.id,
player_uuid=entry.player_uuid,
from_world=entry.from_world,
from_x=entry.from_x,
from_y=entry.from_y,
from_z=entry.from_z,
to_world=entry.to_world,
to_x=entry.to_x,
to_y=entry.to_y,
to_z=entry.to_z,
tp_type=entry.tp_type,
target_name=entry.target_name,
created_at=entry.created_at,
updated_at=entry.updated_at
)
async def list_player_teleports(self, player_uuid: str, limit: int = 100) -> List[TeleportHistory]:
"""List teleport history for player."""
entries = await self.repo.list_by_player(player_uuid, limit)
return [
TeleportHistory(
id=entry.id,
player_uuid=entry.player_uuid,
from_world=entry.from_world,
from_x=entry.from_x,
from_y=entry.from_y,
from_z=entry.from_z,
to_world=entry.to_world,
to_x=entry.to_x,
to_y=entry.to_y,
to_z=entry.to_z,
tp_type=entry.tp_type,
target_name=entry.target_name,
created_at=entry.created_at,
updated_at=entry.updated_at
)
for entry in entries
]