Update dashboard and Docker compose
Some checks failed
CI/CD Pipeline / Security Scanning (push) Has been cancelled
CI/CD Pipeline / Tests (3.11) (push) Has been cancelled
CI/CD Pipeline / Tests (3.12) (push) Has been cancelled
CI/CD Pipeline / Build Docker Image (push) Has been cancelled
CI/CD Pipeline / Code Quality Checks (push) Has been cancelled

This commit is contained in:
2026-01-24 19:14:00 +01:00
parent a5811113f0
commit 574a07d127
17 changed files with 838 additions and 252 deletions

View File

@@ -3,7 +3,7 @@
from collections.abc import AsyncIterator
from fastapi import APIRouter, Depends, Query, Request
from sqlalchemy import func, select
from sqlalchemy import func, or_, select
from sqlalchemy.ext.asyncio import AsyncSession
from guardden.dashboard.auth import require_owner
@@ -27,7 +27,9 @@ def create_api_router(
def require_owner_dep(request: Request) -> None:
require_owner(settings, request)
@router.get("/guilds", response_model=list[GuildSummary], dependencies=[Depends(require_owner_dep)])
@router.get(
"/guilds", response_model=list[GuildSummary], dependencies=[Depends(require_owner_dep)]
)
async def list_guilds(
session: AsyncSession = Depends(get_session),
) -> list[GuildSummary]:
@@ -47,6 +49,9 @@ def create_api_router(
guild_id: int | None = Query(default=None),
limit: int = Query(default=50, ge=1, le=200),
offset: int = Query(default=0, ge=0),
action: str | None = Query(default=None),
message_only: bool = Query(default=False),
search: str | None = Query(default=None),
session: AsyncSession = Depends(get_session),
) -> PaginatedLogs:
query = select(ModerationLog)
@@ -55,6 +60,25 @@ def create_api_router(
query = query.where(ModerationLog.guild_id == guild_id)
count_query = count_query.where(ModerationLog.guild_id == guild_id)
if action:
query = query.where(ModerationLog.action == action)
count_query = count_query.where(ModerationLog.action == action)
if message_only:
query = query.where(ModerationLog.message_content.is_not(None))
count_query = count_query.where(ModerationLog.message_content.is_not(None))
if search:
like = f"%{search}%"
search_filter = or_(
ModerationLog.target_name.ilike(like),
ModerationLog.moderator_name.ilike(like),
ModerationLog.reason.ilike(like),
ModerationLog.message_content.ilike(like),
)
query = query.where(search_filter)
count_query = count_query.where(search_filter)
query = query.order_by(ModerationLog.created_at.desc()).offset(offset).limit(limit)
total_result = await session.execute(count_query)
total = int(total_result.scalar() or 0)

View File

@@ -71,6 +71,8 @@ class AnalyticsSummary(BaseModel):
# User Management Schemas
class UserProfile(BaseModel):
guild_id: int
guild_name: str
user_id: int
username: str
strike_count: int

View File

@@ -11,7 +11,7 @@ from guardden.dashboard.auth import require_owner
from guardden.dashboard.config import DashboardSettings
from guardden.dashboard.db import DashboardDatabase
from guardden.dashboard.schemas import CreateUserNote, UserNote, UserProfile
from guardden.models import ModerationLog, UserActivity
from guardden.models import Guild, ModerationLog, UserActivity
from guardden.models import UserNote as UserNoteModel
@@ -35,14 +35,16 @@ def create_users_router(
dependencies=[Depends(require_owner_dep)],
)
async def search_users(
guild_id: int = Query(...),
guild_id: int | None = Query(default=None),
username: str | None = Query(default=None),
min_strikes: int | None = Query(default=None, ge=0),
limit: int = Query(default=50, ge=1, le=200),
session: AsyncSession = Depends(get_session),
) -> list[UserProfile]:
"""Search for users in a guild with optional filters."""
query = select(UserActivity).where(UserActivity.guild_id == guild_id)
"""Search for users with optional guild and filter parameters."""
query = select(UserActivity, Guild.name).join(Guild, Guild.id == UserActivity.guild_id)
if guild_id:
query = query.where(UserActivity.guild_id == guild_id)
if username:
query = query.where(UserActivity.username.ilike(f"%{username}%"))
@@ -53,14 +55,14 @@ def create_users_router(
query = query.order_by(UserActivity.last_seen.desc()).limit(limit)
result = await session.execute(query)
users = result.scalars().all()
users = result.all()
# Get last moderation action for each user
profiles = []
for user in users:
for user, guild_name in users:
last_action_query = (
select(ModerationLog.created_at)
.where(ModerationLog.guild_id == guild_id)
.where(ModerationLog.guild_id == user.guild_id)
.where(ModerationLog.target_id == user.user_id)
.order_by(ModerationLog.created_at.desc())
.limit(1)
@@ -70,6 +72,8 @@ def create_users_router(
profiles.append(
UserProfile(
guild_id=user.guild_id,
guild_name=guild_name,
user_id=user.user_id,
username=user.username,
strike_count=user.strike_count,
@@ -96,19 +100,21 @@ def create_users_router(
) -> UserProfile:
"""Get detailed profile for a specific user."""
query = (
select(UserActivity)
select(UserActivity, Guild.name)
.join(Guild, Guild.id == UserActivity.guild_id)
.where(UserActivity.guild_id == guild_id)
.where(UserActivity.user_id == user_id)
)
result = await session.execute(query)
user = result.scalar_one_or_none()
row = result.one_or_none()
if not user:
if not row:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="User not found in this guild",
)
user, guild_name = row
# Get last moderation action
last_action_query = (
@@ -122,6 +128,8 @@ def create_users_router(
last_action = last_action_result.scalar()
return UserProfile(
guild_id=user.guild_id,
guild_name=guild_name,
user_id=user.user_id,
username=user.username,
strike_count=user.strike_count,