summaryrefslogtreecommitdiff
path: root/src/SMAPI.Web/Startup.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/SMAPI.Web/Startup.cs')
-rw-r--r--src/SMAPI.Web/Startup.cs220
1 files changed, 148 insertions, 72 deletions
diff --git a/src/SMAPI.Web/Startup.cs b/src/SMAPI.Web/Startup.cs
index 56ef9a79..dee2edc2 100644
--- a/src/SMAPI.Web/Startup.cs
+++ b/src/SMAPI.Web/Startup.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Net;
using Hangfire;
using Hangfire.MemoryStorage;
using Hangfire.Mongo;
@@ -27,7 +28,7 @@ using StardewModdingAPI.Web.Framework.Clients.Nexus;
using StardewModdingAPI.Web.Framework.Clients.Pastebin;
using StardewModdingAPI.Web.Framework.Compression;
using StardewModdingAPI.Web.Framework.ConfigModels;
-using StardewModdingAPI.Web.Framework.RewriteRules;
+using StardewModdingAPI.Web.Framework.RedirectRules;
using StardewModdingAPI.Web.Framework.Storage;
namespace StardewModdingAPI.Web
@@ -47,7 +48,7 @@ namespace StardewModdingAPI.Web
*********/
/// <summary>Construct an instance.</summary>
/// <param name="env">The hosting environment.</param>
- public Startup(IHostingEnvironment env)
+ public Startup(IWebHostEnvironment env)
{
this.Configuration = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
@@ -67,70 +68,91 @@ namespace StardewModdingAPI.Web
.Configure<BackgroundServicesConfig>(this.Configuration.GetSection("BackgroundServices"))
.Configure<ModCompatibilityListConfig>(this.Configuration.GetSection("ModCompatibilityList"))
.Configure<ModUpdateCheckConfig>(this.Configuration.GetSection("ModUpdateCheck"))
- .Configure<MongoDbConfig>(this.Configuration.GetSection("MongoDB"))
+ .Configure<StorageConfig>(this.Configuration.GetSection("Storage"))
.Configure<SiteConfig>(this.Configuration.GetSection("Site"))
.Configure<RouteOptions>(options => options.ConstraintMap.Add("semanticVersion", typeof(VersionConstraint)))
.AddLogging()
- .AddMemoryCache()
- .AddMvc()
- .ConfigureApplicationPartManager(manager => manager.FeatureProviders.Add(new InternalControllerFeatureProvider()))
- .AddJsonOptions(options =>
- {
- foreach (JsonConverter converter in new JsonHelper().JsonSettings.Converters)
- options.SerializerSettings.Converters.Add(converter);
+ .AddMemoryCache();
+ StorageConfig storageConfig = this.Configuration.GetSection("Storage").Get<StorageConfig>();
+ StorageMode storageMode = storageConfig.Mode;
- options.SerializerSettings.Formatting = Formatting.Indented;
- options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
- });
- MongoDbConfig mongoConfig = this.Configuration.GetSection("MongoDB").Get<MongoDbConfig>();
+ // init MVC
+ services
+ .AddControllers()
+ .AddNewtonsoftJson(options => this.ConfigureJsonNet(options.SerializerSettings))
+ .ConfigureApplicationPartManager(manager => manager.FeatureProviders.Add(new InternalControllerFeatureProvider()));
+ services
+ .AddRazorPages();
- // init background service
+ // init storage
+ switch (storageMode)
{
- BackgroundServicesConfig config = this.Configuration.GetSection("BackgroundServices").Get<BackgroundServicesConfig>();
- if (config.Enabled)
- services.AddHostedService<BackgroundService>();
- }
+ case StorageMode.InMemory:
+ services.AddSingleton<IModCacheRepository>(new ModCacheMemoryRepository());
+ services.AddSingleton<IWikiCacheRepository>(new WikiCacheMemoryRepository());
+ break;
- // init MongoDB
- services.AddSingleton<MongoDbRunner>(serv => !mongoConfig.IsConfigured()
- ? MongoDbRunner.Start()
- : throw new InvalidOperationException("The MongoDB connection is configured, so the local development version should not be used.")
- );
- services.AddSingleton<IMongoDatabase>(serv =>
- {
- // get connection string
- string connectionString = mongoConfig.IsConfigured()
- ? mongoConfig.ConnectionString
- : serv.GetRequiredService<MongoDbRunner>().ConnectionString;
-
- // get client
- BsonSerializer.RegisterSerializer(new UtcDateTimeOffsetSerializer());
- return new MongoClient(connectionString).GetDatabase(mongoConfig.Database);
- });
- services.AddSingleton<IModCacheRepository>(serv => new ModCacheRepository(serv.GetRequiredService<IMongoDatabase>()));
- services.AddSingleton<IWikiCacheRepository>(serv => new WikiCacheRepository(serv.GetRequiredService<IMongoDatabase>()));
+ case StorageMode.Mongo:
+ case StorageMode.MongoInMemory:
+ {
+ // local MongoDB instance
+ services.AddSingleton<MongoDbRunner>(_ => storageMode == StorageMode.MongoInMemory
+ ? MongoDbRunner.Start()
+ : throw new NotSupportedException($"The in-memory MongoDB runner isn't available in storage mode {storageMode}.")
+ );
+
+ // MongoDB
+ services.AddSingleton<IMongoDatabase>(serv =>
+ {
+ BsonSerializer.RegisterSerializer(new UtcDateTimeOffsetSerializer());
+ return new MongoClient(this.GetMongoDbConnectionString(serv, storageConfig))
+ .GetDatabase(storageConfig.Database);
+ });
+
+ // repositories
+ services.AddSingleton<IModCacheRepository>(serv => new ModCacheMongoRepository(serv.GetRequiredService<IMongoDatabase>()));
+ services.AddSingleton<IWikiCacheRepository>(serv => new WikiCacheMongoRepository(serv.GetRequiredService<IMongoDatabase>()));
+ }
+ break;
+
+ default:
+ throw new NotSupportedException($"Unhandled storage mode '{storageMode}'.");
+ }
// init Hangfire
services
- .AddHangfire(config =>
+ .AddHangfire((serv, config) =>
{
config
.SetDataCompatibilityLevel(CompatibilityLevel.Version_170)
.UseSimpleAssemblyNameTypeSerializer()
.UseRecommendedSerializerSettings();
- if (mongoConfig.IsConfigured())
+ switch (storageMode)
{
- config.UseMongoStorage(mongoConfig.ConnectionString, $"{mongoConfig.Database}-hangfire", new MongoStorageOptions
- {
- MigrationOptions = new MongoMigrationOptions(MongoMigrationStrategy.Drop),
- CheckConnection = false // error on startup takes down entire process
- });
+ case StorageMode.InMemory:
+ config.UseMemoryStorage();
+ break;
+
+ case StorageMode.MongoInMemory:
+ case StorageMode.Mongo:
+ string connectionString = this.GetMongoDbConnectionString(serv, storageConfig);
+ config.UseMongoStorage(MongoClientSettings.FromConnectionString(connectionString), $"{storageConfig.Database}-hangfire", new MongoStorageOptions
+ {
+ MigrationOptions = new MongoMigrationOptions(MongoMigrationStrategy.Drop),
+ CheckConnection = false // error on startup takes down entire process
+ });
+ break;
}
- else
- config.UseMemoryStorage();
});
+ // init background service
+ {
+ BackgroundServicesConfig config = this.Configuration.GetSection("BackgroundServices").Get<BackgroundServicesConfig>();
+ if (config.Enabled)
+ services.AddHostedService<BackgroundService>();
+ }
+
// init API clients
{
ApiClientsConfig api = this.Configuration.GetSection("ApiClients").Get<ApiClientsConfig>();
@@ -142,6 +164,7 @@ namespace StardewModdingAPI.Web
baseUrl: api.ChucklefishBaseUrl,
modPageUrlFormat: api.ChucklefishModPageUrlFormat
));
+
services.AddSingleton<ICurseForgeClient>(new CurseForgeClient(
userAgent: userAgent,
apiUrl: api.CurseForgeBaseUrl
@@ -188,8 +211,7 @@ namespace StardewModdingAPI.Web
/// <summary>The method called by the runtime to configure the HTTP request pipeline.</summary>
/// <param name="app">The application builder.</param>
- /// <param name="env">The hosting environment.</param>
- public void Configure(IApplicationBuilder app, IHostingEnvironment env)
+ public void Configure(IApplicationBuilder app)
{
// basic config
app.UseDeveloperExceptionPage();
@@ -201,7 +223,13 @@ namespace StardewModdingAPI.Web
)
.UseRewriter(this.GetRedirectRules())
.UseStaticFiles() // wwwroot folder
- .UseMvc();
+ .UseRouting()
+ .UseAuthorization()
+ .UseEndpoints(p =>
+ {
+ p.MapControllers();
+ p.MapRazorPages();
+ });
// enable Hangfire dashboard
app.UseHangfireDashboard("/tasks", new DashboardOptions
@@ -215,29 +243,77 @@ namespace StardewModdingAPI.Web
/*********
** Private methods
*********/
+ /// <summary>Configure a Json.NET serializer.</summary>
+ /// <param name="settings">The serializer settings to edit.</param>
+ private void ConfigureJsonNet(JsonSerializerSettings settings)
+ {
+ foreach (JsonConverter converter in new JsonHelper().JsonSettings.Converters)
+ settings.Converters.Add(converter);
+
+ settings.Formatting = Formatting.Indented;
+ settings.NullValueHandling = NullValueHandling.Ignore;
+ }
+
+ /// <summary>Get the MongoDB connection string for the given storage configuration.</summary>
+ /// <param name="services">The service provider.</param>
+ /// <param name="storageConfig">The storage configuration</param>
+ /// <exception cref="NotSupportedException">There's no MongoDB instance in the given storage mode.</exception>
+ private string GetMongoDbConnectionString(IServiceProvider services, StorageConfig storageConfig)
+ {
+ return storageConfig.Mode switch
+ {
+ StorageMode.Mongo => storageConfig.ConnectionString,
+ StorageMode.MongoInMemory => services.GetRequiredService<MongoDbRunner>().ConnectionString,
+ _ => throw new NotSupportedException($"There's no MongoDB instance in storage mode {storageConfig.Mode}.")
+ };
+ }
+
/// <summary>Get the redirect rules to apply.</summary>
private RewriteOptions GetRedirectRules()
{
- var redirects = new RewriteOptions();
-
- // redirect to HTTPS (except API for Linux/Mac Mono compatibility)
- redirects.Add(new ConditionalRedirectToHttpsRule(
- shouldRewrite: req =>
- req.Host.Host != "localhost"
- && !req.Path.StartsWithSegments("/api")
- ));
-
- // shortcut redirects
- redirects.Add(new RedirectToUrlRule(@"^/3\.0\.?$", "https://stardewvalleywiki.com/Modding:Migrate_to_SMAPI_3.0"));
- redirects.Add(new RedirectToUrlRule(@"^/(?:buildmsg|package)(?:/?(.*))$", "https://github.com/Pathoschild/SMAPI/blob/develop/docs/technical/mod-package.md#$1")); // buildmsg deprecated, remove when SDV 1.4 is released
- redirects.Add(new RedirectToUrlRule(@"^/community\.?$", "https://stardewvalleywiki.com/Modding:Community"));
- redirects.Add(new RedirectToUrlRule(@"^/compat\.?$", "https://smapi.io/mods"));
- redirects.Add(new RedirectToUrlRule(@"^/docs\.?$", "https://stardewvalleywiki.com/Modding:Index"));
- redirects.Add(new RedirectToUrlRule(@"^/install\.?$", "https://stardewvalleywiki.com/Modding:Player_Guide/Getting_Started#Install_SMAPI"));
- redirects.Add(new RedirectToUrlRule(@"^/troubleshoot(.*)$", "https://stardewvalleywiki.com/Modding:Player_Guide/Troubleshooting$1"));
- redirects.Add(new RedirectToUrlRule(@"^/xnb\.?$", "https://stardewvalleywiki.com/Modding:Using_XNB_mods"));
-
- // redirect legacy canimod.com URLs
+ var redirects = new RewriteOptions()
+ // shortcut paths
+ .Add(new RedirectPathsToUrlsRule(new Dictionary<string, string>
+ {
+ [@"^/3\.0\.?$"] = "https://stardewvalleywiki.com/Modding:Migrate_to_SMAPI_3.0",
+ [@"^/(?:buildmsg|package)(?:/?(.*))$"] = "https://github.com/Pathoschild/SMAPI/blob/develop/docs/technical/mod-package.md#$1", // buildmsg deprecated, remove when SDV 1.4 is released
+ [@"^/community\.?$"] = "https://stardewvalleywiki.com/Modding:Community",
+ [@"^/compat\.?$"] = "https://smapi.io/mods",
+ [@"^/docs\.?$"] = "https://stardewvalleywiki.com/Modding:Index",
+ [@"^/install\.?$"] = "https://stardewvalleywiki.com/Modding:Player_Guide/Getting_Started#Install_SMAPI",
+ [@"^/troubleshoot(.*)$"] = "https://stardewvalleywiki.com/Modding:Player_Guide/Troubleshooting$1",
+ [@"^/xnb\.?$"] = "https://stardewvalleywiki.com/Modding:Using_XNB_mods"
+ }))
+
+ // legacy paths
+ .Add(new RedirectPathsToUrlsRule(this.GetLegacyPathRedirects()))
+
+ // subdomains
+ .Add(new RedirectHostsToUrlsRule(HttpStatusCode.PermanentRedirect, host => host switch
+ {
+ "api.smapi.io" => "smapi.io/api",
+ "json.smapi.io" => "smapi.io/json",
+ "log.smapi.io" => "smapi.io/log",
+ "mods.smapi.io" => "smapi.io/mods",
+ _ => host.EndsWith(".smapi.io")
+ ? "smapi.io"
+ : null
+ }))
+
+ // redirect to HTTPS (except API for Linux/Mac Mono compatibility)
+ .Add(
+ new RedirectToHttpsRule(except: req => req.Host.Host == "localhost" || req.Path.StartsWithSegments("/api"))
+ );
+
+ return redirects;
+ }
+
+ /// <summary>Get the redirects for legacy paths that have been moved elsewhere.</summary>
+ private IDictionary<string, string> GetLegacyPathRedirects()
+ {
+ var redirects = new Dictionary<string, string>();
+
+ // canimod.com => wiki
var wikiRedirects = new Dictionary<string, string[]>
{
["Modding:Index#Migration_guides"] = new[] { "^/for-devs/updating-a-smapi-mod", "^/guides/updating-a-smapi-mod" },
@@ -251,10 +327,10 @@ namespace StardewModdingAPI.Web
["Modding:Object_data"] = new[] { "^/for-devs/object-data", "^/guides/object-data" },
["Modding:Weather_data"] = new[] { "^/for-devs/weather", "^/guides/weather" }
};
- foreach (KeyValuePair<string, string[]> pair in wikiRedirects)
+ foreach ((string page, string[] patterns) in wikiRedirects)
{
- foreach (string pattern in pair.Value)
- redirects.Add(new RedirectToUrlRule(pattern, "https://stardewvalleywiki.com/" + pair.Key));
+ foreach (string pattern in patterns)
+ redirects.Add(pattern, "https://stardewvalleywiki.com/" + page);
}
return redirects;