feat: enhance profile editing with avatar support and UI adjustments

- Added AvatarPath to ProfileEditResult for user profile updates.
- Updated ProfileEditDialog to include avatar selection with a browse button.
- Increased dialog height to accommodate new avatar input fields.
- Implemented avatar file path handling in the profile edit dialog.

feat: introduce moderation features and user roles

- Added ServerRole enum to define user roles (Member, Mod, Admin, Owner).
- Extended User model to include role, mute, and ban status.
- Created ModerationController for user role assignment, kicking, banning, and muting.
- Implemented methods in IChatBroadcaster and SignalRBroadcaster for user moderation actions.
- Updated database schema with new columns for user roles and moderation states.

fix: ensure muted users cannot send messages

- Added mute status checks in ChatService to prevent message sending for muted users.
- Updated user presence and status handling to reflect role changes and moderation actions.

chore: update constants for ASCII art rendering

- Introduced AsciiArtHeightHalfBlock constant for improved ASCII art rendering.
This commit is contained in:
HueByte
2026-02-19 17:47:10 +01:00
parent 257ac34224
commit b4c3ebd254
28 changed files with 1457 additions and 73 deletions
+67
View File
@@ -3,6 +3,7 @@ using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json;
using EchoHub.Core.DTOs;
using EchoHub.Core.Models;
namespace EchoHub.Client.Services;
@@ -211,6 +212,72 @@ public sealed class ApiClient : IDisposable
await EnsureSuccessAsync(response);
}
// ── Moderation ────────────────────────────────────────────────────────
public async Task AssignRoleAsync(string username, ServerRole role)
{
EnsureAuthenticated();
var response = await AuthenticatedRequestAsync(() =>
_http.PostAsJsonAsync("/api/moderation/role", new AssignRoleRequest(username, role)));
await EnsureSuccessAsync(response);
}
public async Task KickUserAsync(string username, string? reason = null)
{
EnsureAuthenticated();
var response = await AuthenticatedRequestAsync(() =>
_http.PostAsJsonAsync($"/api/moderation/kick/{Uri.EscapeDataString(username)}", new KickRequest(reason)));
await EnsureSuccessAsync(response);
}
public async Task BanUserAsync(string username, string? reason = null)
{
EnsureAuthenticated();
var response = await AuthenticatedRequestAsync(() =>
_http.PostAsJsonAsync($"/api/moderation/ban/{Uri.EscapeDataString(username)}", new BanRequest(reason)));
await EnsureSuccessAsync(response);
}
public async Task UnbanUserAsync(string username)
{
EnsureAuthenticated();
var response = await AuthenticatedRequestAsync(() =>
_http.PostAsJsonAsync($"/api/moderation/unban/{Uri.EscapeDataString(username)}", new {}));
await EnsureSuccessAsync(response);
}
public async Task MuteUserAsync(string username, int? durationMinutes = null, string? reason = null)
{
EnsureAuthenticated();
var response = await AuthenticatedRequestAsync(() =>
_http.PostAsJsonAsync($"/api/moderation/mute/{Uri.EscapeDataString(username)}", new MuteRequest(reason, durationMinutes)));
await EnsureSuccessAsync(response);
}
public async Task UnmuteUserAsync(string username)
{
EnsureAuthenticated();
var response = await AuthenticatedRequestAsync(() =>
_http.PostAsJsonAsync($"/api/moderation/unmute/{Uri.EscapeDataString(username)}", new {}));
await EnsureSuccessAsync(response);
}
public async Task DeleteMessageAsync(Guid messageId)
{
EnsureAuthenticated();
var response = await AuthenticatedRequestAsync(() =>
_http.DeleteAsync($"/api/moderation/messages/{messageId}"));
await EnsureSuccessAsync(response);
}
public async Task NukeChannelAsync(string channelName)
{
EnsureAuthenticated();
var response = await AuthenticatedRequestAsync(() =>
_http.DeleteAsync($"/api/moderation/channels/{Uri.EscapeDataString(channelName)}/nuke"));
await EnsureSuccessAsync(response);
}
private void SetTokens(LoginResponse result)
{
_accessToken = result.Token;
@@ -14,6 +14,10 @@ public sealed class EchoHubConnection : IAsyncDisposable
public event Action<string, string>? OnUserLeft;
public event Action<ChannelDto>? OnChannelUpdated;
public event Action<UserPresenceDto>? OnUserStatusChanged;
public event Action<string, string, string?>? OnUserKicked;
public event Action<string, string?>? OnUserBanned;
public event Action<string, Guid>? OnMessageDeleted;
public event Action<string>? OnChannelNuked;
public event Action<string>? OnError;
public event Action<string>? OnConnectionStateChanged;
public event Action? OnReconnected;
@@ -81,6 +85,26 @@ public sealed class EchoHubConnection : IAsyncDisposable
OnUserStatusChanged?.Invoke(presence);
});
_connection.On<string, string, string?>(nameof(Core.Contracts.IEchoHubClient.UserKicked), (channelName, username, reason) =>
{
OnUserKicked?.Invoke(channelName, username, reason);
});
_connection.On<string, string?>(nameof(Core.Contracts.IEchoHubClient.UserBanned), (username, reason) =>
{
OnUserBanned?.Invoke(username, reason);
});
_connection.On<string, Guid>(nameof(Core.Contracts.IEchoHubClient.MessageDeleted), (channelName, messageId) =>
{
OnMessageDeleted?.Invoke(channelName, messageId);
});
_connection.On<string>(nameof(Core.Contracts.IEchoHubClient.ChannelNuked), channelName =>
{
OnChannelNuked?.Invoke(channelName);
});
_connection.On<string>(nameof(Core.Contracts.IEchoHubClient.Error), message =>
{
OnError?.Invoke(message);