This commit is contained in:
ciomek committed 2026-09-04 17:02:25 +02:00
1 parent c500c3fadf
commit 5732e26380
10 files changed
+176 -2

No files matched your search

+47
View File
@@ -0,0 +1,47 @@
"""logs
Revision ID: 69a46d2e17df
Revises: 2df7e2c6af43
Create Date: 2026-09-03 22:23:08.848825
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
import sqlmodel
# revision identifiers, used by Alembic.
revision: str = '69a46d2e17df'
down_revision: Union[str, Sequence[str], None] = '2df7e2c6af43'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('logtype',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('code', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('code')
)
op.create_table('logentry',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('log_type_id', sa.Integer(), nullable=False),
sa.Column('created_at', sa.DateTime(), nullable=False),
sa.Column('message', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
sa.ForeignKeyConstraint(['log_type_id'], ['logtype.id'], ),
sa.PrimaryKeyConstraint('id')
)
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('logentry')
op.drop_table('logtype')
# ### end Alembic commands ###
@@ -4,6 +4,8 @@ from sqlalchemy.exc import IntegrityError
from sqlmodel import Session, select
from loon.web.db import engine
from loon.web.logs.service import write_log
from loon.web.logs.types import LogTypeEnum
from loon.web.users.models import User
@@ -34,6 +36,7 @@ async def change_password_handler(client, userdata, msg, data):
select(User).where(User.uuid == uuid)
).first()
user.password = password
internal_username = user.internal_username
try:
session.commit()
@@ -44,4 +47,6 @@ async def change_password_handler(client, userdata, msg, data):
return
await write_log(LogTypeEnum.PLAYER_PASSWORD_CHANGED, f"Player {internal_username} ({uuid}) changed their password.")
client.publish(f"loon/auth/change_password/{uuid}/response", json.dumps({"success": True, "error": None}))
+30 -2
View File
@@ -1,10 +1,14 @@
import json
import random
from loon.web.logs.service import write_log
from loon.web.logs.types import LogTypeEnum
from loon.web.users.state import user_threads
PLAYER_EVENT_SUFFIXES = ("died", "joined", "left", "kicked")
BAN_KICK_REASON = "Banned by admin"
DEATH_BY_DAMAGE = {
"fall": (
"{username} fell from a high place",
@@ -78,7 +82,8 @@ async def marquee_handler(client, userdata, msg, data):
if not isinstance(player, dict):
return
username = player.get("username")
if not username:
uuid = player.get("uuid")
if not username or not uuid:
return
if subtopic == "died":
@@ -88,20 +93,43 @@ async def marquee_handler(client, userdata, msg, data):
if killer:
templates = DEATH_BY_KILLER.get(damage_type, DEATH_BY_KILLER_DEFAULT)
message = get_random(templates, username=username, killer=killer)
await write_log(LogTypeEnum.PLAYER_DEAD, f"Player {username} ({uuid}) was killed by {killer}.")
elif damage_type in DEATH_BY_DAMAGE:
message = get_random(DEATH_BY_DAMAGE[damage_type], username=username)
await write_log(LogTypeEnum.PLAYER_DEAD, f"Player {username} ({uuid}) died of {damage_type}.")
elif damage_type:
message = f"{username} died of {damage_type}"
await write_log(LogTypeEnum.PLAYER_DEAD, f"Player {username} ({uuid}) died of {damage_type}.")
else:
message = f"{username} died"
await write_log(LogTypeEnum.PLAYER_DEAD, f"Player {username} ({uuid}) died.")
elif subtopic == "joined":
message = f"{username} joined the game"
await write_log(LogTypeEnum.PLAYER_JOINED, f"Player {username} ({uuid}) joined the game.")
elif subtopic == "kicked":
reason = data.get("reason")
message = f"{username} was kicked: {reason}" if reason else f"{username} was kicked"
if reason == BAN_KICK_REASON:
message = f"{username} was banned"
await write_log(LogTypeEnum.PLAYER_BANNED, f"Player {username} ({uuid}) was banned.")
else:
message = f"{username} was kicked: {reason}" if reason else f"{username} was kicked"
await write_log(
LogTypeEnum.PLAYER_KICKED,
f"Player {username} ({uuid}) was kicked: {reason}." if reason else f"Player {username} ({uuid}) was kicked.",
)
else:
message = f"{username} left the game"
await write_log(LogTypeEnum.PLAYER_LEFT, f"Player {username} ({uuid}) left the game.")
envelope = json.dumps({"topic": "server/marquee", "payload": {"message": message}})
for queue in list(user_threads.values()):
+4
View File
@@ -4,6 +4,8 @@ from sqlalchemy.exc import IntegrityError
from sqlmodel import Session, select
from loon.web.db import engine
from loon.web.logs.service import write_log
from loon.web.logs.types import LogTypeEnum
from loon.web.users.models import User
@@ -50,4 +52,6 @@ async def register_request_handler(client, userdata, msg, data):
return
await write_log(LogTypeEnum.PLAYER_REGISTERED, f"Player {internal_username} ({uuid}) registered.")
client.publish(f"loon/auth/register/{uuid}/response", json.dumps({"success": True, "error": None}))
+25
View File
@@ -1,7 +1,32 @@
from fastapi import APIRouter
from sqlalchemy.orm import selectinload
from sqlmodel import Session, select
from starlette.requests import Request
from loon.web.auth.middleware import admin
from loon.web.db import engine
from loon.web.logs.models import LogEntry
from loon.web.logs.schema import LogEntryResponse
router = APIRouter(prefix="/admin", tags=["admin"])
@router.get("/logs", response_model=list[LogEntryResponse])
@admin
async def list_logs(request: Request):
with Session(engine) as session:
statement = (
select(LogEntry)
.options(selectinload(LogEntry.log_type))
.order_by(LogEntry.created_at)
)
entries = session.exec(statement).all()
return [
LogEntryResponse(
id=entry.id,
log_type=entry.log_type.code,
created_at=entry.created_at,
message=entry.message,
)
for entry in entries
]
View File
Whitespace-only changes.
+17
View File
@@ -0,0 +1,17 @@
from datetime import datetime, timezone
from sqlmodel import SQLModel, Field, Relationship
class LogType(SQLModel, table=True):
id: int = Field(default=None, primary_key=True)
code: str = Field(nullable=False, unique=True)
entries: list["LogEntry"] = Relationship(back_populates="log_type")
class LogEntry(SQLModel, table=True):
id: int = Field(default=None, primary_key=True)
log_type_id: int = Field(foreign_key="logtype.id", nullable=False)
log_type: LogType = Relationship(back_populates="entries")
created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
message: str = Field(nullable=False)
+10
View File
@@ -0,0 +1,10 @@
from datetime import datetime
from pydantic import BaseModel
class LogEntryResponse(BaseModel):
id: int
log_type: str
created_at: datetime
message: str
+27
View File
@@ -0,0 +1,27 @@
import asyncio
from sqlmodel import Session, select
from loon.web.db import engine
from loon.web.logs.models import LogType, LogEntry
from loon.web.logs.types import LogTypeEnum
def _write_log(log_type: LogTypeEnum, message: str) -> None:
with Session(engine) as session:
statement = select(LogType).where(LogType.code == log_type.value)
log_type_object = session.exec(statement).first()
if not log_type_object:
log_type_object = LogType(code=log_type.value)
session.add(log_type_object)
session.commit()
session.refresh(log_type_object)
log_entry = LogEntry(log_type_id=log_type_object.id, message=message)
session.add(log_entry)
session.commit()
async def write_log(log_type: LogTypeEnum, message: str) -> None:
await asyncio.to_thread(_write_log, log_type, message)
+11
View File
@@ -0,0 +1,11 @@
from enum import Enum
class LogTypeEnum(Enum):
PLAYER_DEAD = "player_dead"
PLAYER_JOINED = "player_joined"
PLAYER_LEFT = "player_left"
PLAYER_KICKED = "player_kicked"
PLAYER_BANNED = "player_banned"
PLAYER_REGISTERED = "player_registered"
PLAYER_PASSWORD_CHANGED = "player_password_changed"