From 5acad5d4b6f83726979420e17d596d248c1ebfe0 Mon Sep 17 00:00:00 2001 From: Steven Hildreth Date: Sat, 12 Jan 2019 15:10:00 -0600 Subject: [PATCH] Merge work --- Roadie.Api.Services/AdminService.cs | 15 ++-- Roadie.Api.Services/ArtistService.cs | 47 +++++++++++ Roadie.Api.Services/IArtistService.cs | 2 + Roadie.Api.Services/IReleaseService.cs | 2 + Roadie.Api.Services/ReleaseService.cs | 39 +++++++++ Roadie.Api.Services/ServiceBase.cs | 94 +++++++++++++++------ Roadie.Api.Services/UserService.cs | 26 +++--- Roadie.Api/Controllers/ArtistController.cs | 21 +++++ Roadie.Api/Controllers/ReleaseController.cs | 20 +++++ 9 files changed, 221 insertions(+), 45 deletions(-) diff --git a/Roadie.Api.Services/AdminService.cs b/Roadie.Api.Services/AdminService.cs index 515345d..43dafb3 100644 --- a/Roadie.Api.Services/AdminService.cs +++ b/Roadie.Api.Services/AdminService.cs @@ -396,17 +396,18 @@ namespace Roadie.Api.Services var newArtists = 0; var newReleases = 0; var newTracks = 0; + OperationResult result = null; foreach (var folder in Directory.EnumerateDirectories(d.FullName).ToArray()) { - var result = await folderProcessor.Process(new DirectoryInfo(folder), isReadOnly); - if (result.AdditionalData != null) - { - newArtists += SafeParser.ToNumber(result.AdditionalData["newArtists"]); - newReleases += SafeParser.ToNumber(result.AdditionalData["newReleases"]); - newTracks += SafeParser.ToNumber(result.AdditionalData["newTracks"]); - } + result = await folderProcessor.Process(new DirectoryInfo(folder), isReadOnly); processedFolders++; } + if (result.AdditionalData != null) + { + newArtists = SafeParser.ToNumber(result.AdditionalData["newArtists"]); + newReleases = SafeParser.ToNumber(result.AdditionalData["newReleases"]); + newTracks = SafeParser.ToNumber(result.AdditionalData["newTracks"]); + } if (!isReadOnly) { FolderProcessor.DeleteEmptyFolders(d, this.Logger); diff --git a/Roadie.Api.Services/ArtistService.cs b/Roadie.Api.Services/ArtistService.cs index f3f145d..e1fe3e3 100644 --- a/Roadie.Api.Services/ArtistService.cs +++ b/Roadie.Api.Services/ArtistService.cs @@ -544,6 +544,53 @@ namespace Roadie.Api.Services }; } + public async Task> MergeArtists(User user, Guid artistToMergeId, Guid artistToMergeIntoId) + { + var sw = new Stopwatch(); + sw.Start(); + + var errors = new List(); + var artistToMerge = this.GetArtist(artistToMergeId); + if (artistToMerge == null) + { + this.Logger.LogWarning("MergeArtists Unknown Artist [{0}]", artistToMergeId); + return new OperationResult(true, string.Format("Artist Not Found [{0}]", artistToMergeId)); + } + var mergeIntoArtist = this.GetArtist(artistToMergeIntoId); + if (mergeIntoArtist == null) + { + this.Logger.LogWarning("MergeArtists Unknown Artist [{0}]", artistToMergeIntoId); + return new OperationResult(true, string.Format("Artist Not Found [{0}]", artistToMergeIntoId)); + } + + try + { + var result = await this.ArtistFactory.MergeArtists(artistToMerge, mergeIntoArtist, true); + if (!result.IsSuccess) + { + this.CacheManager.ClearRegion(artistToMerge.CacheRegion); + this.CacheManager.ClearRegion(mergeIntoArtist.CacheRegion); + this.Logger.LogInformation("MergeArtists `{0}` => `{1}`, By User `{2}`", artistToMerge, mergeIntoArtist, user); + } + } + catch (Exception ex) + { + this.Logger.LogError(ex); + errors.Add(ex); + } + sw.Stop(); + + return new OperationResult + { + IsSuccess = !errors.Any(), + Data = !errors.Any(), + OperationTime = sw.ElapsedMilliseconds, + Errors = errors + }; + + } + + public async Task> UploadArtistImage(User user, Guid id, IFormFile file) { var bytes = new byte[0]; diff --git a/Roadie.Api.Services/IArtistService.cs b/Roadie.Api.Services/IArtistService.cs index e9f5d48..84642c2 100644 --- a/Roadie.Api.Services/IArtistService.cs +++ b/Roadie.Api.Services/IArtistService.cs @@ -20,5 +20,7 @@ namespace Roadie.Api.Services Task> UpdateArtist(User user, Artist artist); Task> UploadArtistImage(User user, Guid id, IFormFile file); + + Task> MergeArtists(User user, Guid artistToMergeId, Guid artistToMergeIntoId); } } \ No newline at end of file diff --git a/Roadie.Api.Services/IReleaseService.cs b/Roadie.Api.Services/IReleaseService.cs index 90a3744..d8c1638 100644 --- a/Roadie.Api.Services/IReleaseService.cs +++ b/Roadie.Api.Services/IReleaseService.cs @@ -22,5 +22,7 @@ namespace Roadie.Api.Services Task> UpdateRelease(User user, Release release); Task> UploadReleaseImage(User user, Guid id, IFormFile file); + + Task> MergeReleases(User user, Guid releaseToMergeId, Guid releaseToMergeIntoId, bool addAsMedia); } } \ No newline at end of file diff --git a/Roadie.Api.Services/ReleaseService.cs b/Roadie.Api.Services/ReleaseService.cs index 120b4b5..ff822e0 100644 --- a/Roadie.Api.Services/ReleaseService.cs +++ b/Roadie.Api.Services/ReleaseService.cs @@ -678,6 +678,45 @@ namespace Roadie.Api.Services }; } + public async Task> MergeReleases(User user, Guid releaseToMergeId, Guid releaseToMergeIntoId, bool addAsMedia) + { + var sw = new Stopwatch(); + sw.Start(); + + var errors = new List(); + var releaseToMerge = this.GetRelease(releaseToMergeId); + if (releaseToMerge == null) + { + this.Logger.LogWarning("MergeReleases Unknown Release [{0}]", releaseToMergeId); + return new OperationResult(true, string.Format("Release Not Found [{0}]", releaseToMergeId)); + } + var releaseToMergeInfo = this.GetRelease(releaseToMergeIntoId); + if (releaseToMergeInfo == null) + { + this.Logger.LogWarning("MergeReleases Unknown Release [{0}]", releaseToMergeIntoId); + return new OperationResult(true, string.Format("Release Not Found [{0}]", releaseToMergeIntoId)); + } + try + { + await this.ReleaseFactory.MergeReleases(releaseToMerge, releaseToMergeInfo, addAsMedia); + } + catch (Exception ex) + { + this.Logger.LogError(ex); + errors.Add(ex); + } + sw.Stop(); + this.Logger.LogInformation("MergeReleases Release `{0}` Merged Into Release `{1}`, By User `{2}`", releaseToMerge, releaseToMergeInfo, user); + return new OperationResult + { + IsSuccess = !errors.Any(), + Data = !errors.Any(), + OperationTime = sw.ElapsedMilliseconds, + Errors = errors + }; + } + + public async Task> UploadReleaseImage(User user, Guid id, IFormFile file) { var bytes = new byte[0]; diff --git a/Roadie.Api.Services/ServiceBase.cs b/Roadie.Api.Services/ServiceBase.cs index b084e3c..736b126 100644 --- a/Roadie.Api.Services/ServiceBase.cs +++ b/Roadie.Api.Services/ServiceBase.cs @@ -209,6 +209,7 @@ namespace Roadie.Api.Services return null; } + // Only read operations protected data.Track GetTrack(Guid id) { return this.CacheManager.Get(data.Track.CacheUrn(id), () => @@ -324,11 +325,15 @@ namespace Roadie.Api.Services protected async Task> SetArtistRating(Guid artistId, ApplicationUser user, short rating) { - var artist = this.GetArtist(artistId); + var artist = this.DbContext.Artists + .Include(x => x.Genres) + .Include("Genres.Genre") + .FirstOrDefault(x => x.RoadieId == artistId); if (artist == null) { return new OperationResult(true, $"Invalid Artist Id [{ artistId }]"); } + var now = DateTime.UtcNow; var userArtist = this.DbContext.UserArtists.FirstOrDefault(x => x.ArtistId == artist.Id && x.UserId == user.Id); if (userArtist == null) { @@ -343,15 +348,13 @@ namespace Roadie.Api.Services else { userArtist.Rating = rating; - userArtist.LastUpdated = DateTime.UtcNow; + userArtist.LastUpdated = now; } await this.DbContext.SaveChangesAsync(); - var sql = "UPDATE `artist` set lastUpdated = UTC_DATE(), rating = (SELECT cast(avg(ur.rating) as signed) " + - "FROM `userartist` ur " + - "where artistId = {0}) " + - "WHERE id = {0};"; - await this.DbContext.Database.ExecuteSqlCommandAsync(sql, artist.Id); + artist.Rating = (short)this.DbContext.UserArtists.Where(x => x.ArtistId == artist.Id && x.Rating > 0).Average(x => (decimal)x.Rating); + artist.LastUpdated = now; + await this.DbContext.SaveChangesAsync(); this.CacheManager.ClearRegion(user.CacheRegion); this.CacheManager.ClearRegion(artist.CacheRegion); @@ -367,7 +370,14 @@ namespace Roadie.Api.Services protected async Task> SetReleaseRating(Guid releaseId, ApplicationUser user, short rating) { - var release = this.GetRelease(releaseId); + var release = this.DbContext.Releases + .Include(x => x.Artist) + .Include(x => x.Genres) + .Include("Genres.Genre") + .Include(x => x.Medias) + .Include("Medias.Tracks") + .Include("Medias.Tracks.TrackArtist") + .FirstOrDefault(x => x.RoadieId == releaseId); if (release == null) { return new OperationResult(true, $"Invalid Release Id [{ releaseId }]"); @@ -391,11 +401,9 @@ namespace Roadie.Api.Services } await this.DbContext.SaveChangesAsync(); - var sql = "UPDATE `release` set lastUpdated = UTC_DATE(), rating = (SELECT cast(avg(ur.rating) as signed) " + - "FROM `userrelease` ur " + - "where releaseId = {0}) " + - "WHERE id = {0};"; - await this.DbContext.Database.ExecuteSqlCommandAsync(sql, release.Id); + release.Rating = (short)this.DbContext.UserReleases.Where(x => x.ReleaseId == release.Id && x.Rating > 0).Average(x => (decimal)x.Rating); + release.LastUpdated = now; + await this.DbContext.SaveChangesAsync(); this.CacheManager.ClearRegion(user.CacheRegion); this.CacheManager.ClearRegion(release.CacheRegion); @@ -412,11 +420,17 @@ namespace Roadie.Api.Services protected async Task> SetTrackRating(Guid trackId, ApplicationUser user, short rating) { - var track = this.GetTrack(trackId); + var track = this.DbContext.Tracks + .Include(x => x.ReleaseMedia) + .Include(x => x.ReleaseMedia.Release) + .Include(x => x.ReleaseMedia.Release.Artist) + .Include(x => x.TrackArtist) + .FirstOrDefault(x => x.RoadieId == trackId); if (track == null) { return new OperationResult(true, $"Invalid Track Id [{ trackId }]"); } + var now = DateTime.UtcNow; var userTrack = this.DbContext.UserTracks.FirstOrDefault(x => x.TrackId == track.Id && x.UserId == user.Id); if (userTrack == null) { @@ -431,15 +445,13 @@ namespace Roadie.Api.Services else { userTrack.Rating = rating; - userTrack.LastUpdated = DateTime.UtcNow; + userTrack.LastUpdated = now; } await this.DbContext.SaveChangesAsync(); - var sql = "UPDATE `track` set lastUpdated = UTC_DATE(), rating = (SELECT cast(avg(ur.rating) as signed) " + - "FROM `usertrack` ur " + - "where trackId = {0}) " + - "WHERE id = {0};"; - await this.DbContext.Database.ExecuteSqlCommandAsync(sql, track.Id); + track.Rating = (short)this.DbContext.UserTracks.Where(x => x.TrackId == track.Id && x.Rating > 0).Average(x => (decimal)x.Rating); + track.LastUpdated = now; + await this.DbContext.SaveChangesAsync(); this.CacheManager.ClearRegion(user.CacheRegion); this.CacheManager.ClearRegion(track.CacheRegion); @@ -457,7 +469,10 @@ namespace Roadie.Api.Services protected async Task> ToggleArtistDisliked(Guid artistId, ApplicationUser user, bool isDisliked) { - var artist = this.GetArtist(artistId); + var artist = this.DbContext.Artists + .Include(x => x.Genres) + .Include("Genres.Genre") + .FirstOrDefault(x => x.RoadieId == artistId); if (artist == null) { return new OperationResult(true, $"Invalid Artist Id [{ artistId }]"); @@ -492,7 +507,10 @@ namespace Roadie.Api.Services protected async Task> ToggleArtistFavorite(Guid artistId, ApplicationUser user, bool isFavorite) { - var artist = this.GetArtist(artistId); + var artist = this.DbContext.Artists + .Include(x => x.Genres) + .Include("Genres.Genre") + .FirstOrDefault(x => x.RoadieId == artistId); if (artist == null) { return new OperationResult(true, $"Invalid Artist Id [{ artistId }]"); @@ -527,7 +545,14 @@ namespace Roadie.Api.Services protected async Task> ToggleReleaseDisliked(Guid releaseId, ApplicationUser user, bool isDisliked) { - var release = this.GetRelease(releaseId); + var release = this.DbContext.Releases + .Include(x => x.Artist) + .Include(x => x.Genres) + .Include("Genres.Genre") + .Include(x => x.Medias) + .Include("Medias.Tracks") + .Include("Medias.Tracks.TrackArtist") + .FirstOrDefault(x => x.RoadieId == releaseId); if (release == null) { return new OperationResult(true, $"Invalid Release Id [{ releaseId }]"); @@ -563,7 +588,14 @@ namespace Roadie.Api.Services protected async Task> ToggleReleaseFavorite(Guid releaseId, ApplicationUser user, bool isFavorite) { - var release = this.GetRelease(releaseId); + var release = this.DbContext.Releases + .Include(x => x.Artist) + .Include(x => x.Genres) + .Include("Genres.Genre") + .Include(x => x.Medias) + .Include("Medias.Tracks") + .Include("Medias.Tracks.TrackArtist") + .FirstOrDefault(x => x.RoadieId == releaseId); if (release == null) { return new OperationResult(true, $"Invalid Release Id [{ releaseId }]"); @@ -599,7 +631,12 @@ namespace Roadie.Api.Services protected async Task> ToggleTrackDisliked(Guid trackId, ApplicationUser user, bool isDisliked) { - var track = this.GetTrack(trackId); + var track = this.DbContext.Tracks + .Include(x => x.ReleaseMedia) + .Include(x => x.ReleaseMedia.Release) + .Include(x => x.ReleaseMedia.Release.Artist) + .Include(x => x.TrackArtist) + .FirstOrDefault(x => x.RoadieId == trackId); if (track == null) { return new OperationResult(true, $"Invalid Track Id [{ trackId }]"); @@ -636,7 +673,12 @@ namespace Roadie.Api.Services protected async Task> ToggleTrackFavorite(Guid trackId, ApplicationUser user, bool isFavorite) { - var track = this.GetTrack(trackId); + var track = this.DbContext.Tracks + .Include(x => x.ReleaseMedia) + .Include(x => x.ReleaseMedia.Release) + .Include(x => x.ReleaseMedia.Release.Artist) + .Include(x => x.TrackArtist) + .FirstOrDefault(x => x.RoadieId == trackId); if (track == null) { return new OperationResult(true, $"Invalid Track Id [{ trackId }]"); diff --git a/Roadie.Api.Services/UserService.cs b/Roadie.Api.Services/UserService.cs index 1f43753..7d78e13 100644 --- a/Roadie.Api.Services/UserService.cs +++ b/Roadie.Api.Services/UserService.cs @@ -20,6 +20,7 @@ using System.Linq; using System.Linq.Dynamic.Core; using System.Threading.Tasks; using data = Roadie.Library.Data; + using models = Roadie.Library.Models; namespace Roadie.Api.Services @@ -533,7 +534,6 @@ namespace Roadie.Api.Services { if (includes.Contains("stats")) { - var userArtists = this.DbContext.UserArtists.Include(x => x.Artist).Where(x => x.UserId == user.Id).ToArray(); var userReleases = this.DbContext.UserReleases.Include(x => x.Release).Where(x => x.UserId == user.Id).ToArray(); var userTracks = this.DbContext.UserTracks.Include(x => x.Track).Where(x => x.UserId == user.Id).ToArray(); @@ -546,7 +546,8 @@ namespace Roadie.Api.Services where ut.UserId == user.Id select new { a, ut.PlayedCount }) .GroupBy(a => a.a) - .Select(x => new { + .Select(x => new + { Artist = x.Key, Played = x.Sum(t => t.PlayedCount) }) @@ -554,13 +555,14 @@ namespace Roadie.Api.Services .FirstOrDefault(); var mostPlayedReleaseId = (from r in this.DbContext.Releases - join rm in this.DbContext.ReleaseMedias on r.Id equals rm.ReleaseId - join t in this.DbContext.Tracks on rm.Id equals t.ReleaseMediaId - join ut in this.DbContext.UserTracks on t.Id equals ut.TrackId - where ut.UserId == user.Id - select new { r, ut.PlayedCount }) + join rm in this.DbContext.ReleaseMedias on r.Id equals rm.ReleaseId + join t in this.DbContext.Tracks on rm.Id equals t.ReleaseMediaId + join ut in this.DbContext.UserTracks on t.Id equals ut.TrackId + where ut.UserId == user.Id + select new { r, ut.PlayedCount }) .GroupBy(r => r.r) - .Select(x => new { + .Select(x => new + { Release = x.Key, Played = x.Sum(t => t.PlayedCount) }) @@ -583,10 +585,10 @@ namespace Roadie.Api.Services model.Statistics = new UserStatistics { MostPlayedArtist = mostPlayedArtist == null ? null : models.ArtistList.FromDataArtist(mostPlayedArtist.Artist, this.MakeArtistThumbnailImage(mostPlayedArtist.Artist.RoadieId)), - MostPlayedRelease = mostPlayedRelease == null ? null : models.Releases.ReleaseList.FromDataRelease(mostPlayedRelease, - mostPlayedRelease.Artist, - this.HttpContext.BaseUrl, - this.MakeArtistThumbnailImage(mostPlayedRelease.Artist.RoadieId), + MostPlayedRelease = mostPlayedRelease == null ? null : models.Releases.ReleaseList.FromDataRelease(mostPlayedRelease, + mostPlayedRelease.Artist, + this.HttpContext.BaseUrl, + this.MakeArtistThumbnailImage(mostPlayedRelease.Artist.RoadieId), this.MakeReleaseThumbnailImage(mostPlayedRelease.RoadieId)), MostPlayedTrack = mostPlayedTrack == null ? null : TrackList.FromDataTrack(this.MakeTrackPlayUrl(user, mostPlayedTrack.Id, mostPlayedTrack.RoadieId), mostPlayedTrack, diff --git a/Roadie.Api/Controllers/ArtistController.cs b/Roadie.Api/Controllers/ArtistController.cs index 1da59b4..6262acb 100644 --- a/Roadie.Api/Controllers/ArtistController.cs +++ b/Roadie.Api/Controllers/ArtistController.cs @@ -68,9 +68,29 @@ namespace Roadie.Api.Controllers return Ok(result); } + [HttpPost("mergeArtists/{artistToMergeId}/{artistToMergeIntoId}")] + [ProducesResponseType(200)] + [ProducesResponseType(404)] + [Authorize(Policy = "Editor")] + public async Task MergeArtists(Guid artistToMergeId, Guid artistToMergeIntoId) + { + var result = await this.ArtistService.MergeArtists(await this.CurrentUserModel(), artistToMergeId, artistToMergeIntoId); + if (result == null || result.IsNotFoundResult) + { + return NotFound(); + } + if (!result.IsSuccess) + { + return StatusCode((int)HttpStatusCode.InternalServerError); + } + return Ok(result); + } + + [HttpPost("setImageByUrl/{id}/{imageUrl}")] [ProducesResponseType(200)] [ProducesResponseType(404)] + [Authorize(Policy = "Editor")] public async Task SetArtistImageByUrl(Guid id, string imageUrl) { var result = await this.ArtistService.SetReleaseImageByUrl(await this.CurrentUserModel(), id, HttpUtility.UrlDecode(imageUrl)); @@ -88,6 +108,7 @@ namespace Roadie.Api.Controllers [HttpPost("uploadImage/{id}")] [ProducesResponseType(200)] [ProducesResponseType(404)] + [Authorize(Policy = "Editor")] public async Task UploadImage(Guid id, IFormFile file) { var result = await this.ArtistService.UploadArtistImage(await this.CurrentUserModel(), id, file); diff --git a/Roadie.Api/Controllers/ReleaseController.cs b/Roadie.Api/Controllers/ReleaseController.cs index 2738d41..fef5bc3 100644 --- a/Roadie.Api/Controllers/ReleaseController.cs +++ b/Roadie.Api/Controllers/ReleaseController.cs @@ -50,6 +50,24 @@ namespace Roadie.Api.Controllers return Ok(result); } + [HttpPost("mergeReleases/{releaseToMergeId}/{releaseToMergeIntoId}")] + [ProducesResponseType(200)] + [ProducesResponseType(404)] + [Authorize(Policy = "Editor")] + public async Task MergeReleases(Guid releaseToMergeId, Guid releaseToMergeIntoId, bool addAsMedia) + { + var result = await this.ReleaseService.MergeReleases(await this.CurrentUserModel(), releaseToMergeId, releaseToMergeIntoId, addAsMedia); + if (result == null || result.IsNotFoundResult) + { + return NotFound(); + } + if (!result.IsSuccess) + { + return StatusCode((int)HttpStatusCode.InternalServerError); + } + return Ok(result); + } + [HttpPost("edit")] [ProducesResponseType(200)] [ProducesResponseType(404)] @@ -98,6 +116,7 @@ namespace Roadie.Api.Controllers [HttpPost("setImageByUrl/{id}/{imageUrl}")] [ProducesResponseType(200)] [ProducesResponseType(404)] + [Authorize(Policy = "Editor")] public async Task SetReleaseImageByUrl(Guid id, string imageUrl) { var result = await this.ReleaseService.SetReleaseImageByUrl(await this.CurrentUserModel(), id, HttpUtility.UrlDecode(imageUrl)); @@ -115,6 +134,7 @@ namespace Roadie.Api.Controllers [HttpPost("uploadImage/{id}")] [ProducesResponseType(200)] [ProducesResponseType(404)] + [Authorize(Policy = "Editor")] public async Task UploadImage(Guid id, IFormFile file) { var result = await this.ReleaseService.UploadReleaseImage(await this.CurrentUserModel(), id, file);