Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 45 additions & 24 deletions src/XtremeIdiots.Portal.Integrations.Forums/AdminActionTopics.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using System.Globalization;
using MX.InvisionCommunity.Api.Abstractions;
using XtremeIdiots.Portal.Integrations.Forums.Extensions;
Expand All @@ -11,39 +12,22 @@
/// </summary>
/// <param name="logger">Logger for tracking operations and errors</param>
/// <param name="forumsClient">Invision Community API client for forum operations</param>
public class AdminActionTopics(ILogger<AdminActionTopics> logger, IInvisionApiClient forumsClient) : IAdminActionTopics
public class AdminActionTopics(ILogger<AdminActionTopics> logger, IInvisionApiClient forumsClient, IConfiguration configuration) : IAdminActionTopics
{

/// <summary>
/// Creates a forum topic for a new admin action
/// </summary>
Comment thread
frasermolyneux marked this conversation as resolved.
/// <param name="type">Type of admin action (warning, ban, etc.)</param>
/// <param name="gameType">Game type to determine appropriate forum section</param>
/// <param name="playerId">Unique identifier of the player</param>
/// <param name="username">Player's username</param>
/// <param name="created">When the admin action was created</param>
/// <param name="text">Admin action description/reason</param>
/// <param name="adminId">ID of the admin who created the action</param>
/// <param name="cancellationToken">Cancellation token for the async operation</param>
/// <returns>Topic ID of the created forum topic, or 0 if creation failed</returns>
public async Task<int> CreateTopicForAdminAction(AdminActionType type, GameType gameType, Guid playerId, string username, DateTime created, string text, string? adminId, CancellationToken cancellationToken = default)
{
try
{
var userId = 21145;
var userId = int.Parse(configuration["XtremeIdiots:Forums:DefaultAdminUserId"] ?? "21145");
Comment thread
frasermolyneux marked this conversation as resolved.
Outdated

if (adminId is not null)
userId = Convert.ToInt32(adminId);

var forumId = type switch
{
AdminActionType.Observation => gameType.ForumIdForObservations(),
AdminActionType.Warning => gameType.ForumIdForWarnings(),
AdminActionType.Kick => gameType.ForumIdForKicks(),
AdminActionType.TempBan => gameType.ForumIdForTempBans(),
AdminActionType.Ban => gameType.ForumIdForBans(),
_ => 28
};
var forumId = ResolveForumId(type, gameType);

var postTopicResult = await forumsClient.Forums.PostTopic(forumId, userId, $"{username} - {type}", PostContent(type, playerId, username, created, text), type.ToString(), cancellationToken).ConfigureAwait(false);

Expand Down Expand Up @@ -83,19 +67,20 @@
if (topicId == 0)
return;

var userId = 21145;
var userId = int.Parse(configuration["XtremeIdiots:Forums:DefaultAdminUserId"] ?? "21145");
Comment thread
frasermolyneux marked this conversation as resolved.
Outdated

if (adminId is not null)
userId = Convert.ToInt32(adminId);

await forumsClient.Forums.UpdateTopic(topicId, userId, PostContent(type, playerId, username, created, text), cancellationToken).ConfigureAwait(false);
}

private static string PostContent(AdminActionType type, Guid playerId, string username, DateTime created, string text)
private string PostContent(AdminActionType type, Guid playerId, string username, DateTime created, string text)
{
var portalBaseUrl = (configuration["XtremeIdiots:PortalBaseUrl"] ?? "https://portal.xtremeidiots.com").TrimEnd('/');

Check warning on line 80 in src/XtremeIdiots.Portal.Integrations.Forums/AdminActionTopics.cs

View workflow job for this annotation

GitHub Actions / quality / Code Quality

Refactor your code not to use hardcoded absolute paths or URIs. (https://rules.sonarsource.com/csharp/RSPEC-1075)
return "<p>" +
$" Username: {username}<br>" +
$" Player Link: <a href=\"https://portal.xtremeidiots.com/Players/Details/{playerId}\">Portal</a><br>" +
$" Player Link: <a href=\"{portalBaseUrl}/Players/Details/{playerId}\">Portal</a><br>" +
$" {type} Created: {created.ToString(CultureInfo.InvariantCulture)}" +
"</p>" +
"<p>" +
Expand All @@ -105,4 +90,40 @@
" <small>Do not edit this post directly as it will be overwritten by the Portal. Add comments on posts below or edit the record in the Portal.</small>" +
"</p>";
}

private int ResolveForumId(AdminActionType type, GameType gameType)
{
var defaultForumId = int.Parse(configuration["XtremeIdiots:Forums:DefaultForumId"] ?? "28");
Comment thread
frasermolyneux marked this conversation as resolved.
Outdated

var category = type switch
{
AdminActionType.Observation or AdminActionType.Warning or AdminActionType.Kick => "AdminLogs",
AdminActionType.TempBan or AdminActionType.Ban => "Bans",
_ => null
};

if (category is null)
return defaultForumId;

var gameKey = gameType switch

Check warning on line 108 in src/XtremeIdiots.Portal.Integrations.Forums/AdminActionTopics.cs

View workflow job for this annotation

GitHub Actions / build-and-test

Populate switch (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0072)

Check warning on line 108 in src/XtremeIdiots.Portal.Integrations.Forums/AdminActionTopics.cs

View workflow job for this annotation

GitHub Actions / build-and-test

Populate switch (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0072)

Check warning on line 108 in src/XtremeIdiots.Portal.Integrations.Forums/AdminActionTopics.cs

View workflow job for this annotation

GitHub Actions / build-and-test

Populate switch (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0072)

Check warning on line 108 in src/XtremeIdiots.Portal.Integrations.Forums/AdminActionTopics.cs

View workflow job for this annotation

GitHub Actions / build-and-test

Populate switch (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0072)

Check warning on line 108 in src/XtremeIdiots.Portal.Integrations.Forums/AdminActionTopics.cs

View workflow job for this annotation

GitHub Actions / build-and-test

Populate switch (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0072)

Check warning on line 108 in src/XtremeIdiots.Portal.Integrations.Forums/AdminActionTopics.cs

View workflow job for this annotation

GitHub Actions / build-and-test

Populate switch (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0072)

Check warning on line 108 in src/XtremeIdiots.Portal.Integrations.Forums/AdminActionTopics.cs

View workflow job for this annotation

GitHub Actions / quality / Code Quality

Populate switch (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0072)
{
GameType.Arma or GameType.Arma2 or GameType.Arma3 => "Arma",
_ => gameType.ToString()
};

var configValue = configuration[$"XtremeIdiots:Forums:{category}:{gameKey}"];
if (configValue is not null && int.TryParse(configValue, out var forumId))
return forumId;

// Fallback to hardcoded values from GameTypeExtensions
return type switch
{
AdminActionType.Observation => gameType.ForumIdForObservations(),
AdminActionType.Warning => gameType.ForumIdForWarnings(),
AdminActionType.Kick => gameType.ForumIdForKicks(),
AdminActionType.TempBan => gameType.ForumIdForTempBans(),
AdminActionType.Ban => gameType.ForumIdForBans(),
_ => defaultForumId
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
IConfiguration configuration) : BaseApiController(telemetryClient, logger, configuration)
{
private const string GameServersListCacheKey = nameof(GameServersListCacheKey);
private readonly string gameTrackerBannerBaseUrl = (configuration["GameTracker:BannerBaseUrl"] ?? "https://cache.gametracker.com/server_info/").TrimEnd('/') + "/";

/// <summary>
/// Gets HTML banners for game servers that are enabled for banner display
Expand Down Expand Up @@ -168,7 +169,7 @@
{ "BannerUrl", bannerData.BannerUrl ?? "null" }
});

return Redirect(bannerData.BannerUrl ?? $"https://cache.gametracker.com/server_info/{ipAddress}:{queryPort}/{imageName}");
return Redirect(bannerData.BannerUrl ?? $"{gameTrackerBannerBaseUrl}{ipAddress}:{queryPort}/{imageName}");
Comment thread Dismissed
}

Logger.LogWarning("Failed to retrieve GameTracker banner data for {IpAddress}:{QueryPort}/{ImageName}, falling back to default GameTracker URL",
Expand All @@ -184,7 +185,7 @@
{ "Fallback", "true" }
});

return Redirect($"https://cache.gametracker.com/server_info/{ipAddress}:{queryPort}/{imageName}");
return Redirect($"{gameTrackerBannerBaseUrl}{ipAddress}:{queryPort}/{imageName}");
Comment thread Dismissed
}, nameof(GetGameTrackerBanner), $"ipAddress: {ipAddress}, queryPort: {queryPort}, imageName: {imageName}").ConfigureAwait(false);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
ILogger<ExternalController> logger,
IConfiguration configuration) : BaseApiController(telemetryClient, logger, configuration)
{
private readonly string portalBaseUrl = (configuration["XtremeIdiots:PortalBaseUrl"] ?? "https://portal.xtremeidiots.com").TrimEnd('/');

Check warning on line 27 in src/XtremeIdiots.Portal.Web/ApiControllers/ExternalController.cs

View workflow job for this annotation

GitHub Actions / quality / Code Quality

Refactor your code not to use hardcoded absolute paths or URIs. (https://rules.sonarsource.com/csharp/RSPEC-1075)

/// <summary>
/// Retrieves the latest admin actions for display in external forum widgets
Expand Down Expand Up @@ -60,13 +61,13 @@

results.Add(new
{
GameIconUrl = $"https://portal.xtremeidiots.com/images/game-icons/{adminActionDto.Player?.GameType.ToString()}.png",
GameIconUrl = $"{portalBaseUrl}/images/game-icons/{adminActionDto.Player?.GameType.ToString()}.png",
AdminName = adminName,
AdminId = adminId,
ActionType = adminActionDto.Type.ToString(),
ActionText = actionText,
PlayerName = adminActionDto.Player?.Username,
PlayerLink = $"https://portal.xtremeidiots.com/Players/Details/{adminActionDto.PlayerId}"
PlayerLink = $"{portalBaseUrl}/Players/Details/{adminActionDto.PlayerId}"
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ public HealthCheckController(
ArgumentNullException.ThrowIfNull(forumsClient);
this.forumsClient = forumsClient;

var expectedCommunityUrl = configuration["XtremeIdiots:Forums:BaseUrl"] ?? "https://www.xtremeidiots.com";
var expectedCommunityUrlWithTrailingSlash = expectedCommunityUrl.TrimEnd('/') + "/";

healthCheckComponents.Add(new HealthCheckComponent
{
Name = "forums-api",
Expand All @@ -41,7 +44,7 @@ public HealthCheckController(
try
{
var response = await this.forumsClient.Core.GetCoreHello().ConfigureAwait(false);
var checkResponse = response?.Result?.Data?.CommunityUrl == "https://www.xtremeidiots.com/";
var checkResponse = response?.Result?.Data?.CommunityUrl == expectedCommunityUrlWithTrailingSlash;
return new Tuple<bool, string>(checkResponse, checkResponse ? "OK" : "Unexpected or missing CommunityUrl in forums API response");
}
catch (Exception ex)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,10 @@ private static void ConfigureAuthentication(IServiceCollection services, IConfig

options.CallbackPath = new PathString("/signin-xtremeidiots");

options.AuthorizationEndpoint = configuration["xtremeidiots_auth_authorization_endpoint"] ?? "https://www.xtremeidiots.com/oauth/authorize/";
options.TokenEndpoint = configuration["xtremeidiots_auth_token_endpoint"] ?? "https://www.xtremeidiots.com/oauth/token/";
options.UserInformationEndpoint = configuration["xtremeidiots_auth_userinfo_endpoint"] ?? "https://www.xtremeidiots.com/api/core/me";
var forumSiteUrl = (configuration["XtremeIdiots:Forums:BaseUrl"] ?? "https://www.xtremeidiots.com").TrimEnd('/');
options.AuthorizationEndpoint = configuration["xtremeidiots_auth_authorization_endpoint"] ?? $"{forumSiteUrl}/oauth/authorize/";
options.TokenEndpoint = configuration["xtremeidiots_auth_token_endpoint"] ?? $"{forumSiteUrl}/oauth/token/";
options.UserInformationEndpoint = configuration["xtremeidiots_auth_userinfo_endpoint"] ?? $"{forumSiteUrl}/api/core/me";

options.ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "id");
options.ClaimActions.MapJsonKey(ClaimTypes.Name, "name");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@
ILogger<AdminActionsController> logger,
IConfiguration configuration) : BaseController(telemetryClient, logger, configuration)
{
private const string DefaultForumBaseUrl = "https://www.xtremeidiots.com/forums/topic/";
private const string DefaultFallbackAdminId = "21145";
private const int DefaultTempBanDurationDays = 7;
private readonly string forumBaseUrl = (configuration["XtremeIdiots:Forums:TopicBaseUrl"] ?? "https://www.xtremeidiots.com/forums/topic/").TrimEnd('/') + "/";
private readonly string fallbackAdminId = configuration["XtremeIdiots:Forums:DefaultAdminUserId"] ?? "21145";
private readonly int tempBanDurationDays = int.TryParse(configuration["XtremeIdiots:Forums:DefaultTempBanDays"], out var days) ? days : 7;

/// <summary>
/// Displays the create admin action form for a specific player
Expand Down Expand Up @@ -63,7 +63,7 @@
Type = adminActionType,
PlayerId = playerData.PlayerId,
PlayerDto = playerData,
Expires = adminActionType == AdminActionType.TempBan ? DateTime.UtcNow.AddDays(DefaultTempBanDurationDays) : null
Expires = adminActionType == AdminActionType.TempBan ? DateTime.UtcNow.AddDays(tempBanDurationDays) : null
};
Comment thread
frasermolyneux marked this conversation as resolved.

return View(createAdminActionViewModel);
Expand Down Expand Up @@ -366,7 +366,7 @@
{
Logger.LogWarning("Player {PlayerId} not found when enriching admin action {AdminActionId} for claim operation", adminActionData.PlayerId, id);
return NotFound();
}

Check warning on line 369 in src/XtremeIdiots.Portal.Web/Controllers/AdminActionsController.cs

View workflow job for this annotation

GitHub Actions / quality / Code Quality

Blank line required between block and subsequent statement (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide2003)
playerData = playerResult.Result.Data;
}

Expand Down Expand Up @@ -607,12 +607,12 @@

private string GetForumBaseUrl()
{
return GetConfigurationValue("AdminActions:ForumBaseUrl", DefaultForumBaseUrl);
return forumBaseUrl;
}

private string GetFallbackAdminId()
{
return GetConfigurationValue("AdminActions:FallbackAdminId", DefaultFallbackAdminId);
return fallbackAdminId;
}

private async Task<PlayerDto?> GetPlayerDataAsync(Guid playerId, CancellationToken cancellationToken = default)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@
ILogger<ServerAdminController> logger,
IConfiguration configuration) : BaseController(telemetryClient, logger, configuration)
{
private const string DefaultForumBaseUrl = "https://www.xtremeidiots.com/forums/topic/";
private const string DefaultFallbackAdminId = "21145";
private const int DefaultTempBanDurationDays = 7;
private readonly string forumBaseUrl = (configuration["XtremeIdiots:Forums:TopicBaseUrl"] ?? "https://www.xtremeidiots.com/forums/topic/").TrimEnd('/') + "/";
private readonly string fallbackAdminId = configuration["XtremeIdiots:Forums:DefaultAdminUserId"] ?? "21145";
private readonly int tempBanDurationDays = int.TryParse(configuration["XtremeIdiots:Forums:DefaultTempBanDays"], out var days) ? days : 7;

Comment thread
frasermolyneux marked this conversation as resolved.
Outdated
/// <summary>
/// Displays the main server administration dashboard with available game servers
Expand Down Expand Up @@ -302,7 +302,7 @@
Logger.LogWarning(ex, "Failed to extract map name from server status for {ServerId}", id);
}

if (string.IsNullOrWhiteSpace(currentMapName)) currentMapName = "Unknown";

Check warning on line 305 in src/XtremeIdiots.Portal.Web/Controllers/ServerAdminController.cs

View workflow job for this annotation

GitHub Actions / quality / Code Quality

Embedded statements must be on their own line (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide2001)

if (!string.IsNullOrWhiteSpace(currentMapName) &&
currentMapName != "Unknown")
Expand Down Expand Up @@ -856,7 +856,7 @@
// Create admin action record with expiry if we have a GUID
if (!string.IsNullOrWhiteSpace(playerGuid))
{
var expiryDate = DateTime.UtcNow.AddDays(DefaultTempBanDurationDays);
var expiryDate = DateTime.UtcNow.AddDays(tempBanDurationDays);
await CreateAdminActionForRconOperationAsync(
gameServerData.GameType, playerGuid, playerName, AdminActionType.TempBan,
$"Player temp banned from {gameServerData.Title} via RCON by {User.Username()}. Please update with proper reason.",
Expand All @@ -871,7 +871,7 @@
{ "GameType", gameServerData.GameType.ToString() }
});

return Json(new { success = true, message = $"Player {playerName} has been temp banned for {DefaultTempBanDurationDays} days" });
return Json(new { success = true, message = $"Player {playerName} has been temp banned for {tempBanDurationDays} days" });
}
catch (Exception ex)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
namespace XtremeIdiots.Portal.Web.Helpers;

[HtmlTargetElement("game-type-icon")]
public class GameTypeIconTagHelper : TagHelper
public class GameTypeIconTagHelper(IConfiguration configuration) : TagHelper
{
[HtmlAttributeName("game")] public GameType Game { get; set; }
[HtmlAttributeName("external")] public bool External { get; set; }
Expand All @@ -14,7 +14,7 @@ public class GameTypeIconTagHelper : TagHelper
public override void Process(TagHelperContext context, TagHelperOutput output)
{
output.TagName = "img";
var baseUrl = External ? "https://portal.xtremeidiots.com" : string.Empty;
var baseUrl = External ? (configuration["XtremeIdiots:PortalBaseUrl"] ?? "https://portal.xtremeidiots.com").TrimEnd('/') : string.Empty;
output.Attributes.SetAttribute("src", $"{baseUrl}/images/game-icons/{Game}.png");
output.Attributes.SetAttribute("alt", Game.ToString());
output.Attributes.SetAttribute("width", Size.ToString());
Expand Down
5 changes: 3 additions & 2 deletions src/XtremeIdiots.Portal.Web/Helpers/ServerTagHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public override void Process(TagHelperContext context, TagHelperOutput output)
}

[HtmlTargetElement("server-link")]
public class ServerLinkTagHelper : TagHelper
public class ServerLinkTagHelper(IConfiguration configuration) : TagHelper
{
[HtmlAttributeName("type")] public string Type { get; set; } = string.Empty; // gametracker|hlsw|steam
[HtmlAttributeName("game")] public string? Game { get; set; }
Expand All @@ -60,7 +60,8 @@ public override void Process(TagHelperContext context, TagHelperOutput output)
switch (Type.ToLowerInvariant())
{
case "gametracker":
output.Attributes.SetAttribute("href", $"https://www.gametracker.com/server_info/{Host}:{Port}");
var gameTrackerBaseUrl = (configuration["GameTracker:ServerInfoBaseUrl"] ?? "https://www.gametracker.com/server_info/").TrimEnd('/') + "/";
output.Attributes.SetAttribute("href", $"{gameTrackerBaseUrl}{Host}:{Port}");
output.Attributes.SetAttribute("target", "_blank");
output.Content.SetHtmlContent("<img src=\"/images/service-icons/gametracker.png\" alt=\"gametracker\"/>");
break;
Expand Down
Loading
Loading