From 0fd9fc92d0c4ea49cf5601ab303a3b1aac9853c3 Mon Sep 17 00:00:00 2001 From: Steven Hildreth Date: Tue, 18 Jan 2022 16:52:02 -0600 Subject: [PATCH] CodeMaid cleanup, Warning resolve work --- .../ArtistLookupEngineTests.cs | 4 +- Roadie.Api.Library.Tests/LastFmHelperTests.cs | 111 ++++++++++++ .../Roadie.Library.Tests.csproj | 2 +- .../Caching/SystemTextCacheSerializer.cs | 51 ++++++ Roadie.Api.Library/Extensions/ExceptionExt.cs | 2 +- Roadie.Api.Library/Roadie.Library.csproj | 1 + Roadie.Api.Library/Roadie.Library.nuspec | 2 +- .../MetaData/LastFm/JsonEntities.cs | 95 ++++++++++ .../MetaData/LastFm/LastFmHelper.cs | 168 ++++++++++++------ .../MusicBrainz/MusicBrainzProvider.cs | 6 +- .../MusicBrainz/MusicBrainzRepository.cs | 19 +- .../MusicBrainz/MusicBrainzRequestHelper.cs | 40 ++--- .../Utility/EncryptionHelper.cs | 60 +++---- .../Utility/SymmetricEncryptor.cs | 167 +++++++++++++++++ Roadie.Api.Services/AdminService.cs | 4 +- Roadie.Api.Services/IAdminService.cs | 3 + Roadie.Api/Controllers/AccountController.cs | 8 +- Roadie.Api/Controllers/AdminController.cs | 4 +- Roadie.Api/Controllers/ArtistController.cs | 2 +- Roadie.Api/Controllers/BookmarkController.cs | 2 +- .../Controllers/CollectionController.cs | 4 +- Roadie.Api/Controllers/CommentController.cs | 2 +- .../Controllers/EntityControllerBase.cs | 7 +- Roadie.Api/Controllers/GenreController.cs | 2 +- Roadie.Api/Controllers/ImageController.cs | 3 +- Roadie.Api/Controllers/LabelController.cs | 2 +- Roadie.Api/Controllers/LookupController.cs | 2 +- .../Controllers/PlayActivityController.cs | 2 +- Roadie.Api/Controllers/PlayController.cs | 2 +- Roadie.Api/Controllers/PlaylistController.cs | 4 +- Roadie.Api/Controllers/ReleaseController.cs | 2 +- Roadie.Api/Controllers/StatsController.cs | 2 +- Roadie.Api/Controllers/SubsonicController.cs | 3 +- Roadie.Api/Controllers/SystemController.cs | 4 - Roadie.Api/Controllers/TrackController.cs | 2 +- Roadie.Api/Controllers/UserController.cs | 3 +- Roadie.Api/LoggingTraceListener.cs | 3 +- Roadie.Api/ModelBinding/SubsonicRequest.cs | 2 +- .../ModelBinding/SubsonicRequestBinder.cs | 6 +- .../SubsonicRequestBinderProvider.cs | 2 +- Roadie.Api/Models/LoginModel.cs | 3 +- Roadie.Api/Models/RegisterModel.cs | 2 +- Roadie.Api/Models/ResetPasswordModel.cs | 4 +- Roadie.Api/Program.cs | 2 +- Roadie.Api/Startup.cs | 21 +-- 45 files changed, 652 insertions(+), 190 deletions(-) create mode 100644 Roadie.Api.Library.Tests/LastFmHelperTests.cs create mode 100644 Roadie.Api.Library/Caching/SystemTextCacheSerializer.cs create mode 100644 Roadie.Api.Library/SearchEngines/MetaData/LastFm/JsonEntities.cs create mode 100644 Roadie.Api.Library/Utility/SymmetricEncryptor.cs diff --git a/Roadie.Api.Library.Tests/ArtistLookupEngineTests.cs b/Roadie.Api.Library.Tests/ArtistLookupEngineTests.cs index 378e136..bce4646 100644 --- a/Roadie.Api.Library.Tests/ArtistLookupEngineTests.cs +++ b/Roadie.Api.Library.Tests/ArtistLookupEngineTests.cs @@ -20,7 +20,9 @@ namespace Roadie.Library.Tests } private IRoadieSettings Configuration { get; } + public DictionaryCacheManager CacheManager { get; } + private Encoding.IHttpEncoder HttpEncoder { get; } public ArtistLookupEngineTests() @@ -43,4 +45,4 @@ namespace Roadie.Library.Tests Console.WriteLine($"Log Level [{ e.Level }] Log Message [{ e.Message }] "); } } -} \ No newline at end of file +} diff --git a/Roadie.Api.Library.Tests/LastFmHelperTests.cs b/Roadie.Api.Library.Tests/LastFmHelperTests.cs new file mode 100644 index 0000000..585d4af --- /dev/null +++ b/Roadie.Api.Library.Tests/LastFmHelperTests.cs @@ -0,0 +1,111 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using Roadie.Library.Caching; +using Roadie.Library.Configuration; +using Roadie.Library.MetaData.LastFm; +using Roadie.Library.Processors; +using Roadie.Library.MetaData.MusicBrainz; +using Roadie.Library.SearchEngines.MetaData.Discogs; +using System; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; +using Xunit; +using Roadie.Library.Data.Context; +using System.Collections.Generic; + +namespace Roadie.Library.Tests +{ + public class LastFmHelperTests : HttpClientFactoryBaseTests + { + private IEventMessageLogger MessageLogger { get; } + + private IRoadieSettings Configuration { get; } + + public DictionaryCacheManager CacheManager { get; } + + private Encoding.IHttpEncoder HttpEncoder { get; } + + private IRoadieDbContext RoadieDbContext { get; } + + private ILogger Logger + { + get + { + return MessageLogger as ILogger; + } + } + + public LastFmHelperTests() + { + MessageLogger = new EventMessageLogger(); + MessageLogger.Messages += MessageLogger_Messages; + + var settings = new RoadieSettings(); + IConfigurationBuilder configurationBuilder = new ConfigurationBuilder(); + configurationBuilder.AddJsonFile("appsettings.test.json"); + IConfiguration configuration = configurationBuilder.Build(); + configuration.GetSection("RoadieSettings").Bind(settings); + Configuration = settings; + CacheManager = new DictionaryCacheManager(Logger, new SystemTextCacheSerializer(Logger), new CachePolicy(TimeSpan.FromHours(4))); + HttpEncoder = new Encoding.DummyHttpEncoder(); + } + + [Fact] + public async Task LastFMReleaseSearch() + { + if (!Configuration.Integrations.LastFmProviderEnabled) + { + return; + } + var logger = new EventMessageLogger(); + logger.Messages += MessageLogger_Messages; + var lfmHelper = new LastFmHelper(Configuration, CacheManager, new EventMessageLogger(), RoadieDbContext, HttpEncoder, _httpClientFactory); + + var artistName = "Billy Joel"; + var title = "Piano Man"; + var sw = Stopwatch.StartNew(); + + var result = await lfmHelper.PerformReleaseSearch(artistName, title, 1).ConfigureAwait(false); + + sw.Stop(); + + Assert.NotNull(result); + Assert.NotNull(result.Data); + Assert.NotEmpty(result.Data); + var release = result.Data.FirstOrDefault(); + Assert.NotNull(release); + } + + [Fact] + public async Task LastFMArtistSearch() + { + if (!Configuration.Integrations.LastFmProviderEnabled) + { + return; + } + var logger = new EventMessageLogger(); + logger.Messages += MessageLogger_Messages; + var lfmHelper = new LastFmHelper(Configuration, CacheManager, new EventMessageLogger(), RoadieDbContext, HttpEncoder, _httpClientFactory); + + var artistName = "Billy Joel"; + var sw = Stopwatch.StartNew(); + + var result = await lfmHelper.PerformArtistSearchAsync(artistName, 1).ConfigureAwait(false); + + sw.Stop(); + + Assert.NotNull(result); + Assert.NotNull(result.Data); + Assert.NotEmpty(result.Data); + var release = result.Data.FirstOrDefault(); + Assert.NotNull(release); + } + + private void MessageLogger_Messages(object sender, EventMessage e) + { + Console.WriteLine($"Log Level [{ e.Level }] Log Message [{ e.Message }] "); + } + + } +} diff --git a/Roadie.Api.Library.Tests/Roadie.Library.Tests.csproj b/Roadie.Api.Library.Tests/Roadie.Library.Tests.csproj index ac2f6a8..fb537f3 100644 --- a/Roadie.Api.Library.Tests/Roadie.Library.Tests.csproj +++ b/Roadie.Api.Library.Tests/Roadie.Library.Tests.csproj @@ -19,7 +19,7 @@ - + diff --git a/Roadie.Api.Library/Caching/SystemTextCacheSerializer.cs b/Roadie.Api.Library/Caching/SystemTextCacheSerializer.cs new file mode 100644 index 0000000..30a3f81 --- /dev/null +++ b/Roadie.Api.Library/Caching/SystemTextCacheSerializer.cs @@ -0,0 +1,51 @@ +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Roadie.Library.Caching +{ + public sealed class SystemTextCacheSerializer : ICacheSerializer + { + private ILogger Logger { get; } + + public SystemTextCacheSerializer(ILogger logger) + { + Logger = logger; + } + + public string Serialize(object o) + { + if (o == null) + { + return null; + } + try + { + return System.Text.Json.JsonSerializer.Serialize(o); + } + catch (Exception ex) + { + Logger.LogError(ex); + } + return null; + } + + public TOut Deserialize(string s) + { + if (string.IsNullOrEmpty(s)) + { + return default(TOut); + } + try + { + return System.Text.Json.JsonSerializer.Deserialize(s); + } + catch (Exception ex) + { + Logger.LogError(ex); + } + return default(TOut); + } + } +} diff --git a/Roadie.Api.Library/Extensions/ExceptionExt.cs b/Roadie.Api.Library/Extensions/ExceptionExt.cs index f86ac26..9c35174 100644 --- a/Roadie.Api.Library/Extensions/ExceptionExt.cs +++ b/Roadie.Api.Library/Extensions/ExceptionExt.cs @@ -15,7 +15,7 @@ namespace Roadie.Library.Extensions { return JsonSerializer.Serialize(input, new JsonSerializerOptions { - IgnoreNullValues = true, + DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull, WriteIndented = true }); } diff --git a/Roadie.Api.Library/Roadie.Library.csproj b/Roadie.Api.Library/Roadie.Library.csproj index c47a907..c552858 100644 --- a/Roadie.Api.Library/Roadie.Library.csproj +++ b/Roadie.Api.Library/Roadie.Library.csproj @@ -15,6 +15,7 @@ + diff --git a/Roadie.Api.Library/Roadie.Library.nuspec b/Roadie.Api.Library/Roadie.Library.nuspec index eb6fdfc..9ab01d1 100644 --- a/Roadie.Api.Library/Roadie.Library.nuspec +++ b/Roadie.Api.Library/Roadie.Library.nuspec @@ -2,7 +2,7 @@ $id$ - $version$ + 1.0.1-pre $title$ $author$ false diff --git a/Roadie.Api.Library/SearchEngines/MetaData/LastFm/JsonEntities.cs b/Roadie.Api.Library/SearchEngines/MetaData/LastFm/JsonEntities.cs new file mode 100644 index 0000000..da6377e --- /dev/null +++ b/Roadie.Api.Library/SearchEngines/MetaData/LastFm/JsonEntities.cs @@ -0,0 +1,95 @@ +using System.Text.Json.Serialization; + +namespace Roadie.Library.SearchEngines.MetaData.LastFm +{ + public class Rootobject + { + public Album album { get; set; } + } + + public class Album + { + public string artist { get; set; } + + public string mbid { get; set; } + + public Tags tags { get; set; } + + public string name { get; set; } + + public Image[] image { get; set; } + + public Tracks tracks { get; set; } + + public string listeners { get; set; } + + public string playcount { get; set; } + + public string url { get; set; } + } + + public class Tags + { + public Tag[] tag { get; set; } + } + + public class Tag + { + public string url { get; set; } + + public string name { get; set; } + } + + public class Tracks + { + public Track[] track { get; set; } + } + + public class Track + { + public Streamable streamable { get; set; } + + public int duration { get; set; } + + public string url { get; set; } + + public string name { get; set; } + + [JsonPropertyName("@attr")] + public Attr attr { get; set; } + + //public int? TrackNumber => string.IsNullOrWhiteSpace(attr) ? null : int.Parse(attr.Replace("\"@attr\":{\"rank\":", "").Replace("}", "")); + + public int? TrackNumber => attr?.rank; + + public Artist artist { get; set; } + } + + public class Streamable + { + public string fulltrack { get; set; } + + public string text { get; set; } + } + + public class Attr + { + public int rank { get; set; } + } + + public class Artist + { + public string url { get; set; } + + public string name { get; set; } + + public string mbid { get; set; } + } + + public class Image + { + public string size { get; set; } + + public string text { get; set; } + } +} diff --git a/Roadie.Api.Library/SearchEngines/MetaData/LastFm/LastFmHelper.cs b/Roadie.Api.Library/SearchEngines/MetaData/LastFm/LastFmHelper.cs index 298fbd7..dca93b5 100644 --- a/Roadie.Api.Library/SearchEngines/MetaData/LastFm/LastFmHelper.cs +++ b/Roadie.Api.Library/SearchEngines/MetaData/LastFm/LastFmHelper.cs @@ -1,7 +1,6 @@ using IF.Lastfm.Core.Api; using IF.Lastfm.Core.Objects; using Microsoft.Extensions.Logging; -using RestSharp; using Roadie.Library.Caching; using Roadie.Library.Configuration; using Roadie.Library.Data.Context; @@ -18,20 +17,22 @@ using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; -using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; using System.Xml; using System.Xml.XPath; -using data = Roadie.Library.Data; +using static System.Net.Mime.MediaTypeNames; namespace Roadie.Library.MetaData.LastFm { public class LastFmHelper : MetaDataProviderBase, ILastFmHelper { private const string LastFmErrorCodeXPath = "/lfm/error/@code"; + private const string LastFmErrorXPath = "/lfm/error"; + private const string LastFmStatusOk = "ok"; + private const string LastFmStatusXPath = "/lfm/@status"; public override bool IsEnabled => @@ -90,12 +91,17 @@ namespace Roadie.Library.MetaData.LastFm { {"token", token} }; - var request = new RestRequest(); - request.Method = Method.Get; - var client = new RestClient(BuildUrl("auth.getSession", parameters)); - var responseXML = await client.ExecuteAsync(request).ConfigureAwait(false); + string responseXML = null; + var client = _httpClientFactory.CreateClient(); + var request = new HttpRequestMessage(HttpMethod.Get, BuildUrl("auth.getSession", parameters)); + request.Headers.Add("User-Agent", WebHelper.UserAgent); + var response = await client.SendAsync(request).ConfigureAwait(false); + if (response.IsSuccessStatusCode) + { + responseXML = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + } var doc = new XmlDocument(); - doc.LoadXml(responseXML.Content); + doc.LoadXml(responseXML); var sessionKey = doc.GetElementsByTagName("key")[0].InnerText; return new OperationResult { @@ -117,36 +123,31 @@ namespace Roadie.Library.MetaData.LastFm { return new OperationResult("User does not have LastFM Integration setup"); } - await Task.Run(() => + var method = "track.updateNowPlaying"; + var parameters = new RequestParameters { - var method = "track.updateNowPlaying"; - var parameters = new RequestParameters - { - {"artist", scrobble.ArtistName}, - {"track", scrobble.TrackTitle}, - {"album", scrobble.ReleaseTitle}, - {"duration", ((int) scrobble.TrackDuration.TotalSeconds).ToString()} - }; - var url = "http://ws.audioscrobbler.com/2.0/"; - var signature = GenerateMethodSignature(method, parameters, user.LastFMSessionKey); - parameters.Add("api_sig", signature); + {"artist", scrobble.ArtistName}, + {"track", scrobble.TrackTitle}, + {"album", scrobble.ReleaseTitle}, + {"duration", ((int) scrobble.TrackDuration.TotalSeconds).ToString()} + }; + var url = "http://ws.audioscrobbler.com/2.0/"; + var signature = GenerateMethodSignature(method, parameters, user.LastFMSessionKey); + parameters.Add("api_sig", signature); - ServicePointManager.Expect100Continue = false; - var request = WebRequest.Create(url); - request.Method = "POST"; - var postData = parameters.ToString(); - var byteArray = System.Text.Encoding.UTF8.GetBytes(postData); - request.ContentType = "application/x-www-form-urlencoded"; - request.ContentLength = byteArray.Length; - using (var dataStream = request.GetRequestStream()) + ServicePointManager.Expect100Continue = false; + var client = _httpClientFactory.CreateClient(); + XPathNavigator xp = null; + var parametersJson = new StringContent(CacheManager.CacheSerializer.Serialize(parameters), System.Text.Encoding.UTF8, Application.Json); + using (var httpResponseMessage = await client.PostAsync(url, parametersJson).ConfigureAwait(false)) + { + if (httpResponseMessage.IsSuccessStatusCode) { - dataStream.Write(byteArray, 0, byteArray.Length); - dataStream.Close(); + xp = await GetResponseAsXml(httpResponseMessage).ConfigureAwait(false); + result = true; } - var xp = GetResponseAsXml(request); - Logger.LogTrace($"LastFmHelper: RoadieUser `{roadieUser}` NowPlaying `{scrobble}` LastFmResult [{xp.InnerXml}]"); - result = true; - }).ConfigureAwait(false); + } + Logger.LogTrace($"LastFmHelper: Success [{ result }] RoadieUser `{roadieUser}` NowPlaying `{scrobble}` LastFmResult [{xp.InnerXml}]"); } catch (Exception ex) { @@ -169,7 +170,7 @@ namespace Roadie.Library.MetaData.LastFm { Logger.LogTrace("LastFmHelper:PerformArtistSearch:{0}", query); var auth = new LastAuth(ApiKey.Key, ApiKey.KeySecret); - var albumApi = new ArtistApi(auth); + var albumApi = new ArtistApi(auth, _httpClientFactory.CreateClient()); var response = await albumApi.GetInfoAsync(query).ConfigureAwait(false); if (!response.Success) { @@ -205,19 +206,29 @@ namespace Roadie.Library.MetaData.LastFm return new OperationResult>(); } - public async Task>> PerformReleaseSearch(string artistName,string query, int resultsCount) + public async Task>> PerformReleaseSearch(string artistName, string query, int resultsCount) { var cacheKey = $"uri:lastfm:releasesearch:{ artistName.ToAlphanumericName() }:{ query.ToAlphanumericName() }"; var data = await CacheManager.GetAsync(cacheKey, async () => { - var request = new RestRequest(); - request.Method = Method.Get; - var client = new RestClient(string.Format("http://ws.audioscrobbler.com/2.0/?method=album.getinfo&api_key={0}&artist={1}&album={2}&format=xml", ApiKey.Key, artistName, query)); - var responseData = await client.ExecuteAsync(request).ConfigureAwait(false); - + Rootobject response = null; + var client = _httpClientFactory.CreateClient(); + var request = new HttpRequestMessage(HttpMethod.Get, $"http://ws.audioscrobbler.com/2.0/?method=album.getinfo&api_key={ ApiKey.Key }&artist={ artistName }&album={ query }&format=json"); + request.Headers.Add("User-Agent", WebHelper.UserAgent); + var sendResponse = await client.SendAsync(request).ConfigureAwait(false); + if (sendResponse.IsSuccessStatusCode) + { + try + { + var r = await sendResponse.Content.ReadAsStringAsync().ConfigureAwait(false); + response = CacheManager.CacheSerializer.Deserialize(r); + } + catch (Exception ex) + { + Logger.LogError(ex); + } + } ReleaseSearchResult result = null; - - var response = responseData != null && responseData.Data != null ? responseData.Data : null; if (response != null && response.album != null) { var lastFmAlbum = response.album; @@ -229,15 +240,15 @@ namespace Roadie.Library.MetaData.LastFm // No longer fetching/consuming images LastFm says is violation of ToS ; https://getsatisfaction.com/lastfm/topics/api-announcement-dac8oefw5vrxq - if (lastFmAlbum.tags != null) result.Tags = lastFmAlbum.tags.Select(x => x.name).ToList(); + if (lastFmAlbum.tags != null) result.Tags = lastFmAlbum.tags.tag.Select(x => x.name).ToList(); if (lastFmAlbum.tracks != null) { var tracks = new List(); - foreach (var lastFmTrack in lastFmAlbum.tracks) + foreach (var lastFmTrack in lastFmAlbum.tracks.track) { tracks.Add(new TrackSearchResult { - TrackNumber = SafeParser.ToNumber(lastFmTrack.rank), + TrackNumber = SafeParser.ToNumber(lastFmTrack.TrackNumber), Title = lastFmTrack.name, Duration = SafeParser.ToNumber(lastFmTrack.duration), Urls = string.IsNullOrEmpty(lastFmTrack.url) ? new[] { lastFmTrack.url } : null @@ -282,7 +293,9 @@ namespace Roadie.Library.MetaData.LastFm var user = DbContext.Users.FirstOrDefault(x => x.RoadieId == roadieUser.UserId); if (user == null || string.IsNullOrEmpty(user.LastFMSessionKey)) + { return new OperationResult("User does not have LastFM Integration setup"); + } var parameters = new RequestParameters { {"artist", scrobble.ArtistName}, @@ -299,19 +312,17 @@ namespace Roadie.Library.MetaData.LastFm parameters.Add("api_sig", signature); ServicePointManager.Expect100Continue = false; - var request = WebRequest.Create(url); - request.Method = "POST"; - var postData = parameters.ToString(); - var byteArray = System.Text.Encoding.UTF8.GetBytes(postData); - request.ContentType = "application/x-www-form-urlencoded"; - request.ContentLength = byteArray.Length; - using (var dataStream = request.GetRequestStream()) + var client = _httpClientFactory.CreateClient(); + XPathNavigator xp = null; + var parametersJson = new StringContent(CacheManager.CacheSerializer.Serialize(parameters), System.Text.Encoding.UTF8, Application.Json); + using (var httpResponseMessage = await client.PostAsync(url, parametersJson).ConfigureAwait(false)) { - dataStream.Write(byteArray, 0, byteArray.Length); - dataStream.Close(); + if (httpResponseMessage.IsSuccessStatusCode) + { + xp = await GetResponseAsXml(httpResponseMessage).ConfigureAwait(false); + result = true; + } } - - var xp = GetResponseAsXml(request); Logger.LogTrace($"LastFmHelper: RoadieUser `{roadieUser}` Scrobble `{scrobble}` LastFmResult [{xp.InnerXml}]"); result = true; } @@ -389,7 +400,7 @@ namespace Roadie.Library.MetaData.LastFm return result; } - protected internal virtual XPathNavigator GetResponseAsXml(WebRequest request) + protected internal virtual XPathNavigator GetResponseXml(HttpWebRequest request) { WebResponse response; XPathNavigator navigator; @@ -413,6 +424,45 @@ namespace Roadie.Library.MetaData.LastFm return navigator; } + protected internal virtual async Task GetResponseAsXml(HttpResponseMessage request) + { + XPathNavigator navigator; + try + { + navigator = await GetXpathDocumentFromResponse(request); + CheckLastFmStatus(navigator); + } + catch (WebException exception) + { + var response = exception.Response; + + XPathNavigator document; + TryGetXpathDocumentFromResponse(response, out document); + + if (document != null) CheckLastFmStatus(document, exception); + throw; // throw even if Last.fm status is OK + } + + return navigator; + } + + protected virtual async Task GetXpathDocumentFromResponse(HttpResponseMessage response) + { + using (var stream = await response.Content.ReadAsStreamAsync()) + { + if (stream == null) throw new InvalidOperationException("Response Stream is null"); + + try + { + return new XPathDocument(stream).CreateNavigator(); + } + catch (XmlException exception) + { + throw new XmlException("Could not read HTTP Response as XML", exception); + } + } + } + protected virtual XPathNavigator GetXpathDocumentFromResponse(WebResponse response) { using (var stream = response.GetResponseStream()) @@ -482,4 +532,4 @@ namespace Roadie.Library.MetaData.LastFm return HashHelper.CreateMD5(builder.ToString()); } } -} \ No newline at end of file +} diff --git a/Roadie.Api.Library/SearchEngines/MetaData/MusicBrainz/MusicBrainzProvider.cs b/Roadie.Api.Library/SearchEngines/MetaData/MusicBrainz/MusicBrainzProvider.cs index c97c10e..f0f0b00 100644 --- a/Roadie.Api.Library/SearchEngines/MetaData/MusicBrainz/MusicBrainzProvider.cs +++ b/Roadie.Api.Library/SearchEngines/MetaData/MusicBrainz/MusicBrainzProvider.cs @@ -25,7 +25,7 @@ namespace Roadie.Library.MetaData.MusicBrainz IHttpClientFactory httpClientFactory) : base(configuration, cacheManager, logger, httpClientFactory) { - Repository = new MusicBrainzRepository(configuration, logger); + Repository = new MusicBrainzRepository(configuration, logger, httpClientFactory); } public async Task> MusicBrainzReleaseTracksAsync(string artistName, string releaseTitle) @@ -61,7 +61,7 @@ namespace Roadie.Library.MetaData.MusicBrainz } // Now get The Release Details - release = await MusicBrainzRequestHelper.GetAsync(MusicBrainzRequestHelper.CreateLookupUrl("release", ReleaseResult.MusicBrainzId, "recordings")).ConfigureAwait(false); + release = await MusicBrainzRequestHelper.GetAsync(_httpClientFactory, MusicBrainzRequestHelper.CreateLookupUrl("release", ReleaseResult.MusicBrainzId, "recordings")).ConfigureAwait(false); if (release == null) return null; CacheManager.Add(ReleaseCacheKey, release); } @@ -300,7 +300,7 @@ namespace Roadie.Library.MetaData.MusicBrainz }; } - private Task CoverArtForMusicBrainzReleaseByIdAsync(string musicBrainzId) => MusicBrainzRequestHelper.GetAsync(MusicBrainzRequestHelper.CreateCoverArtReleaseUrl(musicBrainzId)); + private Task CoverArtForMusicBrainzReleaseByIdAsync(string musicBrainzId) => MusicBrainzRequestHelper.GetAsync(_httpClientFactory, MusicBrainzRequestHelper.CreateCoverArtReleaseUrl(musicBrainzId)); private async Task> ReleasesForArtistAsync(string artist, string artistMusicBrainzId = null) { diff --git a/Roadie.Api.Library/SearchEngines/MetaData/MusicBrainz/MusicBrainzRepository.cs b/Roadie.Api.Library/SearchEngines/MetaData/MusicBrainz/MusicBrainzRepository.cs index 438e006..28414f8 100644 --- a/Roadie.Api.Library/SearchEngines/MetaData/MusicBrainz/MusicBrainzRepository.cs +++ b/Roadie.Api.Library/SearchEngines/MetaData/MusicBrainz/MusicBrainzRepository.cs @@ -13,9 +13,15 @@ namespace Roadie.Library.MetaData.MusicBrainz public class MusicBrainzRepository { private string FileName { get; } + private ILogger Logger { get; } - public MusicBrainzRepository(IRoadieSettings configuration, ILogger logger) + private IHttpClientFactory HttpClientFactory { get; } + + public MusicBrainzRepository( + IRoadieSettings configuration, + ILogger logger, + IHttpClientFactory httpClientFactory) { Logger = logger; var location = System.Reflection.Assembly.GetEntryAssembly().Location; @@ -25,6 +31,7 @@ namespace Roadie.Library.MetaData.MusicBrainz Directory.CreateDirectory(directory); } FileName = Path.Combine(directory, "MusicBrainzRespository.db"); + HttpClientFactory = httpClientFactory; } /// @@ -47,14 +54,14 @@ namespace Roadie.Library.MetaData.MusicBrainz if (artist == null) { // Perform a query to get the MbId for the Name - var artistResult = await MusicBrainzRequestHelper.GetAsync(MusicBrainzRequestHelper.CreateSearchTemplate("artist", name, resultsCount ?? 1, 0)).ConfigureAwait(false); + var artistResult = await MusicBrainzRequestHelper.GetAsync(HttpClientFactory, MusicBrainzRequestHelper.CreateSearchTemplate("artist", name, resultsCount ?? 1, 0)).ConfigureAwait(false); if (artistResult == null || artistResult.artists == null || !artistResult.artists.Any() || artistResult.count < 1) { return null; } var mbId = artistResult.artists.First().id; // Now perform a detail request to get the details by the MbId - result = await MusicBrainzRequestHelper.GetAsync(MusicBrainzRequestHelper.CreateLookupUrl("artist", mbId, "aliases+tags+genres+url-rels")).ConfigureAwait(false); + result = await MusicBrainzRequestHelper.GetAsync(HttpClientFactory, MusicBrainzRequestHelper.CreateLookupUrl("artist", mbId, "aliases+tags+genres+url-rels")).ConfigureAwait(false); if (result != null) { col.Insert(new RepositoryArtist @@ -108,11 +115,11 @@ namespace Roadie.Library.MetaData.MusicBrainz var releases = col.Find(x => x.ArtistMbId == artistMbId); if (releases == null || !releases.Any()) { - // Query to get collection of Releases for Artist + // Query to get collection of Releases for Artist var pageSize = 50; var page = 0; var url = MusicBrainzRequestHelper.CreateArtistBrowseTemplate(artistMbId, pageSize, 0); - var mbReleaseBrowseResult = await MusicBrainzRequestHelper.GetAsync(url).ConfigureAwait(false); + var mbReleaseBrowseResult = await MusicBrainzRequestHelper.GetAsync(HttpClientFactory, url).ConfigureAwait(false); var totalReleases = mbReleaseBrowseResult != null ? mbReleaseBrowseResult.releasecount : 0; var totalPages = Math.Ceiling((decimal)totalReleases / pageSize); var fetchResult = new List(); @@ -123,7 +130,7 @@ namespace Roadie.Library.MetaData.MusicBrainz fetchResult.AddRange(mbReleaseBrowseResult.releases.Where(x => !string.IsNullOrEmpty(x.date))); } page++; - mbReleaseBrowseResult = await MusicBrainzRequestHelper.GetAsync(MusicBrainzRequestHelper.CreateArtistBrowseTemplate(artistMbId, pageSize, pageSize * page)).ConfigureAwait(false); + mbReleaseBrowseResult = await MusicBrainzRequestHelper.GetAsync(HttpClientFactory, MusicBrainzRequestHelper.CreateArtistBrowseTemplate(artistMbId, pageSize, pageSize * page)).ConfigureAwait(false); } while (page < totalPages); var releasesToInsert = fetchResult.GroupBy(x => x.title).Select(x => x.OrderBy(x => x.date).First()).OrderBy(x => x.date).ThenBy(x => x.title); col.InsertBulk(releasesToInsert.Where(x => x != null).Select(x => new RepositoryRelease diff --git a/Roadie.Api.Library/SearchEngines/MetaData/MusicBrainz/MusicBrainzRequestHelper.cs b/Roadie.Api.Library/SearchEngines/MetaData/MusicBrainz/MusicBrainzRequestHelper.cs index 103db3f..374a389 100644 --- a/Roadie.Api.Library/SearchEngines/MetaData/MusicBrainz/MusicBrainzRequestHelper.cs +++ b/Roadie.Api.Library/SearchEngines/MetaData/MusicBrainz/MusicBrainzRequestHelper.cs @@ -2,6 +2,7 @@ using System; using System.Diagnostics; using System.Net; +using System.Net.Http; using System.Text.Json; using System.Threading; using System.Threading.Tasks; @@ -11,28 +12,23 @@ namespace Roadie.Library.MetaData.MusicBrainz public static class MusicBrainzRequestHelper { private const string LookupTemplate = "{0}/{1}/?inc={2}&fmt=json&limit=100"; + private const int MaxRetries = 6; + private const string ReleaseBrowseTemplate = "release?artist={0}&limit={1}&offset={2}&fmt=json&inc={3}"; + private const string SearchTemplate = "{0}?query={1}&limit={2}&offset={3}&fmt=json"; + private const string WebServiceUrl = "http://musicbrainz.org/ws/2/"; - internal static string CreateArtistBrowseTemplate(string id, int limit, int offset) - { - return string.Format("{0}{1}", WebServiceUrl, string.Format(ReleaseBrowseTemplate, id, limit, offset, "labels+aliases+recordings+release-groups+media+url-rels+tags+genres")); - } + internal static string CreateArtistBrowseTemplate(string id, int limit, int offset) => string.Format("{0}{1}", WebServiceUrl, string.Format(ReleaseBrowseTemplate, id, limit, offset, "labels+aliases+recordings+release-groups+media+url-rels+tags+genres")); - internal static string CreateCoverArtReleaseUrl(string musicBrainzId) - { - return string.Format("http://coverartarchive.org/release/{0}", musicBrainzId); - } + internal static string CreateCoverArtReleaseUrl(string musicBrainzId) => string.Format("http://coverartarchive.org/release/{0}", musicBrainzId); /// /// Creates a webservice lookup template. /// - internal static string CreateLookupUrl(string entity, string mbid, string inc) - { - return string.Format("{0}{1}", WebServiceUrl, string.Format(LookupTemplate, entity, mbid, inc)); - } + internal static string CreateLookupUrl(string entity, string mbid, string inc) => string.Format("{0}{1}", WebServiceUrl, string.Format(LookupTemplate, entity, mbid, inc)); /// /// Creates a webservice search template. @@ -44,7 +40,7 @@ namespace Roadie.Library.MetaData.MusicBrainz return string.Format("{0}{1}", WebServiceUrl, string.Format(SearchTemplate, entity, query, limit, offset)); } - internal static async Task GetAsync(string url, bool withoutMetadata = true) + internal static async Task GetAsync(IHttpClientFactory httpClientFactory, string url, bool withoutMetadata = true) { var tryCount = 0; var result = default(T); @@ -53,20 +49,20 @@ namespace Roadie.Library.MetaData.MusicBrainz { try { - using (var webClient = new WebClient()) + var client = httpClientFactory.CreateClient(); + var request = new HttpRequestMessage(HttpMethod.Get, url); + request.Headers.Add("User-Agent", WebHelper.UserAgent); + var response = await client.SendAsync(request).ConfigureAwait(false); + if (response.IsSuccessStatusCode) { - webClient.Headers.Add("user-agent", WebHelper.UserAgent); - downloadedString = await webClient.DownloadStringTaskAsync(new Uri(url)).ConfigureAwait(false); - if (!string.IsNullOrWhiteSpace(downloadedString)) - { - result = JsonSerializer.Deserialize(downloadedString); - } + downloadedString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + result = JsonSerializer.Deserialize(downloadedString); } } catch (WebException ex) { var response = ex.Response as HttpWebResponse; - if(response?.StatusCode == HttpStatusCode.NotFound) + if (response?.StatusCode == HttpStatusCode.NotFound) { Trace.WriteLine($"GetAsync: 404 Response For url [{ url }]", "Warning"); return result; @@ -86,4 +82,4 @@ namespace Roadie.Library.MetaData.MusicBrainz return result; } } -} \ No newline at end of file +} diff --git a/Roadie.Api.Library/Utility/EncryptionHelper.cs b/Roadie.Api.Library/Utility/EncryptionHelper.cs index 98f22fd..25dc99f 100644 --- a/Roadie.Api.Library/Utility/EncryptionHelper.cs +++ b/Roadie.Api.Library/Utility/EncryptionHelper.cs @@ -1,60 +1,46 @@ using System; -using System.IO; +using System.Linq; using System.Security.Cryptography; namespace Roadie.Library.Utility { public static class EncryptionHelper { + public static string Decrypt(string cyphertext, string key) { - if (string.IsNullOrEmpty(cyphertext) || string.IsNullOrEmpty(key)) return null; - if (key.Length > 16) key = key.Substring(0, 16); + if (string.IsNullOrEmpty(cyphertext) || string.IsNullOrEmpty(key)) + { + return null; + } + if (key.Length > 16) + { + key = key.Substring(0, 16); + } return Decrypt(Convert.FromBase64String(cyphertext), System.Text.Encoding.UTF8.GetBytes(key)); } - public static string Decrypt(byte[] cyphertext, byte[] key) + public static string Decrypt(byte[] encryptedData, byte[] key) { - using (var ms = new MemoryStream(cyphertext)) - using (var desObj = Rijndael.Create()) - { - desObj.Key = key; - var iv = new byte[16]; - var offset = 0; - while (offset < iv.Length) offset += ms.Read(iv, offset, iv.Length - offset); - desObj.IV = iv; - using (var cs = new CryptoStream(ms, desObj.CreateDecryptor(), CryptoStreamMode.Read)) - using (var sr = new StreamReader(cs, System.Text.Encoding.UTF8)) - { - return sr.ReadToEnd(); - } - } + return SymmetricEncryptor.DecryptToString(encryptedData, key); } public static string Encrypt(string plaintext, string key) { - if (string.IsNullOrEmpty(plaintext) || string.IsNullOrEmpty(key)) return null; - if (key.Length > 16) key = key.Substring(0, 16); + if (string.IsNullOrEmpty(plaintext) || string.IsNullOrEmpty(key)) + { + return null; + } + if (key.Length > 16) + { + key = key.Substring(0, 16); + } return Convert.ToBase64String(Encrypt(plaintext, System.Text.Encoding.UTF8.GetBytes(key))); } - public static byte[] Encrypt(string plaintext, byte[] key) + public static byte[] Encrypt(string toEncrypt, byte[] key) { - using (var desObj = Rijndael.Create()) - { - desObj.Key = key; - using (var ms = new MemoryStream()) - { - ms.Write(desObj.IV, 0, desObj.IV.Length); - using (var cs = new CryptoStream(ms, desObj.CreateEncryptor(), CryptoStreamMode.Write)) - { - var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(plaintext); - cs.Write(plainTextBytes, 0, plainTextBytes.Length); - } - - return ms.ToArray(); - } - } + return SymmetricEncryptor.EncryptString(toEncrypt, key); } } -} \ No newline at end of file +} diff --git a/Roadie.Api.Library/Utility/SymmetricEncryptor.cs b/Roadie.Api.Library/Utility/SymmetricEncryptor.cs new file mode 100644 index 0000000..46611c4 --- /dev/null +++ b/Roadie.Api.Library/Utility/SymmetricEncryptor.cs @@ -0,0 +1,167 @@ +using System; +using System.Linq; +using System.Security.Cryptography; + +namespace Roadie.Library.Utility +{ + /// + /// AES Encryption class, from https://tomrucki.com/posts/aes-encryption-in-csharp/ + /// + public static class SymmetricEncryptor + { + private const int AesBlockByteSize = 128 / 8; + + private const int PasswordSaltByteSize = 128 / 8; + + private const int PasswordByteSize = 256 / 8; + + private const int PasswordIterationCount = 100_000; + + private const int SignatureByteSize = 256 / 8; + + private const int MinimumEncryptedMessageByteSize = + PasswordSaltByteSize + // auth salt + PasswordSaltByteSize + // key salt + AesBlockByteSize + // IV + AesBlockByteSize + // cipher text min length + SignatureByteSize; // signature tag + + private static readonly System.Text.Encoding StringEncoding = System.Text.Encoding.UTF8; + + private static readonly RandomNumberGenerator Random = RandomNumberGenerator.Create(); + + public static byte[] EncryptString(string toEncrypt, byte[] password) => EncryptString(toEncrypt, System.Text.Encoding.UTF8.GetString(password)); + + public static byte[] EncryptString(string toEncrypt, string password) + { + // encrypt + var keySalt = GenerateRandomBytes(PasswordSaltByteSize); + var key = GetKey(password, keySalt); + var iv = GenerateRandomBytes(AesBlockByteSize); + + byte[] cipherText; + using (var aes = CreateAes()) + using (var encryptor = aes.CreateEncryptor(key, iv)) + { + var plainText = StringEncoding.GetBytes(toEncrypt); + cipherText = encryptor + .TransformFinalBlock(plainText, 0, plainText.Length); + } + + // sign + var authKeySalt = GenerateRandomBytes(PasswordSaltByteSize); + var authKey = GetKey(password, authKeySalt); + + var result = MergeArrays( + additionalCapacity: SignatureByteSize, + authKeySalt, keySalt, iv, cipherText); + + using (var hmac = new HMACSHA256(authKey)) + { + var payloadToSignLength = result.Length - SignatureByteSize; + var signatureTag = hmac.ComputeHash(result, 0, payloadToSignLength); + signatureTag.CopyTo(result, payloadToSignLength); + } + + return result; + } + + public static string DecryptToString(byte[] encryptedData, byte[] password) => DecryptToString(encryptedData, System.Text.Encoding.UTF8.GetString(password)); + + public static string DecryptToString(byte[] encryptedData, string password) + { + if (encryptedData is null + || encryptedData.Length < MinimumEncryptedMessageByteSize) + { + throw new ArgumentException("Invalid length of encrypted data"); + } + + var authKeySalt = encryptedData + .AsSpan(0, PasswordSaltByteSize).ToArray(); + var keySalt = encryptedData + .AsSpan(PasswordSaltByteSize, PasswordSaltByteSize).ToArray(); + var iv = encryptedData + .AsSpan(2 * PasswordSaltByteSize, AesBlockByteSize).ToArray(); + var signatureTag = encryptedData + .AsSpan(encryptedData.Length - SignatureByteSize, SignatureByteSize).ToArray(); + + var cipherTextIndex = authKeySalt.Length + keySalt.Length + iv.Length; + var cipherTextLength = + encryptedData.Length - cipherTextIndex - signatureTag.Length; + + var authKey = GetKey(password, authKeySalt); + var key = GetKey(password, keySalt); + + // verify signature + using (var hmac = new HMACSHA256(authKey)) + { + var payloadToSignLength = encryptedData.Length - SignatureByteSize; + var signatureTagExpected = hmac + .ComputeHash(encryptedData, 0, payloadToSignLength); + + // constant time checking to prevent timing attacks + var signatureVerificationResult = 0; + for (int i = 0; i < signatureTag.Length; i++) + { + signatureVerificationResult |= signatureTag[i] ^ signatureTagExpected[i]; + } + + if (signatureVerificationResult != 0) + { + throw new CryptographicException("Invalid signature"); + } + } + + // decrypt + using (var aes = CreateAes()) + { + using (var encryptor = aes.CreateDecryptor(key, iv)) + { + var decryptedBytes = encryptor + .TransformFinalBlock(encryptedData, cipherTextIndex, cipherTextLength); + return StringEncoding.GetString(decryptedBytes); + } + } + } + + private static Aes CreateAes() + { + var aes = Aes.Create(); + aes.Mode = CipherMode.CBC; + aes.Padding = PaddingMode.PKCS7; + return aes; + } + + private static byte[] GetKey(string password, byte[] passwordSalt) + { + var keyBytes = StringEncoding.GetBytes(password); + + using (var derivator = new Rfc2898DeriveBytes( + keyBytes, passwordSalt, + PasswordIterationCount, HashAlgorithmName.SHA256)) + { + return derivator.GetBytes(PasswordByteSize); + } + } + + private static byte[] GenerateRandomBytes(int numberOfBytes) + { + var randomBytes = new byte[numberOfBytes]; + Random.GetBytes(randomBytes); + return randomBytes; + } + + private static byte[] MergeArrays(int additionalCapacity = 0, params byte[][] arrays) + { + var merged = new byte[arrays.Sum(a => a.Length) + additionalCapacity]; + var mergeIndex = 0; + for (int i = 0; i < arrays.GetLength(0); i++) + { + arrays[i].CopyTo(merged, mergeIndex); + mergeIndex += arrays[i].Length; + } + + return merged; + } + } +} diff --git a/Roadie.Api.Services/AdminService.cs b/Roadie.Api.Services/AdminService.cs index f183504..8cdbf0d 100644 --- a/Roadie.Api.Services/AdminService.cs +++ b/Roadie.Api.Services/AdminService.cs @@ -658,9 +658,7 @@ namespace Roadie.Api.Services }); } - /// - /// Perform checks/setup on start of application - /// + public void PerformStartUpTasks() { var sw = Stopwatch.StartNew(); diff --git a/Roadie.Api.Services/IAdminService.cs b/Roadie.Api.Services/IAdminService.cs index 981900b..98f9657 100644 --- a/Roadie.Api.Services/IAdminService.cs +++ b/Roadie.Api.Services/IAdminService.cs @@ -31,6 +31,9 @@ namespace Roadie.Api.Services Task>>> MissingCollectionReleasesAsync(User user); + /// + /// Perform checks/setup on start of application + /// void PerformStartUpTasks(); Task> ScanAllCollectionsAsync(User user, bool isReadOnly = false, bool doPurgeFirst = false); diff --git a/Roadie.Api/Controllers/AccountController.cs b/Roadie.Api/Controllers/AccountController.cs index f8a596c..6b4a77b 100644 --- a/Roadie.Api/Controllers/AccountController.cs +++ b/Roadie.Api/Controllers/AccountController.cs @@ -17,7 +17,6 @@ using System.Linq; using System.Net; using System.Text; using System.Text.Encodings.Web; -using System.Text.Json; using System.Threading.Tasks; namespace Roadie.Api.Controllers @@ -28,7 +27,6 @@ namespace Roadie.Api.Controllers [AllowAnonymous] public class AccountController : ControllerBase { - private IAdminService AdminService { get; } private string BaseUrl @@ -65,9 +63,13 @@ namespace Roadie.Api.Controllers private IRoadieSettings RoadieSettings { get; } private string _baseUrl; + private readonly ILogger Logger; + private readonly SignInManager SignInManager; + private readonly ITokenService TokenService; + private readonly UserManager UserManager; public AccountController( @@ -378,4 +380,4 @@ namespace Roadie.Api.Controllers return StatusCode(500); } } -} \ No newline at end of file +} diff --git a/Roadie.Api/Controllers/AdminController.cs b/Roadie.Api/Controllers/AdminController.cs index 6fe25b9..03ae89a 100644 --- a/Roadie.Api/Controllers/AdminController.cs +++ b/Roadie.Api/Controllers/AdminController.cs @@ -78,7 +78,7 @@ namespace Roadie.Api.Controllers [ProducesResponseType(200)] public async Task DeleteArtistSecondaryImage(Guid id, int index) { - var result = await AdminService.DeleteArtistSecondaryImageAsync(await UserManager.GetUserAsync(User).ConfigureAwait(false), id, index).ConfigureAwait(false); + var result = await AdminService.DeleteArtistSecondaryImageAsync(await UserManager.GetUserAsync(User).ConfigureAwait(false), id, index).ConfigureAwait(false); if (!result.IsSuccess) { if (result.Messages?.Any() ?? false) @@ -360,4 +360,4 @@ namespace Roadie.Api.Controllers return Ok(result); } } -} \ No newline at end of file +} diff --git a/Roadie.Api/Controllers/ArtistController.cs b/Roadie.Api/Controllers/ArtistController.cs index e81952b..140e90f 100644 --- a/Roadie.Api/Controllers/ArtistController.cs +++ b/Roadie.Api/Controllers/ArtistController.cs @@ -200,4 +200,4 @@ namespace Roadie.Api.Controllers return Ok(result); } } -} \ No newline at end of file +} diff --git a/Roadie.Api/Controllers/BookmarkController.cs b/Roadie.Api/Controllers/BookmarkController.cs index aceeb11..6f381cb 100644 --- a/Roadie.Api/Controllers/BookmarkController.cs +++ b/Roadie.Api/Controllers/BookmarkController.cs @@ -59,4 +59,4 @@ namespace Roadie.Api.Controllers return StatusCode((int)HttpStatusCode.InternalServerError); } } -} \ No newline at end of file +} diff --git a/Roadie.Api/Controllers/CollectionController.cs b/Roadie.Api/Controllers/CollectionController.cs index 7fd4c3f..7452edc 100644 --- a/Roadie.Api/Controllers/CollectionController.cs +++ b/Roadie.Api/Controllers/CollectionController.cs @@ -88,7 +88,7 @@ namespace Roadie.Api.Controllers public async Task Get(Guid id, string inc = null) { var result = await CollectionService.ByIdAsync(await CurrentUserModel().ConfigureAwait(false), id, - (inc ?? Collection.DefaultIncludes).ToLower().Split(",")).ConfigureAwait(false); + (inc ?? Collection.DefaultIncludes).ToLower().Split(",")).ConfigureAwait(false); if (result == null || result.IsNotFoundResult) { return NotFound(); @@ -165,4 +165,4 @@ namespace Roadie.Api.Controllers return Ok(result); } } -} \ No newline at end of file +} diff --git a/Roadie.Api/Controllers/CommentController.cs b/Roadie.Api/Controllers/CommentController.cs index bcf0661..2a8aa2e 100644 --- a/Roadie.Api/Controllers/CommentController.cs +++ b/Roadie.Api/Controllers/CommentController.cs @@ -304,4 +304,4 @@ namespace Roadie.Api.Controllers return Ok(result); } } -} \ No newline at end of file +} diff --git a/Roadie.Api/Controllers/EntityControllerBase.cs b/Roadie.Api/Controllers/EntityControllerBase.cs index e8637e4..b26e9c2 100644 --- a/Roadie.Api/Controllers/EntityControllerBase.cs +++ b/Roadie.Api/Controllers/EntityControllerBase.cs @@ -12,7 +12,6 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Net; -using System.Text.Json; using System.Threading.Tasks; using models = Roadie.Library.Models.Users; @@ -91,7 +90,7 @@ namespace Roadie.Api.Controllers var info = await trackService.TrackStreamInfoAsync(id, TrackService.DetermineByteStartFromHeaders(Request.Headers), TrackService.DetermineByteEndFromHeaders(Request.Headers, track.Data.FileSize), - user).ConfigureAwait(false); + user).ConfigureAwait(false); if (!info?.IsSuccess ?? false || info?.Data == null) { if (info?.Errors != null && (info?.Errors.Any() ?? false)) @@ -144,7 +143,7 @@ namespace Roadie.Api.Controllers TimePlayed = DateTime.UtcNow, TrackId = id }; - await playActivityService.NowPlayingAsync(user, scrobble).ConfigureAwait(false); + await playActivityService.NowPlayingAsync(user, scrobble).ConfigureAwait(false); sw.Stop(); Logger.LogTrace($"StreamTrack ElapsedTime [{sw.ElapsedMilliseconds}], Timings [{CacheManager.CacheSerializer.Serialize(timings)}], StreamInfo `{info?.Data}`"); return new EmptyResult(); @@ -163,4 +162,4 @@ namespace Roadie.Api.Controllers return result; } } -} \ No newline at end of file +} diff --git a/Roadie.Api/Controllers/GenreController.cs b/Roadie.Api/Controllers/GenreController.cs index 112debe..c496026 100644 --- a/Roadie.Api/Controllers/GenreController.cs +++ b/Roadie.Api/Controllers/GenreController.cs @@ -163,4 +163,4 @@ namespace Roadie.Api.Controllers return Ok(result); } } -} \ No newline at end of file +} diff --git a/Roadie.Api/Controllers/ImageController.cs b/Roadie.Api/Controllers/ImageController.cs index 8d40ee1..1e0c1e3 100644 --- a/Roadie.Api/Controllers/ImageController.cs +++ b/Roadie.Api/Controllers/ImageController.cs @@ -15,7 +15,6 @@ namespace Roadie.Api.Controllers [Produces("application/json")] [Route("images")] [ApiController] - // [Authorize] public class ImageController : EntityControllerBase { private IImageService ImageService { get; } @@ -279,4 +278,4 @@ namespace Roadie.Api.Controllers return MakeFileResult(result.Data.Bytes, $"{id}.gif", result.ContentType, result.LastModified, result.ETag); } } -} \ No newline at end of file +} diff --git a/Roadie.Api/Controllers/LabelController.cs b/Roadie.Api/Controllers/LabelController.cs index 23f6d2f..e81b118 100644 --- a/Roadie.Api/Controllers/LabelController.cs +++ b/Roadie.Api/Controllers/LabelController.cs @@ -164,4 +164,4 @@ namespace Roadie.Api.Controllers return Ok(result); } } -} \ No newline at end of file +} diff --git a/Roadie.Api/Controllers/LookupController.cs b/Roadie.Api/Controllers/LookupController.cs index 83fee09..aa26528 100644 --- a/Roadie.Api/Controllers/LookupController.cs +++ b/Roadie.Api/Controllers/LookupController.cs @@ -172,4 +172,4 @@ namespace Roadie.Api.Controllers return Ok(result); } } -} \ No newline at end of file +} diff --git a/Roadie.Api/Controllers/PlayActivityController.cs b/Roadie.Api/Controllers/PlayActivityController.cs index b57b41d..07e707e 100644 --- a/Roadie.Api/Controllers/PlayActivityController.cs +++ b/Roadie.Api/Controllers/PlayActivityController.cs @@ -68,4 +68,4 @@ namespace Roadie.Api.Controllers return Ok(result); } } -} \ No newline at end of file +} diff --git a/Roadie.Api/Controllers/PlayController.cs b/Roadie.Api/Controllers/PlayController.cs index 2e559c7..02e74ca 100644 --- a/Roadie.Api/Controllers/PlayController.cs +++ b/Roadie.Api/Controllers/PlayController.cs @@ -127,4 +127,4 @@ namespace Roadie.Api.Controllers return await StreamTrack(id, TrackService, PlayActivityService, UserModelForUser(user)).ConfigureAwait(false); } } -} \ No newline at end of file +} diff --git a/Roadie.Api/Controllers/PlaylistController.cs b/Roadie.Api/Controllers/PlaylistController.cs index c108e91..49ea934 100644 --- a/Roadie.Api/Controllers/PlaylistController.cs +++ b/Roadie.Api/Controllers/PlaylistController.cs @@ -110,7 +110,7 @@ namespace Roadie.Api.Controllers [ProducesResponseType(200)] public async Task List([FromQuery] PagedRequest request, string inc) { - var result = await PlaylistService.ListAsync(roadieUser: await CurrentUserModel().ConfigureAwait(false), request: request).ConfigureAwait(false); + var result = await PlaylistService.ListAsync(roadieUser: await CurrentUserModel().ConfigureAwait(false), request: request).ConfigureAwait(false); if (!result.IsSuccess) { return StatusCode((int)HttpStatusCode.InternalServerError); @@ -174,4 +174,4 @@ namespace Roadie.Api.Controllers return Ok(result); } } -} \ No newline at end of file +} diff --git a/Roadie.Api/Controllers/ReleaseController.cs b/Roadie.Api/Controllers/ReleaseController.cs index bea1761..6e12707 100644 --- a/Roadie.Api/Controllers/ReleaseController.cs +++ b/Roadie.Api/Controllers/ReleaseController.cs @@ -190,4 +190,4 @@ namespace Roadie.Api.Controllers return Ok(result); } } -} \ No newline at end of file +} diff --git a/Roadie.Api/Controllers/StatsController.cs b/Roadie.Api/Controllers/StatsController.cs index b4ff8e0..51e5d66 100644 --- a/Roadie.Api/Controllers/StatsController.cs +++ b/Roadie.Api/Controllers/StatsController.cs @@ -85,4 +85,4 @@ namespace Roadie.Api.Controllers [ProducesResponseType(200)] public async Task SongsPlayedByUser() => Ok(await StatisticsService.SongsPlayedByUserAsync().ConfigureAwait(false)); } -} \ No newline at end of file +} diff --git a/Roadie.Api/Controllers/SubsonicController.cs b/Roadie.Api/Controllers/SubsonicController.cs index 292e656..83ee115 100644 --- a/Roadie.Api/Controllers/SubsonicController.cs +++ b/Roadie.Api/Controllers/SubsonicController.cs @@ -14,7 +14,6 @@ using System; using System.Collections.Generic; using System.Linq; using System.Net; -using System.Text.Json; using System.Threading.Tasks; using User = Roadie.Library.Models.Users.User; @@ -985,4 +984,4 @@ namespace Roadie.Api.Controllers #endregion Response Builder Methods } -} \ No newline at end of file +} diff --git a/Roadie.Api/Controllers/SystemController.cs b/Roadie.Api/Controllers/SystemController.cs index 0463661..111d230 100644 --- a/Roadie.Api/Controllers/SystemController.cs +++ b/Roadie.Api/Controllers/SystemController.cs @@ -2,13 +2,9 @@ using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; -using Roadie.Api.Services; using Roadie.Library.Caching; using Roadie.Library.Configuration; using Roadie.Library.Identity; -using System.Collections.Generic; -using System.Diagnostics; -using System.Threading.Tasks; namespace Roadie.Api.Controllers { diff --git a/Roadie.Api/Controllers/TrackController.cs b/Roadie.Api/Controllers/TrackController.cs index d296457..c76011b 100644 --- a/Roadie.Api/Controllers/TrackController.cs +++ b/Roadie.Api/Controllers/TrackController.cs @@ -115,4 +115,4 @@ namespace Roadie.Api.Controllers return Ok(result); } } -} \ No newline at end of file +} diff --git a/Roadie.Api/Controllers/UserController.cs b/Roadie.Api/Controllers/UserController.cs index 2343c2f..12e0a2d 100644 --- a/Roadie.Api/Controllers/UserController.cs +++ b/Roadie.Api/Controllers/UserController.cs @@ -22,6 +22,7 @@ namespace Roadie.Api.Controllers public class UserController : EntityControllerBase { private IHttpContext RoadieHttpContext { get; } + private IUserService UserService { get; } private readonly ITokenService TokenService; @@ -391,4 +392,4 @@ namespace Roadie.Api.Controllers }); } } -} \ No newline at end of file +} diff --git a/Roadie.Api/LoggingTraceListener.cs b/Roadie.Api/LoggingTraceListener.cs index 25b9738..3a3eb80 100644 --- a/Roadie.Api/LoggingTraceListener.cs +++ b/Roadie.Api/LoggingTraceListener.cs @@ -1,5 +1,4 @@ using Serilog; -using System; using System.Diagnostics; namespace Roadie.Api @@ -32,4 +31,4 @@ namespace Roadie.Api } } } -} \ No newline at end of file +} diff --git a/Roadie.Api/ModelBinding/SubsonicRequest.cs b/Roadie.Api/ModelBinding/SubsonicRequest.cs index af1db85..3738e05 100644 --- a/Roadie.Api/ModelBinding/SubsonicRequest.cs +++ b/Roadie.Api/ModelBinding/SubsonicRequest.cs @@ -7,4 +7,4 @@ namespace Roadie.Api.ModelBinding public class SubsonicRequest : Request { } -} \ No newline at end of file +} diff --git a/Roadie.Api/ModelBinding/SubsonicRequestBinder.cs b/Roadie.Api/ModelBinding/SubsonicRequestBinder.cs index e0d6fe3..d608ada 100644 --- a/Roadie.Api/ModelBinding/SubsonicRequestBinder.cs +++ b/Roadie.Api/ModelBinding/SubsonicRequestBinder.cs @@ -10,9 +10,9 @@ using System.Threading.Tasks; namespace Roadie.Api.ModelBinding { /// - /// This is needed as some clienst post some get, some query string some body post. + /// This is needed as some clients post some get, some query string some body post. /// - class SubsonicRequestBinder : IModelBinder + internal class SubsonicRequestBinder : IModelBinder { public Task BindModelAsync(ModelBindingContext bindingContext) { @@ -172,4 +172,4 @@ namespace Roadie.Api.ModelBinding return Task.CompletedTask; } } -} \ No newline at end of file +} diff --git a/Roadie.Api/ModelBinding/SubsonicRequestBinderProvider.cs b/Roadie.Api/ModelBinding/SubsonicRequestBinderProvider.cs index 02b120a..6bbec23 100644 --- a/Roadie.Api/ModelBinding/SubsonicRequestBinderProvider.cs +++ b/Roadie.Api/ModelBinding/SubsonicRequestBinderProvider.cs @@ -14,4 +14,4 @@ namespace Roadie.Api.ModelBinding return null; } } -} \ No newline at end of file +} diff --git a/Roadie.Api/Models/LoginModel.cs b/Roadie.Api/Models/LoginModel.cs index 02d9aa1..0948f4a 100644 --- a/Roadie.Api/Models/LoginModel.cs +++ b/Roadie.Api/Models/LoginModel.cs @@ -5,6 +5,7 @@ namespace Roadie.Api.Models public class LoginModel { [Required] public string Password { get; set; } + [Required] public string Username { get; set; } } -} \ No newline at end of file +} diff --git a/Roadie.Api/Models/RegisterModel.cs b/Roadie.Api/Models/RegisterModel.cs index 7bf42a6..efb0154 100644 --- a/Roadie.Api/Models/RegisterModel.cs +++ b/Roadie.Api/Models/RegisterModel.cs @@ -15,4 +15,4 @@ namespace Roadie.Api.Models [Compare(nameof(Password))] public string PasswordConfirmation { get; set; } } -} \ No newline at end of file +} diff --git a/Roadie.Api/Models/ResetPasswordModel.cs b/Roadie.Api/Models/ResetPasswordModel.cs index 074edd6..527154e 100644 --- a/Roadie.Api/Models/ResetPasswordModel.cs +++ b/Roadie.Api/Models/ResetPasswordModel.cs @@ -4,13 +4,11 @@ namespace Roadie.Api.Models { public class ResetPasswordModel : LoginModel { - [Required] [Compare(nameof(Password))] public string PasswordConfirmation { get; set; } [Required] public string Token { get; set; } - } -} \ No newline at end of file +} diff --git a/Roadie.Api/Program.cs b/Roadie.Api/Program.cs index a3d6ab8..8108cd7 100644 --- a/Roadie.Api/Program.cs +++ b/Roadie.Api/Program.cs @@ -67,4 +67,4 @@ namespace Roadie.Api }) .UseSerilog(); } -} \ No newline at end of file +} diff --git a/Roadie.Api/Startup.cs b/Roadie.Api/Startup.cs index 8308f70..1849f7a 100644 --- a/Roadie.Api/Startup.cs +++ b/Roadie.Api/Startup.cs @@ -16,7 +16,6 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.IdentityModel.Tokens; -using Polly; using Roadie.Api.Hubs; using Roadie.Api.ModelBinding; using Roadie.Api.Services; @@ -46,7 +45,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; -using System.Net.Http; using System.Reflection; using System.Text; @@ -64,7 +62,7 @@ namespace Roadie.Api TypeAdapterConfig.GlobalSettings.Default.PreserveReference(true); } - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IAdminService adminService) { if (env.IsDevelopment()) { @@ -102,6 +100,8 @@ namespace Roadie.Api endpoints.MapControllers(); endpoints.MapDefaultControllerRoute(); }); + + adminService.PerformStartUpTasks(); } // This method gets called by the runtime. Use this method to add services to the container. @@ -117,7 +117,7 @@ namespace Roadie.Api services.AddSingleton(options => { var logger = options.GetService>(); - return new Utf8JsonCacheSerializer(logger); + return new SystemTextCacheSerializer(logger); }); services.AddSingleton(options => @@ -226,7 +226,7 @@ namespace Roadie.Api configuration.GetSection("RoadieSettings").Bind(settings); var hostingEnvironment = ctx.GetService(); settings.ContentPath = hostingEnvironment.WebRootPath; - settings.ConnectionString = _configuration.GetConnectionString("RoadieDatabaseConnection"); + settings.ConnectionString = _configuration.GetConnectionString("RoadieDatabaseConnection"); // This is so 'User Secrets' can be used in Debugging var integrationKeys = _configuration.GetSection("IntegrationKeys").Get(); @@ -335,7 +335,7 @@ namespace Roadie.Api options.RespectBrowserAcceptHeader = true; // false by default options.ModelBinderProviders.Insert(0, new SubsonicRequestBinderProvider()); }) - .AddJsonOptions(options => options.JsonSerializerOptions.IgnoreNullValues = true) + .AddJsonOptions(options => options.JsonSerializerOptions.DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull) .AddXmlSerializerFormatters(); services.Configure(options => @@ -362,9 +362,6 @@ namespace Roadie.Api return new HttpContext(factory.GetService(), new UrlHelper(actionContext)); }); - var sp = services.BuildServiceProvider(); - var adminService = sp.GetService(); - adminService.PerformStartUpTasks(); } private static string _roadieApiVersion = null; @@ -385,10 +382,14 @@ namespace Roadie.Api private class IntegrationKey { public string BingImageSearch { get; set; } + public string DiscogsConsumerKey { get; set; } + public string DiscogsConsumerSecret { get; set; } + public string LastFMApiKey { get; set; } + public string LastFMSecret { get; set; } } } -} \ No newline at end of file +}