From e00fb85ee7822bc7fed2d6bd5a2e4c207a799418 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 7 Jul 2019 00:29:22 -0400 Subject: migrate compatibility list's wiki data to MongoDB cache (#651) --- .../Framework/Caching/BaseCacheRepository.cs | 19 +++ .../Framework/Caching/Wiki/CachedWikiMetadata.cs | 43 +++++ .../Framework/Caching/Wiki/CachedWikiMod.cs | 188 +++++++++++++++++++++ .../Framework/Caching/Wiki/IWikiCacheRepository.cs | 35 ++++ .../Framework/Caching/Wiki/WikiCacheRepository.cs | 73 ++++++++ .../Framework/ConfigModels/MongoDbConfig.cs | 36 ++++ 6 files changed, 394 insertions(+) create mode 100644 src/SMAPI.Web/Framework/Caching/BaseCacheRepository.cs create mode 100644 src/SMAPI.Web/Framework/Caching/Wiki/CachedWikiMetadata.cs create mode 100644 src/SMAPI.Web/Framework/Caching/Wiki/CachedWikiMod.cs create mode 100644 src/SMAPI.Web/Framework/Caching/Wiki/IWikiCacheRepository.cs create mode 100644 src/SMAPI.Web/Framework/Caching/Wiki/WikiCacheRepository.cs create mode 100644 src/SMAPI.Web/Framework/ConfigModels/MongoDbConfig.cs (limited to 'src/SMAPI.Web/Framework') diff --git a/src/SMAPI.Web/Framework/Caching/BaseCacheRepository.cs b/src/SMAPI.Web/Framework/Caching/BaseCacheRepository.cs new file mode 100644 index 00000000..904455c5 --- /dev/null +++ b/src/SMAPI.Web/Framework/Caching/BaseCacheRepository.cs @@ -0,0 +1,19 @@ +using System; + +namespace StardewModdingAPI.Web.Framework.Caching +{ + /// The base logic for a cache repository. + internal abstract class BaseCacheRepository + { + /********* + ** Public methods + *********/ + /// Whether cached data is stale. + /// The date when the data was updated. + /// The age in minutes before data is considered stale. + public bool IsStale(DateTimeOffset lastUpdated, int cacheMinutes) + { + return lastUpdated < DateTimeOffset.UtcNow.AddMinutes(-cacheMinutes); + } + } +} diff --git a/src/SMAPI.Web/Framework/Caching/Wiki/CachedWikiMetadata.cs b/src/SMAPI.Web/Framework/Caching/Wiki/CachedWikiMetadata.cs new file mode 100644 index 00000000..de1ea9db --- /dev/null +++ b/src/SMAPI.Web/Framework/Caching/Wiki/CachedWikiMetadata.cs @@ -0,0 +1,43 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using MongoDB.Bson; + +namespace StardewModdingAPI.Web.Framework.Caching.Wiki +{ + /// The model for cached wiki metadata. + public class CachedWikiMetadata + { + /********* + ** Accessors + *********/ + /// The internal MongoDB ID. + [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Named per MongoDB conventions.")] + public ObjectId _id { get; set; } + + /// When the data was last updated. + public DateTimeOffset LastUpdated { get; set; } + + /// The current stable Stardew Valley version. + public string StableVersion { get; set; } + + /// The current beta Stardew Valley version. + public string BetaVersion { get; set; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + public CachedWikiMetadata() { } + + /// Construct an instance. + /// The current stable Stardew Valley version. + /// The current beta Stardew Valley version. + public CachedWikiMetadata(string stableVersion, string betaVersion) + { + this.StableVersion = stableVersion; + this.BetaVersion = betaVersion; + this.LastUpdated = DateTimeOffset.UtcNow;; + } + } +} diff --git a/src/SMAPI.Web/Framework/Caching/Wiki/CachedWikiMod.cs b/src/SMAPI.Web/Framework/Caching/Wiki/CachedWikiMod.cs new file mode 100644 index 00000000..f4331f8d --- /dev/null +++ b/src/SMAPI.Web/Framework/Caching/Wiki/CachedWikiMod.cs @@ -0,0 +1,188 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using MongoDB.Bson; +using StardewModdingAPI.Toolkit; +using StardewModdingAPI.Toolkit.Framework.Clients.Wiki; + +namespace StardewModdingAPI.Web.Framework.Caching.Wiki +{ + /// The model for cached wiki mods. + public class CachedWikiMod + { + /********* + ** Accessors + *********/ + /**** + ** Tracking + ****/ + /// The internal MongoDB ID. + [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Named per MongoDB conventions.")] + public ObjectId _id { get; set; } + + /// When the data was last updated. + public DateTimeOffset LastUpdated { get; set; } + + /**** + ** Mod info + ****/ + /// The mod's unique ID. If the mod has alternate/old IDs, they're listed in latest to newest order. + public string[] ID { get; set; } + + /// The mod's display name. If the mod has multiple names, the first one is the most canonical name. + public string[] Name { get; set; } + + /// The mod's author name. If the author has multiple names, the first one is the most canonical name. + public string[] Author { get; set; } + + /// The mod ID on Nexus. + public int? NexusID { get; set; } + + /// The mod ID in the Chucklefish mod repo. + public int? ChucklefishID { get; set; } + + /// The mod ID in the ModDrop mod repo. + public int? ModDropID { get; set; } + + /// The GitHub repository in the form 'owner/repo'. + public string GitHubRepo { get; set; } + + /// The URL to a non-GitHub source repo. + public string CustomSourceUrl { get; set; } + + /// The custom mod page URL (if applicable). + public string CustomUrl { get; set; } + + /// The name of the mod which loads this content pack, if applicable. + public string ContentPackFor { get; set; } + + /// The human-readable warnings for players about this mod. + public string[] Warnings { get; set; } + + /// The link anchor for the mod entry in the wiki compatibility list. + public string Anchor { get; set; } + + /**** + ** Stable compatibility + ****/ + /// The compatibility status. + public WikiCompatibilityStatus MainStatus { get; set; } + + /// The human-readable summary of the compatibility status or workaround, without HTML formatting. + public string MainSummary { get; set; } + + /// The game or SMAPI version which broke this mod (if applicable). + public string MainBrokeIn { get; set; } + + /// The version of the latest unofficial update, if applicable. + public string MainUnofficialVersion { get; set; } + + /// The URL to the latest unofficial update, if applicable. + public string MainUnofficialUrl { get; set; } + + /**** + ** Beta compatibility + ****/ + /// The compatibility status. + public WikiCompatibilityStatus? BetaStatus { get; set; } + + /// The human-readable summary of the compatibility status or workaround, without HTML formatting. + public string BetaSummary { get; set; } + + /// The game or SMAPI version which broke this mod (if applicable). + public string BetaBrokeIn { get; set; } + + /// The version of the latest unofficial update, if applicable. + public string BetaUnofficialVersion { get; set; } + + /// The URL to the latest unofficial update, if applicable. + public string BetaUnofficialUrl { get; set; } + + + /********* + ** Accessors + *********/ + /// Construct an instance. + public CachedWikiMod() { } + + /// Construct an instance. + /// The mod data. + public CachedWikiMod(WikiModEntry mod) + { + // tracking + this.LastUpdated = DateTimeOffset.UtcNow; + + // mod info + this.ID = mod.ID; + this.Name = mod.Name; + this.Author = mod.Author; + this.NexusID = mod.NexusID; + this.ChucklefishID = mod.ChucklefishID; + this.ModDropID = mod.ModDropID; + this.GitHubRepo = mod.GitHubRepo; + this.CustomSourceUrl = mod.CustomSourceUrl; + this.CustomUrl = mod.CustomUrl; + this.ContentPackFor = mod.ContentPackFor; + this.Warnings = mod.Warnings; + this.Anchor = mod.Anchor; + + // stable compatibility + this.MainStatus = mod.Compatibility.Status; + this.MainSummary = mod.Compatibility.Summary; + this.MainBrokeIn = mod.Compatibility.BrokeIn; + this.MainUnofficialVersion = mod.Compatibility.UnofficialVersion?.ToString(); + this.MainUnofficialUrl = mod.Compatibility.UnofficialUrl; + + // beta compatibility + this.BetaStatus = mod.BetaCompatibility?.Status; + this.BetaSummary = mod.BetaCompatibility?.Summary; + this.BetaBrokeIn = mod.BetaCompatibility?.BrokeIn; + this.BetaUnofficialVersion = mod.BetaCompatibility?.UnofficialVersion?.ToString(); + this.BetaUnofficialUrl = mod.BetaCompatibility?.UnofficialUrl; + } + + /// Reconstruct the original model. + public WikiModEntry GetModel() + { + var mod = new WikiModEntry + { + ID = this.ID, + Name = this.Name, + Author = this.Author, + NexusID = this.NexusID, + ChucklefishID = this.ChucklefishID, + ModDropID = this.ModDropID, + GitHubRepo = this.GitHubRepo, + CustomSourceUrl = this.CustomSourceUrl, + CustomUrl = this.CustomUrl, + ContentPackFor = this.ContentPackFor, + Warnings = this.Warnings, + Anchor = this.Anchor, + + // stable compatibility + Compatibility = new WikiCompatibilityInfo + { + Status = this.MainStatus, + Summary = this.MainSummary, + BrokeIn = this.MainBrokeIn, + UnofficialVersion = this.MainUnofficialVersion != null ? new SemanticVersion(this.MainUnofficialVersion) : null, + UnofficialUrl = this.MainUnofficialUrl + } + }; + + // beta compatibility + if (this.BetaStatus != null) + { + mod.BetaCompatibility = new WikiCompatibilityInfo + { + Status = this.BetaStatus.Value, + Summary = this.BetaSummary, + BrokeIn = this.BetaBrokeIn, + UnofficialVersion = this.BetaUnofficialVersion != null ? new SemanticVersion(this.BetaUnofficialVersion) : null, + UnofficialUrl = this.BetaUnofficialUrl + }; + } + + return mod; + } + } +} diff --git a/src/SMAPI.Web/Framework/Caching/Wiki/IWikiCacheRepository.cs b/src/SMAPI.Web/Framework/Caching/Wiki/IWikiCacheRepository.cs new file mode 100644 index 00000000..d319db69 --- /dev/null +++ b/src/SMAPI.Web/Framework/Caching/Wiki/IWikiCacheRepository.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using StardewModdingAPI.Toolkit.Framework.Clients.Wiki; + +namespace StardewModdingAPI.Web.Framework.Caching.Wiki +{ + /// Encapsulates logic for accessing the mod data cache. + internal interface IWikiCacheRepository + { + /********* + ** Methods + *********/ + /// Get the cached wiki metadata. + /// The fetched metadata. + bool TryGetWikiMetadata(out CachedWikiMetadata metadata); + + /// Whether cached data is stale. + /// The date when the data was updated. + /// The age in minutes before data is considered stale. + bool IsStale(DateTimeOffset lastUpdated, int cacheMinutes); + + /// Get the cached wiki mods. + /// A filter to apply, if any. + IEnumerable GetWikiMods(Expression> filter = null); + + /// Save data fetched from the wiki compatibility list. + /// The current stable Stardew Valley version. + /// The current beta Stardew Valley version. + /// The mod data. + /// The stored metadata record. + /// The stored mod records. + void SaveWikiData(string stableVersion, string betaVersion, IEnumerable mods, out CachedWikiMetadata cachedMetadata, out CachedWikiMod[] cachedMods); + } +} diff --git a/src/SMAPI.Web/Framework/Caching/Wiki/WikiCacheRepository.cs b/src/SMAPI.Web/Framework/Caching/Wiki/WikiCacheRepository.cs new file mode 100644 index 00000000..5b907462 --- /dev/null +++ b/src/SMAPI.Web/Framework/Caching/Wiki/WikiCacheRepository.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using MongoDB.Driver; +using StardewModdingAPI.Toolkit.Framework.Clients.Wiki; + +namespace StardewModdingAPI.Web.Framework.Caching.Wiki +{ + /// Encapsulates logic for accessing the wiki data cache. + internal class WikiCacheRepository : BaseCacheRepository, IWikiCacheRepository + { + /********* + ** Fields + *********/ + /// The collection for wiki metadata. + private readonly IMongoCollection WikiMetadata; + + /// The collection for wiki mod data. + private readonly IMongoCollection WikiMods; + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The authenticated MongoDB database. + public WikiCacheRepository(IMongoDatabase database) + { + // get collections + this.WikiMetadata = database.GetCollection("wiki-metadata"); + this.WikiMods = database.GetCollection("wiki-mods"); + + // add indexes if needed + this.WikiMods.Indexes.CreateOne(new CreateIndexModel(Builders.IndexKeys.Text(p => p.ID))); + } + + /// Get the cached wiki metadata. + /// The fetched metadata. + public bool TryGetWikiMetadata(out CachedWikiMetadata metadata) + { + metadata = this.WikiMetadata.Find("{}").FirstOrDefault(); + return metadata != null; + } + + /// Get the cached wiki mods. + /// A filter to apply, if any. + public IEnumerable GetWikiMods(Expression> filter = null) + { + return filter != null + ? this.WikiMods.Find(filter).ToList() + : this.WikiMods.Find("{}").ToList(); + } + + /// Save data fetched from the wiki compatibility list. + /// The current stable Stardew Valley version. + /// The current beta Stardew Valley version. + /// The mod data. + /// The stored metadata record. + /// The stored mod records. + public void SaveWikiData(string stableVersion, string betaVersion, IEnumerable mods, out CachedWikiMetadata cachedMetadata, out CachedWikiMod[] cachedMods) + { + cachedMetadata = new CachedWikiMetadata(stableVersion, betaVersion); + cachedMods = mods.Select(mod => new CachedWikiMod(mod)).ToArray(); + + this.WikiMods.DeleteMany("{}"); + this.WikiMods.InsertMany(cachedMods); + + this.WikiMetadata.DeleteMany("{}"); + this.WikiMetadata.InsertOne(cachedMetadata); + } + } +} diff --git a/src/SMAPI.Web/Framework/ConfigModels/MongoDbConfig.cs b/src/SMAPI.Web/Framework/ConfigModels/MongoDbConfig.cs new file mode 100644 index 00000000..352eb960 --- /dev/null +++ b/src/SMAPI.Web/Framework/ConfigModels/MongoDbConfig.cs @@ -0,0 +1,36 @@ +using System; + +namespace StardewModdingAPI.Web.Framework.ConfigModels +{ + /// The config settings for mod compatibility list. + internal class MongoDbConfig + { + /********* + ** Accessors + *********/ + /// The MongoDB hostname. + public string Host { get; set; } + + /// The MongoDB username (if any). + public string Username { get; set; } + + /// The MongoDB password (if any). + public string Password { get; set; } + + + /********* + ** Public method + *********/ + /// Get the MongoDB connection string. + /// The initial database for which to authenticate. + public string GetConnectionString(string authDatabase) + { + bool isLocal = this.Host == "localhost"; + bool hasLogin = !string.IsNullOrWhiteSpace(this.Username) && !string.IsNullOrWhiteSpace(this.Password); + + return $"mongodb{(isLocal ? "" : "+srv")}://" + + (hasLogin ? $"{Uri.EscapeDataString(this.Username)}:{Uri.EscapeDataString(this.Password)}@" : "") + + $"{this.Host}/{authDatabase}retryWrites=true&w=majority"; + } + } +} -- cgit From 2b3f0e740bc09630e881c9967e5ad53d66384094 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 7 Jul 2019 00:39:52 -0400 Subject: make MongoDB database name configurable (#651) --- src/SMAPI.Web/Framework/ConfigModels/MongoDbConfig.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'src/SMAPI.Web/Framework') diff --git a/src/SMAPI.Web/Framework/ConfigModels/MongoDbConfig.cs b/src/SMAPI.Web/Framework/ConfigModels/MongoDbConfig.cs index 352eb960..3c508300 100644 --- a/src/SMAPI.Web/Framework/ConfigModels/MongoDbConfig.cs +++ b/src/SMAPI.Web/Framework/ConfigModels/MongoDbConfig.cs @@ -17,20 +17,22 @@ namespace StardewModdingAPI.Web.Framework.ConfigModels /// The MongoDB password (if any). public string Password { get; set; } + /// The database name. + public string Database { get; set; } + /********* ** Public method *********/ /// Get the MongoDB connection string. - /// The initial database for which to authenticate. - public string GetConnectionString(string authDatabase) + public string GetConnectionString() { bool isLocal = this.Host == "localhost"; bool hasLogin = !string.IsNullOrWhiteSpace(this.Username) && !string.IsNullOrWhiteSpace(this.Password); return $"mongodb{(isLocal ? "" : "+srv")}://" + (hasLogin ? $"{Uri.EscapeDataString(this.Username)}:{Uri.EscapeDataString(this.Password)}@" : "") - + $"{this.Host}/{authDatabase}retryWrites=true&w=majority"; + + $"{this.Host}/{this.Database}?retryWrites=true&w=majority"; } } } -- cgit From a731f5ea9a75aad682c261d747a5d9c71b3d2773 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 7 Jul 2019 00:55:10 -0400 Subject: use better index type (#651) --- src/SMAPI.Web/Framework/Caching/Wiki/WikiCacheRepository.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/SMAPI.Web/Framework') diff --git a/src/SMAPI.Web/Framework/Caching/Wiki/WikiCacheRepository.cs b/src/SMAPI.Web/Framework/Caching/Wiki/WikiCacheRepository.cs index 5b907462..1ae9d38f 100644 --- a/src/SMAPI.Web/Framework/Caching/Wiki/WikiCacheRepository.cs +++ b/src/SMAPI.Web/Framework/Caching/Wiki/WikiCacheRepository.cs @@ -32,7 +32,7 @@ namespace StardewModdingAPI.Web.Framework.Caching.Wiki this.WikiMods = database.GetCollection("wiki-mods"); // add indexes if needed - this.WikiMods.Indexes.CreateOne(new CreateIndexModel(Builders.IndexKeys.Text(p => p.ID))); + this.WikiMods.Indexes.CreateOne(new CreateIndexModel(Builders.IndexKeys.Ascending(p => p.ID))); } /// Get the cached wiki metadata. -- cgit From 48f211f5447ec7580b9f9bba63eab0ec6b1ec5c7 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 14 Jul 2019 17:49:33 -0400 Subject: add metadata links to mod compatibility list --- src/SMAPI.Web/Framework/Caching/Wiki/CachedWikiMod.cs | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'src/SMAPI.Web/Framework') diff --git a/src/SMAPI.Web/Framework/Caching/Wiki/CachedWikiMod.cs b/src/SMAPI.Web/Framework/Caching/Wiki/CachedWikiMod.cs index f4331f8d..850272eb 100644 --- a/src/SMAPI.Web/Framework/Caching/Wiki/CachedWikiMod.cs +++ b/src/SMAPI.Web/Framework/Caching/Wiki/CachedWikiMod.cs @@ -58,6 +58,9 @@ namespace StardewModdingAPI.Web.Framework.Caching.Wiki /// The human-readable warnings for players about this mod. public string[] Warnings { get; set; } + /// Extra metadata links (usually for open pull requests). + public Tuple[] MetadataLinks { get; set; } + /// The link anchor for the mod entry in the wiki compatibility list. public string Anchor { get; set; } @@ -122,6 +125,7 @@ namespace StardewModdingAPI.Web.Framework.Caching.Wiki this.CustomSourceUrl = mod.CustomSourceUrl; this.CustomUrl = mod.CustomUrl; this.ContentPackFor = mod.ContentPackFor; + this.MetadataLinks = mod.MetadataLinks; this.Warnings = mod.Warnings; this.Anchor = mod.Anchor; @@ -156,6 +160,7 @@ namespace StardewModdingAPI.Web.Framework.Caching.Wiki CustomUrl = this.CustomUrl, ContentPackFor = this.ContentPackFor, Warnings = this.Warnings, + MetadataLinks = this.MetadataLinks, Anchor = this.Anchor, // stable compatibility -- cgit From 1bf399ec23368a0666203298768a4b24b63d6a88 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 14 Jul 2019 22:27:00 -0400 Subject: add dev note field to compatibility list --- src/SMAPI.Web/Framework/Caching/Wiki/CachedWikiMod.cs | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'src/SMAPI.Web/Framework') diff --git a/src/SMAPI.Web/Framework/Caching/Wiki/CachedWikiMod.cs b/src/SMAPI.Web/Framework/Caching/Wiki/CachedWikiMod.cs index 850272eb..bf1e2be2 100644 --- a/src/SMAPI.Web/Framework/Caching/Wiki/CachedWikiMod.cs +++ b/src/SMAPI.Web/Framework/Caching/Wiki/CachedWikiMod.cs @@ -61,6 +61,9 @@ namespace StardewModdingAPI.Web.Framework.Caching.Wiki /// Extra metadata links (usually for open pull requests). public Tuple[] MetadataLinks { get; set; } + /// Special notes intended for developers who maintain unofficial updates or submit pull requests. + public string DevNote { get; set; } + /// The link anchor for the mod entry in the wiki compatibility list. public string Anchor { get; set; } @@ -127,6 +130,7 @@ namespace StardewModdingAPI.Web.Framework.Caching.Wiki this.ContentPackFor = mod.ContentPackFor; this.MetadataLinks = mod.MetadataLinks; this.Warnings = mod.Warnings; + this.DevNote = mod.DevNote; this.Anchor = mod.Anchor; // stable compatibility @@ -161,6 +165,7 @@ namespace StardewModdingAPI.Web.Framework.Caching.Wiki ContentPackFor = this.ContentPackFor, Warnings = this.Warnings, MetadataLinks = this.MetadataLinks, + DevNote = this.DevNote, Anchor = this.Anchor, // stable compatibility -- cgit From be1f09f5f9d3318a4bbdf593f671f4eacdd4395d Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 18 Jul 2019 15:13:29 -0400 Subject: update obsolete code --- .../Framework/Clients/Pastebin/PastebinClient.cs | 32 ++++++---------------- 1 file changed, 8 insertions(+), 24 deletions(-) (limited to 'src/SMAPI.Web/Framework') diff --git a/src/SMAPI.Web/Framework/Clients/Pastebin/PastebinClient.cs b/src/SMAPI.Web/Framework/Clients/Pastebin/PastebinClient.cs index 12c3e83f..1e46f2dc 100644 --- a/src/SMAPI.Web/Framework/Clients/Pastebin/PastebinClient.cs +++ b/src/SMAPI.Web/Framework/Clients/Pastebin/PastebinClient.cs @@ -1,11 +1,8 @@ using System; -using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; -using System.Text; using System.Threading.Tasks; -using System.Web; using Pathoschild.Http.Client; namespace StardewModdingAPI.Web.Framework.Clients.Pastebin @@ -82,15 +79,15 @@ namespace StardewModdingAPI.Web.Framework.Clients.Pastebin // post to API string response = await this.Client .PostAsync("api/api_post.php") - .WithBodyContent(this.GetFormUrlEncodedContent(new Dictionary + .WithBody(p => p.FormUrlEncoded(new { - ["api_option"] = "paste", - ["api_user_key"] = this.UserKey, - ["api_dev_key"] = this.DevKey, - ["api_paste_private"] = "1", // unlisted - ["api_paste_name"] = $"SMAPI log {DateTime.UtcNow:s}", - ["api_paste_expire_date"] = "N", // never expire - ["api_paste_code"] = content + api_option = "paste", + api_user_key = this.UserKey, + api_dev_key = this.DevKey, + api_paste_private = 1, // unlisted + api_paste_name = $"SMAPI log {DateTime.UtcNow:s}", + api_paste_expire_date = "N", // never expire + api_paste_code = content })) .AsString(); @@ -117,18 +114,5 @@ namespace StardewModdingAPI.Web.Framework.Clients.Pastebin { this.Client.Dispose(); } - - - /********* - ** Private methods - *********/ - /// Build an HTTP content body with form-url-encoded content. - /// The content to encode. - /// This bypasses an issue where restricts the body length to the maximum size of a URL, which isn't applicable here. - private HttpContent GetFormUrlEncodedContent(IDictionary data) - { - string body = string.Join("&", from arg in data select $"{HttpUtility.UrlEncode(arg.Key)}={HttpUtility.UrlEncode(arg.Value)}"); - return new StringContent(body, Encoding.UTF8, "application/x-www-form-urlencoded"); - } } } -- cgit From ce6cedaf4be53d52f2e558055b91e515b92e4c83 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 19 Jul 2019 13:15:45 -0400 Subject: add background fetch for mod compatibility list (#651) --- src/SMAPI.Web/Framework/Caching/BaseCacheRepository.cs | 6 +++--- src/SMAPI.Web/Framework/Caching/Wiki/CachedWikiMetadata.cs | 2 +- src/SMAPI.Web/Framework/Caching/Wiki/IWikiCacheRepository.cs | 4 ++-- .../Framework/ConfigModels/BackgroundServicesConfig.cs | 12 ++++++++++++ .../Framework/ConfigModels/ModCompatibilityListConfig.cs | 6 +++--- 5 files changed, 21 insertions(+), 9 deletions(-) create mode 100644 src/SMAPI.Web/Framework/ConfigModels/BackgroundServicesConfig.cs (limited to 'src/SMAPI.Web/Framework') diff --git a/src/SMAPI.Web/Framework/Caching/BaseCacheRepository.cs b/src/SMAPI.Web/Framework/Caching/BaseCacheRepository.cs index 904455c5..f5354b93 100644 --- a/src/SMAPI.Web/Framework/Caching/BaseCacheRepository.cs +++ b/src/SMAPI.Web/Framework/Caching/BaseCacheRepository.cs @@ -10,10 +10,10 @@ namespace StardewModdingAPI.Web.Framework.Caching *********/ /// Whether cached data is stale. /// The date when the data was updated. - /// The age in minutes before data is considered stale. - public bool IsStale(DateTimeOffset lastUpdated, int cacheMinutes) + /// The age in minutes before data is considered stale. + public bool IsStale(DateTimeOffset lastUpdated, int staleMinutes) { - return lastUpdated < DateTimeOffset.UtcNow.AddMinutes(-cacheMinutes); + return lastUpdated < DateTimeOffset.UtcNow.AddMinutes(-staleMinutes); } } } diff --git a/src/SMAPI.Web/Framework/Caching/Wiki/CachedWikiMetadata.cs b/src/SMAPI.Web/Framework/Caching/Wiki/CachedWikiMetadata.cs index de1ea9db..4d6b4b10 100644 --- a/src/SMAPI.Web/Framework/Caching/Wiki/CachedWikiMetadata.cs +++ b/src/SMAPI.Web/Framework/Caching/Wiki/CachedWikiMetadata.cs @@ -37,7 +37,7 @@ namespace StardewModdingAPI.Web.Framework.Caching.Wiki { this.StableVersion = stableVersion; this.BetaVersion = betaVersion; - this.LastUpdated = DateTimeOffset.UtcNow;; + this.LastUpdated = DateTimeOffset.UtcNow; } } } diff --git a/src/SMAPI.Web/Framework/Caching/Wiki/IWikiCacheRepository.cs b/src/SMAPI.Web/Framework/Caching/Wiki/IWikiCacheRepository.cs index d319db69..6031123d 100644 --- a/src/SMAPI.Web/Framework/Caching/Wiki/IWikiCacheRepository.cs +++ b/src/SMAPI.Web/Framework/Caching/Wiki/IWikiCacheRepository.cs @@ -17,8 +17,8 @@ namespace StardewModdingAPI.Web.Framework.Caching.Wiki /// Whether cached data is stale. /// The date when the data was updated. - /// The age in minutes before data is considered stale. - bool IsStale(DateTimeOffset lastUpdated, int cacheMinutes); + /// The age in minutes before data is considered stale. + bool IsStale(DateTimeOffset lastUpdated, int staleMinutes); /// Get the cached wiki mods. /// A filter to apply, if any. diff --git a/src/SMAPI.Web/Framework/ConfigModels/BackgroundServicesConfig.cs b/src/SMAPI.Web/Framework/ConfigModels/BackgroundServicesConfig.cs new file mode 100644 index 00000000..de871c9a --- /dev/null +++ b/src/SMAPI.Web/Framework/ConfigModels/BackgroundServicesConfig.cs @@ -0,0 +1,12 @@ +namespace StardewModdingAPI.Web.Framework.ConfigModels +{ + /// The config settings for background services. + internal class BackgroundServicesConfig + { + /********* + ** Accessors + *********/ + /// Whether to enable background update services. + public bool Enabled { get; set; } + } +} diff --git a/src/SMAPI.Web/Framework/ConfigModels/ModCompatibilityListConfig.cs b/src/SMAPI.Web/Framework/ConfigModels/ModCompatibilityListConfig.cs index d9ac9f02..24b540cd 100644 --- a/src/SMAPI.Web/Framework/ConfigModels/ModCompatibilityListConfig.cs +++ b/src/SMAPI.Web/Framework/ConfigModels/ModCompatibilityListConfig.cs @@ -1,12 +1,12 @@ namespace StardewModdingAPI.Web.Framework.ConfigModels { - /// The config settings for mod compatibility list. + /// The config settings for the mod compatibility list. internal class ModCompatibilityListConfig { /********* ** Accessors *********/ - /// The number of minutes data from the wiki should be cached before refetching it. - public int CacheMinutes { get; set; } + /// The number of minutes before which wiki data should be considered old. + public int StaleMinutes { get; set; } } } -- cgit From ec747b518b28184c440dcea7ce74f3e80b627505 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 19 Jul 2019 17:01:22 -0400 Subject: enable readonly access to job dashboard when deployed (#651) --- .../Framework/JobDashboardAuthorizationFilter.cs | 34 ++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 src/SMAPI.Web/Framework/JobDashboardAuthorizationFilter.cs (limited to 'src/SMAPI.Web/Framework') diff --git a/src/SMAPI.Web/Framework/JobDashboardAuthorizationFilter.cs b/src/SMAPI.Web/Framework/JobDashboardAuthorizationFilter.cs new file mode 100644 index 00000000..9471d5fe --- /dev/null +++ b/src/SMAPI.Web/Framework/JobDashboardAuthorizationFilter.cs @@ -0,0 +1,34 @@ +using Hangfire.Dashboard; + +namespace StardewModdingAPI.Web.Framework +{ + /// Authorises requests to access the Hangfire job dashboard. + internal class JobDashboardAuthorizationFilter : IDashboardAuthorizationFilter + { + /********* + ** Fields + *********/ + /// An authorization filter that allows local requests. + private static readonly LocalRequestsOnlyAuthorizationFilter LocalRequestsOnlyFilter = new LocalRequestsOnlyAuthorizationFilter(); + + + /********* + ** Public methods + *********/ + /// Authorise a request. + /// The dashboard context. + public bool Authorize(DashboardContext context) + { + return + context.IsReadOnly // always allow readonly access + || JobDashboardAuthorizationFilter.IsLocalRequest(context); // else allow access from localhost + } + + /// Get whether a request originated from a user on the server machine. + /// The dashboard context. + public static bool IsLocalRequest(DashboardContext context) + { + return JobDashboardAuthorizationFilter.LocalRequestsOnlyFilter.Authorize(context); + } + } +} -- cgit From e856d5efebe12b3aa65d5868ea7baa59cc54863d Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 24 Jul 2019 18:29:50 -0400 Subject: add remote mod status to update check info (#651) --- .../Framework/Clients/ModDrop/ModDropMod.cs | 3 --- .../ModRepositories/ChucklefishRepository.cs | 6 ++--- .../Framework/ModRepositories/GitHubRepository.cs | 6 ++--- .../Framework/ModRepositories/ModDropRepository.cs | 8 +++---- .../Framework/ModRepositories/ModInfoModel.cs | 26 ++++++++++++---------- .../Framework/ModRepositories/NexusRepository.cs | 8 +++---- .../Framework/ModRepositories/RemoteModStatus.cs | 18 +++++++++++++++ 7 files changed, 45 insertions(+), 30 deletions(-) create mode 100644 src/SMAPI.Web/Framework/ModRepositories/RemoteModStatus.cs (limited to 'src/SMAPI.Web/Framework') diff --git a/src/SMAPI.Web/Framework/Clients/ModDrop/ModDropMod.cs b/src/SMAPI.Web/Framework/Clients/ModDrop/ModDropMod.cs index 291fb353..def79106 100644 --- a/src/SMAPI.Web/Framework/Clients/ModDrop/ModDropMod.cs +++ b/src/SMAPI.Web/Framework/Clients/ModDrop/ModDropMod.cs @@ -17,8 +17,5 @@ namespace StardewModdingAPI.Web.Framework.Clients.ModDrop /// The mod's web URL. public string Url { get; set; } - - /// A user-friendly error which indicates why fetching the mod info failed (if applicable). - public string Error { get; set; } } } diff --git a/src/SMAPI.Web/Framework/ModRepositories/ChucklefishRepository.cs b/src/SMAPI.Web/Framework/ModRepositories/ChucklefishRepository.cs index 87e29a2f..04c80dd2 100644 --- a/src/SMAPI.Web/Framework/ModRepositories/ChucklefishRepository.cs +++ b/src/SMAPI.Web/Framework/ModRepositories/ChucklefishRepository.cs @@ -32,21 +32,21 @@ namespace StardewModdingAPI.Web.Framework.ModRepositories { // validate ID format if (!uint.TryParse(id, out uint realID)) - return new ModInfoModel($"The value '{id}' isn't a valid Chucklefish mod ID, must be an integer ID."); + return new ModInfoModel().WithError(RemoteModStatus.DoesNotExist, $"The value '{id}' isn't a valid Chucklefish mod ID, must be an integer ID."); // fetch info try { var mod = await this.Client.GetModAsync(realID); if (mod == null) - return new ModInfoModel("Found no mod with this ID."); + return new ModInfoModel().WithError(RemoteModStatus.DoesNotExist, "Found no Chucklefish mod with this ID."); // create model return new ModInfoModel(name: mod.Name, version: this.NormaliseVersion(mod.Version), url: mod.Url); } catch (Exception ex) { - return new ModInfoModel(ex.ToString()); + return new ModInfoModel().WithError(RemoteModStatus.TemporaryError, ex.ToString()); } } diff --git a/src/SMAPI.Web/Framework/ModRepositories/GitHubRepository.cs b/src/SMAPI.Web/Framework/ModRepositories/GitHubRepository.cs index 14f44dc0..614e00c2 100644 --- a/src/SMAPI.Web/Framework/ModRepositories/GitHubRepository.cs +++ b/src/SMAPI.Web/Framework/ModRepositories/GitHubRepository.cs @@ -32,7 +32,7 @@ namespace StardewModdingAPI.Web.Framework.ModRepositories { // validate ID format if (!id.Contains("/") || id.IndexOf("/", StringComparison.InvariantCultureIgnoreCase) != id.LastIndexOf("/", StringComparison.InvariantCultureIgnoreCase)) - return new ModInfoModel($"The value '{id}' isn't a valid GitHub mod ID, must be a username and project name like 'Pathoschild/LookupAnything'."); + return new ModInfoModel().WithError(RemoteModStatus.DoesNotExist, $"The value '{id}' isn't a valid GitHub mod ID, must be a username and project name like 'Pathoschild/LookupAnything'."); // fetch info try @@ -40,7 +40,7 @@ namespace StardewModdingAPI.Web.Framework.ModRepositories // get latest release (whether preview or stable) GitRelease latest = await this.Client.GetLatestReleaseAsync(id, includePrerelease: true); if (latest == null) - return new ModInfoModel("Found no mod with this ID."); + return new ModInfoModel().WithError(RemoteModStatus.DoesNotExist, "Found no GitHub release for this ID."); // split stable/prerelease if applicable GitRelease preview = null; @@ -59,7 +59,7 @@ namespace StardewModdingAPI.Web.Framework.ModRepositories } catch (Exception ex) { - return new ModInfoModel(ex.ToString()); + return new ModInfoModel().WithError(RemoteModStatus.TemporaryError, ex.ToString()); } } diff --git a/src/SMAPI.Web/Framework/ModRepositories/ModDropRepository.cs b/src/SMAPI.Web/Framework/ModRepositories/ModDropRepository.cs index 1994f515..5703b34e 100644 --- a/src/SMAPI.Web/Framework/ModRepositories/ModDropRepository.cs +++ b/src/SMAPI.Web/Framework/ModRepositories/ModDropRepository.cs @@ -32,21 +32,19 @@ namespace StardewModdingAPI.Web.Framework.ModRepositories { // validate ID format if (!long.TryParse(id, out long modDropID)) - return new ModInfoModel($"The value '{id}' isn't a valid ModDrop mod ID, must be an integer ID."); + return new ModInfoModel().WithError(RemoteModStatus.DoesNotExist, $"The value '{id}' isn't a valid ModDrop mod ID, must be an integer ID."); // fetch info try { ModDropMod mod = await this.Client.GetModAsync(modDropID); if (mod == null) - return new ModInfoModel("Found no mod with this ID."); - if (mod.Error != null) - return new ModInfoModel(mod.Error); + return new ModInfoModel().WithError(RemoteModStatus.DoesNotExist, "Found no ModDrop mod with this ID."); return new ModInfoModel(name: mod.Name, version: mod.LatestDefaultVersion?.ToString(), previewVersion: mod.LatestOptionalVersion?.ToString(), url: mod.Url); } catch (Exception ex) { - return new ModInfoModel(ex.ToString()); + return new ModInfoModel().WithError(RemoteModStatus.TemporaryError, ex.ToString()); } } diff --git a/src/SMAPI.Web/Framework/ModRepositories/ModInfoModel.cs b/src/SMAPI.Web/Framework/ModRepositories/ModInfoModel.cs index 18252298..16885bfd 100644 --- a/src/SMAPI.Web/Framework/ModRepositories/ModInfoModel.cs +++ b/src/SMAPI.Web/Framework/ModRepositories/ModInfoModel.cs @@ -9,15 +9,18 @@ namespace StardewModdingAPI.Web.Framework.ModRepositories /// The mod name. public string Name { get; set; } - /// The mod's latest release number. + /// The mod's latest version. public string Version { get; set; } - /// The mod's latest optional release, if newer than . + /// The mod's latest optional or prerelease version, if newer than . public string PreviewVersion { get; set; } /// The mod's web URL. public string Url { get; set; } + /// The mod availability status. + public RemoteModStatus Status { get; set; } = RemoteModStatus.Ok; + /// The error message indicating why the mod is invalid (if applicable). public string Error { get; set; } @@ -26,31 +29,30 @@ namespace StardewModdingAPI.Web.Framework.ModRepositories ** Public methods *********/ /// Construct an empty instance. - public ModInfoModel() - { - // needed for JSON deserialising - } + public ModInfoModel() { } /// Construct an instance. /// The mod name. /// The semantic version for the mod's latest release. /// The semantic version for the mod's latest preview release, if available and different from . /// The mod's web URL. - /// The error message indicating why the mod is invalid (if applicable). - public ModInfoModel(string name, string version, string url, string previewVersion = null, string error = null) + public ModInfoModel(string name, string version, string url, string previewVersion = null) { this.Name = name; this.Version = version; this.PreviewVersion = previewVersion; this.Url = url; - this.Error = error; } - /// Construct an instance. - /// The error message indicating why the mod is invalid. - public ModInfoModel(string error) + /// Set a mod error. + /// The mod availability status. + /// The error message indicating why the mod is invalid (if applicable). + public ModInfoModel WithError(RemoteModStatus status, string error) { + this.Status = status; this.Error = error; + + return this; } } } diff --git a/src/SMAPI.Web/Framework/ModRepositories/NexusRepository.cs b/src/SMAPI.Web/Framework/ModRepositories/NexusRepository.cs index 4c5fe9bf..9679f93e 100644 --- a/src/SMAPI.Web/Framework/ModRepositories/NexusRepository.cs +++ b/src/SMAPI.Web/Framework/ModRepositories/NexusRepository.cs @@ -32,21 +32,21 @@ namespace StardewModdingAPI.Web.Framework.ModRepositories { // validate ID format if (!uint.TryParse(id, out uint nexusID)) - return new ModInfoModel($"The value '{id}' isn't a valid Nexus mod ID, must be an integer ID."); + return new ModInfoModel().WithError(RemoteModStatus.DoesNotExist, $"The value '{id}' isn't a valid Nexus mod ID, must be an integer ID."); // fetch info try { NexusMod mod = await this.Client.GetModAsync(nexusID); if (mod == null) - return new ModInfoModel("Found no mod with this ID."); + return new ModInfoModel().WithError(RemoteModStatus.DoesNotExist, "Found no Nexus mod with this ID."); if (mod.Error != null) - return new ModInfoModel(mod.Error); + return new ModInfoModel().WithError(RemoteModStatus.TemporaryError, mod.Error); return new ModInfoModel(name: mod.Name, version: this.NormaliseVersion(mod.Version), previewVersion: mod.LatestFileVersion?.ToString(), url: mod.Url); } catch (Exception ex) { - return new ModInfoModel(ex.ToString()); + return new ModInfoModel().WithError(RemoteModStatus.TemporaryError, ex.ToString()); } } diff --git a/src/SMAPI.Web/Framework/ModRepositories/RemoteModStatus.cs b/src/SMAPI.Web/Framework/ModRepositories/RemoteModStatus.cs new file mode 100644 index 00000000..02876556 --- /dev/null +++ b/src/SMAPI.Web/Framework/ModRepositories/RemoteModStatus.cs @@ -0,0 +1,18 @@ +namespace StardewModdingAPI.Web.Framework.ModRepositories +{ + /// The mod availability status on a remote site. + internal enum RemoteModStatus + { + /// The mod is valid. + Ok, + + /// The mod data was fetched, but the data is not valid (e.g. version isn't semantic). + InvalidData, + + /// The mod does not exist. + DoesNotExist, + + /// The mod was temporarily unavailable (e.g. the site could not be reached or an unknown error occurred). + TemporaryError + } +} -- cgit From f6b336def7b05c3ef202f6c6b5e77dcaa0c498fa Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 24 Jul 2019 18:31:24 -0400 Subject: store DateTimeOffset values in date fields instead of the default array (#651) --- .../Caching/UtcDateTimeOffsetSerializer.cs | 40 ++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 src/SMAPI.Web/Framework/Caching/UtcDateTimeOffsetSerializer.cs (limited to 'src/SMAPI.Web/Framework') diff --git a/src/SMAPI.Web/Framework/Caching/UtcDateTimeOffsetSerializer.cs b/src/SMAPI.Web/Framework/Caching/UtcDateTimeOffsetSerializer.cs new file mode 100644 index 00000000..ad95a975 --- /dev/null +++ b/src/SMAPI.Web/Framework/Caching/UtcDateTimeOffsetSerializer.cs @@ -0,0 +1,40 @@ +using System; +using MongoDB.Bson; +using MongoDB.Bson.Serialization; +using MongoDB.Bson.Serialization.Serializers; + +namespace StardewModdingAPI.Web.Framework.Caching +{ + /// Serialises to a UTC date field instead of the default array. + public class UtcDateTimeOffsetSerializer : StructSerializerBase + { + /********* + ** Fields + *********/ + /// The underlying date serializer. + private static readonly DateTimeSerializer DateTimeSerializer = new DateTimeSerializer(DateTimeKind.Utc, BsonType.DateTime); + + + /********* + ** Public methods + *********/ + /// Deserializes a value. + /// The deserialization context. + /// The deserialization args. + /// A deserialized value. + public override DateTimeOffset Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) + { + DateTime date = UtcDateTimeOffsetSerializer.DateTimeSerializer.Deserialize(context, args); + return new DateTimeOffset(date, TimeSpan.Zero); + } + + /// Serializes a value. + /// The serialization context. + /// The serialization args. + /// The object. + public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, DateTimeOffset value) + { + UtcDateTimeOffsetSerializer.DateTimeSerializer.Serialize(context, args, value.UtcDateTime); + } + } +} -- cgit From 03a082297a750dace586dc2d15d17c840d96d95f Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 24 Jul 2019 18:31:43 -0400 Subject: add generic cache repository interface (#651) --- src/SMAPI.Web/Framework/Caching/ICacheRepository.cs | 13 +++++++++++++ src/SMAPI.Web/Framework/Caching/Wiki/CachedWikiMetadata.cs | 2 +- src/SMAPI.Web/Framework/Caching/Wiki/CachedWikiMod.cs | 2 +- .../Framework/Caching/Wiki/IWikiCacheRepository.cs | 9 ++------- 4 files changed, 17 insertions(+), 9 deletions(-) create mode 100644 src/SMAPI.Web/Framework/Caching/ICacheRepository.cs (limited to 'src/SMAPI.Web/Framework') diff --git a/src/SMAPI.Web/Framework/Caching/ICacheRepository.cs b/src/SMAPI.Web/Framework/Caching/ICacheRepository.cs new file mode 100644 index 00000000..5de7e731 --- /dev/null +++ b/src/SMAPI.Web/Framework/Caching/ICacheRepository.cs @@ -0,0 +1,13 @@ +using System; + +namespace StardewModdingAPI.Web.Framework.Caching +{ + /// Encapsulates logic for accessing data in the cache. + internal interface ICacheRepository + { + /// Whether cached data is stale. + /// The date when the data was updated. + /// The age in minutes before data is considered stale. + bool IsStale(DateTimeOffset lastUpdated, int staleMinutes); + } +} diff --git a/src/SMAPI.Web/Framework/Caching/Wiki/CachedWikiMetadata.cs b/src/SMAPI.Web/Framework/Caching/Wiki/CachedWikiMetadata.cs index 4d6b4b10..6a560eb4 100644 --- a/src/SMAPI.Web/Framework/Caching/Wiki/CachedWikiMetadata.cs +++ b/src/SMAPI.Web/Framework/Caching/Wiki/CachedWikiMetadata.cs @@ -5,7 +5,7 @@ using MongoDB.Bson; namespace StardewModdingAPI.Web.Framework.Caching.Wiki { /// The model for cached wiki metadata. - public class CachedWikiMetadata + internal class CachedWikiMetadata { /********* ** Accessors diff --git a/src/SMAPI.Web/Framework/Caching/Wiki/CachedWikiMod.cs b/src/SMAPI.Web/Framework/Caching/Wiki/CachedWikiMod.cs index bf1e2be2..37f55db1 100644 --- a/src/SMAPI.Web/Framework/Caching/Wiki/CachedWikiMod.cs +++ b/src/SMAPI.Web/Framework/Caching/Wiki/CachedWikiMod.cs @@ -7,7 +7,7 @@ using StardewModdingAPI.Toolkit.Framework.Clients.Wiki; namespace StardewModdingAPI.Web.Framework.Caching.Wiki { /// The model for cached wiki mods. - public class CachedWikiMod + internal class CachedWikiMod { /********* ** Accessors diff --git a/src/SMAPI.Web/Framework/Caching/Wiki/IWikiCacheRepository.cs b/src/SMAPI.Web/Framework/Caching/Wiki/IWikiCacheRepository.cs index 6031123d..b54c8a2f 100644 --- a/src/SMAPI.Web/Framework/Caching/Wiki/IWikiCacheRepository.cs +++ b/src/SMAPI.Web/Framework/Caching/Wiki/IWikiCacheRepository.cs @@ -5,8 +5,8 @@ using StardewModdingAPI.Toolkit.Framework.Clients.Wiki; namespace StardewModdingAPI.Web.Framework.Caching.Wiki { - /// Encapsulates logic for accessing the mod data cache. - internal interface IWikiCacheRepository + /// Encapsulates logic for accessing the wiki data cache. + internal interface IWikiCacheRepository : ICacheRepository { /********* ** Methods @@ -15,11 +15,6 @@ namespace StardewModdingAPI.Web.Framework.Caching.Wiki /// The fetched metadata. bool TryGetWikiMetadata(out CachedWikiMetadata metadata); - /// Whether cached data is stale. - /// The date when the data was updated. - /// The age in minutes before data is considered stale. - bool IsStale(DateTimeOffset lastUpdated, int staleMinutes); - /// Get the cached wiki mods. /// A filter to apply, if any. IEnumerable GetWikiMods(Expression> filter = null); -- cgit From 17c6ae7ed995344111513ca91b18ec6598ec2399 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 24 Jul 2019 18:33:26 -0400 Subject: migrate update check caching to MongoDB (#651) --- src/SMAPI.Web/Framework/Caching/Mods/CachedMod.cs | 97 ++++++++++++++++++++++ .../Framework/Caching/Mods/IModCacheRepository.cs | 26 ++++++ .../Framework/Caching/Mods/ModCacheRepository.cs | 96 +++++++++++++++++++++ .../Framework/ConfigModels/ModUpdateCheckConfig.cs | 4 - .../Framework/ModRepositories/ModInfoModel.cs | 4 +- 5 files changed, 221 insertions(+), 6 deletions(-) create mode 100644 src/SMAPI.Web/Framework/Caching/Mods/CachedMod.cs create mode 100644 src/SMAPI.Web/Framework/Caching/Mods/IModCacheRepository.cs create mode 100644 src/SMAPI.Web/Framework/Caching/Mods/ModCacheRepository.cs (limited to 'src/SMAPI.Web/Framework') diff --git a/src/SMAPI.Web/Framework/Caching/Mods/CachedMod.cs b/src/SMAPI.Web/Framework/Caching/Mods/CachedMod.cs new file mode 100644 index 00000000..fe8a7a1f --- /dev/null +++ b/src/SMAPI.Web/Framework/Caching/Mods/CachedMod.cs @@ -0,0 +1,97 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using MongoDB.Bson; +using MongoDB.Bson.Serialization.Attributes; +using StardewModdingAPI.Toolkit.Framework.UpdateData; +using StardewModdingAPI.Web.Framework.ModRepositories; + +namespace StardewModdingAPI.Web.Framework.Caching.Mods +{ + /// The model for cached mod data. + internal class CachedMod + { + /********* + ** Accessors + *********/ + /**** + ** Tracking + ****/ + /// The internal MongoDB ID. + [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Named per MongoDB conventions.")] + [BsonIgnoreIfDefault] + public ObjectId _id { get; set; } + + /// When the data was last updated. + public DateTimeOffset LastUpdated { get; set; } + + /// When the data was last requested through the web API. + public DateTimeOffset LastRequested { get; set; } + + /**** + ** Metadata + ****/ + /// The mod site on which the mod is found. + public ModRepositoryKey Site { get; set; } + + /// The mod's unique ID within the . + public string ID { get; set; } + + /// The mod availability status on the remote site. + public RemoteModStatus FetchStatus { get; set; } + + /// The error message providing more info for the , if applicable. + public string FetchError { get; set; } + + + /**** + ** Mod info + ****/ + /// The mod's display name. + public string Name { get; set; } + + /// The mod's latest version. + public string MainVersion { get; set; } + + /// The mod's latest optional or prerelease version, if newer than . + public string PreviewVersion { get; set; } + + /// The URL for the mod page. + public string Url { get; set; } + + + /********* + ** Accessors + *********/ + /// Construct an instance. + public CachedMod() { } + + /// Construct an instance. + /// The mod site on which the mod is found. + /// The mod's unique ID within the . + /// The mod data. + public CachedMod(ModRepositoryKey site, string id, ModInfoModel mod) + { + // tracking + this.LastUpdated = DateTimeOffset.UtcNow; + this.LastRequested = DateTimeOffset.UtcNow; + + // metadata + this.Site = site; + this.ID = id; + this.FetchStatus = mod.Status; + this.FetchError = mod.Error; + + // mod info + this.Name = mod.Name; + this.MainVersion = mod.Version; + this.PreviewVersion = mod.PreviewVersion; + this.Url = mod.Url; + } + + /// Get the API model for the cached data. + public ModInfoModel GetModel() + { + return new ModInfoModel(name: this.Name, version: this.MainVersion, previewVersion: this.PreviewVersion, url: this.Url).WithError(this.FetchStatus, this.FetchError); + } + } +} diff --git a/src/SMAPI.Web/Framework/Caching/Mods/IModCacheRepository.cs b/src/SMAPI.Web/Framework/Caching/Mods/IModCacheRepository.cs new file mode 100644 index 00000000..23929d1d --- /dev/null +++ b/src/SMAPI.Web/Framework/Caching/Mods/IModCacheRepository.cs @@ -0,0 +1,26 @@ +using StardewModdingAPI.Toolkit.Framework.UpdateData; +using StardewModdingAPI.Web.Framework.ModRepositories; + +namespace StardewModdingAPI.Web.Framework.Caching.Mods +{ + /// Encapsulates logic for accessing the mod data cache. + internal interface IModCacheRepository : ICacheRepository + { + /********* + ** Methods + *********/ + /// Get the cached mod data. + /// The mod site to search. + /// The mod's unique ID within the . + /// The fetched mod. + /// Whether to update the mod's 'last requested' date. + bool TryGetMod(ModRepositoryKey site, string id, out CachedMod mod, bool markRequested = true); + + /// Save data fetched for a mod. + /// The mod site on which the mod is found. + /// The mod's unique ID within the . + /// The mod data. + /// The stored mod record. + void SaveMod(ModRepositoryKey site, string id, ModInfoModel mod, out CachedMod cachedMod); + } +} diff --git a/src/SMAPI.Web/Framework/Caching/Mods/ModCacheRepository.cs b/src/SMAPI.Web/Framework/Caching/Mods/ModCacheRepository.cs new file mode 100644 index 00000000..d8ad7d21 --- /dev/null +++ b/src/SMAPI.Web/Framework/Caching/Mods/ModCacheRepository.cs @@ -0,0 +1,96 @@ +using System; +using MongoDB.Driver; +using StardewModdingAPI.Toolkit.Framework.UpdateData; +using StardewModdingAPI.Web.Framework.ModRepositories; + +namespace StardewModdingAPI.Web.Framework.Caching.Mods +{ + /// Encapsulates logic for accessing the mod data cache. + internal class ModCacheRepository : BaseCacheRepository, IModCacheRepository + { + /********* + ** Fields + *********/ + /// The collection for cached mod data. + private readonly IMongoCollection Mods; + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The authenticated MongoDB database. + public ModCacheRepository(IMongoDatabase database) + { + // get collections + this.Mods = database.GetCollection("mods"); + + // add indexes if needed + this.Mods.Indexes.CreateOne(new CreateIndexModel(Builders.IndexKeys.Ascending(p => p.ID).Ascending(p => p.Site))); + } + + /********* + ** Public methods + *********/ + /// Get the cached mod data. + /// The mod site to search. + /// The mod's unique ID within the . + /// The fetched mod. + /// Whether to update the mod's 'last requested' date. + public bool TryGetMod(ModRepositoryKey site, string id, out CachedMod mod, bool markRequested = true) + { + // get mod + id = this.NormaliseId(id); + mod = this.Mods.Find(entry => entry.ID == id && entry.Site == site).FirstOrDefault(); + if (mod == null) + return false; + + // bump 'last requested' + if (markRequested) + { + mod.LastRequested = DateTimeOffset.UtcNow; + mod = this.SaveMod(mod); + } + + return true; + } + + /// Save data fetched for a mod. + /// The mod site on which the mod is found. + /// The mod's unique ID within the . + /// The mod data. + /// The stored mod record. + public void SaveMod(ModRepositoryKey site, string id, ModInfoModel mod, out CachedMod cachedMod) + { + id = this.NormaliseId(id); + + cachedMod = this.SaveMod(new CachedMod(site, id, mod)); + } + + + /********* + ** Private methods + *********/ + /// Save data fetched for a mod. + /// The mod data. + public CachedMod SaveMod(CachedMod mod) + { + string id