diff --git a/.github/workflows/docker-deploy.yaml b/.github/workflows/docker-deploy.yaml index dfcf5d9..3f8f7e4 100644 --- a/.github/workflows/docker-deploy.yaml +++ b/.github/workflows/docker-deploy.yaml @@ -26,8 +26,9 @@ jobs: run: | cd GuildWarsPartySearch $content = Get-Content Config.json - $updatedContent = $content.Replace("[API_KEY_PLACEHOLDER]", "${{ secrets.APIKEY }}") - Set-Content -Path Config.json -Value $updatedContent + $content = $content.Replace("[APIKEY]", "${{ secrets.APIKEY }}") + $content = $content.Replace("[AZURE_TABLESTORAGE_CONNECTIONSTRING]", "${{ secrets.AZURE_TABLESTORAGE_CONNECTIONSTRING }}") + Set-Content -Path Config.json -Value $content Write-Host "Placeholder replaced successfully in Config.json" - name: Build and push Docker image diff --git a/GuildWarsPartySearch/Config.json b/GuildWarsPartySearch/Config.json index 7ac09ab..09e6242 100644 --- a/GuildWarsPartySearch/Config.json +++ b/GuildWarsPartySearch/Config.json @@ -1,7 +1,11 @@ { "ServerOptions": { - "ApiKey": "[API_KEY_PLACEHOLDER]", + "ApiKey": "[APIKEY]", "InactivityTimeout": "0:0:5", "HeartbeatFrequency": "0:0:1" + }, + "TableStorageOptions": { + "TableName": "searches", + "ConnectionString": "[AZURE_TABLESTORAGE_CONNECTIONSTRING]" } } \ No newline at end of file diff --git a/GuildWarsPartySearch/Endpoints/LiveFeed.cs b/GuildWarsPartySearch/Endpoints/LiveFeed.cs index e2edca6..6695ec9 100644 --- a/GuildWarsPartySearch/Endpoints/LiveFeed.cs +++ b/GuildWarsPartySearch/Endpoints/LiveFeed.cs @@ -30,25 +30,39 @@ public LiveFeed( public override void ConnectionClosed() { var scopedLogger = this.logger.CreateScopedLogger(nameof(this.ConnectionInitialized), this.ClientData.Socket.RemoteEndPoint?.ToString() ?? string.Empty); - scopedLogger.LogInformation("Client disconnected"); - this.liveFeedService.RemoveClient(this.ClientData); + try + { + scopedLogger.LogInformation("Client disconnected"); + this.liveFeedService.RemoveClient(this.ClientData); + } + catch(Exception e) + { + scopedLogger.LogError(e, "Encountered exception"); + } } public override async void ConnectionInitialized() { var scopedLogger = this.logger.CreateScopedLogger(nameof(this.ConnectionInitialized), this.ClientData.Socket.RemoteEndPoint?.ToString() ?? string.Empty); - scopedLogger.LogInformation("Client connected"); - this.liveFeedService.AddClient(this.ClientData); - scopedLogger.LogInformation("Sending all party searches"); - var updates = await this.partySearchService.GetAllPartySearches(); - var serialized = JsonConvert.SerializeObject(updates); - var payload = Encoding.UTF8.GetBytes(serialized); - this.SendMessage(new WebsocketMessage + try + { + scopedLogger.LogInformation("Client connected"); + this.liveFeedService.AddClient(this.ClientData); + scopedLogger.LogInformation("Sending all party searches"); + var updates = await this.partySearchService.GetAllPartySearches(this.ClientData.CancellationToken); + var serialized = JsonConvert.SerializeObject(updates); + var payload = Encoding.UTF8.GetBytes(serialized); + this.SendMessage(new WebsocketMessage + { + Data = payload, + FIN = true, + Opcode = WebsocketMessage.Opcodes.Text + }); + } + catch(Exception e) { - Data = payload, - FIN = true, - Opcode = WebsocketMessage.Opcodes.Text - }); + scopedLogger.LogError(e, "Encountered exception"); + } } public override void HandleReceivedMessage(None message) diff --git a/GuildWarsPartySearch/Endpoints/PostPartySearch.cs b/GuildWarsPartySearch/Endpoints/PostPartySearch.cs index 23b82e6..48a812e 100644 --- a/GuildWarsPartySearch/Endpoints/PostPartySearch.cs +++ b/GuildWarsPartySearch/Endpoints/PostPartySearch.cs @@ -1,4 +1,5 @@ -using GuildWarsPartySearch.Server.Models.Endpoints; +using GuildWarsPartySearch.Server.Models; +using GuildWarsPartySearch.Server.Models.Endpoints; using GuildWarsPartySearch.Server.Services.Feed; using GuildWarsPartySearch.Server.Services.PartySearch; using Microsoft.Extensions.Logging; @@ -38,38 +39,47 @@ public override void ConnectionInitialized() public override async void HandleReceivedMessage(PostPartySearchRequest message) { - var result = await this.partySearchService.PostPartySearch(message); - var response = result.Switch( - onSuccess: _ => - { - this.liveFeedService.PushUpdate(this.Server, new PartySearchUpdate + var scopedLogger = this.logger.CreateScopedLogger(nameof(this.HandleReceivedMessage), string.Empty); + try + { + var result = await this.partySearchService.PostPartySearch(message, this.ClientData.CancellationToken); + var response = result.Switch( + onSuccess: _ => { - Campaign = message.Campaign, - Continent = message.Continent, - District = message.District, - Map = message.Map, - PartySearchEntries = message.PartySearchEntries, - Region = message.Region + this.liveFeedService.PushUpdate(this.Server, new PartySearch + { + Campaign = message.Campaign, + Continent = message.Continent, + District = message.District, + Map = message.Map, + PartySearchEntries = message.PartySearchEntries, + Region = message.Region + }); + return Success; + }, + onFailure: failure => failure switch + { + PostPartySearchFailure.InvalidPayload => InvalidPayload, + PostPartySearchFailure.InvalidCampaign => InvalidCampaign, + PostPartySearchFailure.InvalidContinent => InvalidContinent, + PostPartySearchFailure.InvalidRegion => InvalidRegion, + PostPartySearchFailure.InvalidMap => InvalidMap, + PostPartySearchFailure.InvalidDistrict => InvalidDistrict, + PostPartySearchFailure.InvalidEntries => InvalidEntries, + PostPartySearchFailure.InvalidPartySize => InvalidPartySize, + PostPartySearchFailure.InvalidPartyMaxSize => InvalidPartyMaxSize, + PostPartySearchFailure.InvalidNpcs => InvalidNpcs, + PostPartySearchFailure.InvalidCharName => InvalidCharName, + PostPartySearchFailure.UnspecifiedFailure => UnspecifiedFailure, + _ => UnspecifiedFailure }); - return Success; - }, - onFailure: failure => failure switch - { - PostPartySearchFailure.InvalidPayload => InvalidPayload, - PostPartySearchFailure.InvalidCampaign => InvalidCampaign, - PostPartySearchFailure.InvalidContinent => InvalidContinent, - PostPartySearchFailure.InvalidRegion => InvalidRegion, - PostPartySearchFailure.InvalidMap => InvalidMap, - PostPartySearchFailure.InvalidDistrict => InvalidDistrict, - PostPartySearchFailure.InvalidEntries => InvalidEntries, - PostPartySearchFailure.InvalidPartySize => InvalidPartySize, - PostPartySearchFailure.InvalidPartyMaxSize => InvalidPartyMaxSize, - PostPartySearchFailure.InvalidNpcs => InvalidNpcs, - PostPartySearchFailure.UnspecifiedFailure => UnspecifiedFailure, - _ => UnspecifiedFailure - }); - - this.SendMessage(response); + + this.SendMessage(response); + } + catch(Exception e) + { + scopedLogger.LogError(e, "Encountered exception"); + } } public override void Tick() @@ -142,6 +152,12 @@ public override void Tick() Description = "Invalid npcs" }; + private static PostPartySearchResponse InvalidCharName => new() + { + Result = 0, + Description = "Invalid char name" + }; + private static PostPartySearchResponse UnspecifiedFailure => new() { Result = 0, diff --git a/GuildWarsPartySearch/GuildWarsPartySearch.Server.csproj b/GuildWarsPartySearch/GuildWarsPartySearch.Server.csproj index 0cfac37..2f6716b 100644 --- a/GuildWarsPartySearch/GuildWarsPartySearch.Server.csproj +++ b/GuildWarsPartySearch/GuildWarsPartySearch.Server.csproj @@ -9,9 +9,12 @@ + + + diff --git a/GuildWarsPartySearch/Launch/ServerConfiguration.cs b/GuildWarsPartySearch/Launch/ServerConfiguration.cs index b2ab406..2cc078b 100644 --- a/GuildWarsPartySearch/Launch/ServerConfiguration.cs +++ b/GuildWarsPartySearch/Launch/ServerConfiguration.cs @@ -7,6 +7,7 @@ using GuildWarsPartySearch.Server.Services.Options; using GuildWarsPartySearch.Server.Services.PartySearch; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using MTSC.Common.Http; using MTSC.ServerSide; @@ -15,7 +16,6 @@ using System.Core.Extensions; using System.Extensions; using System.Logging; -using System.Runtime.CompilerServices; using static MTSC.Common.Http.HttpMessage; namespace GuildWarsPartySearch.Server.Launch; @@ -30,9 +30,17 @@ public static IServiceManager SetupServiceManager(this IServiceManager serviceMa serviceManager.RegisterResolver(new LoggerResolver()); serviceManager.RegisterOptionsResolver(); - serviceManager.RegisterLogWriter(); + serviceManager.RegisterSingleton(); + serviceManager.RegisterScoped(sp => + { + var loggerFactory = LoggerFactory.Create(builder => + { + builder.AddFilter("Azure", LogLevel.Information); + builder.AddProvider(new CVLoggerProvider(sp.GetService())); + }); + return loggerFactory; + }); serviceManager.RegisterOptionsManager(); - return serviceManager; } @@ -41,7 +49,7 @@ public static IServiceCollection SetupServices(this IServiceCollection services) services.ThrowIfNull(); services.AddScoped(); - services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddSingleton(); return services; diff --git a/GuildWarsPartySearch/Models/Database/PartySearch.cs b/GuildWarsPartySearch/Models/Database/PartySearch.cs deleted file mode 100644 index 7290718..0000000 --- a/GuildWarsPartySearch/Models/Database/PartySearch.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace GuildWarsPartySearch.Server.Models.Database; - -public sealed class PartySearch -{ - public int? PartySize { get; set; } - - public int? PartyMaxSize { get; set; } - - public int? Npcs { get; set; } -} diff --git a/GuildWarsPartySearch/Models/Endpoints/PostPartySearchFailure.cs b/GuildWarsPartySearch/Models/Endpoints/PostPartySearchFailure.cs index 9215b9f..7d6fa30 100644 --- a/GuildWarsPartySearch/Models/Endpoints/PostPartySearchFailure.cs +++ b/GuildWarsPartySearch/Models/Endpoints/PostPartySearchFailure.cs @@ -44,6 +44,10 @@ public sealed class InvalidNpcs : PostPartySearchFailure { } + public sealed class InvalidCharName : PostPartySearchFailure + { + } + public sealed class UnspecifiedFailure : PostPartySearchFailure { } diff --git a/GuildWarsPartySearch/Models/Endpoints/PartySearchUpdate.cs b/GuildWarsPartySearch/Models/PartySearch.cs similarity index 86% rename from GuildWarsPartySearch/Models/Endpoints/PartySearchUpdate.cs rename to GuildWarsPartySearch/Models/PartySearch.cs index 91afa3c..ab250df 100644 --- a/GuildWarsPartySearch/Models/Endpoints/PartySearchUpdate.cs +++ b/GuildWarsPartySearch/Models/PartySearch.cs @@ -1,9 +1,9 @@ using GuildWarsPartySearch.Common.Models.GuildWars; using Newtonsoft.Json; -namespace GuildWarsPartySearch.Server.Models.Endpoints; +namespace GuildWarsPartySearch.Server.Models; -public sealed class PartySearchUpdate +public sealed class PartySearch { [JsonProperty(nameof(Campaign))] public Campaign? Campaign { get; set; } diff --git a/GuildWarsPartySearch/Models/Endpoints/PartySearchEntry.cs b/GuildWarsPartySearch/Models/PartySearchEntry.cs similarity index 70% rename from GuildWarsPartySearch/Models/Endpoints/PartySearchEntry.cs rename to GuildWarsPartySearch/Models/PartySearchEntry.cs index 45f4bd9..8d5920b 100644 --- a/GuildWarsPartySearch/Models/Endpoints/PartySearchEntry.cs +++ b/GuildWarsPartySearch/Models/PartySearchEntry.cs @@ -1,9 +1,12 @@ using Newtonsoft.Json; -namespace GuildWarsPartySearch.Server.Models.Endpoints; +namespace GuildWarsPartySearch.Server.Models; public sealed class PartySearchEntry { + [JsonProperty(nameof(CharName))] + public string? CharName { get; set; } + [JsonProperty(nameof(PartySize))] public int? PartySize { get; set; } diff --git a/GuildWarsPartySearch/Options/TableStorageOptions.cs b/GuildWarsPartySearch/Options/TableStorageOptions.cs new file mode 100644 index 0000000..f58205f --- /dev/null +++ b/GuildWarsPartySearch/Options/TableStorageOptions.cs @@ -0,0 +1,12 @@ +using Newtonsoft.Json; + +namespace GuildWarsPartySearch.Server.Options; + +public sealed class TableStorageOptions +{ + [JsonProperty(nameof(TableName))] + public string? TableName { get; set; } + + [JsonProperty(nameof(ConnectionString))] + public string? ConnectionString { get; set; } +} diff --git a/GuildWarsPartySearch/Services/Database/IPartySearchDatabase.cs b/GuildWarsPartySearch/Services/Database/IPartySearchDatabase.cs index ea0de87..b293b4e 100644 --- a/GuildWarsPartySearch/Services/Database/IPartySearchDatabase.cs +++ b/GuildWarsPartySearch/Services/Database/IPartySearchDatabase.cs @@ -1,11 +1,11 @@ using GuildWarsPartySearch.Common.Models.GuildWars; -using GuildWarsPartySearch.Server.Models.Endpoints; +using GuildWarsPartySearch.Server.Models; namespace GuildWarsPartySearch.Server.Services.Database; public interface IPartySearchDatabase { - Task> GetAllPartySearches(); - Task SetPartySearches(Campaign campaign, Continent continent, Region region, Map map, string district, List partySearch); - Task?> GetPartySearches(Campaign campaign, Continent continent, Region region, Map map, string district); + Task> GetAllPartySearches(CancellationToken cancellationToken); + Task SetPartySearches(Campaign campaign, Continent continent, Region region, Map map, string district, List partySearch, CancellationToken cancellationToken); + Task?> GetPartySearches(Campaign campaign, Continent continent, Region region, Map map, string district, CancellationToken cancellationToken); } diff --git a/GuildWarsPartySearch/Services/Database/InMemoryPartySearchDatabase.cs b/GuildWarsPartySearch/Services/Database/InMemoryPartySearchDatabase.cs deleted file mode 100644 index ce857d4..0000000 --- a/GuildWarsPartySearch/Services/Database/InMemoryPartySearchDatabase.cs +++ /dev/null @@ -1,69 +0,0 @@ -using GuildWarsPartySearch.Common.Models.GuildWars; -using GuildWarsPartySearch.Server.Models.Endpoints; -using Microsoft.Extensions.Logging; -using System.Collections.Concurrent; -using System.Core.Extensions; -using System.Extensions; - -namespace GuildWarsPartySearch.Server.Services.Database; - -public sealed class InMemoryPartySearchDatabase : IPartySearchDatabase -{ - private static readonly ConcurrentDictionary<(Campaign Campaign, Continent Continent, Region Region, Map Map, string District), List> PartySearchCache = new(); - - private readonly ILogger logger; - - public InMemoryPartySearchDatabase( - ILogger logger) - { - this.logger = logger.ThrowIfNull(); - } - - public Task SetPartySearches(Campaign campaign, Continent continent, Region region, Map map, string district, List partySearch) - { - var scopedLogger = this.logger.CreateScopedLogger(nameof(SetPartySearches), string.Empty); - var key = BuildKey(campaign, continent, region, map, district); - PartySearchCache[key] = partySearch; - scopedLogger.LogInformation($"Set cache for {key}"); - return Task.FromResult(true); - } - - public Task?> GetPartySearches(Campaign campaign, Continent continent, Region region, Map map, string district) - { - district = district.Replace("%20", " "); - var scopedLogger = this.logger.CreateScopedLogger(nameof(GetPartySearches), string.Empty); - var key = BuildKey(campaign, continent, region, map, district); - if (!PartySearchCache.TryGetValue(key, out var partySearch)) - { - return Task.FromResult?>(default); - } - - return Task.FromResult?>(partySearch); - } - - public Task> GetAllPartySearches() - { - return Task.FromResult(PartySearchCache.Select(t => - { - return new PartySearchUpdate - { - Campaign = t.Key.Campaign, - Continent = t.Key.Continent, - Region = t.Key.Region, - Map = t.Key.Map, - District = t.Key.District, - PartySearchEntries = t.Value.Select(e => new PartySearchEntry - { - Npcs = e.Npcs, - PartyMaxSize = e.PartyMaxSize, - PartySize = e.PartySize, - }).ToList() - }; - }).ToList()); - } - - private static (Campaign Campaign, Continent Continent, Region Region, Map Map, string District) BuildKey(Campaign campaign, Continent continent, Region region, Map map, string district) - { - return (campaign, continent, region, map, district); - } -} diff --git a/GuildWarsPartySearch/Services/Database/Models/PartySearchTableEntity.cs b/GuildWarsPartySearch/Services/Database/Models/PartySearchTableEntity.cs new file mode 100644 index 0000000..4b250da --- /dev/null +++ b/GuildWarsPartySearch/Services/Database/Models/PartySearchTableEntity.cs @@ -0,0 +1,33 @@ +using Azure; +using Azure.Data.Tables; + +namespace GuildWarsPartySearch.Server.Services.Database.Models; + +public sealed class PartySearchTableEntity : ITableEntity +{ + public string PartitionKey { get; set; } = default!; + + public string RowKey { get; set; } = default!; + + public string? Campaign { get; set; } + + public string? Continent { get; set; } + + public string? Region { get; set; } + + public string? Map { get; set; } + + public string? District { get; set; } + + public string? CharName { get; set; } + + public int? PartySize { get; set; } + + public int? PartyMaxSize { get; set; } + + public int? Npcs { get; set; } + + public DateTimeOffset? Timestamp { get; set; } + + public ETag ETag { get; set; } +} diff --git a/GuildWarsPartySearch/Services/Database/TableStorageDatabase.cs b/GuildWarsPartySearch/Services/Database/TableStorageDatabase.cs new file mode 100644 index 0000000..1793a01 --- /dev/null +++ b/GuildWarsPartySearch/Services/Database/TableStorageDatabase.cs @@ -0,0 +1,163 @@ +using Azure.Core.Diagnostics; +using Azure.Data.Tables; +using GuildWarsPartySearch.Common.Models.GuildWars; +using GuildWarsPartySearch.Server.Models; +using GuildWarsPartySearch.Server.Options; +using GuildWarsPartySearch.Server.Services.Database.Models; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using System.Collections.Concurrent; +using System.Core.Extensions; +using System.Extensions; +using System.Threading; + +namespace GuildWarsPartySearch.Server.Services.Database; + +public sealed class TableStorageDatabase : IPartySearchDatabase +{ + private readonly TableClient tableClient; + private readonly IOptions options; + private readonly ILogger logger; + + public TableStorageDatabase( + IOptions options, + ILogger logger) + { + var tableClientOptions = new TableClientOptions(); + tableClientOptions.Diagnostics.IsLoggingEnabled = true; + + this.options = options.ThrowIfNull(); + this.logger = logger.ThrowIfNull(); + + var tableServiceClient = new TableServiceClient(this.options?.Value.ConnectionString ?? throw new InvalidOperationException("Config contains no connection string")); + var tableName = options.Value.TableName?.ThrowIfNull() ?? throw new InvalidOperationException("Config contains no table name"); + this.tableClient = tableServiceClient.GetTableClient(tableName); + } + + public async Task> GetAllPartySearches(CancellationToken cancellationToken) + { + var scopedLogger = this.logger.CreateScopedLogger(nameof(this.GetAllPartySearches), string.Empty); + try + { + var response = await this.QuerySearches(string.Empty, cancellationToken); + return response; + } + catch (Exception e) + { + scopedLogger.LogError(e, "Encountered exception"); + return new List(); + } + } + + public async Task?> GetPartySearches(Campaign campaign, Continent continent, Region region, Map map, string district, CancellationToken cancellationToken) + { + var partitionKey = BuildPartitionKey(campaign, continent, region, map, district); + var scopedLogger = this.logger.CreateScopedLogger(nameof(this.GetPartySearches), partitionKey); + try + { + var response = await this.QuerySearches($"PartitionKey eq '{partitionKey}'", cancellationToken); + var partition = response.FirstOrDefault(); + if (partition is null) + { + return default; + } + + return partition.PartySearchEntries; + } + catch(Exception e) + { + scopedLogger.LogError(e, "Encountered exception"); + } + + return default; + } + + public async Task SetPartySearches(Campaign campaign, Continent continent, Region region, Map map, string district, List partySearch, CancellationToken cancellationToken) + { + var partitionKey = BuildPartitionKey(campaign, continent, region, map, district); + var scopedLogger = this.logger.CreateScopedLogger(nameof(this.SetPartySearches), partitionKey); + try + { + var entries = partySearch.Select(e => + { + var rowKey = BuildRowKey(e.CharName!, e.PartySize!.Value, e.PartyMaxSize!.Value, e.Npcs!.Value); + return new PartySearchTableEntity + { + PartitionKey = partitionKey, + RowKey = rowKey, + Campaign = campaign.Name, + Continent = continent.Name, + Region = region.Name, + Map = map.Name, + District = district, + CharName = e.CharName, + PartySize = e.PartySize, + PartyMaxSize = e.PartyMaxSize, + Npcs = e.Npcs, + Timestamp = DateTimeOffset.UtcNow + }; + }); + + scopedLogger.LogInformation("Batch transaction"); + var transactions = entries.Select(e => new TableTransactionAction(TableTransactionActionType.UpsertReplace, e)); + var responses = await this.tableClient.SubmitTransactionAsync(transactions, cancellationToken); + foreach(var response in responses.Value) + { + scopedLogger.LogInformation($"[{response.Status}] {response.ReasonPhrase}"); + } + + return responses.Value.None(r => r.IsError); + } + catch(Exception e) + { + scopedLogger.LogError(e, "Encountered exception while setting party searches"); + return false; + } + } + + private async Task> QuerySearches(string query, CancellationToken cancellationToken) + { + var responseList = new Dictionary)>(); + var response = this.tableClient.QueryAsync(query, cancellationToken: cancellationToken); + await foreach (var entry in response) + { + if (!responseList.TryGetValue(entry.PartitionKey, out var tuple)) + { + Campaign.TryParse(entry.Campaign!, out var campaign); + Continent.TryParse(entry.Continent!, out var continent); + Region.TryParse(entry.Region!, out var region); + Map.TryParse(entry.Map!, out var map); + tuple = ((campaign!, continent!, region!, map!, entry.District!), new List()); + responseList[entry.PartitionKey] = tuple; + } + + tuple.Item2.Add(new PartySearchEntry + { + CharName = entry.CharName, + Npcs = entry.Npcs, + PartyMaxSize = entry.PartyMaxSize, + PartySize = entry.PartySize + }); + } + + return responseList.Values.Select(tuple => new Server.Models.PartySearch + { + Campaign = tuple.Item1.Item1, + Continent = tuple.Item1.Item2, + Region = tuple.Item1.Item3, + Map = tuple.Item1.Item4, + District = tuple.Item1.Item5, + PartySearchEntries = tuple.Item2 + }).ToList(); + } + + private static string BuildPartitionKey(Campaign campaign, Continent continent, Region region, Map map, string district) + { + return $"{campaign.Name};{continent.Name};{region.Name};{map.Name};{district}"; + } + + private static string BuildRowKey(string charName, int partySize, int partyMaxSize, int npcs) + { + return $"{charName};{partySize};{partyMaxSize};{npcs}"; + } +} diff --git a/GuildWarsPartySearch/Services/Feed/ILiveFeedService.cs b/GuildWarsPartySearch/Services/Feed/ILiveFeedService.cs index 4b85249..2be6eaa 100644 --- a/GuildWarsPartySearch/Services/Feed/ILiveFeedService.cs +++ b/GuildWarsPartySearch/Services/Feed/ILiveFeedService.cs @@ -1,5 +1,4 @@ -using GuildWarsPartySearch.Server.Models.Endpoints; -using MTSC.ServerSide; +using MTSC.ServerSide; namespace GuildWarsPartySearch.Server.Services.Feed; @@ -7,5 +6,5 @@ public interface ILiveFeedService { void AddClient(ClientData client); void RemoveClient(ClientData client); - void PushUpdate(MTSC.ServerSide.Server server, PartySearchUpdate partySearchUpdate); + void PushUpdate(MTSC.ServerSide.Server server, Models.PartySearch partySearchUpdate); } diff --git a/GuildWarsPartySearch/Services/Feed/LiveFeedService.cs b/GuildWarsPartySearch/Services/Feed/LiveFeedService.cs index 2218430..68ce717 100644 --- a/GuildWarsPartySearch/Services/Feed/LiveFeedService.cs +++ b/GuildWarsPartySearch/Services/Feed/LiveFeedService.cs @@ -1,5 +1,4 @@ -using GuildWarsPartySearch.Server.Models.Endpoints; -using MTSC.Common.WebSockets; +using MTSC.Common.WebSockets; using MTSC.ServerSide; using Newtonsoft.Json; using System.Text; @@ -15,7 +14,7 @@ public void AddClient(ClientData client) AddClientInternal(client); } - public void PushUpdate(MTSC.ServerSide.Server server, PartySearchUpdate partySearchUpdate) + public void PushUpdate(MTSC.ServerSide.Server server, Models.PartySearch partySearchUpdate) { var payloadString = JsonConvert.SerializeObject(partySearchUpdate); var payload = Encoding.UTF8.GetBytes(payloadString); diff --git a/GuildWarsPartySearch/Services/PartySearch/IPartySearchService.cs b/GuildWarsPartySearch/Services/PartySearch/IPartySearchService.cs index ddaa44d..d25554c 100644 --- a/GuildWarsPartySearch/Services/PartySearch/IPartySearchService.cs +++ b/GuildWarsPartySearch/Services/PartySearch/IPartySearchService.cs @@ -1,4 +1,5 @@ using GuildWarsPartySearch.Common.Models.GuildWars; +using GuildWarsPartySearch.Server.Models; using GuildWarsPartySearch.Server.Models.Endpoints; using System.Extensions; @@ -6,9 +7,9 @@ namespace GuildWarsPartySearch.Server.Services.PartySearch; public interface IPartySearchService { - Task> GetAllPartySearches(); + Task> GetAllPartySearches(CancellationToken cancellationToken); - Task> PostPartySearch(PostPartySearchRequest? request); + Task> PostPartySearch(PostPartySearchRequest? request, CancellationToken cancellationToken); - Task, GetPartySearchFailure>> GetPartySearch(Campaign? campaign, Continent? continent, Region? region, Map? map, string? district); + Task, GetPartySearchFailure>> GetPartySearch(Campaign? campaign, Continent? continent, Region? region, Map? map, string? district, CancellationToken cancellationToken); } diff --git a/GuildWarsPartySearch/Services/PartySearch/PartySearchService.cs b/GuildWarsPartySearch/Services/PartySearch/PartySearchService.cs index 93cbd1b..cea2e20 100644 --- a/GuildWarsPartySearch/Services/PartySearch/PartySearchService.cs +++ b/GuildWarsPartySearch/Services/PartySearch/PartySearchService.cs @@ -1,4 +1,5 @@ using GuildWarsPartySearch.Common.Models.GuildWars; +using GuildWarsPartySearch.Server.Models; using GuildWarsPartySearch.Server.Models.Endpoints; using GuildWarsPartySearch.Server.Services.Database; using Microsoft.Extensions.Logging; @@ -20,12 +21,12 @@ public PartySearchService( this.logger = logger.ThrowIfNull(); } - public Task> GetAllPartySearches() + public Task> GetAllPartySearches(CancellationToken cancellationToken) { - return this.partySearchDatabase.GetAllPartySearches(); + return this.partySearchDatabase.GetAllPartySearches(cancellationToken); } - public async Task, GetPartySearchFailure>> GetPartySearch(Campaign? campaign, Continent? continent, Region? region, Map? map, string? district) + public async Task, GetPartySearchFailure>> GetPartySearch(Campaign? campaign, Continent? continent, Region? region, Map? map, string? district, CancellationToken cancellationToken) { if (campaign is null) { @@ -54,21 +55,16 @@ public async Task, GetPartySearchFailure>> GetPart //TODO: Validate district - var result = await this.partySearchDatabase.GetPartySearches(campaign, continent, region, map, district); - if (result is not List entries) + var result = await this.partySearchDatabase.GetPartySearches(campaign, continent, region, map, district, cancellationToken); + if (result is not List entries) { return new GetPartySearchFailure.EntriesNotFound(); } - return entries.Select(e => new PartySearchEntry - { - PartySize = e.PartySize, - PartyMaxSize = e.PartyMaxSize, - Npcs = e.Npcs - }).ToList(); + return entries.ToList(); } - public async Task> PostPartySearch(PostPartySearchRequest? request) + public async Task> PostPartySearch(PostPartySearchRequest? request, CancellationToken cancellationToken) { if (request is null) { @@ -121,6 +117,11 @@ public async Task> PostPa { return new PostPartySearchFailure.InvalidNpcs(); } + + if (entry.CharName is null) + { + return new PostPartySearchFailure.InvalidCharName(); + } } //TODO: Implement district validation, party size validation, party max size validation and npcs validation @@ -130,12 +131,8 @@ public async Task> PostPa request.Region, request.Map, request.District, - request.PartySearchEntries.Select(e => new Models.Database.PartySearch - { - Npcs = e.Npcs, - PartySize = e.PartySize, - PartyMaxSize = e.PartyMaxSize - }).ToList()); + request.PartySearchEntries, + cancellationToken); if (!result) {