using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using MoreLinq; using Nancy; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using Sony.Filtr.AdminAPI.Config; using Sony.Filtr.AdminAPI.ViewModels.Spotify.Charts; using Sony.Filtr.Applications; using Sony.Filtr.Contracts.Definitions; using Sony.Filtr.Core.SonyMusic; using Sony.Filtr.Core.SpotifyCharts; using Sony.Filtr.Core.SpotifyCharts.Data; using Sony.Filtr.Core.TopLists; using Sony.Filtr.DistributedCaching; using Sony.Filtr.SpotifyWebAPI; using Sony.Filtr.SpotifyWebAPI.Model; using Sony.Filtr.Utility; using Sony.Filtr.Utility.Extensions; using Sony.Filtr.Utility.Serialization; namespace Sony.Filtr.AdminAPI.Modules.SpotifySpecific { public class SpotifyChartsModule : BaseModule { private readonly SpotifyChartsManager _chartsManager; private readonly SpotifyWebApi _spotifyWebApi; private readonly SonyMusicManager _sonyMusicManager; private readonly DistributedCacheHandler _distributedCacheHandler; public SpotifyChartsModule(SpotifyChartsManager chartsManager, ApplicationInstanceManager applicationInstanceManager, SpotifyWebApi spotifyWebApi, SonyMusicManager sonyMusicManager, DistributedCacheHandler distributedCacheHandler) : base(applicationInstanceManager) { _chartsManager = chartsManager; _spotifyWebApi = spotifyWebApi; _sonyMusicManager = sonyMusicManager; _distributedCacheHandler = distributedCacheHandler; Get["GetSpotifyChartsChartTypes", "/SpotifyCharts/AvailableCharts", true] = async (parameters, cancellationToken) => { SpotifyChartsListType? listType = Maybe.ToEnumOrDefault((string)Request.Query.listType); SpotifyChartsTimeWindow? timeWindow = Maybe.ToEnumOrDefault((string)Request.Query.timeWindow); string market = (string)Request.Query.market; var availableCharts = await _chartsManager.GetAvailableChartsAsync(market, listType, timeWindow); return Response.AsJson(availableCharts); }; Get["GetSpotifyChartsData", "/SpotifyCharts/Chart", true] = async (parameters, cx) => { string spotifyMarket = (string)Request.Query.market; string chartMarket = (string)Request.Query.chartMarket; if (string.IsNullOrWhiteSpace(chartMarket)) throw new NancyStatusException(HttpStatusCode.BadRequest, "Missing market."); var listType = Maybe.ToEnumOrDefault((string)Request.Query.listType); if (listType == null) throw new NancyStatusException(HttpStatusCode.BadRequest, "Missing listType."); var timeWindow = Maybe.ToEnumOrDefault((string)Request.Query.timeWindow); if (timeWindow == null) throw new NancyStatusException(HttpStatusCode.BadRequest, "Missing timeWindow."); var chartDate = Maybe.ToDateTimeOrDefault((string)Request.Query.date); if (chartDate == null) throw new NancyStatusException(HttpStatusCode.BadRequest, "missing date."); var cacheKey = string.Join(":", "GetSpotifyChartsData-1.0-", chartMarket, listType, timeWindow, chartDate, spotifyMarket); var chartViewModel = _distributedCacheHandler.Get(cacheKey) as SpotifyChartViewModel; if (chartViewModel == null) { var chart = await _chartsManager.GetChartAsync(chartMarket, listType.Value, timeWindow.Value, chartDate.Value); var comparisonDate = chartDate.Value.AddDays(-7); var comparisonChart = await _chartsManager.GetChartAsync(chartMarket, listType.Value, timeWindow.Value, comparisonDate); chartViewModel = await BuildChartViewModelAsync(chart, comparisonChart, spotifyMarket); if (chartViewModel.ChartPositions.Any()) { _distributedCacheHandler.Put(cacheKey, chartViewModel, DateTime.UtcNow.AddHours(1)); } } return Response.AsJson(chartViewModel); }; Get["GetSpotifyTrackChartSummary", "/SpotifyCharts/TrackSummary/{ISRC}/", true] = async (parameters, cx) => { var isrc = (string)parameters.ISRC; var chartSummaries = await _chartsManager.GetChartSummaryForTrackAsync(isrc); return Response.AsJson(chartSummaries); }; Get["GetChartInfoForTrack", "/SpotifyCharts/TrackChartInfo/", true] = async (parameters, cx) => { var isrc = (string)Request.Query.ISRC; var market = (string)Request.Query.market; var listType = Maybe.ToEnumOrDefault((string)Request.Query.listType); var timeWindow = Maybe.ToEnumOrDefault((string)Request.Query.timeWindow); if (!listType.HasValue || !timeWindow.HasValue) throw new NancyStatusException(HttpStatusCode.BadRequest, "No list type or time window supplied."); if (string.IsNullOrWhiteSpace(isrc) || string.IsNullOrWhiteSpace(market)) throw new NancyStatusException(HttpStatusCode.BadRequest, "No isrc or market supplied."); var trackData = await _chartsManager.GetLatestTrackInfoAsync(market, isrc, listType.Value, timeWindow.Value); var viewModel = new TrackInfoViewModel() { EntryPosition = trackData?.EarliestPosition?.Position, EntryPositionDate = FormatDateForOutput(trackData?.EarliestPosition?.Date), LatestPosition = trackData?.LatestPosition?.Position, LatestPositionDate = FormatDateForOutput(trackData?.LatestPosition?.Date), EarliestPeakPosition = trackData?.EarliestPeakPosition?.Position, EarliestPeakPositionDate = FormatDateForOutput(trackData?.EarliestPeakPosition?.Date), LatestPeakPosition = trackData?.LatestPeakPosition?.Position, LatestPeakPositionDate = FormatDateForOutput(trackData?.LatestPeakPosition?.Date), CurrentPosition = trackData?.CurrentPosition?.Position, CurrentPositionDate = FormatDateForOutput(trackData?.CurrentPosition?.Date), Market = market, ISRC = isrc, TimeWindow = timeWindow.Value, ListType = listType.Value }; return Response.AsJson(viewModel); }; } private async Task BuildChartViewModelAsync(SpotifyChart chart, SpotifyChart comparisonChart, string market) { var spotifyTrackIds = chart.ChartPositions.Select(p => p.TrackLink.ExtractTrackID()).Distinct().ToList(); var spotifyTracks = await _spotifyWebApi.GetAllTracksAsync(spotifyTrackIds, market); var spotifyTracksDic = spotifyTracks.ToDictionary(k => k.linked_from?.id ?? k.id, v => v); var albumIds = spotifyTracks.Select(p => p.album.id).Distinct(); var albums = await _spotifyWebApi.GetAllAlbumInfoAsync(albumIds); var albumUpcs = albums.Select(a => a.external_ids?.upc).Where(u => !string.IsNullOrWhiteSpace(u)).Distinct().ToList(); var sonyUpcs = await _sonyMusicManager.GetSonyAlbumAsync(albumUpcs, MusicService.Spotify, market); var isSonyAlbumLookup = albums.Select(a => new IsSonyAlbumInfo() { AlbumId = a.id, UPC = a.external_ids?.upc, SonyReleaseType = sonyUpcs.GetValueOrNull(a.external_ids?.upc)}).ToDictionary(k=> k.AlbumId, v=> v); var comparisonDic = comparisonChart.ChartPositions.DistinctBy(p => p.ISRC).ToDictionary(k=> k.ISRC, v=> v); //Note: If multiple isrc we don't specify which position we care about. return new SpotifyChartViewModel() { Market = chart.Market, ChartType = chart.ChartType, TimeWindow = chart.TimeWindow, ChartDate = chart.ChartDate, ChartPositions = chart.ChartPositions.Select(p => BuildChartPositionViewModel(p, comparisonDic, spotifyTracksDic, isSonyAlbumLookup)).ToList() }; } private SpotifyChartPositionViewModel BuildChartPositionViewModel(ChartPosition chartPosition, Dictionary comparisonDic, Dictionary spotifyTracksDic, Dictionary isSonyAlbumLookup) { ChartPosition comparisonValue = null; if (!string.IsNullOrWhiteSpace(chartPosition.ISRC)) { comparisonValue = comparisonDic.GetValueOrDefault(chartPosition.ISRC); } var spotifyTrack = spotifyTracksDic.GetValueOrDefault(chartPosition.TrackLink.ExtractTrackID()); var albumId = spotifyTrack?.album?.id; IsSonyAlbumInfo isSonyAlbumInfo = null; if (!string.IsNullOrWhiteSpace(albumId)) { isSonyAlbumInfo = isSonyAlbumLookup.GetValueOrDefault(albumId); } return new SpotifyChartPositionViewModel() { ISRC = chartPosition.ISRC, Position = chartPosition.Position, TrackUri = chartPosition.TrackLink.Uri, AlbumImageUrl = spotifyTrack?.album?.images?.FirstOrDefault()?.url, PreviewUrl = spotifyTrack?.preview_url, TrackName = chartPosition.TrackName, ArtistName = chartPosition.ArtistName, AlbumUri = spotifyTrack?.album?.uri, AlbumUpc = isSonyAlbumInfo?.UPC, IsSony = isSonyAlbumInfo?.SonyReleaseType == SpotifyAnalyticsAccount.Sony, SonyRelease = isSonyAlbumInfo?.SonyReleaseType, Artists = spotifyTrack?.artists?.Select(p => new SpotifyArtistReferenceViewModel() { Name = p.name, Uri = p.uri }).ToList(), NumberOfStreams = chartPosition.NumberOfStreams, ComparisonNumberOfStreams = comparisonValue?.NumberOfStreams, ComparisonPosition = comparisonValue?.Position, }; } private string FormatDateForOutput(DateTime? date) { if (!date.HasValue) { return null; } return date.Value.ToString("yyyy-MM-dd"); } } [Serializable] public class SpotifyChartViewModel { public string Market { get; set; } [JsonConverter(typeof(StringEnumConverter))] public SpotifyChartsListType ChartType { get; set; } [JsonConverter(typeof(StringEnumConverter))] public SpotifyChartsTimeWindow TimeWindow { get; set; } [JsonConverter(typeof(DateNoTimeConverter))] public DateTime ChartDate { get; set; } public List ChartPositions { get; set; } } [Serializable] public class SpotifyChartPositionViewModel { public int Position { get; set; } public int? ComparisonPosition { get; set; } //public int PeakPosition { get; set; } public string TrackUri { get; set; } public string TrackName { get; set; } public string ArtistName { get; set; } public int NumberOfStreams { get; set; } public int? ComparisonNumberOfStreams { get; set; } public string ISRC { get; set; } public List Artists { get; set; } public string PreviewUrl { get; set; } public string AlbumUri { get; set; } public string AlbumImageUrl { get; set; } public string AlbumUpc { get; set; } public bool? IsSony { get; set; } public SpotifyAnalyticsAccount? SonyRelease { get; set; } } public class IsSonyAlbumInfo { public string AlbumId { get; set; } public string UPC { get; set; } public SpotifyAnalyticsAccount? SonyReleaseType { get; set; } } }