summaryrefslogtreecommitdiff
path: root/src/SMAPI.Web/Controllers
diff options
context:
space:
mode:
authorJesse Plamondon-Willard <Pathoschild@users.noreply.github.com>2020-01-05 20:18:16 -0500
committerJesse Plamondon-Willard <Pathoschild@users.noreply.github.com>2020-01-05 20:18:16 -0500
commitf976b5c0f095a881fc20f6ce5dcf5a50ebb2b5da (patch)
tree260fa7579e1c361283bda09c2616783c3fdb5b9a /src/SMAPI.Web/Controllers
parentd34f369d35290bca96cc7225d9765d1a8a66fa8b (diff)
parent48959375b9ef52abf7c7a9404d43aac6ba780047 (diff)
downloadSMAPI-f976b5c0f095a881fc20f6ce5dcf5a50ebb2b5da.tar.gz
SMAPI-f976b5c0f095a881fc20f6ce5dcf5a50ebb2b5da.tar.bz2
SMAPI-f976b5c0f095a881fc20f6ce5dcf5a50ebb2b5da.zip
Merge branch 'develop' into stable
Diffstat (limited to 'src/SMAPI.Web/Controllers')
-rw-r--r--src/SMAPI.Web/Controllers/IndexController.cs3
-rw-r--r--src/SMAPI.Web/Controllers/JsonValidatorController.cs64
-rw-r--r--src/SMAPI.Web/Controllers/LogParserController.cs178
3 files changed, 44 insertions, 201 deletions
diff --git a/src/SMAPI.Web/Controllers/IndexController.cs b/src/SMAPI.Web/Controllers/IndexController.cs
index 4e3602d5..080285ab 100644
--- a/src/SMAPI.Web/Controllers/IndexController.cs
+++ b/src/SMAPI.Web/Controllers/IndexController.cs
@@ -16,7 +16,6 @@ namespace StardewModdingAPI.Web.Controllers
{
/// <summary>Provides an info/download page about SMAPI.</summary>
[Route("")]
- [Route("install")]
internal class IndexController : Controller
{
/*********
@@ -72,7 +71,7 @@ namespace StardewModdingAPI.Web.Controllers
: null;
// render view
- var model = new IndexModel(stableVersionModel, betaVersionModel, this.SiteConfig.BetaBlurb);
+ var model = new IndexModel(stableVersionModel, betaVersionModel, this.SiteConfig.BetaBlurb, this.SiteConfig.SupporterList);
return this.View(model);
}
diff --git a/src/SMAPI.Web/Controllers/JsonValidatorController.cs b/src/SMAPI.Web/Controllers/JsonValidatorController.cs
index 40599abc..2ade3e3d 100644
--- a/src/SMAPI.Web/Controllers/JsonValidatorController.cs
+++ b/src/SMAPI.Web/Controllers/JsonValidatorController.cs
@@ -9,8 +9,7 @@ using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Schema;
using StardewModdingAPI.Web.Framework;
-using StardewModdingAPI.Web.Framework.Clients.Pastebin;
-using StardewModdingAPI.Web.Framework.Compression;
+using StardewModdingAPI.Web.Framework.Storage;
using StardewModdingAPI.Web.ViewModels.JsonValidator;
namespace StardewModdingAPI.Web.Controllers
@@ -21,11 +20,8 @@ namespace StardewModdingAPI.Web.Controllers
/*********
** Fields
*********/
- /// <summary>The underlying Pastebin client.</summary>
- private readonly IPastebinClient Pastebin;
-
- /// <summary>The underlying text compression helper.</summary>
- private readonly IGzipHelper GzipHelper;
+ /// <summary>Provides access to raw data storage.</summary>
+ private readonly IStorageProvider Storage;
/// <summary>The supported JSON schemas (names indexed by ID).</summary>
private readonly IDictionary<string, string> SchemaFormats = new Dictionary<string, string>
@@ -49,20 +45,18 @@ namespace StardewModdingAPI.Web.Controllers
** Constructor
***/
/// <summary>Construct an instance.</summary>
- /// <param name="pastebin">The Pastebin API client.</param>
- /// <param name="gzipHelper">The underlying text compression helper.</param>
- public JsonValidatorController(IPastebinClient pastebin, IGzipHelper gzipHelper)
+ /// <param name="storage">Provides access to raw data storage.</param>
+ public JsonValidatorController(IStorageProvider storage)
{
- this.Pastebin = pastebin;
- this.GzipHelper = gzipHelper;
+ this.Storage = storage;
}
/***
** Web UI
***/
/// <summary>Render the schema validator UI.</summary>
- /// <param name="schemaName">The schema name with which to validate the JSON.</param>
- /// <param name="id">The paste ID.</param>
+ /// <param name="schemaName">The schema name with which to validate the JSON, or 'edit' to return to the edit screen.</param>
+ /// <param name="id">The stored file ID.</param>
[HttpGet]
[Route("json")]
[Route("json/{schemaName}")]
@@ -76,16 +70,20 @@ namespace StardewModdingAPI.Web.Controllers
return this.View("Index", result);
// fetch raw JSON
- PasteInfo paste = await this.GetAsync(id);
- if (string.IsNullOrWhiteSpace(paste.Content))
+ StoredFileInfo file = await this.Storage.GetAsync(id);
+ if (string.IsNullOrWhiteSpace(file.Content))
return this.View("Index", result.SetUploadError("The JSON file seems to be empty."));
- result.SetContent(paste.Content);
+ result.SetContent(file.Content, expiry: file.Expiry, uploadWarning: file.Warning);
+
+ // skip parsing if we're going to the edit screen
+ if (schemaName?.ToLower() == "edit")
+ return this.View("Index", result);
// parse JSON
JToken parsed;
try
{
- parsed = JToken.Parse(paste.Content, new JsonLoadSettings
+ parsed = JToken.Parse(file.Content, new JsonLoadSettings
{
DuplicatePropertyNameHandling = DuplicatePropertyNameHandling.Error,
CommentHandling = CommentHandling.Load
@@ -97,7 +95,7 @@ namespace StardewModdingAPI.Web.Controllers
}
// format JSON
- result.SetContent(parsed.ToString(Formatting.Indented));
+ result.SetContent(parsed.ToString(Formatting.Indented), expiry: file.Expiry, uploadWarning: file.Warning);
// skip if no schema selected
if (schemaName == "none")
@@ -132,23 +130,20 @@ namespace StardewModdingAPI.Web.Controllers
public async Task<ActionResult> PostAsync(JsonValidatorRequestModel request)
{
if (request == null)
- return this.View("Index", new JsonValidatorModel(null, null, this.SchemaFormats).SetUploadError("The request seems to be invalid."));
+ return this.View("Index", this.GetModel(null, null).SetUploadError("The request seems to be invalid."));
// normalize schema name
string schemaName = this.NormalizeSchemaName(request.SchemaName);
- // get raw log text
+ // get raw text
string input = request.Content;
if (string.IsNullOrWhiteSpace(input))
- return this.View("Index", new JsonValidatorModel(null, schemaName, this.SchemaFormats).SetUploadError("The JSON file seems to be empty."));
-
- // upload log
- input = this.GzipHelper.CompressString(input);
- SavePasteResult result = await this.Pastebin.PostAsync($"JSON validator {DateTime.UtcNow:s}", input);
+ return this.View("Index", this.GetModel(null, schemaName).SetUploadError("The JSON file seems to be empty."));
- // handle errors
- if (!result.Success)
- return this.View("Index", new JsonValidatorModel(result.ID, schemaName, this.SchemaFormats).SetUploadError($"Pastebin error: {result.Error ?? "unknown error"}"));
+ // upload file
+ UploadResult result = await this.Storage.SaveAsync(input);
+ if (!result.Succeeded)
+ return this.View("Index", this.GetModel(result.ID, schemaName).SetUploadError(result.UploadError));
// redirect to view
return this.Redirect(this.Url.PlainAction("Index", "JsonValidator", new { schemaName = schemaName, id = result.ID }));
@@ -158,13 +153,12 @@ namespace StardewModdingAPI.Web.Controllers
/*********
** Private methods
*********/
- /// <summary>Fetch raw text from Pastebin.</summary>
- /// <param name="id">The Pastebin paste ID.</param>
- private async Task<PasteInfo> GetAsync(string id)
+ /// <summary>Build a JSON validator model.</summary>
+ /// <param name="pasteID">The stored file ID.</param>
+ /// <param name="schemaName">The schema name with which the JSON was validated.</param>
+ private JsonValidatorModel GetModel(string pasteID, string schemaName)
{
- PasteInfo response = await this.Pastebin.GetAsync(id);
- response.Content = this.GzipHelper.DecompressString(response.Content);
- return response;
+ return new JsonValidatorModel(pasteID, schemaName, this.SchemaFormats);
}
/// <summary>Get a normalized schema name, or the <see cref="DefaultSchemaID"/> if blank.</summary>
diff --git a/src/SMAPI.Web/Controllers/LogParserController.cs b/src/SMAPI.Web/Controllers/LogParserController.cs
index 318b34d0..97c419d9 100644
--- a/src/SMAPI.Web/Controllers/LogParserController.cs
+++ b/src/SMAPI.Web/Controllers/LogParserController.cs
@@ -1,22 +1,12 @@
using System;
-using System.IO;
using System.Linq;
-using System.Text;
using System.Threading.Tasks;
-using Amazon;
-using Amazon.Runtime;
-using Amazon.S3;
-using Amazon.S3.Model;
-using Amazon.S3.Transfer;
using Microsoft.AspNetCore.Mvc;
-using Microsoft.Extensions.Options;
using StardewModdingAPI.Toolkit.Utilities;
using StardewModdingAPI.Web.Framework;
-using StardewModdingAPI.Web.Framework.Clients.Pastebin;
-using StardewModdingAPI.Web.Framework.Compression;
-using StardewModdingAPI.Web.Framework.ConfigModels;
using StardewModdingAPI.Web.Framework.LogParsing;
using StardewModdingAPI.Web.Framework.LogParsing.Models;
+using StardewModdingAPI.Web.Framework.Storage;
using StardewModdingAPI.Web.ViewModels;
namespace StardewModdingAPI.Web.Controllers
@@ -27,14 +17,8 @@ namespace StardewModdingAPI.Web.Controllers
/*********
** Fields
*********/
- /// <summary>The API client settings.</summary>
- private readonly ApiClientsConfig ClientsConfig;
-
- /// <summary>The underlying Pastebin client.</summary>
- private readonly IPastebinClient Pastebin;
-
- /// <summary>The underlying text compression helper.</summary>
- private readonly IGzipHelper GzipHelper;
+ /// <summary>Provides access to raw data storage.</summary>
+ private readonly IStorageProvider Storage;
/*********
@@ -44,21 +28,17 @@ namespace StardewModdingAPI.Web.Controllers
** Constructor
***/
/// <summary>Construct an instance.</summary>
- /// <param name="clientsConfig">The API client settings.</param>
- /// <param name="pastebin">The Pastebin API client.</param>
- /// <param name="gzipHelper">The underlying text compression helper.</param>
- public LogParserController(IOptions<ApiClientsConfig> clientsConfig, IPastebinClient pastebin, IGzipHelper gzipHelper)
+ /// <param name="storage">Provides access to raw data storage.</param>
+ public LogParserController(IStorageProvider storage)
{
- this.ClientsConfig = clientsConfig.Value;
- this.Pastebin = pastebin;
- this.GzipHelper = gzipHelper;
+ this.Storage = storage;
}
/***
** Web UI
***/
/// <summary>Render the log parser UI.</summary>
- /// <param name="id">The paste ID.</param>
+ /// <param name="id">The stored file ID.</param>
/// <param name="raw">Whether to display the raw unparsed log.</param>
[HttpGet]
[Route("log")]
@@ -70,12 +50,12 @@ namespace StardewModdingAPI.Web.Controllers
return this.View("Index", this.GetModel(id));
// log page
- PasteInfo paste = await this.GetAsync(id);
- ParsedLog log = paste.Success
- ? new LogParser().Parse(paste.Content)
- : new ParsedLog { IsValid = false, Error = paste.Error };
+ StoredFileInfo file = await this.Storage.GetAsync(id);
+ ParsedLog log = file.Success
+ ? new LogParser().Parse(file.Content)
+ : new ParsedLog { IsValid = false, Error = file.Error };
- return this.View("Index", this.GetModel(id, uploadWarning: paste.Warning, expiry: paste.Expiry).SetResult(log, raw));
+ return this.View("Index", this.GetModel(id, uploadWarning: file.Warning, expiry: file.Expiry).SetResult(log, raw));
}
/***
@@ -92,8 +72,7 @@ namespace StardewModdingAPI.Web.Controllers
return this.View("Index", this.GetModel(null, uploadError: "The log file seems to be empty."));
// upload log
- input = this.GzipHelper.CompressString(input);
- var uploadResult = await this.TrySaveLog(input);
+ UploadResult uploadResult = await this.Storage.SaveAsync(input);
if (!uploadResult.Succeeded)
return this.View("Index", this.GetModel(null, uploadError: uploadResult.UploadError));
@@ -105,106 +84,8 @@ namespace StardewModdingAPI.Web.Controllers
/*********
** Private methods
*********/
- /// <summary>Fetch raw text from Pastebin.</summary>
- /// <param name="id">The Pastebin paste ID.</param>
- private async Task<PasteInfo> GetAsync(string id)
- {
- // get from Amazon S3
- if (Guid.TryParseExact(id, "N", out Guid _))
- {
- var credentials = new BasicAWSCredentials(accessKey: this.ClientsConfig.AmazonAccessKey, secretKey: this.ClientsConfig.AmazonSecretKey);
-
- using (IAmazonS3 s3 = new AmazonS3Client(credentials, RegionEndpoint.GetBySystemName(this.ClientsConfig.AmazonRegion)))
- {
- try
- {
- using (GetObjectResponse response = await s3.GetObjectAsync(this.ClientsConfig.AmazonLogBucket, $"logs/{id}"))
- using (Stream responseStream = response.ResponseStream)
- using (StreamReader reader = new StreamReader(responseStream))
- {
- DateTime expiry = response.Expiration.ExpiryDateUtc;
- string pastebinError = response.Metadata["x-amz-meta-pastebin-error"];
- string content = this.GzipHelper.DecompressString(reader.ReadToEnd());
-
- return new PasteInfo
- {
- Success = true,
- Content = content,
- Expiry = expiry,
- Warning = pastebinError
- };
- }
- }
- catch (AmazonServiceException ex)
- {
- return ex.ErrorCode == "NoSuchKey"
- ? new PasteInfo { Error = "There's no log with that ID." }
- : new PasteInfo { Error = $"Could not fetch that log from AWS S3 ({ex.ErrorCode}: {ex.Message})." };
- }
- }
- }
-
- // get from PasteBin
- else
- {
- PasteInfo response = await this.Pastebin.GetAsync(id);
- response.Content = this.GzipHelper.DecompressString(response.Content);
- return response;
- }
- }
-
- /// <summary>Save a log to Pastebin or Amazon S3, if available.</summary>
- /// <param name="content">The content to upload.</param>
- /// <returns>Returns metadata about the save attempt.</returns>
- private async Task<UploadResult> TrySaveLog(string content)
- {
- // save to PasteBin
- string uploadError;
- {
- SavePasteResult result = await this.Pastebin.PostAsync($"SMAPI log {DateTime.UtcNow:s}", content);
- if (result.Success)
- return new UploadResult(true, result.ID, null);
-
- uploadError = $"Pastebin error: {result.Error ?? "unknown error"}";
- }
-
- // fallback to S3
- try
- {
- var credentials = new BasicAWSCredentials(accessKey: this.ClientsConfig.AmazonAccessKey, secretKey: this.ClientsConfig.AmazonSecretKey);
-
- using (Stream stream = new MemoryStream(Encoding.UTF8.GetBytes(content)))
- using (IAmazonS3 s3 = new AmazonS3Client(credentials, RegionEndpoint.GetBySystemName(this.ClientsConfig.AmazonRegion)))
- using (TransferUtility uploader = new TransferUtility(s3))
- {
- string id = Guid.NewGuid().ToString("N");
-
- var uploadRequest = new TransferUtilityUploadRequest
- {
- BucketName = this.ClientsConfig.AmazonLogBucket,
- Key = $"logs/{id}",
- InputStream = stream,
- Metadata =
- {
- // note: AWS will lowercase keys and prefix 'x-amz-meta-'
- ["smapi-uploaded"] = DateTime.UtcNow.ToString("O"),
- ["pastebin-error"] = uploadError
- }
- };
-
- await uploader.UploadAsync(uploadRequest);
-
- return new UploadResult(true, id, uploadError);
- }
- }
- catch (Exception ex)
- {
- return new UploadResult(false, null, $"{uploadError}\n{ex.Message}");
- }
- }
-
/// <summary>Build a log parser model.</summary>
- /// <param name="pasteID">The paste ID.</param>
+ /// <param name="pasteID">The stored file ID.</param>
/// <param name="expiry">When the uploaded file will no longer be available.</param>
/// <param name="uploadWarning">A non-blocking warning while uploading the log.</param>
/// <param name="uploadError">An error which occurred while uploading the log.</param>
@@ -243,36 +124,5 @@ namespace StardewModdingAPI.Web.Controllers
return null;
}
}
-
- /// <summary>The result of an attempt to upload a file.</summary>
- private class UploadResult
- {
- /*********
- ** Accessors
- *********/
- /// <summary>Whether the file upload succeeded.</summary>
- public bool Succeeded { get; }
-
- /// <summary>The file ID, if applicable.</summary>
- public string ID { get; }
-
- /// <summary>The upload error, if any.</summary>
- public string UploadError { get; }
-
-
- /*********
- ** Public methods
- *********/
- /// <summary>Construct an instance.</summary>
- /// <param name="succeeded">Whether the file upload succeeded.</param>
- /// <param name="id">The file ID, if applicable.</param>
- /// <param name="uploadError">The upload error, if any.</param>
- public UploadResult(bool succeeded, string id, string uploadError)
- {
- this.Succeeded = succeeded;
- this.ID = id;
- this.UploadError = uploadError;
- }
- }
}
}