From cddcd9a8cfde182e843f8b2224d00ba742596c76 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 21 Sep 2017 23:39:12 -0400 Subject: standardise project name (#336) --- .../Controllers/CheckController.cs | 66 ++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 src/StardewModdingAPI.Web/Controllers/CheckController.cs (limited to 'src/StardewModdingAPI.Web/Controllers') diff --git a/src/StardewModdingAPI.Web/Controllers/CheckController.cs b/src/StardewModdingAPI.Web/Controllers/CheckController.cs new file mode 100644 index 00000000..8ab4611b --- /dev/null +++ b/src/StardewModdingAPI.Web/Controllers/CheckController.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Newtonsoft.Json; +using StardewModdingAPI.Web.Models; + +namespace StardewModdingAPI.Web.Controllers +{ + /// Provides an API to perform mod update checks. + [Produces("application/json")] + [Route("api/check")] + public class CheckController : Controller + { + /********* + ** Public methods + *********/ + /// Fetch version metadata for the given mods. + /// The mods for which to fetch update metadata. + [HttpPost] + public async Task Post([FromBody] NexusResponseModel[] mods) + { + using (var client = new HttpClient()) + { + // the return array of mods + var modList = new List(); + + foreach (var mod in mods) + { + try + { + // create request with HttpRequestMessage + var request = new HttpRequestMessage(HttpMethod.Get, new Uri($"http://www.nexusmods.com/stardewvalley/mods/{mod.ID}")); + + // add the Nexus Client useragent to get JSON response from the site + request.Headers.UserAgent.ParseAdd("Nexus Client v0.63.15"); + + // send the request out + var response = await client.SendAsync(request); + // ensure the response is valid (throws exception) + response.EnsureSuccessStatusCode(); + + // get the JSON string of the response + var stringResponse = await response.Content.ReadAsStringAsync(); + + // create the mod data from the JSON string + var modData = JsonConvert.DeserializeObject(stringResponse); + + // add to the list of mods + modList.Add(modData.ModInfo()); + } + catch (Exception ex) + { + var modData = mod.ModInfo(); + modData.Valid = false; + + modList.Add(modData); + } + } + + return JsonConvert.SerializeObject(modList, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); + } + } + } +} -- cgit From 1bd59fc1d82d6d6316812a5609f61c4157af78c3 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 21 Sep 2017 23:48:47 -0400 Subject: split input model from Nexus response model (#336) --- src/StardewModdingAPI.Web/Controllers/CheckController.cs | 16 ++++++++-------- src/StardewModdingAPI.Web/Models/ModSearchModel.cs | 9 +++++++++ 2 files changed, 17 insertions(+), 8 deletions(-) create mode 100644 src/StardewModdingAPI.Web/Models/ModSearchModel.cs (limited to 'src/StardewModdingAPI.Web/Controllers') diff --git a/src/StardewModdingAPI.Web/Controllers/CheckController.cs b/src/StardewModdingAPI.Web/Controllers/CheckController.cs index 8ab4611b..47b61b2f 100644 --- a/src/StardewModdingAPI.Web/Controllers/CheckController.cs +++ b/src/StardewModdingAPI.Web/Controllers/CheckController.cs @@ -19,7 +19,7 @@ namespace StardewModdingAPI.Web.Controllers /// Fetch version metadata for the given mods. /// The mods for which to fetch update metadata. [HttpPost] - public async Task Post([FromBody] NexusResponseModel[] mods) + public async Task Post([FromBody] ModSearchModel[] mods) { using (var client = new HttpClient()) { @@ -28,10 +28,13 @@ namespace StardewModdingAPI.Web.Controllers foreach (var mod in mods) { + if (!mod.NexusID.HasValue) + continue; + try { // create request with HttpRequestMessage - var request = new HttpRequestMessage(HttpMethod.Get, new Uri($"http://www.nexusmods.com/stardewvalley/mods/{mod.ID}")); + var request = new HttpRequestMessage(HttpMethod.Get, new Uri($"http://www.nexusmods.com/stardewvalley/mods/{mod.NexusID}")); // add the Nexus Client useragent to get JSON response from the site request.Headers.UserAgent.ParseAdd("Nexus Client v0.63.15"); @@ -42,7 +45,7 @@ namespace StardewModdingAPI.Web.Controllers response.EnsureSuccessStatusCode(); // get the JSON string of the response - var stringResponse = await response.Content.ReadAsStringAsync(); + string stringResponse = await response.Content.ReadAsStringAsync(); // create the mod data from the JSON string var modData = JsonConvert.DeserializeObject(stringResponse); @@ -50,12 +53,9 @@ namespace StardewModdingAPI.Web.Controllers // add to the list of mods modList.Add(modData.ModInfo()); } - catch (Exception ex) + catch (Exception) { - var modData = mod.ModInfo(); - modData.Valid = false; - - modList.Add(modData); + modList.Add(new ModGenericModel { ID = mod.NexusID.Value, Vendor = "Nexus", Valid = false }); } } diff --git a/src/StardewModdingAPI.Web/Models/ModSearchModel.cs b/src/StardewModdingAPI.Web/Models/ModSearchModel.cs new file mode 100644 index 00000000..eb9ac920 --- /dev/null +++ b/src/StardewModdingAPI.Web/Models/ModSearchModel.cs @@ -0,0 +1,9 @@ +namespace StardewModdingAPI.Web.Models +{ + /// The search criteria for a mod. + public class ModSearchModel + { + /// The Nexus Mods ID (if any). + public int? NexusID { get; set; } + } +} -- cgit From dfae52b1e57677c1efa052b1073a2e8a56f32163 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 21 Sep 2017 23:50:34 -0400 Subject: fix manual serialisation (#336) --- src/StardewModdingAPI.Web/Controllers/CheckController.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/StardewModdingAPI.Web/Controllers') diff --git a/src/StardewModdingAPI.Web/Controllers/CheckController.cs b/src/StardewModdingAPI.Web/Controllers/CheckController.cs index 47b61b2f..a7582edd 100644 --- a/src/StardewModdingAPI.Web/Controllers/CheckController.cs +++ b/src/StardewModdingAPI.Web/Controllers/CheckController.cs @@ -19,7 +19,7 @@ namespace StardewModdingAPI.Web.Controllers /// Fetch version metadata for the given mods. /// The mods for which to fetch update metadata. [HttpPost] - public async Task Post([FromBody] ModSearchModel[] mods) + public async Task Post([FromBody] ModSearchModel[] mods) { using (var client = new HttpClient()) { @@ -59,7 +59,7 @@ namespace StardewModdingAPI.Web.Controllers } } - return JsonConvert.SerializeObject(modList, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); + return modList.ToArray(); } } } -- cgit From edbc3ef3c08909486d6c4b8797f0e3c2934854fa Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 22 Sep 2017 00:13:04 -0400 Subject: refactor Nexus code into generic vendor, rewrite using fluent HTTP client (#336) --- .../Controllers/CheckController.cs | 46 +++---------- .../Framework/IModRepository.cs | 17 +++++ .../Framework/NexusModsClient.cs | 75 ++++++++++++++++++++++ src/StardewModdingAPI.Web/Models/IModModel.cs | 12 ---- .../Models/ModGenericModel.cs | 43 +++++++++++-- .../Models/NexusResponseModel.cs | 41 ------------ .../StardewModdingAPI.Web.csproj | 1 + 7 files changed, 139 insertions(+), 96 deletions(-) create mode 100644 src/StardewModdingAPI.Web/Framework/IModRepository.cs create mode 100644 src/StardewModdingAPI.Web/Framework/NexusModsClient.cs delete mode 100644 src/StardewModdingAPI.Web/Models/IModModel.cs delete mode 100644 src/StardewModdingAPI.Web/Models/NexusResponseModel.cs (limited to 'src/StardewModdingAPI.Web/Controllers') diff --git a/src/StardewModdingAPI.Web/Controllers/CheckController.cs b/src/StardewModdingAPI.Web/Controllers/CheckController.cs index a7582edd..9ec068cb 100644 --- a/src/StardewModdingAPI.Web/Controllers/CheckController.cs +++ b/src/StardewModdingAPI.Web/Controllers/CheckController.cs @@ -1,9 +1,7 @@ -using System; using System.Collections.Generic; -using System.Net.Http; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; -using Newtonsoft.Json; +using StardewModdingAPI.Web.Framework; using StardewModdingAPI.Web.Models; namespace StardewModdingAPI.Web.Controllers @@ -21,45 +19,19 @@ namespace StardewModdingAPI.Web.Controllers [HttpPost] public async Task Post([FromBody] ModSearchModel[] mods) { - using (var client = new HttpClient()) + using (NexusModsClient client = new NexusModsClient()) { - // the return array of mods - var modList = new List(); + List result = new List(); - foreach (var mod in mods) + foreach (ModSearchModel mod in mods) { - if (!mod.NexusID.HasValue) - continue; - - try - { - // create request with HttpRequestMessage - var request = new HttpRequestMessage(HttpMethod.Get, new Uri($"http://www.nexusmods.com/stardewvalley/mods/{mod.NexusID}")); - - // add the Nexus Client useragent to get JSON response from the site - request.Headers.UserAgent.ParseAdd("Nexus Client v0.63.15"); - - // send the request out - var response = await client.SendAsync(request); - // ensure the response is valid (throws exception) - response.EnsureSuccessStatusCode(); - - // get the JSON string of the response - string stringResponse = await response.Content.ReadAsStringAsync(); - - // create the mod data from the JSON string - var modData = JsonConvert.DeserializeObject(stringResponse); - - // add to the list of mods - modList.Add(modData.ModInfo()); - } - catch (Exception) - { - modList.Add(new ModGenericModel { ID = mod.NexusID.Value, Vendor = "Nexus", Valid = false }); - } + if (mod.NexusID.HasValue) + result.Add(await client.GetModInfoAsync(mod.NexusID.Value)); + else + result.Add(new ModGenericModel(null, mod.NexusID ?? 0)); } - return modList.ToArray(); + return result.ToArray(); } } } diff --git a/src/StardewModdingAPI.Web/Framework/IModRepository.cs b/src/StardewModdingAPI.Web/Framework/IModRepository.cs new file mode 100644 index 00000000..ebf9850f --- /dev/null +++ b/src/StardewModdingAPI.Web/Framework/IModRepository.cs @@ -0,0 +1,17 @@ +using System; +using System.Threading.Tasks; +using StardewModdingAPI.Web.Models; + +namespace StardewModdingAPI.Web.Framework +{ + /// A repository which provides mod metadata. + internal interface IModRepository : IDisposable + { + /********* + ** Public methods + *********/ + /// Get metadata about a mod in the repository. + /// The mod ID in this repository. + Task GetModInfoAsync(int id); + } +} diff --git a/src/StardewModdingAPI.Web/Framework/NexusModsClient.cs b/src/StardewModdingAPI.Web/Framework/NexusModsClient.cs new file mode 100644 index 00000000..8f010d56 --- /dev/null +++ b/src/StardewModdingAPI.Web/Framework/NexusModsClient.cs @@ -0,0 +1,75 @@ +using System; +using System.Threading.Tasks; +using Newtonsoft.Json; +using Pathoschild.Http.Client; +using StardewModdingAPI.Web.Models; + +namespace StardewModdingAPI.Web.Framework +{ + /// An HTTP client for fetching mod metadata from Nexus Mods. + internal class NexusModsClient : IModRepository + { + /********* + ** Properties + *********/ + /// The underlying HTTP client. + private readonly IClient Client; + + /********* + ** Public methods + *********/ + /// Construct an instance. + public NexusModsClient() + { + this.Client = new FluentClient("http://www.nexusmods.com/stardewvalley") + .SetUserAgent("Nexus Client v0.63.15"); + } + + /// Get metadata about a mod in the repository. + /// The mod ID in this repository. + public async Task GetModInfoAsync(int id) + { + try + { + NexusResponseModel response = await this.Client + .GetAsync($"mods/{id}") + .As(); + return new ModGenericModel("Nexus", id, response.Name, response.Version, response.Url); + } + catch (Exception) + { + return new ModGenericModel("Nexus", id); + } + } + + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + public void Dispose() + { + this.Client.Dispose(); + } + + + /********* + ** Private models + *********/ + /// A mod metadata response from Nexus Mods. + private class NexusResponseModel + { + /********* + ** Accessors + *********/ + /// The unique mod ID. + public int ID { get; set; } + + /// The mod name. + public string Name { get; set; } + + /// The mod's semantic version number. + public string Version { get; set; } + + /// The mod's web URL. + [JsonProperty("mod_page_uri")] + public string Url { get; set; } + } + } +} diff --git a/src/StardewModdingAPI.Web/Models/IModModel.cs b/src/StardewModdingAPI.Web/Models/IModModel.cs deleted file mode 100644 index 2eadcaec..00000000 --- a/src/StardewModdingAPI.Web/Models/IModModel.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace StardewModdingAPI.Web.Models -{ - /// A mod metadata response which provides a method to extract generic info. - internal interface IModModel - { - /********* - ** Public methods - *********/ - /// Get basic mod metadata. - ModGenericModel ModInfo(); - } -} diff --git a/src/StardewModdingAPI.Web/Models/ModGenericModel.cs b/src/StardewModdingAPI.Web/Models/ModGenericModel.cs index 208af416..dc36c7f4 100644 --- a/src/StardewModdingAPI.Web/Models/ModGenericModel.cs +++ b/src/StardewModdingAPI.Web/Models/ModGenericModel.cs @@ -7,21 +7,52 @@ namespace StardewModdingAPI.Web.Models ** Accessors *********/ /// The unique mod ID. - public int ID { get; set; } + public int ID { get; } /// The mod name. - public string Name { get; set; } + public string Name { get; } /// The mod's vendor ID. - public string Vendor { get; set; } + public string Vendor { get; } /// The mod's semantic version number. - public string Version { get; set; } + public string Version { get; } /// The mod's web URL. - public string Url { get; set; } + public string Url { get; } /// Whether the mod is valid. - public bool Valid { get; set; } = true; + public bool Valid { get; } + + + /********* + ** Public methods + *********/ + /// Construct a valid instance. + /// The mod's vendor ID. + /// The unique mod ID. + /// The mod name. + /// The mod's semantic version number. + /// The mod's web URL. + /// Whether the mod is valid. + public ModGenericModel(string vendor, int id, string name, string version, string url, bool valid = true) + { + this.Vendor = vendor; + this.ID = id; + this.Name = name; + this.Version = version; + this.Url = url; + this.Valid = valid; + } + + /// Construct an valid instance. + /// The mod's vendor ID. + /// The unique mod ID. + public ModGenericModel(string vendor, int id) + { + this.Vendor = vendor; + this.ID = id; + this.Valid = false; + } } } diff --git a/src/StardewModdingAPI.Web/Models/NexusResponseModel.cs b/src/StardewModdingAPI.Web/Models/NexusResponseModel.cs deleted file mode 100644 index ae5c691c..00000000 --- a/src/StardewModdingAPI.Web/Models/NexusResponseModel.cs +++ /dev/null @@ -1,41 +0,0 @@ -using Newtonsoft.Json; - -namespace StardewModdingAPI.Web.Models -{ - /// A mod metadata response from Nexus Mods. - public class NexusResponseModel : IModModel - { - /********* - ** Accessors - *********/ - /// The unique mod ID. - public int ID { get; set; } - - /// The mod name. - public string Name { get; set; } - - /// The mod's semantic version number. - public string Version { get; set; } - - /// The mod's web URL. - [JsonProperty("mod_page_uri")] - public string Url { get; set; } - - - /********* - ** Public methods - *********/ - /// Get basic mod metadata. - public ModGenericModel ModInfo() - { - return new ModGenericModel - { - ID = this.ID, - Version = this.Version, - Name = this.Name, - Url = this.Url, - Vendor = "Nexus" - }; - } - } -} diff --git a/src/StardewModdingAPI.Web/StardewModdingAPI.Web.csproj b/src/StardewModdingAPI.Web/StardewModdingAPI.Web.csproj index fa1d88eb..eee0f3f3 100644 --- a/src/StardewModdingAPI.Web/StardewModdingAPI.Web.csproj +++ b/src/StardewModdingAPI.Web/StardewModdingAPI.Web.csproj @@ -19,6 +19,7 @@ + -- cgit From 2c02dfe45a6d1252bfef557db8f39f97e57d3d19 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 22 Sep 2017 00:47:46 -0400 Subject: rewrite to make update-check logic vendor-agnostic (#336) --- .../Controllers/CheckController.cs | 71 ++++++++++++++++--- .../Framework/IModRepository.cs | 17 ----- .../Framework/ModRepositories/IModRepository.cs | 24 +++++++ .../Framework/ModRepositories/NexusRepository.cs | 80 ++++++++++++++++++++++ .../Framework/NexusModsClient.cs | 75 -------------------- .../Models/ModGenericModel.cs | 37 +++++----- src/StardewModdingAPI.Web/Models/ModSearchModel.cs | 6 +- 7 files changed, 184 insertions(+), 126 deletions(-) delete mode 100644 src/StardewModdingAPI.Web/Framework/IModRepository.cs create mode 100644 src/StardewModdingAPI.Web/Framework/ModRepositories/IModRepository.cs create mode 100644 src/StardewModdingAPI.Web/Framework/ModRepositories/NexusRepository.cs delete mode 100644 src/StardewModdingAPI.Web/Framework/NexusModsClient.cs (limited to 'src/StardewModdingAPI.Web/Controllers') diff --git a/src/StardewModdingAPI.Web/Controllers/CheckController.cs b/src/StardewModdingAPI.Web/Controllers/CheckController.cs index 9ec068cb..2a346cf3 100644 --- a/src/StardewModdingAPI.Web/Controllers/CheckController.cs +++ b/src/StardewModdingAPI.Web/Controllers/CheckController.cs @@ -1,7 +1,9 @@ +using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; -using StardewModdingAPI.Web.Framework; +using StardewModdingAPI.Web.Framework.ModRepositories; using StardewModdingAPI.Web.Models; namespace StardewModdingAPI.Web.Controllers @@ -11,28 +13,75 @@ namespace StardewModdingAPI.Web.Controllers [Route("api/check")] public class CheckController : Controller { + /********* + ** Properties + *********/ + /// The mod repositories which provide mod metadata. + private readonly IDictionary Repositories = + new IModRepository[] + { + new NexusRepository() + } + .ToDictionary(p => p.VendorKey, StringComparer.CurrentCultureIgnoreCase); + + /********* ** Public methods *********/ /// Fetch version metadata for the given mods. - /// The mods for which to fetch update metadata. + /// The mod update search criteria. [HttpPost] - public async Task Post([FromBody] ModSearchModel[] mods) + public async Task Post([FromBody] ModSearchModel search) { - using (NexusModsClient client = new NexusModsClient()) + IList result = new List(); + + foreach (string modKey in search.ModKeys) { - List result = new List(); + // parse mod key + if (!this.TryParseModKey(modKey, out string vendorKey, out string modID)) + { + result.Add(new ModGenericModel(modKey, "The mod key isn't in a valid format. It should contain the mod repository key and mod ID like 'Nexus:541'.")); + continue; + } - foreach (ModSearchModel mod in mods) + // get matching repository + if (!this.Repositories.TryGetValue(vendorKey, out IModRepository repository)) { - if (mod.NexusID.HasValue) - result.Add(await client.GetModInfoAsync(mod.NexusID.Value)); - else - result.Add(new ModGenericModel(null, mod.NexusID ?? 0)); + result.Add(new ModGenericModel(modKey, "There's no mod repository matching this namespaced mod ID.")); + continue; } - return result.ToArray(); + // fetch mod info + result.Add(await repository.GetModInfoAsync(modID)); } + + return result.ToArray(); + } + + + /********* + ** Private methods + *********/ + /// Parse a namespaced mod ID. + /// The raw mod ID to parse. + /// The parsed vendor key. + /// The parsed mod ID. + /// Returns whether the value could be parsed. + private bool TryParseModKey(string raw, out string vendorKey, out string modID) + { + // split parts + string[] parts = raw?.Split(':'); + if (parts == null || parts.Length != 2) + { + vendorKey = null; + modID = null; + return false; + } + + // parse + vendorKey = parts[0]; + modID = parts[1]; + return true; } } } diff --git a/src/StardewModdingAPI.Web/Framework/IModRepository.cs b/src/StardewModdingAPI.Web/Framework/IModRepository.cs deleted file mode 100644 index ebf9850f..00000000 --- a/src/StardewModdingAPI.Web/Framework/IModRepository.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Threading.Tasks; -using StardewModdingAPI.Web.Models; - -namespace StardewModdingAPI.Web.Framework -{ - /// A repository which provides mod metadata. - internal interface IModRepository : IDisposable - { - /********* - ** Public methods - *********/ - /// Get metadata about a mod in the repository. - /// The mod ID in this repository. - Task GetModInfoAsync(int id); - } -} diff --git a/src/StardewModdingAPI.Web/Framework/ModRepositories/IModRepository.cs b/src/StardewModdingAPI.Web/Framework/ModRepositories/IModRepository.cs new file mode 100644 index 00000000..43bad4e9 --- /dev/null +++ b/src/StardewModdingAPI.Web/Framework/ModRepositories/IModRepository.cs @@ -0,0 +1,24 @@ +using System; +using System.Threading.Tasks; +using StardewModdingAPI.Web.Models; + +namespace StardewModdingAPI.Web.Framework.ModRepositories +{ + /// A repository which provides mod metadata. + internal interface IModRepository : IDisposable + { + /********* + ** Accessors + *********/ + /// The unique key for this vendor. + string VendorKey { get; } + + + /********* + ** Public methods + *********/ + /// Get metadata about a mod in the repository. + /// The mod ID in this repository. + Task GetModInfoAsync(string id); + } +} diff --git a/src/StardewModdingAPI.Web/Framework/ModRepositories/NexusRepository.cs b/src/StardewModdingAPI.Web/Framework/ModRepositories/NexusRepository.cs new file mode 100644 index 00000000..37f309da --- /dev/null +++ b/src/StardewModdingAPI.Web/Framework/ModRepositories/NexusRepository.cs @@ -0,0 +1,80 @@ +using System; +using System.Threading.Tasks; +using Newtonsoft.Json; +using Pathoschild.Http.Client; +using StardewModdingAPI.Web.Models; + +namespace StardewModdingAPI.Web.Framework.ModRepositories +{ + /// An HTTP client for fetching mod metadata from Nexus Mods. + internal class NexusRepository : IModRepository + { + /********* + ** Properties + *********/ + /// The underlying HTTP client. + private readonly IClient Client; + + + /********* + ** Accessors + *********/ + /// The unique key for this vendor. + public string VendorKey { get; } = "Nexus"; + + + /********* + ** Public methods + *********/ + /// Construct an instance. + public NexusRepository() + { + this.Client = new FluentClient("http://www.nexusmods.com/stardewvalley") + .SetUserAgent("Nexus Client v0.63.15"); + } + + /// Get metadata about a mod in the repository. + /// The mod ID in this repository. + public async Task GetModInfoAsync(string id) + { + try + { + NexusResponseModel response = await this.Client + .GetAsync($"mods/{id}") + .As(); + return new ModGenericModel($"{this.VendorKey}:{id}", response.Name, response.Version, response.Url); + } + catch (Exception ex) + { + return new ModGenericModel($"{this.VendorKey}:{id}", ex.ToString()); + } + } + + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + public void Dispose() + { + this.Client.Dispose(); + } + + + /********* + ** Private models + *********/ + /// A mod metadata response from Nexus Mods. + private class NexusResponseModel + { + /********* + ** Accessors + *********/ + /// The mod name. + public string Name { get; set; } + + /// The mod's semantic version number. + public string Version { get; set; } + + /// The mod's web URL. + [JsonProperty("mod_page_uri")] + public string Url { get; set; } + } + } +} diff --git a/src/StardewModdingAPI.Web/Framework/NexusModsClient.cs b/src/StardewModdingAPI.Web/Framework/NexusModsClient.cs deleted file mode 100644 index 8f010d56..00000000 --- a/src/StardewModdingAPI.Web/Framework/NexusModsClient.cs +++ /dev/null @@ -1,75 +0,0 @@ -using System; -using System.Threading.Tasks; -using Newtonsoft.Json; -using Pathoschild.Http.Client; -using StardewModdingAPI.Web.Models; - -namespace StardewModdingAPI.Web.Framework -{ - /// An HTTP client for fetching mod metadata from Nexus Mods. - internal class NexusModsClient : IModRepository - { - /********* - ** Properties - *********/ - /// The underlying HTTP client. - private readonly IClient Client; - - /********* - ** Public methods - *********/ - /// Construct an instance. - public NexusModsClient() - { - this.Client = new FluentClient("http://www.nexusmods.com/stardewvalley") - .SetUserAgent("Nexus Client v0.63.15"); - } - - /// Get metadata about a mod in the repository. - /// The mod ID in this repository. - public async Task GetModInfoAsync(int id) - { - try - { - NexusResponseModel response = await this.Client - .GetAsync($"mods/{id}") - .As(); - return new ModGenericModel("Nexus", id, response.Name, response.Version, response.Url); - } - catch (Exception) - { - return new ModGenericModel("Nexus", id); - } - } - - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - public void Dispose() - { - this.Client.Dispose(); - } - - - /********* - ** Private models - *********/ - /// A mod metadata response from Nexus Mods. - private class NexusResponseModel - { - /********* - ** Accessors - *********/ - /// The unique mod ID. - public int ID { get; set; } - - /// The mod name. - public string Name { get; set; } - - /// The mod's semantic version number. - public string Version { get; set; } - - /// The mod's web URL. - [JsonProperty("mod_page_uri")] - public string Url { get; set; } - } - } -} diff --git a/src/StardewModdingAPI.Web/Models/ModGenericModel.cs b/src/StardewModdingAPI.Web/Models/ModGenericModel.cs index dc36c7f4..88a6e4bd 100644 --- a/src/StardewModdingAPI.Web/Models/ModGenericModel.cs +++ b/src/StardewModdingAPI.Web/Models/ModGenericModel.cs @@ -1,3 +1,5 @@ +using Newtonsoft.Json; + namespace StardewModdingAPI.Web.Models { /// Generic metadata about a mod. @@ -6,53 +8,48 @@ namespace StardewModdingAPI.Web.Models /********* ** Accessors *********/ - /// The unique mod ID. - public int ID { get; } + /// The namespaced mod key. + public string ModKey { get; } /// The mod name. public string Name { get; } - /// The mod's vendor ID. - public string Vendor { get; } - /// The mod's semantic version number. public string Version { get; } /// The mod's web URL. public string Url { get; } - /// Whether the mod is valid. - public bool Valid { get; } + /// The error message indicating why the mod is invalid (if applicable). + public string Error { get; } /********* ** Public methods *********/ /// Construct a valid instance. - /// The mod's vendor ID. - /// The unique mod ID. + /// The namespaced mod key. /// The mod name. /// The mod's semantic version number. /// The mod's web URL. - /// Whether the mod is valid. - public ModGenericModel(string vendor, int id, string name, string version, string url, bool valid = true) + /// The error message indicating why the mod is invalid (if applicable). + [JsonConstructor] + public ModGenericModel(string modKey, string name, string version, string url, string error = null) { - this.Vendor = vendor; - this.ID = id; + this.ModKey = modKey; this.Name = name; this.Version = version; this.Url = url; - this.Valid = valid; + this.Error = error; // mainly initialised here for the JSON deserialiser } /// Construct an valid instance. - /// The mod's vendor ID. - /// The unique mod ID. - public ModGenericModel(string vendor, int id) + /// The namespaced mod key. + /// The error message indicating why the mod is invalid. + public ModGenericModel(string modKey, string error) { - this.Vendor = vendor; - this.ID = id; - this.Valid = false; + this.ModKey = modKey; + this.Error = error; } } } diff --git a/src/StardewModdingAPI.Web/Models/ModSearchModel.cs b/src/StardewModdingAPI.Web/Models/ModSearchModel.cs index eb9ac920..852ea439 100644 --- a/src/StardewModdingAPI.Web/Models/ModSearchModel.cs +++ b/src/StardewModdingAPI.Web/Models/ModSearchModel.cs @@ -1,9 +1,9 @@ namespace StardewModdingAPI.Web.Models { - /// The search criteria for a mod. + /// The mod update search criteria. public class ModSearchModel { - /// The Nexus Mods ID (if any). - public int? NexusID { get; set; } + /// The namespaced mod keys to search. + public string[] ModKeys { get; set; } } } -- cgit From 9c072333d161d2510ee884d71dc9a714bbf86033 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 22 Sep 2017 00:58:25 -0400 Subject: rename mods endpoint & model (#336) --- .../Controllers/CheckController.cs | 87 ---------------------- .../Controllers/ModsController.cs | 87 ++++++++++++++++++++++ .../Framework/ModRepositories/IModRepository.cs | 2 +- .../Framework/ModRepositories/NexusRepository.cs | 6 +- .../Models/ModGenericModel.cs | 55 -------------- src/StardewModdingAPI.Web/Models/ModInfoModel.cs | 55 ++++++++++++++ 6 files changed, 146 insertions(+), 146 deletions(-) delete mode 100644 src/StardewModdingAPI.Web/Controllers/CheckController.cs create mode 100644 src/StardewModdingAPI.Web/Controllers/ModsController.cs delete mode 100644 src/StardewModdingAPI.Web/Models/ModGenericModel.cs create mode 100644 src/StardewModdingAPI.Web/Models/ModInfoModel.cs (limited to 'src/StardewModdingAPI.Web/Controllers') diff --git a/src/StardewModdingAPI.Web/Controllers/CheckController.cs b/src/StardewModdingAPI.Web/Controllers/CheckController.cs deleted file mode 100644 index 2a346cf3..00000000 --- a/src/StardewModdingAPI.Web/Controllers/CheckController.cs +++ /dev/null @@ -1,87 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc; -using StardewModdingAPI.Web.Framework.ModRepositories; -using StardewModdingAPI.Web.Models; - -namespace StardewModdingAPI.Web.Controllers -{ - /// Provides an API to perform mod update checks. - [Produces("application/json")] - [Route("api/check")] - public class CheckController : Controller - { - /********* - ** Properties - *********/ - /// The mod repositories which provide mod metadata. - private readonly IDictionary Repositories = - new IModRepository[] - { - new NexusRepository() - } - .ToDictionary(p => p.VendorKey, StringComparer.CurrentCultureIgnoreCase); - - - /********* - ** Public methods - *********/ - /// Fetch version metadata for the given mods. - /// The mod update search criteria. - [HttpPost] - public async Task Post([FromBody] ModSearchModel search) - { - IList result = new List(); - - foreach (string modKey in search.ModKeys) - { - // parse mod key - if (!this.TryParseModKey(modKey, out string vendorKey, out string modID)) - { - result.Add(new ModGenericModel(modKey, "The mod key isn't in a valid format. It should contain the mod repository key and mod ID like 'Nexus:541'.")); - continue; - } - - // get matching repository - if (!this.Repositories.TryGetValue(vendorKey, out IModRepository repository)) - { - result.Add(new ModGenericModel(modKey, "There's no mod repository matching this namespaced mod ID.")); - continue; - } - - // fetch mod info - result.Add(await repository.GetModInfoAsync(modID)); - } - - return result.ToArray(); - } - - - /********* - ** Private methods - *********/ - /// Parse a namespaced mod ID. - /// The raw mod ID to parse. - /// The parsed vendor key. - /// The parsed mod ID. - /// Returns whether the value could be parsed. - private bool TryParseModKey(string raw, out string vendorKey, out string modID) - { - // split parts - string[] parts = raw?.Split(':'); - if (parts == null || parts.Length != 2) - { - vendorKey = null; - modID = null; - return false; - } - - // parse - vendorKey = parts[0]; - modID = parts[1]; - return true; - } - } -} diff --git a/src/StardewModdingAPI.Web/Controllers/ModsController.cs b/src/StardewModdingAPI.Web/Controllers/ModsController.cs new file mode 100644 index 00000000..bbf1744f --- /dev/null +++ b/src/StardewModdingAPI.Web/Controllers/ModsController.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using StardewModdingAPI.Web.Framework.ModRepositories; +using StardewModdingAPI.Web.Models; + +namespace StardewModdingAPI.Web.Controllers +{ + /// Provides an API to perform mod update checks. + [Produces("application/json")] + public class ModsController : Controller + { + /********* + ** Properties + *********/ + /// The mod repositories which provide mod metadata. + private readonly IDictionary Repositories = + new IModRepository[] + { + new NexusRepository() + } + .ToDictionary(p => p.VendorKey, StringComparer.CurrentCultureIgnoreCase); + + + /********* + ** Public methods + *********/ + /// Fetch version metadata for the given mods. + /// The mod update search criteria. + [HttpPost] + [Route("mods")] + public async Task Post([FromBody] ModSearchModel search) + { + IList result = new List(); + + foreach (string modKey in search.ModKeys) + { + // parse mod key + if (!this.TryParseModKey(modKey, out string vendorKey, out string modID)) + { + result.Add(new ModInfoModel(modKey, "The mod key isn't in a valid format. It should contain the mod repository key and mod ID like 'Nexus:541'.")); + continue; + } + + // get matching repository + if (!this.Repositories.TryGetValue(vendorKey, out IModRepository repository)) + { + result.Add(new ModInfoModel(modKey, "There's no mod repository matching this namespaced mod ID.")); + continue; + } + + // fetch mod info + result.Add(await repository.GetModInfoAsync(modID)); + } + + return result.ToArray(); + } + + + /********* + ** Private methods + *********/ + /// Parse a namespaced mod ID. + /// The raw mod ID to parse. + /// The parsed vendor key. + /// The parsed mod ID. + /// Returns whether the value could be parsed. + private bool TryParseModKey(string raw, out string vendorKey, out string modID) + { + // split parts + string[] parts = raw?.Split(':'); + if (parts == null || parts.Length != 2) + { + vendorKey = null; + modID = null; + return false; + } + + // parse + vendorKey = parts[0]; + modID = parts[1]; + return true; + } + } +} diff --git a/src/StardewModdingAPI.Web/Framework/ModRepositories/IModRepository.cs b/src/StardewModdingAPI.Web/Framework/ModRepositories/IModRepository.cs index 43bad4e9..7fd735cd 100644 --- a/src/StardewModdingAPI.Web/Framework/ModRepositories/IModRepository.cs +++ b/src/StardewModdingAPI.Web/Framework/ModRepositories/IModRepository.cs @@ -19,6 +19,6 @@ namespace StardewModdingAPI.Web.Framework.ModRepositories *********/ /// Get metadata about a mod in the repository. /// The mod ID in this repository. - Task GetModInfoAsync(string id); + Task GetModInfoAsync(string id); } } diff --git a/src/StardewModdingAPI.Web/Framework/ModRepositories/NexusRepository.cs b/src/StardewModdingAPI.Web/Framework/ModRepositories/NexusRepository.cs index 37f309da..74eef2ef 100644 --- a/src/StardewModdingAPI.Web/Framework/ModRepositories/NexusRepository.cs +++ b/src/StardewModdingAPI.Web/Framework/ModRepositories/NexusRepository.cs @@ -35,18 +35,18 @@ namespace StardewModdingAPI.Web.Framework.ModRepositories /// Get metadata about a mod in the repository. /// The mod ID in this repository. - public async Task GetModInfoAsync(string id) + public async Task GetModInfoAsync(string id) { try { NexusResponseModel response = await this.Client .GetAsync($"mods/{id}") .As(); - return new ModGenericModel($"{this.VendorKey}:{id}", response.Name, response.Version, response.Url); + return new ModInfoModel($"{this.VendorKey}:{id}", response.Name, response.Version, response.Url); } catch (Exception ex) { - return new ModGenericModel($"{this.VendorKey}:{id}", ex.ToString()); + return new ModInfoModel($"{this.VendorKey}:{id}", ex.ToString()); } } diff --git a/src/StardewModdingAPI.Web/Models/ModGenericModel.cs b/src/StardewModdingAPI.Web/Models/ModGenericModel.cs deleted file mode 100644 index 88a6e4bd..00000000 --- a/src/StardewModdingAPI.Web/Models/ModGenericModel.cs +++ /dev/null @@ -1,55 +0,0 @@ -using Newtonsoft.Json; - -namespace StardewModdingAPI.Web.Models -{ - /// Generic metadata about a mod. - public class ModGenericModel - { - /********* - ** Accessors - *********/ - /// The namespaced mod key. - public string ModKey { get; } - - /// The mod name. - public string Name { get; } - - /// The mod's semantic version number. - public string Version { get; } - - /// The mod's web URL. - public string Url { get; } - - /// The error message indicating why the mod is invalid (if applicable). - public string Error { get; } - - - /********* - ** Public methods - *********/ - /// Construct a valid instance. - /// The namespaced mod key. - /// The mod name. - /// The mod's semantic version number. - /// The mod's web URL. - /// The error message indicating why the mod is invalid (if applicable). - [JsonConstructor] - public ModGenericModel(string modKey, string name, string version, string url, string error = null) - { - this.ModKey = modKey; - this.Name = name; - this.Version = version; - this.Url = url; - this.Error = error; // mainly initialised here for the JSON deserialiser - } - - /// Construct an valid instance. - /// The namespaced mod key. - /// The error message indicating why the mod is invalid. - public ModGenericModel(string modKey, string error) - { - this.ModKey = modKey; - this.Error = error; - } - } -} diff --git a/src/StardewModdingAPI.Web/Models/ModInfoModel.cs b/src/StardewModdingAPI.Web/Models/ModInfoModel.cs new file mode 100644 index 00000000..723d6c73 --- /dev/null +++ b/src/StardewModdingAPI.Web/Models/ModInfoModel.cs @@ -0,0 +1,55 @@ +using Newtonsoft.Json; + +namespace StardewModdingAPI.Web.Models +{ + /// Generic metadata about a mod. + public class ModInfoModel + { + /********* + ** Accessors + *********/ + /// The namespaced mod key. + public string ModKey { get; } + + /// The mod name. + public string Name { get; } + + /// The mod's semantic version number. + public string Version { get; } + + /// The mod's web URL. + public string Url { get; } + + /// The error message indicating why the mod is invalid (if applicable). + public string Error { get; } + + + /********* + ** Public methods + *********/ + /// Construct a valid instance. + /// The namespaced mod key. + /// The mod name. + /// The mod's semantic version number. + /// The mod's web URL. + /// The error message indicating why the mod is invalid (if applicable). + [JsonConstructor] + public ModInfoModel(string modKey, string name, string version, string url, string error = null) + { + this.ModKey = modKey; + this.Name = name; + this.Version = version; + this.Url = url; + this.Error = error; // mainly initialised here for the JSON deserialiser + } + + /// Construct an valid instance. + /// The namespaced mod key. + /// The error message indicating why the mod is invalid. + public ModInfoModel(string modKey, string error) + { + this.ModKey = modKey; + this.Error = error; + } + } +} -- cgit From ef60b8d32abf7c8613749766155d80139e33b9d1 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 22 Sep 2017 01:23:27 -0400 Subject: add version number to route for future use (#336) --- src/StardewModdingAPI.Web/Controllers/ModsController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/StardewModdingAPI.Web/Controllers') diff --git a/src/StardewModdingAPI.Web/Controllers/ModsController.cs b/src/StardewModdingAPI.Web/Controllers/ModsController.cs index bbf1744f..c7b2aba7 100644 --- a/src/StardewModdingAPI.Web/Controllers/ModsController.cs +++ b/src/StardewModdingAPI.Web/Controllers/ModsController.cs @@ -9,6 +9,7 @@ using StardewModdingAPI.Web.Models; namespace StardewModdingAPI.Web.Controllers { /// Provides an API to perform mod update checks. + [Route("v1.0/mods")] [Produces("application/json")] public class ModsController : Controller { @@ -30,7 +31,6 @@ namespace StardewModdingAPI.Web.Controllers /// Fetch version metadata for the given mods. /// The mod update search criteria. [HttpPost] - [Route("mods")] public async Task Post([FromBody] ModSearchModel search) { IList result = new List(); -- cgit From 86e55596786f8d65854a75632512750b6e09faae Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 22 Sep 2017 01:57:18 -0400 Subject: switch mod update endpoint to GET with comma-delimited mod keys (#336) --- .../Controllers/ModsController.cs | 8 +-- .../Framework/CommaDelimitedModelBinder.cs | 58 ++++++++++++++++++++++ .../Framework/CommaDelimitedModelBinderProvider.cs | 27 ++++++++++ src/StardewModdingAPI.Web/Models/ModSearchModel.cs | 9 ---- src/StardewModdingAPI.Web/Startup.cs | 13 ++++- 5 files changed, 100 insertions(+), 15 deletions(-) create mode 100644 src/StardewModdingAPI.Web/Framework/CommaDelimitedModelBinder.cs create mode 100644 src/StardewModdingAPI.Web/Framework/CommaDelimitedModelBinderProvider.cs delete mode 100644 src/StardewModdingAPI.Web/Models/ModSearchModel.cs (limited to 'src/StardewModdingAPI.Web/Controllers') diff --git a/src/StardewModdingAPI.Web/Controllers/ModsController.cs b/src/StardewModdingAPI.Web/Controllers/ModsController.cs index c7b2aba7..d3b49445 100644 --- a/src/StardewModdingAPI.Web/Controllers/ModsController.cs +++ b/src/StardewModdingAPI.Web/Controllers/ModsController.cs @@ -29,13 +29,13 @@ namespace StardewModdingAPI.Web.Controllers ** Public methods *********/ /// Fetch version metadata for the given mods. - /// The mod update search criteria. - [HttpPost] - public async Task Post([FromBody] ModSearchModel search) + /// The namespaced mod keys to search. + [HttpGet] + public async Task Post(IEnumerable modKeys) { IList result = new List(); - foreach (string modKey in search.ModKeys) + foreach (string modKey in modKeys) { // parse mod key if (!this.TryParseModKey(modKey, out string vendorKey, out string modID)) diff --git a/src/StardewModdingAPI.Web/Framework/CommaDelimitedModelBinder.cs b/src/StardewModdingAPI.Web/Framework/CommaDelimitedModelBinder.cs new file mode 100644 index 00000000..119b18e6 --- /dev/null +++ b/src/StardewModdingAPI.Web/Framework/CommaDelimitedModelBinder.cs @@ -0,0 +1,58 @@ +using System; +using System.ComponentModel; +using System.Reflection; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace StardewModdingAPI.Web.Framework +{ + /// Maps comma-delimited values to an parameter. + /// Derived from . + public class CommaDelimitedModelBinder : IModelBinder + { + /********* + ** Public methods + *********/ + /// Attempts to bind a model. + /// The model binding context. + public Task BindModelAsync(ModelBindingContext bindingContext) + { + // validate + if (bindingContext == null) + throw new ArgumentNullException(nameof(bindingContext)); + + // extract values + string modelName = bindingContext.ModelName; + ValueProviderResult valueProviderResult = bindingContext.ValueProvider.GetValue(modelName); + string[] values = valueProviderResult + .ToString() + .Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + Type elementType = bindingContext.ModelType.GetTypeInfo().GenericTypeArguments[0]; + if (values.Length == 0) + { + bindingContext.Result = ModelBindingResult.Success(Array.CreateInstance(elementType, 0)); + return Task.CompletedTask; + } + + // map values + TypeConverter converter = TypeDescriptor.GetConverter(elementType); + Array typedArray = Array.CreateInstance(elementType, values.Length); + try + { + for (int i = 0; i < values.Length; ++i) + { + string value = values[i]; + object convertedValue = converter.ConvertFromString(value); + typedArray.SetValue(convertedValue, i); + } + } + catch (Exception exception) + { + bindingContext.ModelState.TryAddModelError(modelName, exception, bindingContext.ModelMetadata); + } + + bindingContext.Result = ModelBindingResult.Success(typedArray); + return Task.CompletedTask; + } + } +} diff --git a/src/StardewModdingAPI.Web/Framework/CommaDelimitedModelBinderProvider.cs b/src/StardewModdingAPI.Web/Framework/CommaDelimitedModelBinderProvider.cs new file mode 100644 index 00000000..1b3f0073 --- /dev/null +++ b/src/StardewModdingAPI.Web/Framework/CommaDelimitedModelBinderProvider.cs @@ -0,0 +1,27 @@ +using System; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace StardewModdingAPI.Web.Framework +{ + /// Provides comma-delimited model binds for mapping parameters. + /// Derived from . + public class CommaDelimitedModelBinderProvider : IModelBinderProvider + { + /********* + ** Public methods + *********/ + /// Creates a model binder based on the given context. + /// The model binding context. + public IModelBinder GetBinder(ModelBinderProviderContext context) + { + // validate + if (context == null) + throw new ArgumentNullException(nameof(context)); + + // get model binder + return context.Metadata.IsEnumerableType && !context.Metadata.ElementMetadata.IsComplexType + ? new CommaDelimitedModelBinder() + : null; + } + } +} diff --git a/src/StardewModdingAPI.Web/Models/ModSearchModel.cs b/src/StardewModdingAPI.Web/Models/ModSearchModel.cs deleted file mode 100644 index 852ea439..00000000 --- a/src/StardewModdingAPI.Web/Models/ModSearchModel.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace StardewModdingAPI.Web.Models -{ - /// The mod update search criteria. - public class ModSearchModel - { - /// The namespaced mod keys to search. - public string[] ModKeys { get; set; } - } -} diff --git a/src/StardewModdingAPI.Web/Startup.cs b/src/StardewModdingAPI.Web/Startup.cs index c7a5e8fe..c1f03b34 100644 --- a/src/StardewModdingAPI.Web/Startup.cs +++ b/src/StardewModdingAPI.Web/Startup.cs @@ -1,8 +1,11 @@ -using Microsoft.AspNetCore.Builder; +using System.Linq; +using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc.ModelBinding.Binders; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using StardewModdingAPI.Web.Framework; namespace StardewModdingAPI.Web { @@ -35,7 +38,13 @@ namespace StardewModdingAPI.Web /// The service injection container. public void ConfigureServices(IServiceCollection services) { - services.AddMvc(); + services + .AddMvc(options => + { + // add support for comma-delimited parameters + ArrayModelBinderProvider arrayModelBinderProvider = options.ModelBinderProviders.OfType().First(); + options.ModelBinderProviders.Insert(options.ModelBinderProviders.IndexOf(arrayModelBinderProvider), new CommaDelimitedModelBinderProvider()); + }); } /// The method called by the runtime to configure the HTTP request pipeline. -- cgit From 86d4827df211cc28549acb88ee7cb08d6cc4d4aa Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 22 Sep 2017 03:01:40 -0400 Subject: simplify input & output format (#336) --- .../Controllers/ModsController.cs | 22 +++++--- .../Framework/CommaDelimitedModelBinder.cs | 58 ---------------------- .../Framework/CommaDelimitedModelBinderProvider.cs | 27 ---------- .../Framework/ModRepositories/NexusRepository.cs | 6 +-- src/StardewModdingAPI.Web/Models/ModInfoModel.cs | 11 +--- src/StardewModdingAPI.Web/Models/ModSearchModel.cs | 9 ++++ src/StardewModdingAPI.Web/Startup.cs | 16 +----- 7 files changed, 30 insertions(+), 119 deletions(-) delete mode 100644 src/StardewModdingAPI.Web/Framework/CommaDelimitedModelBinder.cs delete mode 100644 src/StardewModdingAPI.Web/Framework/CommaDelimitedModelBinderProvider.cs create mode 100644 src/StardewModdingAPI.Web/Models/ModSearchModel.cs (limited to 'src/StardewModdingAPI.Web/Controllers') diff --git a/src/StardewModdingAPI.Web/Controllers/ModsController.cs b/src/StardewModdingAPI.Web/Controllers/ModsController.cs index d3b49445..876f5248 100644 --- a/src/StardewModdingAPI.Web/Controllers/ModsController.cs +++ b/src/StardewModdingAPI.Web/Controllers/ModsController.cs @@ -29,33 +29,39 @@ namespace StardewModdingAPI.Web.Controllers ** Public methods *********/ /// Fetch version metadata for the given mods. - /// The namespaced mod keys to search. - [HttpGet] - public async Task Post(IEnumerable modKeys) + /// The search options. + [HttpPost] + public async Task> Post([FromBody] ModSearchModel search) { - IList result = new List(); + // sort & filter keys + string[] modKeys = (search.ModKeys ?? new string[0]) + .Distinct(StringComparer.CurrentCultureIgnoreCase) + .OrderBy(p => p, StringComparer.CurrentCultureIgnoreCase) + .ToArray(); + // fetch mod info + IDictionary result = new Dictionary(StringComparer.CurrentCultureIgnoreCase); foreach (string modKey in modKeys) { // parse mod key if (!this.TryParseModKey(modKey, out string vendorKey, out string modID)) { - result.Add(new ModInfoModel(modKey, "The mod key isn't in a valid format. It should contain the mod repository key and mod ID like 'Nexus:541'.")); + result[modKey] = new ModInfoModel("The mod key isn't in a valid format. It should contain the mod repository key and mod ID like 'Nexus:541'."); continue; } // get matching repository if (!this.Repositories.TryGetValue(vendorKey, out IModRepository repository)) { - result.Add(new ModInfoModel(modKey, "There's no mod repository matching this namespaced mod ID.")); + result[modKey] = new ModInfoModel("There's no mod repository matching this namespaced mod ID."); continue; } // fetch mod info - result.Add(await repository.GetModInfoAsync(modID)); + result[modKey] = await repository.GetModInfoAsync(modID); } - return result.ToArray(); + return result; } diff --git a/src/StardewModdingAPI.Web/Framework/CommaDelimitedModelBinder.cs b/src/StardewModdingAPI.Web/Framework/CommaDelimitedModelBinder.cs deleted file mode 100644 index 119b18e6..00000000 --- a/src/StardewModdingAPI.Web/Framework/CommaDelimitedModelBinder.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; -using System.ComponentModel; -using System.Reflection; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc.ModelBinding; - -namespace StardewModdingAPI.Web.Framework -{ - /// Maps comma-delimited values to an parameter. - /// Derived from . - public class CommaDelimitedModelBinder : IModelBinder - { - /********* - ** Public methods - *********/ - /// Attempts to bind a model. - /// The model binding context. - public Task BindModelAsync(ModelBindingContext bindingContext) - { - // validate - if (bindingContext == null) - throw new ArgumentNullException(nameof(bindingContext)); - - // extract values - string modelName = bindingContext.ModelName; - ValueProviderResult valueProviderResult = bindingContext.ValueProvider.GetValue(modelName); - string[] values = valueProviderResult - .ToString() - .Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - Type elementType = bindingContext.ModelType.GetTypeInfo().GenericTypeArguments[0]; - if (values.Length == 0) - { - bindingContext.Result = ModelBindingResult.Success(Array.CreateInstance(elementType, 0)); - return Task.CompletedTask; - } - - // map values - TypeConverter converter = TypeDescriptor.GetConverter(elementType); - Array typedArray = Array.CreateInstance(elementType, values.Length); - try - { - for (int i = 0; i < values.Length; ++i) - { - string value = values[i]; - object convertedValue = converter.ConvertFromString(value); - typedArray.SetValue(convertedValue, i); - } - } - catch (Exception exception) - { - bindingContext.ModelState.TryAddModelError(modelName, exception, bindingContext.ModelMetadata); - } - - bindingContext.Result = ModelBindingResult.Success(typedArray); - return Task.CompletedTask; - } - } -} diff --git a/src/StardewModdingAPI.Web/Framework/CommaDelimitedModelBinderProvider.cs b/src/StardewModdingAPI.Web/Framework/CommaDelimitedModelBinderProvider.cs deleted file mode 100644 index 1b3f0073..00000000 --- a/src/StardewModdingAPI.Web/Framework/CommaDelimitedModelBinderProvider.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using Microsoft.AspNetCore.Mvc.ModelBinding; - -namespace StardewModdingAPI.Web.Framework -{ - /// Provides comma-delimited model binds for mapping parameters. - /// Derived from . - public class CommaDelimitedModelBinderProvider : IModelBinderProvider - { - /********* - ** Public methods - *********/ - /// Creates a model binder based on the given context. - /// The model binding context. - public IModelBinder GetBinder(ModelBinderProviderContext context) - { - // validate - if (context == null) - throw new ArgumentNullException(nameof(context)); - - // get model binder - return context.Metadata.IsEnumerableType && !context.Metadata.ElementMetadata.IsComplexType - ? new CommaDelimitedModelBinder() - : null; - } - } -} diff --git a/src/StardewModdingAPI.Web/Framework/ModRepositories/NexusRepository.cs b/src/StardewModdingAPI.Web/Framework/ModRepositories/NexusRepository.cs index 7e3ce4b6..02c2939a 100644 --- a/src/StardewModdingAPI.Web/Framework/ModRepositories/NexusRepository.cs +++ b/src/StardewModdingAPI.Web/Framework/ModRepositories/NexusRepository.cs @@ -44,12 +44,12 @@ namespace StardewModdingAPI.Web.Framework.ModRepositories .As(); return response != null - ? new ModInfoModel($"{this.VendorKey}:{id}", response.Name, response.Version, response.Url) - : new ModInfoModel($"{this.VendorKey}:{id}", "Found no mod with this ID."); + ? new ModInfoModel(response.Name, response.Version, response.Url) + : new ModInfoModel("Found no mod with this ID."); } catch (Exception ex) { - return new ModInfoModel($"{this.VendorKey}:{id}", ex.ToString()); + return new ModInfoModel(ex.ToString()); } } diff --git a/src/StardewModdingAPI.Web/Models/ModInfoModel.cs b/src/StardewModdingAPI.Web/Models/ModInfoModel.cs index 723d6c73..180420cd 100644 --- a/src/StardewModdingAPI.Web/Models/ModInfoModel.cs +++ b/src/StardewModdingAPI.Web/Models/ModInfoModel.cs @@ -8,9 +8,6 @@ namespace StardewModdingAPI.Web.Models /********* ** Accessors *********/ - /// The namespaced mod key. - public string ModKey { get; } - /// The mod name. public string Name { get; } @@ -28,15 +25,13 @@ namespace StardewModdingAPI.Web.Models ** Public methods *********/ /// Construct a valid instance. - /// The namespaced mod key. /// The mod name. /// The mod's semantic version number. /// The mod's web URL. /// The error message indicating why the mod is invalid (if applicable). [JsonConstructor] - public ModInfoModel(string modKey, string name, string version, string url, string error = null) + public ModInfoModel(string name, string version, string url, string error = null) { - this.ModKey = modKey; this.Name = name; this.Version = version; this.Url = url; @@ -44,11 +39,9 @@ namespace StardewModdingAPI.Web.Models } /// Construct an valid instance. - /// The namespaced mod key. /// The error message indicating why the mod is invalid. - public ModInfoModel(string modKey, string error) + public ModInfoModel(string error) { - this.ModKey = modKey; this.Error = error; } } diff --git a/src/StardewModdingAPI.Web/Models/ModSearchModel.cs b/src/StardewModdingAPI.Web/Models/ModSearchModel.cs new file mode 100644 index 00000000..b5bd12fb --- /dev/null +++ b/src/StardewModdingAPI.Web/Models/ModSearchModel.cs @@ -0,0 +1,9 @@ +namespace StardewModdingAPI.Web.Models +{ + /// Metadata for mods to look up. + public class ModSearchModel + { + /// The namespaced mod keys to search. + public string[] ModKeys { get; set; } + } +} diff --git a/src/StardewModdingAPI.Web/Startup.cs b/src/StardewModdingAPI.Web/Startup.cs index 2d9a95f1..abae06ec 100644 --- a/src/StardewModdingAPI.Web/Startup.cs +++ b/src/StardewModdingAPI.Web/Startup.cs @@ -1,12 +1,9 @@ -using System.Linq; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Mvc.ModelBinding.Binders; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Newtonsoft.Json; -using StardewModdingAPI.Web.Framework; namespace StardewModdingAPI.Web { @@ -40,17 +37,8 @@ namespace StardewModdingAPI.Web public void ConfigureServices(IServiceCollection services) { services - .AddMvc(options => - { - // add support for comma-delimited parameters - ArrayModelBinderProvider arrayModelBinderProvider = options.ModelBinderProviders.OfType().First(); - options.ModelBinderProviders.Insert(options.ModelBinderProviders.IndexOf(arrayModelBinderProvider), new CommaDelimitedModelBinderProvider()); - }) - .AddJsonOptions(options => - { - // suppress null values in JSON responses - options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore; - }); + .AddMvc() + .AddJsonOptions(options => options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore); // suppress null values in JSON responses } /// The method called by the runtime to configure the HTTP request pipeline. -- cgit From 2406d4b0a6fd8b2d439438687b0101a6f8934ca5 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 22 Sep 2017 20:19:12 -0400 Subject: add subdomain rewrite rule (#336) --- .../Controllers/ModsController.cs | 2 +- .../Framework/RewriteSubdomainRule.cs | 30 ++++++++++++++++++++++ .../StardewModdingAPI.Web.csproj | 1 + src/StardewModdingAPI.Web/Startup.cs | 6 ++++- 4 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 src/StardewModdingAPI.Web/Framework/RewriteSubdomainRule.cs (limited to 'src/StardewModdingAPI.Web/Controllers') diff --git a/src/StardewModdingAPI.Web/Controllers/ModsController.cs b/src/StardewModdingAPI.Web/Controllers/ModsController.cs index 876f5248..7f7afbb9 100644 --- a/src/StardewModdingAPI.Web/Controllers/ModsController.cs +++ b/src/StardewModdingAPI.Web/Controllers/ModsController.cs @@ -9,7 +9,7 @@ using StardewModdingAPI.Web.Models; namespace StardewModdingAPI.Web.Controllers { /// Provides an API to perform mod update checks. - [Route("v1.0/mods")] + [Route("api/v1.0/mods")] [Produces("application/json")] public class ModsController : Controller { diff --git a/src/StardewModdingAPI.Web/Framework/RewriteSubdomainRule.cs b/src/StardewModdingAPI.Web/Framework/RewriteSubdomainRule.cs new file mode 100644 index 00000000..9b89cb65 --- /dev/null +++ b/src/StardewModdingAPI.Web/Framework/RewriteSubdomainRule.cs @@ -0,0 +1,30 @@ +using System; +using Microsoft.AspNetCore.Rewrite; + +namespace StardewModdingAPI.Web.Framework +{ + /// Rewrite requests to prepend the subdomain portion (if any) to the path. + /// Derived from . + public class RewriteSubdomainRule : IRule + { + /// Applies the rule. Implementations of ApplyRule should set the value for (defaults to RuleResult.ContinueRules). + /// The rewrite context. + public void ApplyRule(RewriteContext context) + { + context.Result = RuleResult.ContinueRules; + + // get host parts + string host = context.HttpContext.Request.Host.Host; + string[] parts = host.Split('.'); + + // validate + if (parts.Length < 2) + return; + if (parts.Length < 3 && !"localhost".Equals(parts[1], StringComparison.InvariantCultureIgnoreCase)) + return; + + // prepend to path + context.HttpContext.Request.Path = $"/{parts[0]}{context.HttpContext.Request.Path}"; + } + } +} diff --git a/src/StardewModdingAPI.Web/StardewModdingAPI.Web.csproj b/src/StardewModdingAPI.Web/StardewModdingAPI.Web.csproj index 7dff3fb6..2af7c3df 100644 --- a/src/StardewModdingAPI.Web/StardewModdingAPI.Web.csproj +++ b/src/StardewModdingAPI.Web/StardewModdingAPI.Web.csproj @@ -7,6 +7,7 @@ + diff --git a/src/StardewModdingAPI.Web/Startup.cs b/src/StardewModdingAPI.Web/Startup.cs index b35d072b..19dffb88 100644 --- a/src/StardewModdingAPI.Web/Startup.cs +++ b/src/StardewModdingAPI.Web/Startup.cs @@ -1,9 +1,11 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Rewrite; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Newtonsoft.Json; +using StardewModdingAPI.Web.Framework; namespace StardewModdingAPI.Web { @@ -53,7 +55,9 @@ namespace StardewModdingAPI.Web { loggerFactory.AddConsole(this.Configuration.GetSection("Logging")); loggerFactory.AddDebug(); - app.UseMvc(); + app + .UseRewriter(new RewriteOptions().Add(new RewriteSubdomainRule())) // convert subdomain.smapi.io => smapi.io/subdomain for routing + .UseMvc(); } } } -- cgit From e4a2f555170b08263ae2d7e713808bd229b48947 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 22 Sep 2017 20:33:09 -0400 Subject: add GET endpoint for testing (#336) --- src/StardewModdingAPI.Web/Controllers/ModsController.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'src/StardewModdingAPI.Web/Controllers') diff --git a/src/StardewModdingAPI.Web/Controllers/ModsController.cs b/src/StardewModdingAPI.Web/Controllers/ModsController.cs index 7f7afbb9..366201e3 100644 --- a/src/StardewModdingAPI.Web/Controllers/ModsController.cs +++ b/src/StardewModdingAPI.Web/Controllers/ModsController.cs @@ -28,6 +28,17 @@ namespace StardewModdingAPI.Web.Controllers /********* ** Public methods *********/ + /// Fetch version metadata for the given mods. + /// The namespaced mod keys to search as a comma-delimited array. + [HttpGet] + public async Task> GetAsync(string modKeys) + { + return await this.Post(new ModSearchModel + { + ModKeys = modKeys?.Split(',').Select(p => p.Trim()).ToArray() ?? new string[0] + }); + } + /// Fetch version metadata for the given mods. /// The search options. [HttpPost] -- cgit From ba5cc149e265d3f14246db23b09c8feb5f9c0d3a Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 22 Sep 2017 20:46:38 -0400 Subject: add in-memory cache for remote queries (#336) --- src/StardewModdingAPI.Web/Controllers/ModsController.cs | 17 ++++++++++++++++- src/StardewModdingAPI.Web/Startup.cs | 1 + 2 files changed, 17 insertions(+), 1 deletion(-) (limited to 'src/StardewModdingAPI.Web/Controllers') diff --git a/src/StardewModdingAPI.Web/Controllers/ModsController.cs b/src/StardewModdingAPI.Web/Controllers/ModsController.cs index 366201e3..bd5ecef9 100644 --- a/src/StardewModdingAPI.Web/Controllers/ModsController.cs +++ b/src/StardewModdingAPI.Web/Controllers/ModsController.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Caching.Memory; using StardewModdingAPI.Web.Framework.ModRepositories; using StardewModdingAPI.Web.Models; @@ -24,10 +25,20 @@ namespace StardewModdingAPI.Web.Controllers } .ToDictionary(p => p.VendorKey, StringComparer.CurrentCultureIgnoreCase); + /// The cache in which to store mod metadata. + private readonly IMemoryCache Cache; + /********* ** Public methods *********/ + /// Construct an instance. + /// The cache in which to store mod metadata. + public ModsController(IMemoryCache cache) + { + this.Cache = cache; + } + /// Fetch version metadata for the given mods. /// The namespaced mod keys to search as a comma-delimited array. [HttpGet] @@ -69,7 +80,11 @@ namespace StardewModdingAPI.Web.Controllers } // fetch mod info - result[modKey] = await repository.GetModInfoAsync(modID); + result[modKey] = await this.Cache.GetOrCreateAsync($"{repository.VendorKey}:{modID}".ToLower(), async entry => + { + entry.AbsoluteExpiration = DateTimeOffset.UtcNow.AddHours(1); + return await repository.GetModInfoAsync(modID); + }); } return result; diff --git a/src/StardewModdingAPI.Web/Startup.cs b/src/StardewModdingAPI.Web/Startup.cs index 19dffb88..8aba6a5a 100644 --- a/src/StardewModdingAPI.Web/Startup.cs +++ b/src/StardewModdingAPI.Web/Startup.cs @@ -39,6 +39,7 @@ namespace StardewModdingAPI.Web public void ConfigureServices(IServiceCollection services) { services + .AddMemoryCache() .AddMvc() .AddJsonOptions(options => { -- cgit From ecdbefffd9c0acbbecd8178d7d2ac285715b5e7f Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 22 Sep 2017 21:49:05 -0400 Subject: move hardcoded values into config (#336) --- .../Controllers/ModsController.cs | 27 +++++++++++++++------- .../Framework/ConfigModels/ModUpdateCheckConfig.cs | 21 +++++++++++++++++ .../Framework/ModRepositories/NexusRepository.cs | 18 +++++++++++---- src/StardewModdingAPI.Web/Startup.cs | 2 ++ src/StardewModdingAPI.Web/appsettings.json | 11 +++++++-- 5 files changed, 64 insertions(+), 15 deletions(-) create mode 100644 src/StardewModdingAPI.Web/Framework/ConfigModels/ModUpdateCheckConfig.cs (limited to 'src/StardewModdingAPI.Web/Controllers') diff --git a/src/StardewModdingAPI.Web/Controllers/ModsController.cs b/src/StardewModdingAPI.Web/Controllers/ModsController.cs index bd5ecef9..50c23b99 100644 --- a/src/StardewModdingAPI.Web/Controllers/ModsController.cs +++ b/src/StardewModdingAPI.Web/Controllers/ModsController.cs @@ -4,6 +4,8 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Options; +using StardewModdingAPI.Web.Framework.ConfigModels; using StardewModdingAPI.Web.Framework.ModRepositories; using StardewModdingAPI.Web.Models; @@ -18,25 +20,34 @@ namespace StardewModdingAPI.Web.Controllers ** Properties *********/ /// The mod repositories which provide mod metadata. - private readonly IDictionary Repositories = - new IModRepository[] - { - new NexusRepository() - } - .ToDictionary(p => p.VendorKey, StringComparer.CurrentCultureIgnoreCase); + private readonly IDictionary Repositories; /// The cache in which to store mod metadata. private readonly IMemoryCache Cache; + /// The number of minutes update checks should be cached before refetching them. + private readonly int CacheMinutes; + /********* ** Public methods *********/ /// Construct an instance. /// The cache in which to store mod metadata. - public ModsController(IMemoryCache cache) + /// The config settings for mod update checks. + public ModsController(IMemoryCache cache, IOptions configProvider) { + ModUpdateCheckConfig config = configProvider.Value; + this.Cache = cache; + this.CacheMinutes = config.CacheMinutes; + + this.Repositories = + new IModRepository[] + { + new NexusRepository(config.NexusKey, config.NexusUserAgent, config.NexusBaseUrl, config.NexusModUrlFormat) + } + .ToDictionary(p => p.VendorKey, StringComparer.CurrentCultureIgnoreCase); } /// Fetch version metadata for the given mods. @@ -82,7 +93,7 @@ namespace StardewModdingAPI.Web.Controllers // fetch mod info result[modKey] = await this.Cache.GetOrCreateAsync($"{repository.VendorKey}:{modID}".ToLower(), async entry => { - entry.AbsoluteExpiration = DateTimeOffset.UtcNow.AddHours(1); + entry.AbsoluteExpiration = DateTimeOffset.UtcNow.AddMinutes(this.CacheMinutes); return await repository.GetModInfoAsync(modID); }); } diff --git a/src/StardewModdingAPI.Web/Framework/ConfigModels/ModUpdateCheckConfig.cs b/src/StardewModdingAPI.Web/Framework/ConfigModels/ModUpdateCheckConfig.cs new file mode 100644 index 00000000..c8763800 --- /dev/null +++ b/src/StardewModdingAPI.Web/Framework/ConfigModels/ModUpdateCheckConfig.cs @@ -0,0 +1,21 @@ +namespace StardewModdingAPI.Web.Framework.ConfigModels +{ + /// The config settings for mod update checks. + public class ModUpdateCheckConfig + { + /// The number of minutes update checks should be cached before refetching them. + public int CacheMinutes { get; set; } + + /// The repository key for Nexus Mods. + public string NexusKey { get; set; } + + /// The user agent for the Nexus Mods API client. + public string NexusUserAgent { get; set; } + + /// The base URL for the Nexus Mods API. + public string NexusBaseUrl { get; set; } + + /// The URL for a Nexus Mods API query excluding the , where {0} is the mod ID. + public string NexusModUrlFormat { get; set; } + } +} diff --git a/src/StardewModdingAPI.Web/Framework/ModRepositories/NexusRepository.cs b/src/StardewModdingAPI.Web/Framework/ModRepositories/NexusRepository.cs index 02c2939a..312058ae 100644 --- a/src/StardewModdingAPI.Web/Framework/ModRepositories/NexusRepository.cs +++ b/src/StardewModdingAPI.Web/Framework/ModRepositories/NexusRepository.cs @@ -20,17 +20,25 @@ namespace StardewModdingAPI.Web.Framework.ModRepositories ** Accessors *********/ /// The unique key for this vendor. - public string VendorKey { get; } = "Nexus"; + public string VendorKey { get; } + + /// The URL for a Nexus Mods API query excluding the base URL, where {0} is the mod ID. + public string ModUrlFormat { get; } /********* ** Public methods *********/ /// Construct an instance. - public NexusRepository() + /// The unique key for this vendor. + /// The user agent for the Nexus Mods API client. + /// The base URL for the Nexus Mods API. + /// The URL for a Nexus Mods API query excluding the , where {0} is the mod ID. + public NexusRepository(string vendorKey, string userAgent, string baseUrl, string modUrlFormat) { - this.Client = new FluentClient("http://www.nexusmods.com/stardewvalley") - .SetUserAgent("Nexus Client v0.63.15"); + this.VendorKey = vendorKey; + this.ModUrlFormat = modUrlFormat; + this.Client = new FluentClient(baseUrl).SetUserAgent(userAgent); } /// Get metadata about a mod in the repository. @@ -40,7 +48,7 @@ namespace StardewModdingAPI.Web.Framework.ModRepositories try { NexusResponseModel response = await this.Client - .GetAsync($"mods/{id}") + .GetAsync(string.Format(this.ModUrlFormat, id)) .As(); return response != null diff --git a/src/StardewModdingAPI.Web/Startup.cs b/src/StardewModdingAPI.Web/Startup.cs index 8aba6a5a..fbe2bd92 100644 --- a/src/StardewModdingAPI.Web/Startup.cs +++ b/src/StardewModdingAPI.Web/Startup.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using StardewModdingAPI.Web.Framework; +using StardewModdingAPI.Web.Framework.ConfigModels; namespace StardewModdingAPI.Web { @@ -39,6 +40,7 @@ namespace StardewModdingAPI.Web public void ConfigureServices(IServiceCollection services) { services + .Configure(this.Configuration.GetSection("ModUpdateCheck")) .AddMemoryCache() .AddMvc() .AddJsonOptions(options => diff --git a/src/StardewModdingAPI.Web/appsettings.json b/src/StardewModdingAPI.Web/appsettings.json index 5fff67ba..1e624055 100644 --- a/src/StardewModdingAPI.Web/appsettings.json +++ b/src/StardewModdingAPI.Web/appsettings.json @@ -1,8 +1,15 @@ -{ +{ "Logging": { "IncludeScopes": false, "LogLevel": { "Default": "Warning" } - } + }, + "ModUpdateCheck": { + "CacheMinutes": 60, + "NexusKey": "Nexus", + "NexusUserAgent": "Nexus Client v0.63.15", + "NexusBaseUrl": "http://www.nexusmods.com/stardewvalley", + "NexusModUrlFormat": "mods/{0}" + } } -- cgit From 71d85a0c22a9295a08acdf433ef67d8b1b47e57b Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 22 Sep 2017 22:11:04 -0400 Subject: restrict version to a valid SMAPI version (#336) --- src/StardewModdingAPI.Web/Controllers/ModsController.cs | 5 ++--- src/StardewModdingAPI.Web/Startup.cs | 17 ++++++++++++++++- 2 files changed, 18 insertions(+), 4 deletions(-) (limited to 'src/StardewModdingAPI.Web/Controllers') diff --git a/src/StardewModdingAPI.Web/Controllers/ModsController.cs b/src/StardewModdingAPI.Web/Controllers/ModsController.cs index 50c23b99..a3f5001a 100644 --- a/src/StardewModdingAPI.Web/Controllers/ModsController.cs +++ b/src/StardewModdingAPI.Web/Controllers/ModsController.cs @@ -12,7 +12,6 @@ using StardewModdingAPI.Web.Models; namespace StardewModdingAPI.Web.Controllers { /// Provides an API to perform mod update checks. - [Route("api/v1.0/mods")] [Produces("application/json")] public class ModsController : Controller { @@ -55,7 +54,7 @@ namespace StardewModdingAPI.Web.Controllers [HttpGet] public async Task> GetAsync(string modKeys) { - return await this.Post(new ModSearchModel + return await this.PostAsync(new ModSearchModel { ModKeys = modKeys?.Split(',').Select(p => p.Trim()).ToArray() ?? new string[0] }); @@ -64,7 +63,7 @@ namespace StardewModdingAPI.Web.Controllers /// Fetch version metadata for the given mods. /// The search options. [HttpPost] - public async Task> Post([FromBody] ModSearchModel search) + public async Task> PostAsync([FromBody] ModSearchModel search) { // sort & filter keys string[] modKeys = (search.ModKeys ?? new string[0]) diff --git a/src/StardewModdingAPI.Web/Startup.cs b/src/StardewModdingAPI.Web/Startup.cs index fbe2bd92..b668c63e 100644 --- a/src/StardewModdingAPI.Web/Startup.cs +++ b/src/StardewModdingAPI.Web/Startup.cs @@ -60,7 +60,22 @@ namespace StardewModdingAPI.Web loggerFactory.AddDebug(); app .UseRewriter(new RewriteOptions().Add(new RewriteSubdomainRule())) // convert subdomain.smapi.io => smapi.io/subdomain for routing - .UseMvc(); + .UseMvc(route => + { + route.MapRoute( + name: "API", + template: "api/{version}/{controller}/{action?}", + defaults: new + { + action = "GetAsync" + }, + constraints: new + { + // version regex from SMAPI's SemanticVersion implementation + version = @"^v(?>(?0|[1-9]\d*))\.(?>(?0|[1-9]\d*))(?>(?:\.(?0|[1-9]\d*))?)(?:-(?(?>[a-z0-9]+[\-\.]?)+))?$" + } + ); + }); } } } -- cgit From 0d6f6a9acef175fd9ea0df6790111d8d58d7f368 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 22 Sep 2017 22:42:04 -0400 Subject: add GitHub update check support (#336) --- .../Controllers/ModsController.cs | 1 + .../Framework/ConfigModels/ModUpdateCheckConfig.cs | 27 +++++++ .../Framework/ModRepositories/GitHubRepository.cs | 91 ++++++++++++++++++++++ src/StardewModdingAPI.Web/appsettings.json | 7 ++ 4 files changed, 126 insertions(+) create mode 100644 src/StardewModdingAPI.Web/Framework/ModRepositories/GitHubRepository.cs (limited to 'src/StardewModdingAPI.Web/Controllers') diff --git a/src/StardewModdingAPI.Web/Controllers/ModsController.cs b/src/StardewModdingAPI.Web/Controllers/ModsController.cs index a3f5001a..4b1abde4 100644 --- a/src/StardewModdingAPI.Web/Controllers/ModsController.cs +++ b/src/StardewModdingAPI.Web/Controllers/ModsController.cs @@ -44,6 +44,7 @@ namespace StardewModdingAPI.Web.Controllers this.Repositories = new IModRepository[] { + new GitHubRepository(config.GitHubKey, config.GitHubBaseUrl, config.GitHubReleaseUrlFormat, config.GitHubUserAgent, config.GitHubAcceptHeader), new NexusRepository(config.NexusKey, config.NexusUserAgent, config.NexusBaseUrl, config.NexusModUrlFormat) } .ToDictionary(p => p.VendorKey, StringComparer.CurrentCultureIgnoreCase); diff --git a/src/StardewModdingAPI.Web/Framework/ConfigModels/ModUpdateCheckConfig.cs b/src/StardewModdingAPI.Web/Framework/ConfigModels/ModUpdateCheckConfig.cs index c8763800..4dbad506 100644 --- a/src/StardewModdingAPI.Web/Framework/ConfigModels/ModUpdateCheckConfig.cs +++ b/src/StardewModdingAPI.Web/Framework/ConfigModels/ModUpdateCheckConfig.cs @@ -3,9 +3,36 @@ namespace StardewModdingAPI.Web.Framework.ConfigModels /// The config settings for mod update checks. public class ModUpdateCheckConfig { + /********* + ** Accessors + *********/ + /**** + ** General + ****/ /// The number of minutes update checks should be cached before refetching them. public int CacheMinutes { get; set; } + /**** + ** GitHub + ****/ + /// The repository key for Nexus Mods. + public string GitHubKey { get; set; } + + /// The user agent for the GitHub API client. + public string GitHubUserAgent { get; set; } + + /// The base URL for the GitHub API. + public string GitHubBaseUrl { get; set; } + + /// The URL for a GitHub API latest-release query excluding the , where {0} is the organisation and project name. + public string GitHubReleaseUrlFormat { get; set; } + + /// The Accept header value expected by the GitHub API. + public string GitHubAcceptHeader { get; set; } + + /**** + ** Nexus Mods + ****/ /// The repository key for Nexus Mods. public string NexusKey { get; set; } diff --git a/src/StardewModdingAPI.Web/Framework/ModRepositories/GitHubRepository.cs b/src/StardewModdingAPI.Web/Framework/ModRepositories/GitHubRepository.cs new file mode 100644 index 00000000..c5772ad9 --- /dev/null +++ b/src/StardewModdingAPI.Web/Framework/ModRepositories/GitHubRepository.cs @@ -0,0 +1,91 @@ +using System; +using System.Threading.Tasks; +using Newtonsoft.Json; +using Pathoschild.Http.Client; +using StardewModdingAPI.Web.Models; + +namespace StardewModdingAPI.Web.Framework.ModRepositories +{ + /// An HTTP client for fetching mod metadata from GitHub project releases. + internal class GitHubRepository : IModRepository + { + /********* + ** Properties + *********/ + /// The underlying HTTP client. + private readonly IClient Client; + + + /********* + ** Accessors + *********/ + /// The unique key for this vendor. + public string VendorKey { get; } + + /// The URL for a Nexus Mods API query excluding the base URL, where {0} is the mod ID. + public string ReleaseUrlFormat { get; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The unique key for this vendor. + /// The base URL for the Nexus Mods API. + /// The URL for a Nexus Mods API query excluding the , where {0} is the mod ID. + /// The user agent for the GitHub API client. + /// The Accept header value expected by the GitHub API. + public GitHubRepository(string vendorKey, string baseUrl, string releaseUrlFormat, string userAgent, string acceptHeader) + { + this.VendorKey = vendorKey; + this.ReleaseUrlFormat = releaseUrlFormat; + + this.Client = new FluentClient(baseUrl) + .SetUserAgent(string.Format(userAgent, this.GetType().Assembly.GetName().Version)) + .AddDefault(req => req.WithHeader("Accept", acceptHeader)); + } + + /// Get metadata about a mod in the repository. + /// The mod ID in this repository. + public async Task GetModInfoAsync(string id) + { + try + { + GitRelease release = await this.Client + .GetAsync(string.Format(this.ReleaseUrlFormat, id)) + .As(); + + return new ModInfoModel(id, release.Tag, $"https://github.com/{id}/releases"); + } + catch (Exception ex) + { + return new ModInfoModel(ex.ToString()); + } + } + + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + public void Dispose() + { + this.Client.Dispose(); + } + + + /********* + ** Private models + *********/ + /// Metadata about a GitHub release tag. + private class GitRelease + { + /********* + ** Accessors + *********/ + /// The display name. + [JsonProperty("name")] + public string Name { get; set; } + + /// The semantic version string. + [JsonProperty("tag_name")] + public string Tag { get; set; } + } + } +} diff --git a/src/StardewModdingAPI.Web/appsettings.json b/src/StardewModdingAPI.Web/appsettings.json index 1e624055..f996157c 100644 --- a/src/StardewModdingAPI.Web/appsettings.json +++ b/src/StardewModdingAPI.Web/appsettings.json @@ -7,6 +7,13 @@ }, "ModUpdateCheck": { "CacheMinutes": 60, + + "GitHubKey": "GitHub", + "GitHubUserAgent": "SMAPI/{0} (+https://github.com/Pathoschild/SMAPI)", + "GitHubBaseUrl": "https://api.github.com", + "GitHubReleaseUrlFormat": "repos/{0}/releases/latest", + "GitHubAcceptHeader": "application/vnd.github.v3+json", + "NexusKey": "Nexus", "NexusUserAgent": "Nexus Client v0.63.15", "NexusBaseUrl": "http://www.nexusmods.com/stardewvalley", -- cgit From eaabd91f31db35e050b7215f5f36568bf6982a83 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 22 Sep 2017 23:43:58 -0400 Subject: authenticate GitHub queries if auth details are configured (#336) --- src/StardewModdingAPI.Web/Controllers/ModsController.cs | 17 +++++++++++++++-- .../Framework/ConfigModels/ModUpdateCheckConfig.cs | 6 ++++++ .../Framework/ModRepositories/GitHubRepository.cs | 6 +++++- src/StardewModdingAPI.Web/Startup.cs | 1 + src/StardewModdingAPI.Web/appsettings.json | 2 ++ 5 files changed, 29 insertions(+), 3 deletions(-) (limited to 'src/StardewModdingAPI.Web/Controllers') diff --git a/src/StardewModdingAPI.Web/Controllers/ModsController.cs b/src/StardewModdingAPI.Web/Controllers/ModsController.cs index 4b1abde4..4715d379 100644 --- a/src/StardewModdingAPI.Web/Controllers/ModsController.cs +++ b/src/StardewModdingAPI.Web/Controllers/ModsController.cs @@ -44,8 +44,21 @@ namespace StardewModdingAPI.Web.Controllers this.Repositories = new IModRepository[] { - new GitHubRepository(config.GitHubKey, config.GitHubBaseUrl, config.GitHubReleaseUrlFormat, config.GitHubUserAgent, config.GitHubAcceptHeader), - new NexusRepository(config.NexusKey, config.NexusUserAgent, config.NexusBaseUrl, config.NexusModUrlFormat) + new GitHubRepository( + vendorKey: config.GitHubKey, + baseUrl: config.GitHubBaseUrl, + releaseUrlFormat: config.GitHubReleaseUrlFormat, + userAgent: config.GitHubUserAgent, + acceptHeader: config.GitHubAcceptHeader, + username: config.GitHubUsername, + password: config.GitHubPassword + ), + new NexusRepository( + vendorKey: config.NexusKey, + userAgent: config.NexusUserAgent, + baseUrl: config.NexusBaseUrl, + modUrlFormat: config.NexusModUrlFormat + ) } .ToDictionary(p => p.VendorKey, StringComparer.CurrentCultureIgnoreCase); } diff --git a/src/StardewModdingAPI.Web/Framework/ConfigModels/ModUpdateCheckConfig.cs b/src/StardewModdingAPI.Web/Framework/ConfigModels/ModUpdateCheckConfig.cs index 4dbad506..5d55ba18 100644 --- a/src/StardewModdingAPI.Web/Framework/ConfigModels/ModUpdateCheckConfig.cs +++ b/src/StardewModdingAPI.Web/Framework/ConfigModels/ModUpdateCheckConfig.cs @@ -30,6 +30,12 @@ namespace StardewModdingAPI.Web.Framework.ConfigModels /// The Accept header value expected by the GitHub API. public string GitHubAcceptHeader { get; set; } + /// The username with which to authenticate to the GitHub API (if any). + public string GitHubUsername { get; set; } + + /// The password with which to authenticate to the GitHub API (if any). + public string GitHubPassword { get; set; } + /**** ** Nexus Mods ****/ diff --git a/src/StardewModdingAPI.Web/Framework/ModRepositories/GitHubRepository.cs b/src/StardewModdingAPI.Web/Framework/ModRepositories/GitHubRepository.cs index c5772ad9..67e706ed 100644 --- a/src/StardewModdingAPI.Web/Framework/ModRepositories/GitHubRepository.cs +++ b/src/StardewModdingAPI.Web/Framework/ModRepositories/GitHubRepository.cs @@ -35,7 +35,9 @@ namespace StardewModdingAPI.Web.Framework.ModRepositories /// The URL for a Nexus Mods API query excluding the , where {0} is the mod ID. /// The user agent for the GitHub API client. /// The Accept header value expected by the GitHub API. - public GitHubRepository(string vendorKey, string baseUrl, string releaseUrlFormat, string userAgent, string acceptHeader) + /// The username with which to authenticate to the GitHub API. + /// The password with which to authenticate to the GitHub API. + public GitHubRepository(string vendorKey, string baseUrl, string releaseUrlFormat, string userAgent, string acceptHeader, string username, string password) { this.VendorKey = vendorKey; this.ReleaseUrlFormat = releaseUrlFormat; @@ -43,6 +45,8 @@ namespace StardewModdingAPI.Web.Framework.ModRepositories this.Client = new FluentClient(baseUrl) .SetUserAgent(string.Format(userAgent, this.GetType().Assembly.GetName().Version)) .AddDefault(req => req.WithHeader("Accept", acceptHeader)); + if (!string.IsNullOrWhiteSpace(username)) + this.Client = this.Client.SetBasicAuthentication(username, password); } /// Get metadata about a mod in the repository. diff --git a/src/StardewModdingAPI.Web/Startup.cs b/src/StardewModdingAPI.Web/Startup.cs index b668c63e..4806aefe 100644 --- a/src/StardewModdingAPI.Web/Startup.cs +++ b/src/StardewModdingAPI.Web/Startup.cs @@ -29,6 +29,7 @@ namespace StardewModdingAPI.Web { this.Configuration = new ConfigurationBuilder() .SetBasePath(env.ContentRootPath) + .AddEnvironmentVariables() .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) .AddEnvironmentVariables() diff --git a/src/StardewModdingAPI.Web/appsettings.json b/src/StardewModdingAPI.Web/appsettings.json index f996157c..29fb195e 100644 --- a/src/StardewModdingAPI.Web/appsettings.json +++ b/src/StardewModdingAPI.Web/appsettings.json @@ -13,6 +13,8 @@ "GitHubBaseUrl": "https://api.github.com", "GitHubReleaseUrlFormat": "repos/{0}/releases/latest", "GitHubAcceptHeader": "application/vnd.github.v3+json", + "GitHubUsername": null, /* set via environment properties */ + "GitHubPassword": null, /* set via environment properties */ "NexusKey": "Nexus", "NexusUserAgent": "Nexus Client v0.63.15", -- cgit From c2d8760c563da5f6182cb9475a1bb12c8622f455 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 23 Sep 2017 14:15:59 -0400 Subject: make web controllers internal (#336) This is needed to support internal models, which is needed to share the models with the main SMAPI assembly without making them visible to mods. --- .../Controllers/ModsController.cs | 2 +- .../Framework/InternalControllerFeatureProvider.cs | 27 ++++++++++++++++++++++ .../Framework/RewriteSubdomainRule.cs | 2 +- src/StardewModdingAPI.Web/Models/ModInfoModel.cs | 2 +- src/StardewModdingAPI.Web/Models/ModSearchModel.cs | 2 +- src/StardewModdingAPI.Web/Startup.cs | 3 ++- 6 files changed, 33 insertions(+), 5 deletions(-) create mode 100644 src/StardewModdingAPI.Web/Framework/InternalControllerFeatureProvider.cs (limited to 'src/StardewModdingAPI.Web/Controllers') diff --git a/src/StardewModdingAPI.Web/Controllers/ModsController.cs b/src/StardewModdingAPI.Web/Controllers/ModsController.cs index 4715d379..79c31c3f 100644 --- a/src/StardewModdingAPI.Web/Controllers/ModsController.cs +++ b/src/StardewModdingAPI.Web/Controllers/ModsController.cs @@ -13,7 +13,7 @@ namespace StardewModdingAPI.Web.Controllers { /// Provides an API to perform mod update checks. [Produces("application/json")] - public class ModsController : Controller + internal class ModsController : Controller { /********* ** Properties diff --git a/src/StardewModdingAPI.Web/Framework/InternalControllerFeatureProvider.cs b/src/StardewModdingAPI.Web/Framework/InternalControllerFeatureProvider.cs new file mode 100644 index 00000000..2c24c610 --- /dev/null +++ b/src/StardewModdingAPI.Web/Framework/InternalControllerFeatureProvider.cs @@ -0,0 +1,27 @@ +using System; +using System.Reflection; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Controllers; + +namespace StardewModdingAPI.Web.Framework +{ + /// Discovers controllers with support for non-public controllers. + internal class InternalControllerFeatureProvider : ControllerFeatureProvider + { + /********* + ** Public methods + *********/ + /// Determines if a given type is a controller. + /// The candidate. + /// true if the type is a controller; otherwise false. + protected override bool IsController(TypeInfo type) + { + return + type.IsClass + && !type.IsAbstract + && (/*type.IsPublic &&*/ !type.ContainsGenericParameters) + && (!type.IsDefined(typeof(NonControllerAttribute)) + && (type.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase) || type.IsDefined(typeof(ControllerAttribute)))); + } + } +} diff --git a/src/StardewModdingAPI.Web/Framework/RewriteSubdomainRule.cs b/src/StardewModdingAPI.Web/Framework/RewriteSubdomainRule.cs index 9b89cb65..5a56844f 100644 --- a/src/StardewModdingAPI.Web/Framework/RewriteSubdomainRule.cs +++ b/src/StardewModdingAPI.Web/Framework/RewriteSubdomainRule.cs @@ -5,7 +5,7 @@ namespace StardewModdingAPI.Web.Framework { /// Rewrite requests to prepend the subdomain portion (if any) to the path. /// Derived from . - public class RewriteSubdomainRule : IRule + internal class RewriteSubdomainRule : IRule { /// Applies the rule. Implementations of ApplyRule should set the value for (defaults to RuleResult.ContinueRules). /// The rewrite context. diff --git a/src/StardewModdingAPI.Web/Models/ModInfoModel.cs b/src/StardewModdingAPI.Web/Models/ModInfoModel.cs index 180420cd..4e9762e5 100644 --- a/src/StardewModdingAPI.Web/Models/ModInfoModel.cs +++ b/src/StardewModdingAPI.Web/Models/ModInfoModel.cs @@ -3,7 +3,7 @@ using Newtonsoft.Json; namespace StardewModdingAPI.Web.Models { /// Generic metadata about a mod. - public class ModInfoModel + internal class ModInfoModel { /********* ** Accessors diff --git a/src/StardewModdingAPI.Web/Models/ModSearchModel.cs b/src/StardewModdingAPI.Web/Models/ModSearchModel.cs index b5bd12fb..b9a73af6 100644 --- a/src/StardewModdingAPI.Web/Models/ModSearchModel.cs +++ b/src/StardewModdingAPI.Web/Models/ModSearchModel.cs @@ -1,7 +1,7 @@ namespace StardewModdingAPI.Web.Models { /// Metadata for mods to look up. - public class ModSearchModel + internal class ModSearchModel { /// The namespaced mod keys to search. public string[] ModKeys { get; set; } diff --git a/src/StardewModdingAPI.Web/Startup.cs b/src/StardewModdingAPI.Web/Startup.cs index 4806aefe..d5b828b7 100644 --- a/src/StardewModdingAPI.Web/Startup.cs +++ b/src/StardewModdingAPI.Web/Startup.cs @@ -11,7 +11,7 @@ using StardewModdingAPI.Web.Framework.ConfigModels; namespace StardewModdingAPI.Web { /// The web app startup configuration. - public class Startup + internal class Startup { /********* ** Accessors @@ -44,6 +44,7 @@ namespace StardewModdingAPI.Web .Configure(this.Configuration.GetSection("ModUpdateCheck")) .AddMemoryCache() .AddMvc() + .ConfigureApplicationPartManager(manager => manager.FeatureProviders.Add(new InternalControllerFeatureProvider())) .AddJsonOptions(options => { options.SerializerSettings.Formatting = Formatting.Indented; -- cgit From 9ffe0bd37100336933a9768ceca2e6a65bdc0a58 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 23 Sep 2017 14:19:10 -0400 Subject: switch to the mods GET endpoint (#336) --- src/StardewModdingAPI.Web/Controllers/ModsController.cs | 15 ++------------- src/StardewModdingAPI.Web/Models/ModSearchModel.cs | 9 --------- 2 files changed, 2 insertions(+), 22 deletions(-) delete mode 100644 src/StardewModdingAPI.Web/Models/ModSearchModel.cs (limited to 'src/StardewModdingAPI.Web/Controllers') diff --git a/src/StardewModdingAPI.Web/Controllers/ModsController.cs b/src/StardewModdingAPI.Web/Controllers/ModsController.cs index 79c31c3f..06a80638 100644 --- a/src/StardewModdingAPI.Web/Controllers/ModsController.cs +++ b/src/StardewModdingAPI.Web/Controllers/ModsController.cs @@ -67,27 +67,16 @@ namespace StardewModdingAPI.Web.Controllers /// The namespaced mod keys to search as a comma-delimited array. [HttpGet] public async Task> GetAsync(string modKeys) - { - return await this.PostAsync(new ModSearchModel - { - ModKeys = modKeys?.Split(',').Select(p => p.Trim()).ToArray() ?? new string[0] - }); - } - - /// Fetch version metadata for the given mods. - /// The search options. - [HttpPost] - public async Task> PostAsync([FromBody] ModSearchModel search) { // sort & filter keys - string[] modKeys = (search.ModKeys ?? new string[0]) + string[] modKeysArray = (modKeys?.Split(',').Select(p => p.Trim()).ToArray() ?? new string[0]) .Distinct(StringComparer.CurrentCultureIgnoreCase) .OrderBy(p => p, StringComparer.CurrentCultureIgnoreCase) .ToArray(); // fetch mod info IDictionary result = new Dictionary(StringComparer.CurrentCultureIgnoreCase); - foreach (string modKey in modKeys) + foreach (string modKey in modKeysArray) { // parse mod key if (!this.TryParseModKey(modKey, out string vendorKey, out string modID)) diff --git a/src/StardewModdingAPI.Web/Models/ModSearchModel.cs b/src/StardewModdingAPI.Web/Models/ModSearchModel.cs deleted file mode 100644 index b9a73af6..00000000 --- a/src/StardewModdingAPI.Web/Models/ModSearchModel.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace StardewModdingAPI.Web.Models -{ - /// Metadata for mods to look up. - internal class ModSearchModel - { - /// The namespaced mod keys to search. - public string[] ModKeys { get; set; } - } -} -- cgit From bdee7f88e9ddbab51869431efbff90100b65bbd1 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 23 Sep 2017 14:37:58 -0400 Subject: move API models into shared project for reuse (#336) --- src/StardewModdingAPI.Models/ModInfoModel.cs | 48 ++++++++++++++++++++++ .../StardewModdingAPI.Models.projitems | 14 +++++++ .../StardewModdingAPI.Models.shproj | 13 ++++++ .../Controllers/ModsController.cs | 2 +- .../Framework/ModRepositories/GitHubRepository.cs | 2 +- .../Framework/ModRepositories/IModRepository.cs | 2 +- .../Framework/ModRepositories/NexusRepository.cs | 2 +- src/StardewModdingAPI.Web/Models/ModInfoModel.cs | 48 ---------------------- .../StardewModdingAPI.Web.csproj | 1 + src/StardewModdingAPI.sln | 13 +++++- 10 files changed, 92 insertions(+), 53 deletions(-) create mode 100644 src/StardewModdingAPI.Models/ModInfoModel.cs create mode 100644 src/StardewModdingAPI.Models/StardewModdingAPI.Models.projitems create mode 100644 src/StardewModdingAPI.Models/StardewModdingAPI.Models.shproj delete mode 100644 src/StardewModdingAPI.Web/Models/ModInfoModel.cs (limited to 'src/StardewModdingAPI.Web/Controllers') diff --git a/src/StardewModdingAPI.Models/ModInfoModel.cs b/src/StardewModdingAPI.Models/ModInfoModel.cs new file mode 100644 index 00000000..44071230 --- /dev/null +++ b/src/StardewModdingAPI.Models/ModInfoModel.cs @@ -0,0 +1,48 @@ +using Newtonsoft.Json; + +namespace StardewModdingAPI.Models +{ + /// Generic metadata about a mod. + internal class ModInfoModel + { + /********* + ** Accessors + *********/ + /// The mod name. + public string Name { get; } + + /// The mod's semantic version number. + public string Version { get; } + + /// The mod's web URL. + public string Url { get; } + + /// The error message indicating why the mod is invalid (if applicable). + public string Error { get; } + + + /********* + ** Public methods + *********/ + /// Construct a valid instance. + /// The mod name. + /// The mod's semantic version number. + /// The mod's web URL. + /// The error message indicating why the mod is invalid (if applicable). + [JsonConstructor] + public ModInfoModel(string name, string version, string url, string error = null) + { + this.Name = name; + this.Version = version; + this.Url = url; + this.Error = error; // mainly initialised here for the JSON deserialiser + } + + /// Construct an valid instance. + /// The error message indicating why the mod is invalid. + public ModInfoModel(string error) + { + this.Error = error; + } + } +} diff --git a/src/StardewModdingAPI.Models/StardewModdingAPI.Models.projitems b/src/StardewModdingAPI.Models/StardewModdingAPI.Models.projitems new file mode 100644 index 00000000..2465760e --- /dev/null +++ b/src/StardewModdingAPI.Models/StardewModdingAPI.Models.projitems @@ -0,0 +1,14 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + 2aa02fb6-ff03-41cf-a215-2ee60ab4f5dc + + + StardewModdingAPI.Models + + + + + \ No newline at end of file diff --git a/src/StardewModdingAPI.Models/StardewModdingAPI.Models.shproj b/src/StardewModdingAPI.Models/StardewModdingAPI.Models.shproj new file mode 100644 index 00000000..c80517af --- /dev/null +++ b/src/StardewModdingAPI.Models/StardewModdingAPI.Models.shproj @@ -0,0 +1,13 @@ + + + + 2aa02fb6-ff03-41cf-a215-2ee60ab4f5dc + 14.0 + + + + + + + + diff --git a/src/StardewModdingAPI.Web/Controllers/ModsController.cs b/src/StardewModdingAPI.Web/Controllers/ModsController.cs index 06a80638..8fc2cb51 100644 --- a/src/StardewModdingAPI.Web/Controllers/ModsController.cs +++ b/src/StardewModdingAPI.Web/Controllers/ModsController.cs @@ -7,7 +7,7 @@ using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Options; using StardewModdingAPI.Web.Framework.ConfigModels; using StardewModdingAPI.Web.Framework.ModRepositories; -using StardewModdingAPI.Web.Models; +using StardewModdingAPI.Models; namespace StardewModdingAPI.Web.Controllers { diff --git a/src/StardewModdingAPI.Web/Framework/ModRepositories/GitHubRepository.cs b/src/StardewModdingAPI.Web/Framework/ModRepositories/GitHubRepository.cs index 67e706ed..421220de 100644 --- a/src/StardewModdingAPI.Web/Framework/ModRepositories/GitHubRepository.cs +++ b/src/StardewModdingAPI.Web/Framework/ModRepositories/GitHubRepository.cs @@ -2,7 +2,7 @@ using System; using System.Threading.Tasks; using Newtonsoft.Json; using Pathoschild.Http.Client; -using StardewModdingAPI.Web.Models; +using StardewModdingAPI.Models; namespace StardewModdingAPI.Web.Framework.ModRepositories { diff --git a/src/StardewModdingAPI.Web/Framework/ModRepositories/IModRepository.cs b/src/StardewModdingAPI.Web/Framework/ModRepositories/IModRepository.cs index 7fd735cd..98e4c957 100644 --- a/src/StardewModdingAPI.Web/Framework/ModRepositories/IModRepository.cs +++ b/src/StardewModdingAPI.Web/Framework/ModRepositories/IModRepository.cs @@ -1,6 +1,6 @@ using System; using System.Threading.Tasks; -using StardewModdingAPI.Web.Models; +using StardewModdingAPI.Models; namespace StardewModdingAPI.Web.Framework.ModRepositories { diff --git a/src/StardewModdingAPI.Web/Framework/ModRepositories/NexusRepository.cs b/src/StardewModdingAPI.Web/Framework/ModRepositories/NexusRepository.cs index 312058ae..6cf5b04a 100644 --- a/src/StardewModdingAPI.Web/Framework/ModRepositories/NexusRepository.cs +++ b/src/StardewModdingAPI.Web/Framework/ModRepositories/NexusRepository.cs @@ -2,7 +2,7 @@ using System; using System.Threading.Tasks; using Newtonsoft.Json; using Pathoschild.Http.Client; -using StardewModdingAPI.Web.Models; +using StardewModdingAPI.Models; namespace StardewModdingAPI.Web.Framework.ModRepositories { diff --git a/src/StardewModdingAPI.Web/Models/ModInfoModel.cs b/src/StardewModdingAPI.Web/Models/ModInfoModel.cs deleted file mode 100644 index 4e9762e5..00000000 --- a/src/StardewModdingAPI.Web/Models/ModInfoModel.cs +++ /dev/null @@ -1,48 +0,0 @@ -using Newtonsoft.Json; - -namespace StardewModdingAPI.Web.Models -{ - /// Generic metadata about a mod. - internal class ModInfoModel - { - /********* - ** Accessors - *********/ - /// The mod name. - public string Name { get; } - - /// The mod's semantic version number. - public string Version { get; } - - /// The mod's web URL. - public string Url { get; } - - /// The error message indicating why the mod is invalid (if applicable). - public string Error { get; } - - - /********* - ** Public methods - *********/ - /// Construct a valid instance. - /// The mod name. - /// The mod's semantic version number. - /// The mod's web URL. - /// The error message indicating why the mod is invalid (if applicable). - [JsonConstructor] - public ModInfoModel(string name, string version, string url, string error = null) - { - this.Name = name; - this.Version = version; - this.Url = url; - this.Error = error; // mainly initialised here for the JSON deserialiser - } - - /// Construct an valid instance. - /// The error message indicating why the mod is invalid. - public ModInfoModel(string error) - { - this.Error = error; - } - } -} diff --git a/src/StardewModdingAPI.Web/StardewModdingAPI.Web.csproj b/src/StardewModdingAPI.Web/StardewModdingAPI.Web.csproj index 2af7c3df..c30abc55 100644 --- a/src/StardewModdingAPI.Web/StardewModdingAPI.Web.csproj +++ b/src/StardewModdingAPI.Web/StardewModdingAPI.Web.csproj @@ -15,5 +15,6 @@ + diff --git a/src/StardewModdingAPI.sln b/src/StardewModdingAPI.sln index 031e2d5f..bc2bb8b2 100644 --- a/src/StardewModdingAPI.sln +++ b/src/StardewModdingAPI.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.26730.15 +VisualStudioVersion = 15.0.26730.16 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TrainerMod", "TrainerMod\TrainerMod.csproj", "{28480467-1A48-46A7-99F8-236D95225359}" EndProject @@ -31,7 +31,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StardewModdingAPI.Tests", " EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StardewModdingAPI.Web", "StardewModdingAPI.Web\StardewModdingAPI.Web.csproj", "{A308F679-51A3-4006-92D5-BAEC7EBD01A1}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Internal", "Internal", "{82D22ED7-A0A7-4D64-8E92-4B6A5E74ED11}" +EndProject +Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "StardewModdingAPI.Models", "StardewModdingAPI.Models\StardewModdingAPI.Models.shproj", "{2AA02FB6-FF03-41CF-A215-2EE60AB4F5DC}" +EndProject Global + GlobalSection(SharedMSBuildProjectFiles) = preSolution + StardewModdingAPI.Models\StardewModdingAPI.Models.projitems*{2aa02fb6-ff03-41cf-a215-2ee60ab4f5dc}*SharedItemsImports = 13 + EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Debug|Mixed Platforms = Debug|Mixed Platforms @@ -97,6 +104,10 @@ Global GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {36CCB19E-92EB-48C7-9615-98EEFD45109B} = {82D22ED7-A0A7-4D64-8E92-4B6A5E74ED11} + {2AA02FB6-FF03-41CF-A215-2EE60AB4F5DC} = {82D22ED7-A0A7-4D64-8E92-4B6A5E74ED11} + EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {70143042-A862-47A8-A677-7C819DDC90DC} EndGlobalSection -- cgit