mirror of
https://github.com/RedWizardsLab/EchoHub.git
synced 2026-09-06 23:34:13 +02:00
Refactor classes to use constructor injection for dependencies
- Updated IrcBroadcaster to use constructor injection for IrcGatewayService. - Refactored JwtTokenService to initialize configuration values in the constructor. - Modified AuthController to use constructor injection for EchoHubDbContext and JwtTokenService. - Refactored ChannelsController to utilize constructor injection for dependencies. - Updated FilesController to use constructor injection for FileStorageService. - Refactored ServerController to initialize EchoHubDbContext and IConfiguration via constructor. - Modified UsersController to use constructor injection for EchoHubDbContext and ImageToAsciiService. - Updated EchoHubDbContext to use constructor for DbContextOptions. - Refactored ChatHub to use constructor injection for IChatService and ILogger. - Modified ChatService to utilize constructor injection for dependencies. - Refactored ServerDirectoryService to use constructor injection for IConfiguration, PresenceTracker, and ILogger.
This commit is contained in:
@@ -9,17 +9,30 @@ using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace EchoHub.Server.Services;
|
||||
|
||||
public class ChatService(
|
||||
IServiceScopeFactory scopeFactory,
|
||||
PresenceTracker presenceTracker,
|
||||
IEnumerable<IChatBroadcaster> broadcasters,
|
||||
ILogger<ChatService> logger) : IChatService
|
||||
public class ChatService : IChatService
|
||||
{
|
||||
private readonly IServiceScopeFactory _scopeFactory;
|
||||
private readonly PresenceTracker _presenceTracker;
|
||||
private readonly IEnumerable<IChatBroadcaster> _broadcasters;
|
||||
private readonly ILogger<ChatService> _logger;
|
||||
|
||||
public ChatService(
|
||||
IServiceScopeFactory scopeFactory,
|
||||
PresenceTracker presenceTracker,
|
||||
IEnumerable<IChatBroadcaster> broadcasters,
|
||||
ILogger<ChatService> logger)
|
||||
{
|
||||
_scopeFactory = scopeFactory;
|
||||
_presenceTracker = presenceTracker;
|
||||
_broadcasters = broadcasters;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task UserConnectedAsync(string connectionId, Guid userId, string username)
|
||||
{
|
||||
presenceTracker.UserConnected(connectionId, userId, username);
|
||||
_presenceTracker.UserConnected(connectionId, userId, username);
|
||||
|
||||
using var scope = scopeFactory.CreateScope();
|
||||
using var scope = _scopeFactory.CreateScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<EchoHubDbContext>();
|
||||
|
||||
var user = await db.Users.FindAsync(userId);
|
||||
@@ -30,21 +43,21 @@ public class ChatService(
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
logger.LogInformation("{User} connected (ConnectionId: {ConnectionId})", username, connectionId);
|
||||
_logger.LogInformation("{User} connected (ConnectionId: {ConnectionId})", username, connectionId);
|
||||
}
|
||||
|
||||
public async Task<string?> UserDisconnectedAsync(string connectionId)
|
||||
{
|
||||
var preDisconnectUsername = presenceTracker.GetUsernameForConnection(connectionId);
|
||||
var preDisconnectUsername = _presenceTracker.GetUsernameForConnection(connectionId);
|
||||
var channelsBeforeDisconnect = preDisconnectUsername is not null
|
||||
? presenceTracker.GetChannelsForUser(preDisconnectUsername)
|
||||
? _presenceTracker.GetChannelsForUser(preDisconnectUsername)
|
||||
: [];
|
||||
|
||||
var username = presenceTracker.UserDisconnected(connectionId);
|
||||
var username = _presenceTracker.UserDisconnected(connectionId);
|
||||
|
||||
if (username is not null && !presenceTracker.IsOnline(username))
|
||||
if (username is not null && !_presenceTracker.IsOnline(username))
|
||||
{
|
||||
using var scope = scopeFactory.CreateScope();
|
||||
using var scope = _scopeFactory.CreateScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<EchoHubDbContext>();
|
||||
|
||||
var user = await db.Users.FirstOrDefaultAsync(u => u.Username == username);
|
||||
@@ -65,7 +78,7 @@ public class ChatService(
|
||||
}
|
||||
}
|
||||
|
||||
logger.LogInformation("{User} disconnected (ConnectionId: {ConnectionId})", username ?? "Unknown", connectionId);
|
||||
_logger.LogInformation("{User} disconnected (ConnectionId: {ConnectionId})", username ?? "Unknown", connectionId);
|
||||
return username;
|
||||
}
|
||||
|
||||
@@ -77,19 +90,19 @@ public class ChatService(
|
||||
if (!ValidationConstants.ChannelNameRegex().IsMatch(channelName))
|
||||
return ([], "Invalid channel name. Use 2-100 characters: letters, digits, underscores, or hyphens.");
|
||||
|
||||
using var scope = scopeFactory.CreateScope();
|
||||
using var scope = _scopeFactory.CreateScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<EchoHubDbContext>();
|
||||
|
||||
var channel = await db.Channels.FirstOrDefaultAsync(c => c.Name == channelName);
|
||||
if (channel is null)
|
||||
return ([], $"Channel '{channelName}' does not exist. Create it first via the channel list.");
|
||||
|
||||
var isNewJoin = presenceTracker.JoinChannel(username, channelName);
|
||||
var isNewJoin = _presenceTracker.JoinChannel(username, channelName);
|
||||
|
||||
if (isNewJoin)
|
||||
{
|
||||
await BroadcastToAllAsync(b => b.SendUserJoinedAsync(channelName, username, connectionId));
|
||||
logger.LogInformation("{User} joined channel '{Channel}'", username, channelName);
|
||||
_logger.LogInformation("{User} joined channel '{Channel}'", username, channelName);
|
||||
}
|
||||
|
||||
var history = await GetChannelHistoryInternalAsync(db, channelName, HubConstants.DefaultHistoryCount);
|
||||
@@ -99,9 +112,9 @@ public class ChatService(
|
||||
public async Task LeaveChannelAsync(string connectionId, string username, string channelName)
|
||||
{
|
||||
channelName = channelName.ToLowerInvariant().Trim();
|
||||
presenceTracker.LeaveChannel(username, channelName);
|
||||
_presenceTracker.LeaveChannel(username, channelName);
|
||||
await BroadcastToAllAsync(b => b.SendUserLeftAsync(channelName, username));
|
||||
logger.LogInformation("{User} left channel '{Channel}'", username, channelName);
|
||||
_logger.LogInformation("{User} left channel '{Channel}'", username, channelName);
|
||||
}
|
||||
|
||||
public async Task<string?> SendMessageAsync(Guid userId, string username, string channelName, string content)
|
||||
@@ -117,7 +130,7 @@ public class ChatService(
|
||||
if (content.Length > HubConstants.MaxMessageLength)
|
||||
return $"Message exceeds maximum length of {HubConstants.MaxMessageLength} characters.";
|
||||
|
||||
using var scope = scopeFactory.CreateScope();
|
||||
using var scope = _scopeFactory.CreateScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<EchoHubDbContext>();
|
||||
|
||||
var channel = await db.Channels.FirstOrDefaultAsync(c => c.Name == channelName);
|
||||
@@ -153,7 +166,7 @@ public class ChatService(
|
||||
|
||||
await BroadcastToAllAsync(b => b.SendMessageToChannelAsync(channelName, messageDto));
|
||||
|
||||
logger.LogDebug("{User} sent message in '{Channel}'", username, channelName);
|
||||
_logger.LogDebug("{User} sent message in '{Channel}'", username, channelName);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -162,7 +175,7 @@ public class ChatService(
|
||||
channelName = channelName.ToLowerInvariant().Trim();
|
||||
count = Math.Clamp(count, 1, ValidationConstants.MaxHistoryCount);
|
||||
|
||||
using var scope = scopeFactory.CreateScope();
|
||||
using var scope = _scopeFactory.CreateScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<EchoHubDbContext>();
|
||||
|
||||
return await GetChannelHistoryInternalAsync(db, channelName, count);
|
||||
@@ -173,7 +186,7 @@ public class ChatService(
|
||||
if (statusMessage is not null && statusMessage.Length > ValidationConstants.MaxStatusMessageLength)
|
||||
return $"Status message must not exceed {ValidationConstants.MaxStatusMessageLength} characters.";
|
||||
|
||||
using var scope = scopeFactory.CreateScope();
|
||||
using var scope = _scopeFactory.CreateScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<EchoHubDbContext>();
|
||||
|
||||
var user = await db.Users.FindAsync(userId);
|
||||
@@ -192,7 +205,7 @@ public class ChatService(
|
||||
status,
|
||||
statusMessage);
|
||||
|
||||
var channels = presenceTracker.GetChannelsForUser(username);
|
||||
var channels = _presenceTracker.GetChannelsForUser(username);
|
||||
await BroadcastToAllAsync(b => b.SendUserStatusChangedAsync(channels, presence));
|
||||
|
||||
return null;
|
||||
@@ -201,9 +214,9 @@ public class ChatService(
|
||||
public async Task<List<UserPresenceDto>> GetOnlineUsersAsync(string channelName)
|
||||
{
|
||||
channelName = channelName.ToLowerInvariant().Trim();
|
||||
var onlineUsernames = presenceTracker.GetOnlineUsersInChannel(channelName);
|
||||
var onlineUsernames = _presenceTracker.GetOnlineUsersInChannel(channelName);
|
||||
|
||||
using var scope = scopeFactory.CreateScope();
|
||||
using var scope = _scopeFactory.CreateScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<EchoHubDbContext>();
|
||||
|
||||
return await db.Users
|
||||
@@ -225,7 +238,7 @@ public class ChatService(
|
||||
|
||||
private async Task BroadcastToAllAsync(Func<IChatBroadcaster, Task> action)
|
||||
{
|
||||
foreach (var broadcaster in broadcasters)
|
||||
foreach (var broadcaster in _broadcasters)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -233,7 +246,7 @@ public class ChatService(
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Broadcaster {Type} failed", broadcaster.GetType().Name);
|
||||
_logger.LogError(ex, "Broadcaster {Type} failed", broadcaster.GetType().Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -242,7 +255,7 @@ public class ChatService(
|
||||
{
|
||||
username = username.ToLowerInvariant();
|
||||
|
||||
using var scope = scopeFactory.CreateScope();
|
||||
using var scope = _scopeFactory.CreateScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<EchoHubDbContext>();
|
||||
|
||||
var user = await db.Users.FirstOrDefaultAsync(u => u.Username == username);
|
||||
@@ -258,7 +271,7 @@ public class ChatService(
|
||||
{
|
||||
channelName = channelName.ToLowerInvariant().Trim();
|
||||
|
||||
using var scope = scopeFactory.CreateScope();
|
||||
using var scope = _scopeFactory.CreateScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<EchoHubDbContext>();
|
||||
|
||||
var channel = await db.Channels.FirstOrDefaultAsync(c => c.Name == channelName);
|
||||
@@ -269,7 +282,7 @@ public class ChatService(
|
||||
|
||||
public async Task<List<ChannelListItem>> GetChannelListAsync()
|
||||
{
|
||||
using var scope = scopeFactory.CreateScope();
|
||||
using var scope = _scopeFactory.CreateScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<EchoHubDbContext>();
|
||||
|
||||
var channels = await db.Channels.OrderBy(c => c.Name).ToListAsync();
|
||||
@@ -277,17 +290,17 @@ public class ChatService(
|
||||
return channels.Select(c => new ChannelListItem(
|
||||
c.Name,
|
||||
c.Topic,
|
||||
presenceTracker.GetOnlineUsersInChannel(c.Name).Count)).ToList();
|
||||
_presenceTracker.GetOnlineUsersInChannel(c.Name).Count)).ToList();
|
||||
}
|
||||
|
||||
public Task<List<string>> GetChannelsForUserAsync(string username)
|
||||
=> Task.FromResult(presenceTracker.GetChannelsForUser(username));
|
||||
=> Task.FromResult(_presenceTracker.GetChannelsForUser(username));
|
||||
|
||||
public async Task<(Guid UserId, string Username)?> AuthenticateUserAsync(string username, string password)
|
||||
{
|
||||
username = username.ToLowerInvariant();
|
||||
|
||||
using var scope = scopeFactory.CreateScope();
|
||||
using var scope = _scopeFactory.CreateScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<EchoHubDbContext>();
|
||||
|
||||
var user = await db.Users.FirstOrDefaultAsync(u => u.Username == username);
|
||||
|
||||
@@ -2,19 +2,30 @@ using Microsoft.AspNetCore.SignalR.Client;
|
||||
|
||||
namespace EchoHub.Server.Services;
|
||||
|
||||
public sealed class ServerDirectoryService(
|
||||
IConfiguration configuration,
|
||||
PresenceTracker presenceTracker,
|
||||
ILogger<ServerDirectoryService> logger) : BackgroundService
|
||||
public sealed class ServerDirectoryService : BackgroundService
|
||||
{
|
||||
private const string DirectoryHubUrl = "https://echohub.voidcube.cloud/hubs/servers";
|
||||
private static readonly TimeSpan UpdateInterval = TimeSpan.FromSeconds(30);
|
||||
private static readonly TimeSpan ReconnectBaseDelay = TimeSpan.FromSeconds(2);
|
||||
private static readonly TimeSpan ReconnectMaxDelay = TimeSpan.FromSeconds(30);
|
||||
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly PresenceTracker _presenceTracker;
|
||||
private readonly ILogger<ServerDirectoryService> _logger;
|
||||
|
||||
private HubConnection? _connection;
|
||||
private int _lastReportedUserCount = -1;
|
||||
|
||||
public ServerDirectoryService(
|
||||
IConfiguration configuration,
|
||||
PresenceTracker presenceTracker,
|
||||
ILogger<ServerDirectoryService> logger)
|
||||
{
|
||||
_configuration = configuration;
|
||||
_presenceTracker = presenceTracker;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
Console.Error.WriteLine("[DIAG] ServerDirectoryService.ExecuteAsync entered.");
|
||||
@@ -22,27 +33,27 @@ public sealed class ServerDirectoryService(
|
||||
await Task.Yield();
|
||||
Console.Error.WriteLine("[DIAG] ServerDirectoryService.ExecuteAsync resumed after Task.Yield().");
|
||||
|
||||
var isPublic = configuration.GetValue<bool>("Server:PublicServer");
|
||||
var isPublic = _configuration.GetValue<bool>("Server:PublicServer");
|
||||
Console.Error.WriteLine($"[DIAG] ServerDirectoryService: PublicServer={isPublic}");
|
||||
if (!isPublic)
|
||||
{
|
||||
logger.LogInformation("PublicServer is disabled — not registering with directory");
|
||||
_logger.LogInformation("PublicServer is disabled — not registering with directory");
|
||||
return;
|
||||
}
|
||||
|
||||
var host = configuration["Server:PublicHost"];
|
||||
var host = _configuration["Server:PublicHost"];
|
||||
Console.Error.WriteLine($"[DIAG] ServerDirectoryService: PublicHost={host}");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(host))
|
||||
{
|
||||
logger.LogWarning("PublicServer is enabled but Server:PublicHost is not set — skipping directory registration");
|
||||
_logger.LogWarning("PublicServer is enabled but Server:PublicHost is not set — skipping directory registration");
|
||||
return;
|
||||
}
|
||||
|
||||
var serverName = configuration["Server:Name"] ?? "EchoHub Server";
|
||||
var description = configuration["Server:Description"];
|
||||
var serverName = _configuration["Server:Name"] ?? "EchoHub Server";
|
||||
var description = _configuration["Server:Description"];
|
||||
|
||||
logger.LogInformation("PublicServer is enabled — connecting to EchoHubSpace directory as {Name} ({Host})", serverName, host);
|
||||
_logger.LogInformation("PublicServer is enabled — connecting to EchoHubSpace directory as {Name} ({Host})", serverName, host);
|
||||
|
||||
// Outer loop: rebuilds the connection if automatic reconnect permanently fails
|
||||
while (!stoppingToken.IsCancellationRequested)
|
||||
@@ -57,7 +68,7 @@ public sealed class ServerDirectoryService(
|
||||
|
||||
connection.Reconnected += async _ =>
|
||||
{
|
||||
logger.LogInformation("Reconnected to directory — re-registering server");
|
||||
_logger.LogInformation("Reconnected to directory — re-registering server");
|
||||
_lastReportedUserCount = -1;
|
||||
await RegisterAsync(serverName, description, host);
|
||||
};
|
||||
@@ -65,9 +76,9 @@ public sealed class ServerDirectoryService(
|
||||
connection.Closed += ex =>
|
||||
{
|
||||
if (ex is not null)
|
||||
logger.LogWarning(ex, "Directory connection permanently closed — will rebuild");
|
||||
_logger.LogWarning(ex, "Directory connection permanently closed — will rebuild");
|
||||
else
|
||||
logger.LogWarning("Directory connection permanently closed — will rebuild");
|
||||
_logger.LogWarning("Directory connection permanently closed — will rebuild");
|
||||
|
||||
connectionPermanentlyClosed.TrySetResult();
|
||||
return Task.CompletedTask;
|
||||
@@ -82,7 +93,7 @@ public sealed class ServerDirectoryService(
|
||||
}
|
||||
|
||||
Console.Error.WriteLine("[DIAG] ServerDirectoryService: Connected successfully!");
|
||||
logger.LogInformation("Successfully connected to EchoHubSpace API at {Url}", DirectoryHubUrl);
|
||||
_logger.LogInformation("Successfully connected to EchoHubSpace API at {Url}", DirectoryHubUrl);
|
||||
await RegisterAsync(serverName, description, host);
|
||||
|
||||
// Poll user count until the connection is permanently closed or cancellation
|
||||
@@ -92,7 +103,7 @@ public sealed class ServerDirectoryService(
|
||||
return;
|
||||
|
||||
// Connection was permanently closed — wait briefly then rebuild
|
||||
logger.LogInformation("Rebuilding directory connection...");
|
||||
_logger.LogInformation("Rebuilding directory connection...");
|
||||
await Task.Delay(ReconnectBaseDelay, stoppingToken);
|
||||
}
|
||||
catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
|
||||
@@ -129,7 +140,7 @@ public sealed class ServerDirectoryService(
|
||||
{
|
||||
attempt++;
|
||||
var delay = GetBackoffDelay(attempt);
|
||||
logger.LogWarning(ex, "Failed to connect to directory — retrying in {Delay}s", delay.TotalSeconds);
|
||||
_logger.LogWarning(ex, "Failed to connect to directory — retrying in {Delay}s", delay.TotalSeconds);
|
||||
await Task.Delay(delay, ct);
|
||||
}
|
||||
}
|
||||
@@ -154,7 +165,7 @@ public sealed class ServerDirectoryService(
|
||||
if (connection.State != HubConnectionState.Connected)
|
||||
continue;
|
||||
|
||||
var currentCount = presenceTracker.GetOnlineUserCount();
|
||||
var currentCount = _presenceTracker.GetOnlineUserCount();
|
||||
if (currentCount == _lastReportedUserCount)
|
||||
continue;
|
||||
|
||||
@@ -162,11 +173,11 @@ public sealed class ServerDirectoryService(
|
||||
{
|
||||
await connection.InvokeAsync("UpdateUserCount", currentCount, ct);
|
||||
_lastReportedUserCount = currentCount;
|
||||
logger.LogDebug("Updated directory user count to {Count}", currentCount);
|
||||
_logger.LogDebug("Updated directory user count to {Count}", currentCount);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogWarning(ex, "Failed to update user count on directory");
|
||||
_logger.LogWarning(ex, "Failed to update user count on directory");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -184,15 +195,15 @@ public sealed class ServerDirectoryService(
|
||||
|
||||
try
|
||||
{
|
||||
var userCount = presenceTracker.GetOnlineUserCount();
|
||||
var userCount = _presenceTracker.GetOnlineUserCount();
|
||||
var dto = new RegisterServerDto(name, description, host, userCount);
|
||||
await _connection.InvokeAsync("RegisterServer", dto);
|
||||
_lastReportedUserCount = userCount;
|
||||
logger.LogInformation("Registered with directory as {Name} at {Host}", name, host);
|
||||
_logger.LogInformation("Registered with directory as {Name} at {Host}", name, host);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogWarning(ex, "Failed to register with directory");
|
||||
_logger.LogWarning(ex, "Failed to register with directory");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user