From 9791de306c22c744732219dadfd97b7dd556a5b2 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 21 Sep 2017 23:35:18 -0400 Subject: minor cleanup, formatting, documentation (#336) --- src/StardewModdingAPI.sln | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) (limited to 'src') diff --git a/src/StardewModdingAPI.sln b/src/StardewModdingAPI.sln index a2e0ec44..3cf129c0 100644 --- a/src/StardewModdingAPI.sln +++ b/src/StardewModdingAPI.sln @@ -29,6 +29,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StardewModdingAPI.Installer EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StardewModdingAPI.Tests", "StardewModdingAPI.Tests\StardewModdingAPI.Tests.csproj", "{36CCB19E-92EB-48C7-9615-98EEFD45109B}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dewdrop", "..\Dewdrop\Dewdrop.csproj", "{A308F679-51A3-4006-92D5-BAEC7EBD01A1}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -79,8 +81,23 @@ Global {36CCB19E-92EB-48C7-9615-98EEFD45109B}.Release|Mixed Platforms.Build.0 = Release|x86 {36CCB19E-92EB-48C7-9615-98EEFD45109B}.Release|x86.ActiveCfg = Release|x86 {36CCB19E-92EB-48C7-9615-98EEFD45109B}.Release|x86.Build.0 = Release|x86 + {A308F679-51A3-4006-92D5-BAEC7EBD01A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A308F679-51A3-4006-92D5-BAEC7EBD01A1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A308F679-51A3-4006-92D5-BAEC7EBD01A1}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {A308F679-51A3-4006-92D5-BAEC7EBD01A1}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {A308F679-51A3-4006-92D5-BAEC7EBD01A1}.Debug|x86.ActiveCfg = Debug|Any CPU + {A308F679-51A3-4006-92D5-BAEC7EBD01A1}.Debug|x86.Build.0 = Debug|Any CPU + {A308F679-51A3-4006-92D5-BAEC7EBD01A1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A308F679-51A3-4006-92D5-BAEC7EBD01A1}.Release|Any CPU.Build.0 = Release|Any CPU + {A308F679-51A3-4006-92D5-BAEC7EBD01A1}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {A308F679-51A3-4006-92D5-BAEC7EBD01A1}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {A308F679-51A3-4006-92D5-BAEC7EBD01A1}.Release|x86.ActiveCfg = Release|Any CPU + {A308F679-51A3-4006-92D5-BAEC7EBD01A1}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {70143042-A862-47A8-A677-7C819DDC90DC} + EndGlobalSection EndGlobal -- cgit 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) --- Dewdrop/Controllers/CheckController.cs | 66 ---------------------- Dewdrop/Dewdrop.csproj | 27 --------- Dewdrop/Models/IModModel.cs | 12 ---- Dewdrop/Models/ModGenericModel.cs | 27 --------- Dewdrop/Models/NexusResponseModel.cs | 41 -------------- Dewdrop/Program.cs | 27 --------- Dewdrop/Properties/launchSettings.json | 29 ---------- Dewdrop/Startup.cs | 52 ----------------- Dewdrop/appsettings.Development.json | 10 ---- Dewdrop/appsettings.json | 8 --- .../Controllers/CheckController.cs | 66 ++++++++++++++++++++++ src/StardewModdingAPI.Web/Models/IModModel.cs | 12 ++++ .../Models/ModGenericModel.cs | 27 +++++++++ .../Models/NexusResponseModel.cs | 41 ++++++++++++++ src/StardewModdingAPI.Web/Program.cs | 27 +++++++++ .../Properties/launchSettings.json | 29 ++++++++++ .../StardewModdingAPI.Web.csproj | 27 +++++++++ src/StardewModdingAPI.Web/Startup.cs | 52 +++++++++++++++++ .../appsettings.Development.json | 10 ++++ src/StardewModdingAPI.Web/appsettings.json | 8 +++ src/StardewModdingAPI.sln | 2 +- 21 files changed, 300 insertions(+), 300 deletions(-) delete mode 100644 Dewdrop/Controllers/CheckController.cs delete mode 100644 Dewdrop/Dewdrop.csproj delete mode 100644 Dewdrop/Models/IModModel.cs delete mode 100644 Dewdrop/Models/ModGenericModel.cs delete mode 100644 Dewdrop/Models/NexusResponseModel.cs delete mode 100644 Dewdrop/Program.cs delete mode 100644 Dewdrop/Properties/launchSettings.json delete mode 100644 Dewdrop/Startup.cs delete mode 100644 Dewdrop/appsettings.Development.json delete mode 100644 Dewdrop/appsettings.json create mode 100644 src/StardewModdingAPI.Web/Controllers/CheckController.cs create mode 100644 src/StardewModdingAPI.Web/Models/IModModel.cs create mode 100644 src/StardewModdingAPI.Web/Models/ModGenericModel.cs create mode 100644 src/StardewModdingAPI.Web/Models/NexusResponseModel.cs create mode 100644 src/StardewModdingAPI.Web/Program.cs create mode 100644 src/StardewModdingAPI.Web/Properties/launchSettings.json create mode 100644 src/StardewModdingAPI.Web/StardewModdingAPI.Web.csproj create mode 100644 src/StardewModdingAPI.Web/Startup.cs create mode 100644 src/StardewModdingAPI.Web/appsettings.Development.json create mode 100644 src/StardewModdingAPI.Web/appsettings.json (limited to 'src') diff --git a/Dewdrop/Controllers/CheckController.cs b/Dewdrop/Controllers/CheckController.cs deleted file mode 100644 index def2edbc..00000000 --- a/Dewdrop/Controllers/CheckController.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System; -using System.Net.Http; -using Microsoft.AspNetCore.Mvc; -using System.Threading.Tasks; -using Newtonsoft.Json; -using System.Collections.Generic; -using Dewdrop.Models; - -namespace Dewdrop.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 }); - } - } - } -} \ No newline at end of file diff --git a/Dewdrop/Dewdrop.csproj b/Dewdrop/Dewdrop.csproj deleted file mode 100644 index fa1d88eb..00000000 --- a/Dewdrop/Dewdrop.csproj +++ /dev/null @@ -1,27 +0,0 @@ - - - - netcoreapp1.1 - portable-net45+win8 - - - - - - - - - - - - - - - - - - - - - - diff --git a/Dewdrop/Models/IModModel.cs b/Dewdrop/Models/IModModel.cs deleted file mode 100644 index f1b09f8a..00000000 --- a/Dewdrop/Models/IModModel.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Dewdrop.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/Dewdrop/Models/ModGenericModel.cs b/Dewdrop/Models/ModGenericModel.cs deleted file mode 100644 index 0da04295..00000000 --- a/Dewdrop/Models/ModGenericModel.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace Dewdrop.Models -{ - /// Generic metadata about a mod. - public class ModGenericModel - { - /********* - ** Accessors - *********/ - /// The unique mod ID. - public int ID { get; set; } - - /// The mod name. - public string Name { get; set; } - - /// The mod's vendor ID. - public string Vendor { get; set; } - - /// The mod's semantic version number. - public string Version { get; set; } - - /// The mod's web URL. - public string Url { get; set; } - - /// Whether the mod is valid. - public bool Valid { get; set; } = true; - } -} diff --git a/Dewdrop/Models/NexusResponseModel.cs b/Dewdrop/Models/NexusResponseModel.cs deleted file mode 100644 index fa663910..00000000 --- a/Dewdrop/Models/NexusResponseModel.cs +++ /dev/null @@ -1,41 +0,0 @@ -using Newtonsoft.Json; - -namespace Dewdrop.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/Dewdrop/Program.cs b/Dewdrop/Program.cs deleted file mode 100644 index 0e831a23..00000000 --- a/Dewdrop/Program.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.IO; -using Microsoft.AspNetCore.Hosting; - -namespace Dewdrop -{ - /// The main app entry point. - public class Program - { - /********* - ** Public methods - *********/ - /// The main app entry point. - /// The command-line arguments. - public static void Main(string[] args) - { - // configure web server - new WebHostBuilder() - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseStartup() - .UseApplicationInsights() - .Build() - .Run(); - } - } -} diff --git a/Dewdrop/Properties/launchSettings.json b/Dewdrop/Properties/launchSettings.json deleted file mode 100644 index c15134dc..00000000 --- a/Dewdrop/Properties/launchSettings.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:59482/", - "sslPort": 0 - } - }, - "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "launchUrl": "api/check", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "Dewdrop": { - "commandName": "Project", - "launchBrowser": true, - "launchUrl": "api/check", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "applicationUrl": "http://localhost:59483" - } - } -} diff --git a/Dewdrop/Startup.cs b/Dewdrop/Startup.cs deleted file mode 100644 index d1618037..00000000 --- a/Dewdrop/Startup.cs +++ /dev/null @@ -1,52 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; - -namespace Dewdrop -{ - /// The web app startup configuration. - public class Startup - { - /********* - ** Accessors - *********/ - /// The web app configuration. - public IConfigurationRoot Configuration { get; } - - - /********* - ** Public methods - *********/ - /// Construct an instance. - /// The hosting environment. - public Startup(IHostingEnvironment env) - { - this.Configuration = new ConfigurationBuilder() - .SetBasePath(env.ContentRootPath) - .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) - .AddEnvironmentVariables() - .Build(); - } - - /// The method called by the runtime to add services to the container. - /// The service injection container. - public void ConfigureServices(IServiceCollection services) - { - services.AddMvc(); - } - - /// The method called by the runtime to configure the HTTP request pipeline. - /// The application builder. - /// The hosting environment. - /// The logger factory. - public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) - { - loggerFactory.AddConsole(this.Configuration.GetSection("Logging")); - loggerFactory.AddDebug(); - app.UseMvc(); - } - } -} diff --git a/Dewdrop/appsettings.Development.json b/Dewdrop/appsettings.Development.json deleted file mode 100644 index fa8ce71a..00000000 --- a/Dewdrop/appsettings.Development.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "Logging": { - "IncludeScopes": false, - "LogLevel": { - "Default": "Debug", - "System": "Information", - "Microsoft": "Information" - } - } -} diff --git a/Dewdrop/appsettings.json b/Dewdrop/appsettings.json deleted file mode 100644 index 5fff67ba..00000000 --- a/Dewdrop/appsettings.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "Logging": { - "IncludeScopes": false, - "LogLevel": { - "Default": "Warning" - } - } -} 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 }); + } + } + } +} diff --git a/src/StardewModdingAPI.Web/Models/IModModel.cs b/src/StardewModdingAPI.Web/Models/IModModel.cs new file mode 100644 index 00000000..2eadcaec --- /dev/null +++ b/src/StardewModdingAPI.Web/Models/IModModel.cs @@ -0,0 +1,12 @@ +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 new file mode 100644 index 00000000..208af416 --- /dev/null +++ b/src/StardewModdingAPI.Web/Models/ModGenericModel.cs @@ -0,0 +1,27 @@ +namespace StardewModdingAPI.Web.Models +{ + /// Generic metadata about a mod. + public class ModGenericModel + { + /********* + ** Accessors + *********/ + /// The unique mod ID. + public int ID { get; set; } + + /// The mod name. + public string Name { get; set; } + + /// The mod's vendor ID. + public string Vendor { get; set; } + + /// The mod's semantic version number. + public string Version { get; set; } + + /// The mod's web URL. + public string Url { get; set; } + + /// Whether the mod is valid. + public bool Valid { get; set; } = true; + } +} diff --git a/src/StardewModdingAPI.Web/Models/NexusResponseModel.cs b/src/StardewModdingAPI.Web/Models/NexusResponseModel.cs new file mode 100644 index 00000000..ae5c691c --- /dev/null +++ b/src/StardewModdingAPI.Web/Models/NexusResponseModel.cs @@ -0,0 +1,41 @@ +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/Program.cs b/src/StardewModdingAPI.Web/Program.cs new file mode 100644 index 00000000..5e258acc --- /dev/null +++ b/src/StardewModdingAPI.Web/Program.cs @@ -0,0 +1,27 @@ +using System.IO; +using Microsoft.AspNetCore.Hosting; + +namespace StardewModdingAPI.Web +{ + /// The main app entry point. + public class Program + { + /********* + ** Public methods + *********/ + /// The main app entry point. + /// The command-line arguments. + public static void Main(string[] args) + { + // configure web server + new WebHostBuilder() + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseStartup() + .UseApplicationInsights() + .Build() + .Run(); + } + } +} diff --git a/src/StardewModdingAPI.Web/Properties/launchSettings.json b/src/StardewModdingAPI.Web/Properties/launchSettings.json new file mode 100644 index 00000000..c15134dc --- /dev/null +++ b/src/StardewModdingAPI.Web/Properties/launchSettings.json @@ -0,0 +1,29 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:59482/", + "sslPort": 0 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "api/check", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Dewdrop": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "api/check", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "http://localhost:59483" + } + } +} diff --git a/src/StardewModdingAPI.Web/StardewModdingAPI.Web.csproj b/src/StardewModdingAPI.Web/StardewModdingAPI.Web.csproj new file mode 100644 index 00000000..fa1d88eb --- /dev/null +++ b/src/StardewModdingAPI.Web/StardewModdingAPI.Web.csproj @@ -0,0 +1,27 @@ + + + + netcoreapp1.1 + portable-net45+win8 + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/StardewModdingAPI.Web/Startup.cs b/src/StardewModdingAPI.Web/Startup.cs new file mode 100644 index 00000000..c7a5e8fe --- /dev/null +++ b/src/StardewModdingAPI.Web/Startup.cs @@ -0,0 +1,52 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace StardewModdingAPI.Web +{ + /// The web app startup configuration. + public class Startup + { + /********* + ** Accessors + *********/ + /// The web app configuration. + public IConfigurationRoot Configuration { get; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The hosting environment. + public Startup(IHostingEnvironment env) + { + this.Configuration = new ConfigurationBuilder() + .SetBasePath(env.ContentRootPath) + .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) + .AddEnvironmentVariables() + .Build(); + } + + /// The method called by the runtime to add services to the container. + /// The service injection container. + public void ConfigureServices(IServiceCollection services) + { + services.AddMvc(); + } + + /// The method called by the runtime to configure the HTTP request pipeline. + /// The application builder. + /// The hosting environment. + /// The logger factory. + public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) + { + loggerFactory.AddConsole(this.Configuration.GetSection("Logging")); + loggerFactory.AddDebug(); + app.UseMvc(); + } + } +} diff --git a/src/StardewModdingAPI.Web/appsettings.Development.json b/src/StardewModdingAPI.Web/appsettings.Development.json new file mode 100644 index 00000000..fa8ce71a --- /dev/null +++ b/src/StardewModdingAPI.Web/appsettings.Development.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "IncludeScopes": false, + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + } +} diff --git a/src/StardewModdingAPI.Web/appsettings.json b/src/StardewModdingAPI.Web/appsettings.json new file mode 100644 index 00000000..5fff67ba --- /dev/null +++ b/src/StardewModdingAPI.Web/appsettings.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "IncludeScopes": false, + "LogLevel": { + "Default": "Warning" + } + } +} diff --git a/src/StardewModdingAPI.sln b/src/StardewModdingAPI.sln index 3cf129c0..031e2d5f 100644 --- a/src/StardewModdingAPI.sln +++ b/src/StardewModdingAPI.sln @@ -29,7 +29,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StardewModdingAPI.Installer EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StardewModdingAPI.Tests", "StardewModdingAPI.Tests\StardewModdingAPI.Tests.csproj", "{36CCB19E-92EB-48C7-9615-98EEFD45109B}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dewdrop", "..\Dewdrop\Dewdrop.csproj", "{A308F679-51A3-4006-92D5-BAEC7EBD01A1}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StardewModdingAPI.Web", "StardewModdingAPI.Web\StardewModdingAPI.Web.csproj", "{A308F679-51A3-4006-92D5-BAEC7EBD01A1}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution -- 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') 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') 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') 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') 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') 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') 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') 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 7d703c9c5ca76a99fc3d430fa2b3fd22301a5ffb Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 22 Sep 2017 02:00:15 -0400 Subject: handle invalid Nexus mod IDs (#336) --- .../Framework/ModRepositories/NexusRepository.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/StardewModdingAPI.Web/Framework/ModRepositories/NexusRepository.cs b/src/StardewModdingAPI.Web/Framework/ModRepositories/NexusRepository.cs index 74eef2ef..7e3ce4b6 100644 --- a/src/StardewModdingAPI.Web/Framework/ModRepositories/NexusRepository.cs +++ b/src/StardewModdingAPI.Web/Framework/ModRepositories/NexusRepository.cs @@ -42,7 +42,10 @@ namespace StardewModdingAPI.Web.Framework.ModRepositories NexusResponseModel response = await this.Client .GetAsync($"mods/{id}") .As(); - return new ModInfoModel($"{this.VendorKey}:{id}", response.Name, response.Version, response.Url); + + 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."); } catch (Exception ex) { -- cgit From 399b98b36b6111d364702b117fff3c5f21b8783a Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 22 Sep 2017 02:06:54 -0400 Subject: suppress null properties in JSON responses (#336) --- src/StardewModdingAPI.Web/Startup.cs | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'src') diff --git a/src/StardewModdingAPI.Web/Startup.cs b/src/StardewModdingAPI.Web/Startup.cs index c1f03b34..2d9a95f1 100644 --- a/src/StardewModdingAPI.Web/Startup.cs +++ b/src/StardewModdingAPI.Web/Startup.cs @@ -5,6 +5,7 @@ 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 @@ -44,6 +45,11 @@ namespace StardewModdingAPI.Web // 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; }); } -- 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') 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 5619890abf866e608e667bc6cd81b5aba9e3a350 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 22 Sep 2017 18:19:42 -0400 Subject: indent JSON responses (#336) --- src/StardewModdingAPI.Web/Startup.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/StardewModdingAPI.Web/Startup.cs b/src/StardewModdingAPI.Web/Startup.cs index abae06ec..b35d072b 100644 --- a/src/StardewModdingAPI.Web/Startup.cs +++ b/src/StardewModdingAPI.Web/Startup.cs @@ -38,7 +38,11 @@ namespace StardewModdingAPI.Web { services .AddMvc() - .AddJsonOptions(options => options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore); // suppress null values in JSON responses + .AddJsonOptions(options => + { + options.SerializerSettings.Formatting = Formatting.Indented; + options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore; + }); } /// The method called by the runtime to configure the HTTP request pipeline. -- cgit From 89ca5952c51128c23c40b6264aa125ad7e5e2305 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 22 Sep 2017 19:24:31 -0400 Subject: update web API to .NET Core 2.0 so we can use more packages, update all packages (#336) --- .../StardewModdingAPI.Tests.csproj | 4 ++-- src/StardewModdingAPI.Tests/packages.config | 4 ++-- .../StardewModdingAPI.Web.csproj | 22 +++++++++++----------- 3 files changed, 15 insertions(+), 15 deletions(-) (limited to 'src') diff --git a/src/StardewModdingAPI.Tests/StardewModdingAPI.Tests.csproj b/src/StardewModdingAPI.Tests/StardewModdingAPI.Tests.csproj index f3dbcdd4..41525bcb 100644 --- a/src/StardewModdingAPI.Tests/StardewModdingAPI.Tests.csproj +++ b/src/StardewModdingAPI.Tests/StardewModdingAPI.Tests.csproj @@ -39,8 +39,8 @@ ..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll - - ..\packages\NUnit.3.7.1\lib\net45\nunit.framework.dll + + ..\packages\NUnit.3.8.1\lib\net45\nunit.framework.dll diff --git a/src/StardewModdingAPI.Tests/packages.config b/src/StardewModdingAPI.Tests/packages.config index 6f04e625..5fdfebdb 100644 --- a/src/StardewModdingAPI.Tests/packages.config +++ b/src/StardewModdingAPI.Tests/packages.config @@ -3,5 +3,5 @@ - - \ No newline at end of file + + diff --git a/src/StardewModdingAPI.Web/StardewModdingAPI.Web.csproj b/src/StardewModdingAPI.Web/StardewModdingAPI.Web.csproj index eee0f3f3..49f8ca23 100644 --- a/src/StardewModdingAPI.Web/StardewModdingAPI.Web.csproj +++ b/src/StardewModdingAPI.Web/StardewModdingAPI.Web.csproj @@ -1,7 +1,7 @@  - netcoreapp1.1 + netcoreapp2.0 portable-net45+win8 @@ -9,16 +9,16 @@ - - - - - - - - - - + + + + + + + + + + -- cgit From 24afbad3a909f70f3022fe57d06f6188a5b760bb Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 22 Sep 2017 19:55:57 -0400 Subject: remove unneeded project settings (#336) --- src/StardewModdingAPI.Web/StardewModdingAPI.Web.csproj | 4 ---- 1 file changed, 4 deletions(-) (limited to 'src') diff --git a/src/StardewModdingAPI.Web/StardewModdingAPI.Web.csproj b/src/StardewModdingAPI.Web/StardewModdingAPI.Web.csproj index 49f8ca23..01ed0c39 100644 --- a/src/StardewModdingAPI.Web/StardewModdingAPI.Web.csproj +++ b/src/StardewModdingAPI.Web/StardewModdingAPI.Web.csproj @@ -2,12 +2,8 @@ netcoreapp2.0 - portable-net45+win8 - - - -- cgit From 67ce5fcc459ac2e2eface6593592903dd1b68290 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 22 Sep 2017 20:09:06 -0400 Subject: remove unneeded dependencies (#336) --- src/StardewModdingAPI.Web/Program.cs | 3 +-- src/StardewModdingAPI.Web/StardewModdingAPI.Web.csproj | 6 ------ 2 files changed, 1 insertion(+), 8 deletions(-) (limited to 'src') diff --git a/src/StardewModdingAPI.Web/Program.cs b/src/StardewModdingAPI.Web/Program.cs index 5e258acc..eeecb791 100644 --- a/src/StardewModdingAPI.Web/Program.cs +++ b/src/StardewModdingAPI.Web/Program.cs @@ -1,4 +1,4 @@ -using System.IO; +using System.IO; using Microsoft.AspNetCore.Hosting; namespace StardewModdingAPI.Web @@ -19,7 +19,6 @@ namespace StardewModdingAPI.Web .UseContentRoot(Directory.GetCurrentDirectory()) .UseIISIntegration() .UseStartup() - .UseApplicationInsights() .Build() .Run(); } diff --git a/src/StardewModdingAPI.Web/StardewModdingAPI.Web.csproj b/src/StardewModdingAPI.Web/StardewModdingAPI.Web.csproj index 01ed0c39..7dff3fb6 100644 --- a/src/StardewModdingAPI.Web/StardewModdingAPI.Web.csproj +++ b/src/StardewModdingAPI.Web/StardewModdingAPI.Web.csproj @@ -5,16 +5,10 @@ - - - - - - -- cgit From f8566067e0874428c53ffdb6cd12d3f91ad4c51c Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 22 Sep 2017 20:09:22 -0400 Subject: update launch URL (#336) --- src/StardewModdingAPI.Web/Properties/launchSettings.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/StardewModdingAPI.Web/Properties/launchSettings.json b/src/StardewModdingAPI.Web/Properties/launchSettings.json index c15134dc..3acee14d 100644 --- a/src/StardewModdingAPI.Web/Properties/launchSettings.json +++ b/src/StardewModdingAPI.Web/Properties/launchSettings.json @@ -1,4 +1,4 @@ -{ +{ "iisSettings": { "windowsAuthentication": false, "anonymousAuthentication": true, @@ -11,7 +11,7 @@ "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, - "launchUrl": "api/check", + "launchUrl": "api/v1.0/mods", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } @@ -19,7 +19,7 @@ "Dewdrop": { "commandName": "Project", "launchBrowser": true, - "launchUrl": "api/check", + "launchUrl": "api/v1.0/mods", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" }, -- 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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 From e178ed14be24e3971d50addaebb3d13c19c18304 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 23 Sep 2017 18:04:56 -0400 Subject: migrate SMAPI update check to new web API (#336) --- src/StardewModdingAPI.sln | 1 + .../Framework/Models/GitRelease.cs | 19 ------ src/StardewModdingAPI/Framework/Models/SConfig.cs | 14 +++-- src/StardewModdingAPI/Framework/UpdateHelper.cs | 67 ++++++++++++++++------ src/StardewModdingAPI/Program.cs | 12 ++-- .../StardewModdingAPI.config.json | 13 ++++- src/StardewModdingAPI/StardewModdingAPI.csproj | 2 +- 7 files changed, 82 insertions(+), 46 deletions(-) delete mode 100644 src/StardewModdingAPI/Framework/Models/GitRelease.cs (limited to 'src') diff --git a/src/StardewModdingAPI.sln b/src/StardewModdingAPI.sln index bc2bb8b2..9d7baa51 100644 --- a/src/StardewModdingAPI.sln +++ b/src/StardewModdingAPI.sln @@ -38,6 +38,7 @@ EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution StardewModdingAPI.Models\StardewModdingAPI.Models.projitems*{2aa02fb6-ff03-41cf-a215-2ee60ab4f5dc}*SharedItemsImports = 13 + StardewModdingAPI.Models\StardewModdingAPI.Models.projitems*{f1a573b0-f436-472c-ae29-0b91ea6b9f8f}*SharedItemsImports = 4 EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU diff --git a/src/StardewModdingAPI/Framework/Models/GitRelease.cs b/src/StardewModdingAPI/Framework/Models/GitRelease.cs deleted file mode 100644 index bc53468f..00000000 --- a/src/StardewModdingAPI/Framework/Models/GitRelease.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Newtonsoft.Json; - -namespace StardewModdingAPI.Framework.Models -{ - /// Metadata about a GitHub release tag. - internal 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; } - } -} \ No newline at end of file diff --git a/src/StardewModdingAPI/Framework/Models/SConfig.cs b/src/StardewModdingAPI/Framework/Models/SConfig.cs index b2ca4113..36799400 100644 --- a/src/StardewModdingAPI/Framework/Models/SConfig.cs +++ b/src/StardewModdingAPI/Framework/Models/SConfig.cs @@ -1,4 +1,4 @@ -namespace StardewModdingAPI.Framework.Models +namespace StardewModdingAPI.Framework.Models { /// The SMAPI configuration settings. internal class SConfig @@ -9,11 +9,17 @@ /// Whether to enable development features. public bool DeveloperMode { get; set; } - /// Whether to check if a newer version of SMAPI is available on startup. - public bool CheckForUpdates { get; set; } = true; + /// Whether to check for newer versions of SMAPI and mods on startup. + public bool CheckForUpdates { get; set; } + + /// SMAPI's GitHub project name, used to perform update checks. + public string GitHubProjectName { get; set; } + + /// The base URL for SMAPI's web API, used to perform update checks. + public string WebApiBaseUrl { get; set; } /// Whether SMAPI should log more information about the game context. - public bool VerboseLogging { get; set; } = false; + public bool VerboseLogging { get; set; } /// A list of mod versions which should be considered compatible or incompatible regardless of whether SMAPI detects incompatible code. public ModCompatibility[] ModCompatibility { get; set; } diff --git a/src/StardewModdingAPI/Framework/UpdateHelper.cs b/src/StardewModdingAPI/Framework/UpdateHelper.cs index e01e55c8..0ee57648 100644 --- a/src/StardewModdingAPI/Framework/UpdateHelper.cs +++ b/src/StardewModdingAPI/Framework/UpdateHelper.cs @@ -1,36 +1,69 @@ -using System.IO; +using System; +using System.Collections.Generic; +using System.IO; using System.Net; -using System.Reflection; using System.Threading.Tasks; using Newtonsoft.Json; -using StardewModdingAPI.Framework.Models; +using StardewModdingAPI.Models; namespace StardewModdingAPI.Framework { - /// Provides utility methods for mod updates. - internal class UpdateHelper + /// Provides methods for interacting with the SMAPI web API. + internal class WebApiClient { + /********* + ** Properties + *********/ + /// The base URL for the web API. + private readonly Uri BaseUrl; + + /// The API version number. + private readonly ISemanticVersion Version; + + /********* ** Public methods *********/ - /// Get the latest release from a GitHub repository. - /// The name of the repository from which to fetch releases (like "cjsu/SMAPI"). - public static async Task GetLatestVersionAsync(string repository) + /// Construct an instance. + /// The base URL for the web API. + /// The web API version. + public WebApiClient(string baseUrl, ISemanticVersion version) + { +#if !SMAPI_FOR_WINDOWS + baseUrl = baseUrl.Replace("https://", "http://"); // workaround for OpenSSL issues with the game's bundled Mono on Linux/Mac +#endif + this.BaseUrl = new Uri(baseUrl); + this.Version = version; + } + + /// Get the latest SMAPI version. + /// The mod keys for which to fetch the latest version. + public async Task> GetModInfoAsync(params string[] modKeys) { - // build request - // (avoid HttpClient for Mac compatibility) - HttpWebRequest request = WebRequest.CreateHttp($"https://api.github.com/repos/{repository}/releases/latest"); - AssemblyName assembly = typeof(UpdateHelper).Assembly.GetName(); - request.UserAgent = $"{assembly.Name}/{assembly.Version}"; - request.Accept = "application/vnd.github.v3+json"; - - // fetch data + string url = $"v{this.Version}/mods?modKeys={Uri.EscapeDataString(string.Join(",", modKeys))}"; + return await this.GetAsync>(url); + } + + + /********* + ** Private methods + *********/ + /// Fetch the response from the backend API. + /// The expected response type. + /// The request URL, optionally excluding the base URL. + private async Task GetAsync(string url) + { + // build request (avoid HttpClient for Mac compatibility) + HttpWebRequest request = WebRequest.CreateHttp(new Uri(this.BaseUrl, url).ToString()); + request.UserAgent = $"SMAPI/{this.Version}"; + + // fetch data using (WebResponse response = await request.GetResponseAsync()) using (Stream responseStream = response.GetResponseStream()) using (StreamReader reader = new StreamReader(responseStream)) { string responseText = reader.ReadToEnd(); - return JsonConvert.DeserializeObject(responseText); + return JsonConvert.DeserializeObject(responseText); } } } diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index 84af2777..df94b02a 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -21,6 +21,7 @@ using StardewModdingAPI.Framework.ModHelpers; using StardewModdingAPI.Framework.ModLoading; using StardewModdingAPI.Framework.Reflection; using StardewModdingAPI.Framework.Serialisation; +using StardewModdingAPI.Models; using StardewValley; using Monitor = StardewModdingAPI.Framework.Monitor; using SObject = StardewValley.Object; @@ -572,10 +573,13 @@ namespace StardewModdingAPI { try { - GitRelease release = UpdateHelper.GetLatestVersionAsync(Constants.GitHubRepository).Result; - ISemanticVersion latestVersion = new SemanticVersion(release.Tag); - if (latestVersion.IsNewerThan(Constants.ApiVersion)) - this.Monitor.Log($"You can update SMAPI from version {Constants.ApiVersion} to {latestVersion}", LogLevel.Alert); + var client = new WebApiClient(this.Settings.WebApiBaseUrl, Constants.ApiVersion); + string key = $"GitHub:{this.Settings.GitHubProjectName}"; + ModInfoModel info = client.GetModInfoAsync(key).Result[key]; + if (info.Error != null) + this.Monitor.Log($"Couldn't check for a new version of SMAPI. This won't affect your game, but you may not be notified of new versions if this keeps happening.\n{info.Error}"); + else if (new SemanticVersion(info.Version).IsNewerThan(Constants.ApiVersion)) + this.Monitor.Log($"You can update SMAPI from version {Constants.ApiVersion} to {info.Version}.", LogLevel.Alert); } catch (Exception ex) { diff --git a/src/StardewModdingAPI/StardewModdingAPI.config.json b/src/StardewModdingAPI/StardewModdingAPI.config.json index d393f5a9..67d8f270 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.config.json +++ b/src/StardewModdingAPI/StardewModdingAPI.config.json @@ -1,4 +1,4 @@ -/* +/* @@ -21,6 +21,17 @@ This file contains advanced configuration for SMAPI. You generally shouldn't cha */ "CheckForUpdates": true, + /** + * SMAPI's GitHub project name, used to perform update checks. + */ + "GitHubProjectName": "Pathoschild/SMAPI", + + /** + * The base URL for SMAPI's web API, used to perform update checks. + * Note: the protocol will be changed to http:// on Linux/Mac due to OpenSSL issues with the game's bundled Mono. + */ + "WebApiBaseUrl": "https://api.smapi.io", + /** * Whether SMAPI should log more information about the game context. */ diff --git a/src/StardewModdingAPI/StardewModdingAPI.csproj b/src/StardewModdingAPI/StardewModdingAPI.csproj index 8da93bf4..d5fffd3f 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.csproj +++ b/src/StardewModdingAPI/StardewModdingAPI.csproj @@ -219,7 +219,6 @@ - @@ -274,6 +273,7 @@ false + \ No newline at end of file -- cgit From 873abef23563f5273e9b66d7b7e3cc2f5e4e0e92 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 23 Sep 2017 19:15:07 -0400 Subject: add mod update checks based on manifest fields (#336) --- release-notes.md | 5 +- src/StardewModdingAPI/Events/GameEvents.cs | 10 +- src/StardewModdingAPI/Framework/Models/Manifest.cs | 10 +- src/StardewModdingAPI/Framework/SGame.cs | 2 +- src/StardewModdingAPI/IManifest.cs | 10 +- src/StardewModdingAPI/Program.cs | 107 +++++++++++++++++++-- .../StardewModdingAPI.config.json | 6 +- 7 files changed, 123 insertions(+), 27 deletions(-) (limited to 'src') diff --git a/release-notes.md b/release-notes.md index c31d2bae..a07de71f 100644 --- a/release-notes.md +++ b/release-notes.md @@ -6,12 +6,15 @@ For players: * The console is now simpler and easier to read. * The console now adjusts its colors when you have a light terminal background. * SMAPI now detects mods which may impact game stability and shows a warning in the console. -* Updated compatibility list. +* SMAPI now alerts you in the console when one of your mods has a new version. * Renamed installer folder from `SMAPI 2.0` to `SMAPI 2.0 installer` to avoid confusion. +* Updated compatibility list. +* Fixed update check errors on Linux/Mac. For mod developers: * Added new APIs to edit, inject, and reload XNB assets loaded by the game at any time. _This let mods do anything previously only possible with XNB mods, plus enables new mod scenarios (e.g. seasonal textures, NPC clothing that depend on the weather or location, etc)._ +* Added new manifest fields to enable automatic update checks. * Added new input events. _The new `InputEvents` combine keyboard + mouse + controller input into one event for easy handling, add metadata like the cursor position and grab tile to support click handling, and add an option to suppress input from the game to enable new scenarios like action highjacking and UI overlays._ * Added support for optional dependencies. diff --git a/src/StardewModdingAPI/Events/GameEvents.cs b/src/StardewModdingAPI/Events/GameEvents.cs index 5610e67a..deb71a86 100644 --- a/src/StardewModdingAPI/Events/GameEvents.cs +++ b/src/StardewModdingAPI/Events/GameEvents.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Diagnostics.CodeAnalysis; using StardewModdingAPI.Framework; @@ -39,9 +39,6 @@ namespace StardewModdingAPI.Events /// Raised during launch after configuring XNA or MonoGame. The game window hasn't been opened by this point. Called after . internal static event EventHandler InitializeInternal; - /// Raised during launch after configuring Stardew Valley, loading it into memory, and opening the game window. The window is still blank by this point. - internal static event EventHandler GameLoadedInternal; - #if SMAPI_1_x /// Raised during launch after configuring XNA or MonoGame. The game window hasn't been opened by this point. Called after . [Obsolete("The " + nameof(Mod) + "." + nameof(Mod.Entry) + " method is now called after the " + nameof(GameEvents.Initialize) + " event, so any contained logic can be done directly in " + nameof(Mod.Entry) + ".")] @@ -143,19 +140,14 @@ namespace StardewModdingAPI.Events { monitor.SafelyRaisePlainEvent($"{nameof(GameEvents)}.{nameof(GameEvents.LoadContent)}", GameEvents._LoadContent?.GetInvocationList()); } -#endif /// Raise a event. /// Encapsulates monitoring and logging. internal static void InvokeGameLoaded(IMonitor monitor) { - monitor.SafelyRaisePlainEvent($"{nameof(GameEvents)}.{nameof(GameEvents.GameLoadedInternal)}", GameEvents.GameLoadedInternal?.GetInvocationList()); -#if SMAPI_1_x monitor.SafelyRaisePlainEvent($"{nameof(GameEvents)}.{nameof(GameEvents.GameLoaded)}", GameEvents._GameLoaded?.GetInvocationList()); -#endif } -#if SMAPI_1_x /// Raise a event. /// Encapsulates monitoring and logging. internal static void InvokeFirstUpdateTick(IMonitor monitor) diff --git a/src/StardewModdingAPI/Framework/Models/Manifest.cs b/src/StardewModdingAPI/Framework/Models/Manifest.cs index 29c3517e..f97cb8ff 100644 --- a/src/StardewModdingAPI/Framework/Models/Manifest.cs +++ b/src/StardewModdingAPI/Framework/Models/Manifest.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using Newtonsoft.Json; using StardewModdingAPI.Framework.Serialisation; @@ -35,6 +35,14 @@ namespace StardewModdingAPI.Framework.Models [JsonConverter(typeof(SFieldConverter))] public IManifestDependency[] Dependencies { get; set; } +#if !SMAPI_1_x + /// The mod's unique ID in Nexus Mods (if any), used for update checks. + public string NexusID { get; set; } + + /// The mod's organisation and project name on GitHub (if any), used for update checks. + public string GitHubProject { get; set; } +#endif + /// The unique mod ID. public string UniqueID { get; set; } diff --git a/src/StardewModdingAPI/Framework/SGame.cs b/src/StardewModdingAPI/Framework/SGame.cs index 76c106d7..387aeacc 100644 --- a/src/StardewModdingAPI/Framework/SGame.cs +++ b/src/StardewModdingAPI/Framework/SGame.cs @@ -297,8 +297,8 @@ namespace StardewModdingAPI.Framework GameEvents.InvokeInitialize(this.Monitor); #if SMAPI_1_x GameEvents.InvokeLoadContent(this.Monitor); -#endif GameEvents.InvokeGameLoaded(this.Monitor); +#endif } /********* diff --git a/src/StardewModdingAPI/IManifest.cs b/src/StardewModdingAPI/IManifest.cs index 407db1ce..28f6570c 100644 --- a/src/StardewModdingAPI/IManifest.cs +++ b/src/StardewModdingAPI/IManifest.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; namespace StardewModdingAPI { @@ -32,7 +32,13 @@ namespace StardewModdingAPI /// The other mods that must be loaded before this mod. IManifestDependency[] Dependencies { get; } + /// The mod's unique ID in Nexus Mods (if any), used for update checks. + string NexusID { get; set; } + + /// The mod's organisation and project name on GitHub (if any), used for update checks. + string GitHubProject { get; set; } + /// Any manifest fields which didn't match a valid field. IDictionary ExtraFields { get; } } -} \ No newline at end of file +} diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index df94b02a..cee3aefd 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -188,7 +188,6 @@ namespace StardewModdingAPI #endif this.GameInstance.Exiting += (sender, e) => this.Dispose(); GameEvents.InitializeInternal += (sender, e) => this.InitialiseAfterGameStart(); - GameEvents.GameLoadedInternal += (sender, e) => this.CheckForUpdateAsync(); ContentEvents.AfterLocaleChanged += (sender, e) => this.OnLocaleChanged(); // set window titles @@ -437,6 +436,9 @@ namespace StardewModdingAPI #else this.LoadMods(mods, new JsonHelper(), this.ContentManager); #endif + + // check for updates + this.CheckForUpdatesAsync(mods); } if (this.Monitor.IsExiting) { @@ -563,28 +565,113 @@ namespace StardewModdingAPI return !issuesFound; } - /// Asynchronously check for a new version of SMAPI, and print a message to the console if an update is available. - private void CheckForUpdateAsync() + /// Asynchronously check for a new version of SMAPI and any installed mods, and print alerts to the console if an update is available. + /// The mods to include in the update check (if eligible). + private void CheckForUpdatesAsync(IModMetadata[] mods) { if (!this.Settings.CheckForUpdates) return; new Thread(() => { + // update info + List updates = new List(); + bool smapiUpdate = false; + int modUpdates = 0; + + // create client + WebApiClient client = new WebApiClient(this.Settings.WebApiBaseUrl, Constants.ApiVersion); + + // fetch SMAPI version try { - var client = new WebApiClient(this.Settings.WebApiBaseUrl, Constants.ApiVersion); - string key = $"GitHub:{this.Settings.GitHubProjectName}"; - ModInfoModel info = client.GetModInfoAsync(key).Result[key]; - if (info.Error != null) - this.Monitor.Log($"Couldn't check for a new version of SMAPI. This won't affect your game, but you may not be notified of new versions if this keeps happening.\n{info.Error}"); - else if (new SemanticVersion(info.Version).IsNewerThan(Constants.ApiVersion)) - this.Monitor.Log($"You can update SMAPI from version {Constants.ApiVersion} to {info.Version}.", LogLevel.Alert); + ModInfoModel response = client.GetModInfoAsync($"GitHub:{this.Settings.GitHubProjectName}").Result.Single().Value; + if (response.Error != null) + this.Monitor.Log($"Couldn't check for a new version of SMAPI. This won't affect your game, but you may not be notified of new versions if this keeps happening.\n{response.Error}", LogLevel.Warn); + else if (new SemanticVersion(response.Version).IsNewerThan(Constants.ApiVersion)) + { + smapiUpdate = true; + updates.Add($"SMAPI {response.Version}: {response.Url}"); + } } catch (Exception ex) { this.Monitor.Log($"Couldn't check for a new version of SMAPI. This won't affect your game, but you may not be notified of new versions if this keeps happening.\n{ex.GetLogSummary()}"); } + + // fetch mod versions +#if !SMAPI_1_x + try + { + // prepare update-check data + IDictionary modsByKey = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + foreach (IModMetadata mod in mods) + { + if (!string.IsNullOrWhiteSpace(mod.Manifest.NexusID)) + modsByKey[$"Nexus:{mod.Manifest.NexusID}"] = mod; + if (!string.IsNullOrWhiteSpace(mod.Manifest.GitHubProject)) + modsByKey[$"GitHub:{mod.Manifest.GitHubProject}"] = mod; + } + + // fetch results + IDictionary response = client.GetModInfoAsync(modsByKey.Keys.ToArray()).Result; + IDictionary updatesByMod = new Dictionary(); + foreach (var entry in response) + { + // handle error + if (entry.Value.Error != null) + { + this.Monitor.Log($"Couldn't fetch version of {modsByKey[entry.Key].DisplayName} with key {entry.Key}:\n{entry.Value.Error}", LogLevel.Trace); + continue; + } + + // collect latest mod version + IModMetadata mod = modsByKey[entry.Key]; + ISemanticVersion version = new SemanticVersion(entry.Value.Version); + if (version.IsNewerThan(mod.Manifest.Version)) + { + if (!updatesByMod.TryGetValue(mod, out ModInfoModel other) || version.IsNewerThan(other.Version)) + { + updatesByMod[mod] = entry.Value; + modUpdates++; + } + } + } + + // add to output queue + if (updatesByMod.Any()) + { + foreach (var entry in updatesByMod.OrderBy(p => p.Key.DisplayName)) + updates.Add($"{entry.Key.DisplayName} {entry.Value.Version}: {entry.Value.Url}"); + } + } + catch (Exception ex) + { + this.Monitor.Log($"Couldn't check for new mod versions:\n{ex.GetLogSummary()}", LogLevel.Trace); + } +#endif + + // output + if (updates.Any()) + { +#if !SMAPI_1_x + this.Monitor.Newline(); +#endif + + // print intro + string intro = ""; + if (smapiUpdate) + intro = "You can update SMAPI"; + if (modUpdates > 0) + intro += $"{(smapiUpdate ? " and" : "You can update")} {modUpdates} mod{(modUpdates != 1 ? "s" : "")}"; + intro += ":"; + this.Monitor.Log(intro, LogLevel.Alert); + + // print update list + foreach (string line in updates) + this.Monitor.Log($" {line}", LogLevel.Alert); + } + }).Start(); } diff --git a/src/StardewModdingAPI/StardewModdingAPI.config.json b/src/StardewModdingAPI/StardewModdingAPI.config.json index 67d8f270..c91d169c 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.config.json +++ b/src/StardewModdingAPI/StardewModdingAPI.config.json @@ -15,9 +15,9 @@ This file contains advanced configuration for SMAPI. You generally shouldn't cha "DeveloperMode": true, /** - * Whether SMAPI should check for a newer version when you load the game. If a new version is - * available, a small message will appear in the console. This doesn't affect the load time even - * if your connection is offline or slow, because it happens in the background. + * Whether SMAPI should check for newer versions of SMAPI and mods when you load the game. If new + * versions are available, an alert will be shown in the console. This doesn't affect the load + * time even if your connection is offline or slow, because it happens in the background. */ "CheckForUpdates": true, -- cgit From 57111a6e8fa6a23bb56f515b50f8b7ea5924d49f Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 23 Sep 2017 20:03:45 -0400 Subject: update file name (#336) --- src/StardewModdingAPI/Framework/UpdateHelper.cs | 70 ------------------------- src/StardewModdingAPI/Framework/WebApiClient.cs | 70 +++++++++++++++++++++++++ src/StardewModdingAPI/StardewModdingAPI.csproj | 2 +- 3 files changed, 71 insertions(+), 71 deletions(-) delete mode 100644 src/StardewModdingAPI/Framework/UpdateHelper.cs create mode 100644 src/StardewModdingAPI/Framework/WebApiClient.cs (limited to 'src') diff --git a/src/StardewModdingAPI/Framework/UpdateHelper.cs b/src/StardewModdingAPI/Framework/UpdateHelper.cs deleted file mode 100644 index 0ee57648..00000000 --- a/src/StardewModdingAPI/Framework/UpdateHelper.cs +++ /dev/null @@ -1,70 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Net; -using System.Threading.Tasks; -using Newtonsoft.Json; -using StardewModdingAPI.Models; - -namespace StardewModdingAPI.Framework -{ - /// Provides methods for interacting with the SMAPI web API. - internal class WebApiClient - { - /********* - ** Properties - *********/ - /// The base URL for the web API. - private readonly Uri BaseUrl; - - /// The API version number. - private readonly ISemanticVersion Version; - - - /********* - ** Public methods - *********/ - /// Construct an instance. - /// The base URL for the web API. - /// The web API version. - public WebApiClient(string baseUrl, ISemanticVersion version) - { -#if !SMAPI_FOR_WINDOWS - baseUrl = baseUrl.Replace("https://", "http://"); // workaround for OpenSSL issues with the game's bundled Mono on Linux/Mac -#endif - this.BaseUrl = new Uri(baseUrl); - this.Version = version; - } - - /// Get the latest SMAPI version. - /// The mod keys for which to fetch the latest version. - public async Task> GetModInfoAsync(params string[] modKeys) - { - string url = $"v{this.Version}/mods?modKeys={Uri.EscapeDataString(string.Join(",", modKeys))}"; - return await this.GetAsync>(url); - } - - - /********* - ** Private methods - *********/ - /// Fetch the response from the backend API. - /// The expected response type. - /// The request URL, optionally excluding the base URL. - private async Task GetAsync(string url) - { - // build request (avoid HttpClient for Mac compatibility) - HttpWebRequest request = WebRequest.CreateHttp(new Uri(this.BaseUrl, url).ToString()); - request.UserAgent = $"SMAPI/{this.Version}"; - - // fetch data - using (WebResponse response = await request.GetResponseAsync()) - using (Stream responseStream = response.GetResponseStream()) - using (StreamReader reader = new StreamReader(responseStream)) - { - string responseText = reader.ReadToEnd(); - return JsonConvert.DeserializeObject(responseText); - } - } - } -} diff --git a/src/StardewModdingAPI/Framework/WebApiClient.cs b/src/StardewModdingAPI/Framework/WebApiClient.cs new file mode 100644 index 00000000..0ee57648 --- /dev/null +++ b/src/StardewModdingAPI/Framework/WebApiClient.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Threading.Tasks; +using Newtonsoft.Json; +using StardewModdingAPI.Models; + +namespace StardewModdingAPI.Framework +{ + /// Provides methods for interacting with the SMAPI web API. + internal class WebApiClient + { + /********* + ** Properties + *********/ + /// The base URL for the web API. + private readonly Uri BaseUrl; + + /// The API version number. + private readonly ISemanticVersion Version; + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The base URL for the web API. + /// The web API version. + public WebApiClient(string baseUrl, ISemanticVersion version) + { +#if !SMAPI_FOR_WINDOWS + baseUrl = baseUrl.Replace("https://", "http://"); // workaround for OpenSSL issues with the game's bundled Mono on Linux/Mac +#endif + this.BaseUrl = new Uri(baseUrl); + this.Version = version; + } + + /// Get the latest SMAPI version. + /// The mod keys for which to fetch the latest version. + public async Task> GetModInfoAsync(params string[] modKeys) + { + string url = $"v{this.Version}/mods?modKeys={Uri.EscapeDataString(string.Join(",", modKeys))}"; + return await this.GetAsync>(url); + } + + + /********* + ** Private methods + *********/ + /// Fetch the response from the backend API. + /// The expected response type. + /// The request URL, optionally excluding the base URL. + private async Task GetAsync(string url) + { + // build request (avoid HttpClient for Mac compatibility) + HttpWebRequest request = WebRequest.CreateHttp(new Uri(this.BaseUrl, url).ToString()); + request.UserAgent = $"SMAPI/{this.Version}"; + + // fetch data + using (WebResponse response = await request.GetResponseAsync()) + using (Stream responseStream = response.GetResponseStream()) + using (StreamReader reader = new StreamReader(responseStream)) + { + string responseText = reader.ReadToEnd(); + return JsonConvert.DeserializeObject(responseText); + } + } + } +} diff --git a/src/StardewModdingAPI/StardewModdingAPI.csproj b/src/StardewModdingAPI/StardewModdingAPI.csproj index d5fffd3f..07e98674 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.csproj +++ b/src/StardewModdingAPI/StardewModdingAPI.csproj @@ -218,7 +218,7 @@ - + -- cgit