using Nancy; using Sony.Filtr.AdminAPI.Config; using Sony.Filtr.AdminAPI.ViewModels; using Sony.Filtr.AdminAPI.ViewModels.AppleMusic; using Sony.Filtr.AdminAPI.ViewModels.Graph; using Sony.Filtr.AppleMusic; using Sony.Filtr.AppleMusic.Data; using Sony.Filtr.AppleMusic.Data.Streams; using Sony.Filtr.AppleMusic.Playlists; using Sony.Filtr.Buzz; using Sony.Filtr.Contracts.Abstractions; using Sony.Filtr.Contracts.Definitions; using Sony.Filtr.Contracts.Entities; using Sony.Filtr.Contracts.Entities.Buzz; using Sony.Filtr.Utility; using Sony.Filtr.Utility.Extensions; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using TaskExtensions = Sony.Filtr.Utility.Extensions.TaskExtensions; namespace Sony.Filtr.AdminAPI.Modules.AppleMusic { public class AppleMusicPlaylistModule : BaseModule { private readonly BuzzOwnerViewModelHelper _buzzOwnerViewModelHelper; const string defaultDateTimeFormat = "yyyy-MM-dd"; public AppleMusicPlaylistModule(IApplicationInstanceManager applicationInstanceManager, AppleMusicPlaylistManager appleMusicPlaylistManager, BuzzAccountManager buzzAccountManager, AppleMusicStreamsManager streamsManager, AppleMusicApi appleMusicApi, BuzzOwnerViewModelHelper buzzOwnerViewModel) : base(applicationInstanceManager) { _buzzOwnerViewModelHelper = buzzOwnerViewModel; Get["GetAppleMusicPlaylists", "/AppleMusic/Playlists/", true] = async (parameters, cx) => { var limit = Maybe.ToIntOrFallback((string)Request.Query.limit, 100); var offset = Maybe.ToIntOrFallback((string)Request.Query.offset, 0); var buzzCategory = Maybe.ToIntOrDefault((string)Request.Query.buzzCategory); var buzzCountryCode = (string)Request.Query.buzzMarket; var buzzAccount = Maybe.ToIntOrDefault((string)Request.Query.buzzAccountId); var country = (string)Request.Query.country; var playlistsTask = new PaginatedContent(); var buzzUsers = Enumerable.Empty(); ; var buzzCategories = Enumerable.Empty(); var tasks = new List { Task.Run(async () => { playlistsTask = await appleMusicPlaylistManager.GetPlaylistsAsync(buzzCategory, buzzAccount, buzzCountryCode, country, limit, offset); }), Task.Run(async () => { buzzUsers = (await buzzAccountManager.GetBuzzUsersAsync(null, (int)MusicService.AppleMusic)); }), Task.Run(() => { buzzCategories = buzzAccountManager.GetBuzzCategories(); }) }; await TaskExtensions.EnsureAllSuccessAsync(tasks, TaskScheduler.FromCurrentSynchronizationContext()); if (!string.IsNullOrWhiteSpace(buzzCountryCode)) { buzzUsers = buzzUsers.Where(u => u.CountryCode.Equals(buzzCountryCode, StringComparison.InvariantCultureIgnoreCase)).ToList(); } var playlists = playlistsTask; if (!playlists.Items.Any()) { return Response.AsJson(new PaginatedContent() { Pagination = playlists.Pagination }); } var streamCountry = string.IsNullOrWhiteSpace(country) ? "global" : country; var containerSummaries = streamsManager.GetAppleMusicContainerStreamsSummaries(playlists.Items.Select(p => p.Id).ToList(), streamCountry); var playlistViewModels = BuildPlaylistViewModels(playlists, buzzUsers, containerSummaries, buzzCategories); return Response.AsJson(playlistViewModels); }; Get["GetAppleMusicPlaylistsWithTrack", "/AppleMusic/PlaylistsWithTrack/", true] = async (parameters, cx) => { var storefront = (string)Request.Query.country; if (string.IsNullOrWhiteSpace((string)Request.Query.isrc)) { throw new NancyStatusException(HttpStatusCode.BadRequest, "Missing isrc"); } string[] isrcs = ((string)Request.Query.isrc).Split(','); var data = await appleMusicPlaylistManager.GetPlaylistsWithTrackAsync(storefront, isrcs); PaginatedContent playlists = data.Playlists; IEnumerable playlistStorefronts = data.Storefronts; IEnumerable playlistIds = data.PlaylistsId; //TODO: We fetch all combinations of storefronts and playlistIds but we only care about certain combinations Task> playlistStreamSummariesTask = streamsManager.GetContainerStreamSummariesByCountryCodeAsync(playlistStorefronts, playlistIds); Task> globalStreamSummariesTask = streamsManager.GetAppleMusicContainerStreamsSummariesGlobalAsync(playlistIds); Task> buzzUsersTask = buzzAccountManager.GetBuzzUsersAsync(null, (int)MusicService.AppleMusic); Task> historicTracksTask = appleMusicPlaylistManager.GetHistoricTrackSummariesAsync(isrcs, playlistIds, DateTime.Now.AddDays(-14), storefront); await Task.WhenAll(playlistStreamSummariesTask, globalStreamSummariesTask, buzzUsersTask, historicTracksTask); var playlistViewModels = BuildPlaylistViewModels( storefront, playlists, buzzUsersTask.Result, playlistStreamSummariesTask.Result, globalStreamSummariesTask.Result, historicTracksTask.Result); return Response.AsJson(playlistViewModels); }; Get["GetAppleMusicPlaylist", "/AppleMusic/Playlists/{playlistId}", true] = async (parameters, cx) => { var playlistId = (string)parameters.playlistId; var country = (string)Request.Query.country; if (string.IsNullOrWhiteSpace(playlistId)) { return HttpStatusCode.BadRequest; } var buzzCategories = buzzAccountManager.GetBuzzCategories(); var buzzCategoriesDic = buzzCategories.ToDictionary(b => b.ID, b => b.Name); AppleMusicPlaylistWithTracklistViewModel playlistViewModel; var playlist = await appleMusicPlaylistManager.GetPlaylistAsync(playlistId, country); if (playlist != null) { BuzzUser buzzUser = null; if (playlist.CuratorId.HasValue) { buzzUser = await buzzAccountManager.GetBuzzUserAsync(playlist.CuratorId.ToString(), (int)MusicService.AppleMusic); } playlistViewModel = AutoMapper.Mapper.Map(playlist); playlistViewModel.Owner = BuildBuzzOwnerViewModel(buzzUser, buzzCategoriesDic); //ARTEM: use always country from query string // If country code provided to endpoint and playlist is apple curated get track list for specified country, otherwise get tracklist for buzz user country. //var tracklistCountry = buzzUser?.CountryCode ?? AppleMusicPlaylistManager.DefaultStorefrontForTrackList; //if (buzzUser?.BuzzCategoryId == (int)StaticBuzzCategory.AppleCurator && !string.IsNullOrWhiteSpace(country)) //{ // tracklistCountry = country; //} //playlistViewModel.TracklistCountryCode = tracklistCountry; string tracklistCountry = country; playlistViewModel.TracklistCountryCode = tracklistCountry; if (!string.IsNullOrEmpty(tracklistCountry)) { var tracklist = await appleMusicPlaylistManager.GetPlaylistCurrentTracklistAsync(playlistId, tracklistCountry); var historicTrackSummary = await appleMusicPlaylistManager.GetHistoricTrackSummariesForPlaylistAsync(playlistId, tracklistCountry); playlistViewModel.Tracklist = BuildTracklistViewModel(tracklist, historicTrackSummary); if (tracklist != null && tracklist.Any(t => t.Added.HasValue)) { playlistViewModel.TracksLastAdded = tracklist.Max(t => t.Added); } } } else { //TODO: Get playlist from API and return if playlist not present in db var apiPlaylist = await appleMusicApi.GetPlaylistAsync(country, playlistId); if (apiPlaylist == null) throw new NancyStatusException(HttpStatusCode.NotFound, "No playlist with that id / country."); playlistViewModel = new AppleMusicPlaylistWithTracklistViewModel() { Artwork = apiPlaylist.attributes?.artwork?.url, CuratorId = apiPlaylist.relationships?.curator?.data?.FirstOrDefault()?.id, CuratorType = apiPlaylist.relationships?.curator?.data?.FirstOrDefault()?.type, Id = apiPlaylist.id, Name = apiPlaylist.attributes?.name, PlaylistType = apiPlaylist.type, Tracklist = apiPlaylist.relationships?.tracks?.data?.Select(t => new AppleMusicPlaylistSongViewModel() { ArtistName = t.attributes?.artistName, ArtworkUrl = t.attributes?.artwork?.url, ComposerName = t.attributes?.composerName, Id = t.id, ISRC = t.attributes?.isrc, }).ToList() }; } var streams = streamsManager.GetAppleMusicContainerStreamsSummaries(new List { playlistId }, !string.IsNullOrWhiteSpace(country) ? country : "global"); if (streams != null && streams.Any()) { SetStreams(playlistViewModel, streams.First()); } return Response.AsJson(playlistViewModel); }; Get["GetAppleMusicPlaylistTracklistHistoryDates", "/AppleMusic/Playlists/{playlistId}/Tracklisthistory/countryDates", true] = async (parameters, cx) => { var playlistId = (string)parameters.playlistId; string dateTimeFormat = this.GetDateTimeFormatFromQueryString(); if (string.IsNullOrWhiteSpace(playlistId)) { return HttpStatusCode.BadRequest; } var dates = await appleMusicPlaylistManager.GetPlaylistHistoricTracklistDatesAsync(playlistId); var datesAsString = dates.ToDictionary(d => d.Key, d => d.Value.Select(date => date.ToString(dateTimeFormat) ).ToList()); return Response.AsJson(datesAsString); }; Get["GetAppleMusicPlaylistTracklistHistory", "/AppleMusic/playlists/{playlistId}/tracklisthistory/{countryCode}/{date}/", true] = async (parameters, cx) => { var playlistId = (string)parameters.playlistId; var countryCode = (string)parameters.countryCode; var dateString = (string)parameters.date; if (string.IsNullOrWhiteSpace(playlistId)) { return HttpStatusCode.BadRequest; } if (!DateTime.TryParse(dateString, out var date)) { throw new NancyStatusException(HttpStatusCode.BadRequest, "Error in date format"); } var tracklist = await appleMusicPlaylistManager.GetPlaylistHistoricTracklistAsync(playlistId, countryCode, date); // Perfect fix to not expose zero-indexed positions externally. tracklist.ForEach(s => s.Position++); return Response.AsJson(tracklist); }; Get["GetAppleMusicPreviousPlaylistsForTrack", "/AppleMusic/playlists/tracklisthistory/track", true] = async (parameters, cx) => { var limit = Maybe.ToIntOrFallback((string)Request.Query.limit, 1000); var offset = Maybe.ToIntOrFallback((string)Request.Query.offset, 0); if (string.IsNullOrWhiteSpace((string)Request.Query.isrc)) { return HttpStatusCode.BadRequest; } var excludeCurrentPlaylists = Maybe.ToBoolOrFallback((string)Request.Query.excludeCurrentPlaylists, false); IEnumerable previousPlaylists = Enumerable.Empty(); IEnumerable buzzUsers = Enumerable.Empty(); IEnumerable buzzCategories = Enumerable.Empty(); var isrcs = ((string)Request.Query.isrc).Split(','); var tasks = new List { Task.Run(async () => { previousPlaylists = await appleMusicPlaylistManager.GetPreviousPlaylistsAsync(isrcs, excludeCurrentPlaylists); }), Task.Run(async () => { buzzUsers = await buzzAccountManager.GetBuzzUsersAsync(null, (int)MusicService.AppleMusic); }), Task.Run(() => { buzzCategories = buzzAccountManager.GetBuzzCategories(); }) }; await TaskExtensions.EnsureAllSuccessAsync(tasks, TaskScheduler.FromCurrentSynchronizationContext()); var viewModel = BuildTrackListHistoryViewModel(previousPlaylists, buzzUsers, buzzCategories, limit, offset); return Response.AsJson(viewModel); }; Get["GetAppleMusicPreviousPlaylistTracklistsPosition", "/AppleMusic/playlists/trackPositionChange", true] = async (parameters, cx) => { string playlistId = (string)Request.Query.playlistId; string country = (string)Request.Query.country; DateTime startDate = Maybe.ToDateTimeOrFallback((string)Request.Query.startDate, DateTime.Today.AddDays(-30)); DateTime endDate = Maybe.ToDateTimeOrFallback((string)Request.Query.endDate, DateTime.Today); var isrc = (string)Request.Query.isrc; if (string.IsNullOrWhiteSpace(isrc)) { return HttpStatusCode.BadRequest; } List historicPositions = await appleMusicPlaylistManager.GetHistoricTrackPositionsAsync(playlistId, isrc, country, startDate, endDate); var today = DateTime.UtcNow.Date; if (today >= startDate && today <= endDate) { var currentTracks = await appleMusicPlaylistManager.GetPlaylistCurrentTracklistAsync(playlistId, country); var specificTrack = currentTracks.FirstOrDefault(t => isrc.Equals(t.ISRC, StringComparison.InvariantCultureIgnoreCase)); if (specificTrack != null) { var currentPosition = specificTrack.Position; var todaysPosition = historicPositions.FirstOrDefault(p => p.Date.Date == today); if (todaysPosition != null) { todaysPosition.Position = currentPosition; //todaysPosition.Date = DateTime.UtcNow; //Would be nice if we had real date when we fetched playlist tracklist. } else { historicPositions.Add(new AppleMusicHistoricTracklistPosition { Date = DateTime.UtcNow, Position = currentPosition, TotalTracks = currentTracks.Count }); } } } var viewModel = new GraphViewModel { Series = new List() { new PlaylistTrackPositionSeriesViewModel() { Entries = historicPositions.Select(e => new TrackPositionGraphEntry() { Date = e.Date, Position = e.Position + 1, //We store position as zero-indexed but expose it as one-indexed, TrackCount = e.TotalTracks, }).OrderBy(e => e.Date).ToList() } } }; return Response.AsJson(viewModel); }; } private string GetDateTimeFormatFromQueryString() { string datetimeFormat = this.Request.Query["datetimeFormat"]; if (String.IsNullOrWhiteSpace(datetimeFormat)) { return defaultDateTimeFormat; } try { DateTime.Now.ToString(datetimeFormat); } catch (FormatException) { datetimeFormat = defaultDateTimeFormat; } return datetimeFormat; } private BuzzOwnerViewModel BuildBuzzOwnerViewModel(BuzzUser buzzUser, Dictionary buzzCategoriesDic) { return buzzUser != null ? _buzzOwnerViewModelHelper.BuildBuzzOwner(buzzUser, buzzCategoriesDic) : new BuzzOwnerViewModel(); } private List BuildTracklistViewModel( List tracklist, List historicTrackSummary) { var dateCutoff = DateTime.UtcNow.AddDays(-14).Date; var historicTrackSummaryDic = historicTrackSummary.ToDictionary(k => k.Isrc, v => v); var tracklistViewModel = tracklist.Select(t => new AppleMusicPlaylistSongViewModel { Position = t.Position + 1, // Position should be 1-indexed from endpoint AddedDate = t.Added, AlbumId = t.AlbumId, PositionChange = t.LatestPositionChange.HasValue && t.PreviousPosition.HasValue && t.LatestPositionChange.Value.Date > dateCutoff ? (t.Position - t.PreviousPosition) : null, PositionChangeDate = t.LatestPositionChange.HasValue && t.LatestPositionChange.Value.Date > dateCutoff ? t.LatestPositionChange : null, Id = t.Id, Storefront = t.Storefront, ISRC = t.ISRC, ArtistId = t.ArtistId, ArtistIds = t.ArtistIds, ArtistName = t.ArtistName, ArtworkUrl = t.ArtworkUrl, ComposerName = t.ComposerName, DiscNumber = t.DiscNumber, Duration = t.Duration, Name = t.Name, TrackNumber = t.TrackNumber, //IsSony = TODO: Add when we have UPC. //AlbumUpc = TODO: Add when we have UPC. TrackReleaseDate = t.ReleaseDate, AlbumReleaseDate = t.AlbumReleaseDate, EarliestAdded = t.ISRC != null ? historicTrackSummaryDic.GetValueOrDefault(t.ISRC)?.EarliestDate : null, EarliestPosition = t.ISRC != null? historicTrackSummaryDic.GetValueOrDefault(t.ISRC)?.EarliestPosition : null, AlbumName = t.AlbumName }); return tracklistViewModel.ToList(); } private PaginatedContent BuildTrackListHistoryViewModel( IEnumerable previousPlaylists, IEnumerable buzzUsers, IEnumerable buzzCategories, int? limit, int? offset) { var buzzUsersById = buzzUsers.ToDictionary(u => u.Username, u => u); var buzzCategoryDic = buzzCategories.ToDictionary(k => k.ID, v => v.Name); var totalItems = previousPlaylists.Count(); if (limit.HasValue && offset.HasValue) previousPlaylists = previousPlaylists.Skip(offset.Value).Take(limit.Value).ToList(); var tracklistHistory = AutoMapper.Mapper.Map>(previousPlaylists); tracklistHistory.ForEach(p => SetBuzzOwner(p, buzzUsersById, buzzCategoryDic)); return new PaginatedContent { Items = tracklistHistory, Pagination = new Pagination { Limit = limit, Offset = offset, Total = totalItems } }; } private PaginatedContent BuildPlaylistViewModels( string storefront, PaginatedContent playlists, IEnumerable buzzUsers, IDictionary appleMusicContainerStreamsSummaries, IEnumerable globalStreams, IEnumerable historicTracks) { Dictionary> playlistStreamsSummariesPerCountryAndId = appleMusicContainerStreamsSummaries.ToDictionary(k => k.Key.ToLowerInvariant(), v => v.Value.ToDictionary(p => p.ContainerId, p => p)); Dictionary globalStreamsPerPlaylistId = globalStreams.ToDictionary(k => k.ContainerId, v => v); var historicSummariesByPlaylistId = historicTracks.GroupBy(h => h.PlaylistId).ToDictionary(h => h.Key, h => h.ToDictionary(v => new { Storefront = v.Storefront.ToLowerInvariant(), ISRC = v.Isrc }, v => v)); var playlistViewModels = new PaginatedContent(); playlistViewModels.Pagination = playlists.Pagination; foreach (AppleMusicPlaylistWithTrack playlist in playlists.Items) { var playlistViewModel = BuildAppleMusicPlaylistWithTrackViewModel(playlist); SetBuzzOwner(playlistViewModel, buzzUsers); var globalStreamSummary = globalStreamsPerPlaylistId.GetValueOrDefault(playlist.Id); if (globalStreamSummary != null) { SetStreams(playlistViewModel, globalStreamSummary); } if (!string.IsNullOrWhiteSpace(storefront)) { var localStreamSummary = playlistStreamsSummariesPerCountryAndId.GetValueOrDefault(storefront.ToLowerInvariant())?.GetValueOrDefault(playlist.Id); if (localStreamSummary != null) { SetLocalStreams(playlistViewModel, localStreamSummary); } } foreach (var countryPlaylistWithTrack in playlistViewModel.Countries) { var playlistStreamSummary = playlistStreamsSummariesPerCountryAndId.GetValueOrDefault(countryPlaylistWithTrack.CountryCode.ToLowerInvariant())?.GetValueOrDefault(playlist.Id); if (playlistStreamSummary != null) { SetStreams(countryPlaylistWithTrack, playlistStreamSummary); } countryPlaylistWithTrack.TrackPosition = countryPlaylistWithTrack.TrackPosition + 1; // Stored as 0-index, expose as 1-indexed // Only expose position change if change was within past 14 days. if (!countryPlaylistWithTrack.PositionChangeDate.HasValue || countryPlaylistWithTrack.PositionChangeDate.Value < DateTime.UtcNow.AddDays(-14).Date) { countryPlaylistWithTrack.PositionChange = null; countryPlaylistWithTrack.PositionChangeDate = null; } var historicSummary = historicSummariesByPlaylistId.GetValueOrDefault(playlist.Id)? .GetValueOrDefault(new { Storefront = countryPlaylistWithTrack.CountryCode.ToLowerInvariant(), ISRC = countryPlaylistWithTrack.ISRC }); if (historicSummary != null) { countryPlaylistWithTrack.EarliestAdded = historicSummary.EarliestDate; countryPlaylistWithTrack.EarliestPosition = historicSummary.EarliestPosition; } } playlistViewModels.Items.Add(playlistViewModel); } return playlistViewModels; } private static AppleMusicPlaylistWithTrackViewModel BuildAppleMusicPlaylistWithTrackViewModel(AppleMusicPlaylistWithTrack playlist) { var playlistViewModel = AutoMapper.Mapper.Map(playlist); var countryDictionary = playlistViewModel.Countries.ToDictionary(c => new { CountryCode = c.CountryCode.ToLowerInvariant(), Isrc = c.ISRC }, c => c); foreach(var country in playlist.Countries) { var countryViewModel = countryDictionary.GetValueOrDefault(new { CountryCode = country.Storefront.ToLowerInvariant(), Isrc = country.Isrc }); if (countryViewModel == null) { continue; } if (country.PreviousPosition.HasValue) { countryViewModel.PositionChange = country.TrackPosition - country.PreviousPosition; } countryViewModel.PositionChangeDate = country.LatestPositionChange; countryViewModel.TracksLastAdded = country.TrackLatestAdded; } return playlistViewModel; } private void SetBuzzOwner(IAppleMusicPlaylistWithOwner playlistViewModel, Dictionary buzzUsersById, Dictionary buzzCategoryDic) { if (playlistViewModel.CuratorId.HasValue) { if (buzzUsersById.TryGetValue(playlistViewModel.CuratorId.ToString(), out var buzzUser)) playlistViewModel.Owner = _buzzOwnerViewModelHelper.BuildBuzzOwner(buzzUser, buzzCategoryDic); else { playlistViewModel.Owner = new BuzzOwnerViewModel(); } } } /// /// Represnts the same result as /// but performance is optimized. /// /// Represents for getting from list. /// View model ready to for searching by . private void SetBuzzOwner(IAppleMusicPlaylistWithOwner playlistViewModel, IEnumerable buzzUsers) { if (playlistViewModel.CuratorId.HasValue) { BuzzOwnerViewModel user = buzzUsers.FirstOrDefault(x => x.AccountId == playlistViewModel.CuratorId.Value.ToString()); playlistViewModel.Owner = user ?? new BuzzOwnerViewModel(); } } private void SetLocalStreams(AppleMusicPlaylistWithTrackViewModel playlistViewModel, AppleMusicContainerStreamSummary localStreamSummary) { playlistViewModel.LocalStreamsLatest = localStreamSummary.StreamsLatest; playlistViewModel.LocalStreams56Days = localStreamSummary.Streams56Days; playlistViewModel.LocalStreams28Days = localStreamSummary.Streams28Days; playlistViewModel.LocalStreams14Days = localStreamSummary.Streams14Days; playlistViewModel.LocalStreams7Days = localStreamSummary.Streams7Days; playlistViewModel.LocalStreamsLatest = localStreamSummary.StreamsLatest; } private PaginatedContent BuildPlaylistViewModels( PaginatedContent playlists, IEnumerable buzzUsers, IEnumerable containerStreamSummaries, IEnumerable buzzCategories) { var buzzUsersById = buzzUsers.ToDictionary(u => u.Username, u => u); var buzzCategoryDic = buzzCategories.ToDictionary(k => k.ID, v => v.Name); var containerStreamsSummariesPerId = containerStreamSummaries.ToDictionary(k => k.ContainerId, v => v); var playlistViewModels = new PaginatedContent { Pagination = playlists.Pagination }; foreach (var playlist in playlists.Items) { var playlistViewModel = AutoMapper.Mapper.Map(playlist); if (playlistViewModel.CuratorId.HasValue) { var buzzUser = buzzUsersById.GetValueOrDefault(playlistViewModel.CuratorId.ToString()) ?? null; if (buzzUser != null) { playlistViewModel.Owner = _buzzOwnerViewModelHelper.BuildBuzzOwner(buzzUser, buzzCategoryDic); } else { playlistViewModel.Owner = new BuzzOwnerViewModel(); } } var containerStreamSummary = containerStreamsSummariesPerId.GetValueOrDefault(playlist.Id); if (containerStreamSummary != null) { SetStreams(playlistViewModel, containerStreamSummary); } playlistViewModels.Items.Add(playlistViewModel); } return playlistViewModels; } private void SetStreams(IAppleMusicPlaylistStreams playlistViewModel, AppleMusicContainerStreamSummary containerStreamSummary) { playlistViewModel.StreamsLatest = containerStreamSummary.StreamsLatest; playlistViewModel.Streams56Days = containerStreamSummary.Streams56Days; playlistViewModel.Streams28Days = containerStreamSummary.Streams28Days; playlistViewModel.Streams14Days = containerStreamSummary.Streams14Days; playlistViewModel.Streams7Days = containerStreamSummary.Streams7Days; playlistViewModel.StreamDays56Days = containerStreamSummary.StreamDays56Days; playlistViewModel.StreamDays28Days = containerStreamSummary.StreamDays28Days; playlistViewModel.StreamDays14Days = containerStreamSummary.StreamDays14Days; playlistViewModel.StreamDays7Days = containerStreamSummary.StreamDays7Days; } public class PlaylistTrackPositionSeriesViewModel { public List Entries { get; set; } } public class TrackPositionGraphEntry { public DateTime Date { get; set; } public int Position { get; set; } public int TrackCount { get; set; } } } }