From 19e62d87d4fda93589192e8ab353f23c6e4091fe Mon Sep 17 00:00:00 2001 From: itqop Date: Thu, 16 Oct 2025 00:02:52 +0300 Subject: [PATCH] feat: new tables --- src/hubgw/api/deps.py | 12 ++ src/hubgw/api/v1/cooldowns.py | 18 ++- src/hubgw/api/v1/homes.py | 2 +- src/hubgw/api/v1/luckperms.py | 76 +++++++++++++ src/hubgw/api/v1/punishments.py | 4 +- src/hubgw/api/v1/router.py | 4 +- src/hubgw/api/v1/teleport_history.py | 38 +++++++ src/hubgw/models/cooldown.py | 17 +-- src/hubgw/models/home.py | 24 ++-- src/hubgw/models/luckperms.py | 50 +++++++++ src/hubgw/models/punishment.py | 30 ++--- src/hubgw/models/teleport_history.py | 24 ++++ src/hubgw/models/warp.py | 24 ++-- src/hubgw/models/whitelist.py | 14 ++- src/hubgw/repositories/cooldowns_repo.py | 36 ++---- src/hubgw/repositories/homes_repo.py | 2 +- src/hubgw/repositories/luckperms_repo.py | 38 +++++++ src/hubgw/repositories/punishments_repo.py | 24 ++-- .../repositories/teleport_history_repo.py | 31 ++++++ src/hubgw/repositories/warps_repo.py | 2 +- src/hubgw/repositories/whitelist_repo.py | 4 +- src/hubgw/schemas/cooldowns.py | 32 +++--- src/hubgw/schemas/homes.py | 21 ++-- src/hubgw/schemas/luckperms.py | 40 +++++++ src/hubgw/schemas/punishments.py | 28 +++-- src/hubgw/schemas/teleport_history.py | 35 ++++++ src/hubgw/schemas/warps.py | 4 +- src/hubgw/schemas/whitelist.py | 11 +- src/hubgw/services/cooldowns_service.py | 15 ++- src/hubgw/services/homes_service.py | 2 +- src/hubgw/services/luckperms_service.py | 104 ++++++++++++++++++ src/hubgw/services/punishments_service.py | 25 ++++- .../services/teleport_history_service.py | 57 ++++++++++ 33 files changed, 698 insertions(+), 150 deletions(-) create mode 100644 src/hubgw/api/v1/luckperms.py create mode 100644 src/hubgw/api/v1/teleport_history.py create mode 100644 src/hubgw/models/luckperms.py create mode 100644 src/hubgw/models/teleport_history.py create mode 100644 src/hubgw/repositories/luckperms_repo.py create mode 100644 src/hubgw/repositories/teleport_history_repo.py create mode 100644 src/hubgw/schemas/luckperms.py create mode 100644 src/hubgw/schemas/teleport_history.py create mode 100644 src/hubgw/services/luckperms_service.py create mode 100644 src/hubgw/services/teleport_history_service.py diff --git a/src/hubgw/api/deps.py b/src/hubgw/api/deps.py index 7fed23e..bab76e1 100644 --- a/src/hubgw/api/deps.py +++ b/src/hubgw/api/deps.py @@ -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) diff --git a/src/hubgw/api/v1/cooldowns.py b/src/hubgw/api/v1/cooldowns.py index 1e97f26..cdd61d0 100644 --- a/src/hubgw/api/v1/cooldowns.py +++ b/src/hubgw/api/v1/cooldowns.py @@ -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) diff --git a/src/hubgw/api/v1/homes.py b/src/hubgw/api/v1/homes.py index 35965da..942f60e 100644 --- a/src/hubgw/api/v1/homes.py +++ b/src/hubgw/api/v1/homes.py @@ -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)] ): diff --git a/src/hubgw/api/v1/luckperms.py b/src/hubgw/api/v1/luckperms.py new file mode 100644 index 0000000..1d6331e --- /dev/null +++ b/src/hubgw/api/v1/luckperms.py @@ -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) \ No newline at end of file diff --git a/src/hubgw/api/v1/punishments.py b/src/hubgw/api/v1/punishments.py index 1a45e77..4a63200 100644 --- a/src/hubgw/api/v1/punishments.py +++ b/src/hubgw/api/v1/punishments.py @@ -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)] ): diff --git a/src/hubgw/api/v1/router.py b/src/hubgw/api/v1/router.py index f476e71..9e96ba1 100644 --- a/src/hubgw/api/v1/router.py +++ b/src/hubgw/api/v1/router.py @@ -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"]) diff --git a/src/hubgw/api/v1/teleport_history.py b/src/hubgw/api/v1/teleport_history.py new file mode 100644 index 0000000..edb75a2 --- /dev/null +++ b/src/hubgw/api/v1/teleport_history.py @@ -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) \ No newline at end of file diff --git a/src/hubgw/models/cooldown.py b/src/hubgw/models/cooldown.py index e05c583..b3d4202 100644 --- a/src/hubgw/models/cooldown.py +++ b/src/hubgw/models/cooldown.py @@ -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) diff --git a/src/hubgw/models/home.py b/src/hubgw/models/home.py index 6f33ee6..b1c0709 100644 --- a/src/hubgw/models/home.py +++ b/src/hubgw/models/home.py @@ -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) diff --git a/src/hubgw/models/luckperms.py b/src/hubgw/models/luckperms.py new file mode 100644 index 0000000..d8c6745 --- /dev/null +++ b/src/hubgw/models/luckperms.py @@ -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") \ No newline at end of file diff --git a/src/hubgw/models/punishment.py b/src/hubgw/models/punishment.py index 6468740..145f8f8 100644 --- a/src/hubgw/models/punishment.py +++ b/src/hubgw/models/punishment.py @@ -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) diff --git a/src/hubgw/models/teleport_history.py b/src/hubgw/models/teleport_history.py new file mode 100644 index 0000000..0e01dbb --- /dev/null +++ b/src/hubgw/models/teleport_history.py @@ -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)) \ No newline at end of file diff --git a/src/hubgw/models/warp.py b/src/hubgw/models/warp.py index bcef797..ae2619a 100644 --- a/src/hubgw/models/warp.py +++ b/src/hubgw/models/warp.py @@ -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)) diff --git a/src/hubgw/models/whitelist.py b/src/hubgw/models/whitelist.py index c16506b..4c28809 100644 --- a/src/hubgw/models/whitelist.py +++ b/src/hubgw/models/whitelist.py @@ -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)) diff --git a/src/hubgw/repositories/cooldowns_repo.py b/src/hubgw/repositories/cooldowns_repo.py index 3b15fa1..cf6faa5 100644 --- a/src/hubgw/repositories/cooldowns_repo.py +++ b/src/hubgw/repositories/cooldowns_repo.py @@ -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, diff --git a/src/hubgw/repositories/homes_repo.py b/src/hubgw/repositories/homes_repo.py index 20d80f9..bcad233 100644 --- a/src/hubgw/repositories/homes_repo.py +++ b/src/hubgw/repositories/homes_repo.py @@ -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) diff --git a/src/hubgw/repositories/luckperms_repo.py b/src/hubgw/repositories/luckperms_repo.py new file mode 100644 index 0000000..2c55bd7 --- /dev/null +++ b/src/hubgw/repositories/luckperms_repo.py @@ -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()) \ No newline at end of file diff --git a/src/hubgw/repositories/punishments_repo.py b/src/hubgw/repositories/punishments_repo.py index 71d6c7f..bb97dd7 100644 --- a/src/hubgw/repositories/punishments_repo.py +++ b/src/hubgw/repositories/punishments_repo.py @@ -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) diff --git a/src/hubgw/repositories/teleport_history_repo.py b/src/hubgw/repositories/teleport_history_repo.py new file mode 100644 index 0000000..2399558 --- /dev/null +++ b/src/hubgw/repositories/teleport_history_repo.py @@ -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()) \ No newline at end of file diff --git a/src/hubgw/repositories/warps_repo.py b/src/hubgw/repositories/warps_repo.py index 1ca705c..9a5cb6f 100644 --- a/src/hubgw/repositories/warps_repo.py +++ b/src/hubgw/repositories/warps_repo.py @@ -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) diff --git a/src/hubgw/repositories/whitelist_repo.py b/src/hubgw/repositories/whitelist_repo.py index a391af4..8ec6343 100644 --- a/src/hubgw/repositories/whitelist_repo.py +++ b/src/hubgw/repositories/whitelist_repo.py @@ -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) diff --git a/src/hubgw/schemas/cooldowns.py b/src/hubgw/schemas/cooldowns.py index ee0bd67..9976ab5 100644 --- a/src/hubgw/schemas/cooldowns.py +++ b/src/hubgw/schemas/cooldowns.py @@ -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 diff --git a/src/hubgw/schemas/homes.py b/src/hubgw/schemas/homes.py index 65a01a7..5fd2b09 100644 --- a/src/hubgw/schemas/homes.py +++ b/src/hubgw/schemas/homes.py @@ -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 diff --git a/src/hubgw/schemas/luckperms.py b/src/hubgw/schemas/luckperms.py new file mode 100644 index 0000000..5ea1a41 --- /dev/null +++ b/src/hubgw/schemas/luckperms.py @@ -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] = [] \ No newline at end of file diff --git a/src/hubgw/schemas/punishments.py b/src/hubgw/schemas/punishments.py index 13aed38..d44c34b 100644 --- a/src/hubgw/schemas/punishments.py +++ b/src/hubgw/schemas/punishments.py @@ -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): diff --git a/src/hubgw/schemas/teleport_history.py b/src/hubgw/schemas/teleport_history.py new file mode 100644 index 0000000..4b4bc10 --- /dev/null +++ b/src/hubgw/schemas/teleport_history.py @@ -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 \ No newline at end of file diff --git a/src/hubgw/schemas/warps.py b/src/hubgw/schemas/warps.py index 1083e43..27fc83b 100644 --- a/src/hubgw/schemas/warps.py +++ b/src/hubgw/schemas/warps.py @@ -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 diff --git a/src/hubgw/schemas/whitelist.py b/src/hubgw/schemas/whitelist.py index 42172b9..b89cbd6 100644 --- a/src/hubgw/schemas/whitelist.py +++ b/src/hubgw/schemas/whitelist.py @@ -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 diff --git a/src/hubgw/services/cooldowns_service.py b/src/hubgw/services/cooldowns_service.py index 7c48370..32e6ed7 100644 --- a/src/hubgw/services/cooldowns_service.py +++ b/src/hubgw/services/cooldowns_service.py @@ -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 + ) diff --git a/src/hubgw/services/homes_service.py b/src/hubgw/services/homes_service.py index 3fee9f2..6fbbf24 100644 --- a/src/hubgw/services/homes_service.py +++ b/src/hubgw/services/homes_service.py @@ -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) diff --git a/src/hubgw/services/luckperms_service.py b/src/hubgw/services/luckperms_service.py new file mode 100644 index 0000000..2bf899b --- /dev/null +++ b/src/hubgw/services/luckperms_service.py @@ -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 + ) \ No newline at end of file diff --git a/src/hubgw/services/punishments_service.py b/src/hubgw/services/punishments_service.py index 9c272a0..830bb2b 100644 --- a/src/hubgw/services/punishments_service.py +++ b/src/hubgw/services/punishments_service.py @@ -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) diff --git a/src/hubgw/services/teleport_history_service.py b/src/hubgw/services/teleport_history_service.py new file mode 100644 index 0000000..3bc1024 --- /dev/null +++ b/src/hubgw/services/teleport_history_service.py @@ -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 + ] \ No newline at end of file