using System; using System.Collections.Generic; using System.Linq; using Nancy; using Sony.Filtr.API.Nancy.Config; using Sony.Filtr.API.Nancy.Extensions; using Sony.Filtr.API.Nancy.ViewModels; using Sony.Filtr.API.Service; using Sony.Filtr.API.ViewModels.v3; using Sony.Filtr.Contracts.Abstractions; using Sony.Filtr.Contracts.Entities; using Sony.Filtr.Contracts.ViewModels; using Sony.Filtr.PlaylistGeneration; namespace Sony.Filtr.API.Nancy { public class FiltrPublicApiModule : NancyModule { private readonly SpotifyService3 _spotifyService; private readonly DeezerService3 _deezerService; private readonly BaseService3 _commonService; private readonly IApplicationInstanceManager _applicationInstanceManager; public FiltrPublicApiModule(SpotifyService3 spotifyService, DeezerService3 deezerService, BaseService3 baseService, IApplicationInstanceManager applicationInstanceManager) { _spotifyService = spotifyService; _deezerService = deezerService; _commonService = baseService; _applicationInstanceManager = applicationInstanceManager; Get["GetPopularArtists", "/{serviceType}/{region}/popularArtists", true] = async (parameters, cx) => { var service = GetService((string)parameters.serviceType); var market = GetApplicationByCountry((string) parameters.region); var lastModified = await service.GetPopularArtistsLastModifiedAsync(market); var cachedResponse = CheckConditionalRetrieve(Request, lastModified); if (cachedResponse != null) return cachedResponse; var popularArtists = await service.GetPopularArtistsAsync(market); return Response.AsJson(popularArtists).WithLastModified(lastModified).WithFastlyKey("popularArtist", "toplist"); }; Get["GetPopularTags", "/{serviceType}/{region}/popularTags", true] = async (parameters, cx) => { var service = GetService((string)parameters.serviceType); var market = GetApplicationByCountry((string)parameters.region); var popularTags = await service.GetPopularTagsAsync(market); var response = Response.AsJson(popularTags).WithFastlyKey("popularTags", "playlist"); return response; }; Get["GetAutocomplete", "/{serviceType}/{region}/autocomplete/{searchString}", true] = async (parameters, cx) => { var service = GetService((string)parameters.serviceType); var market = GetApplicationByCountry((string)parameters.region); var lastModified = await service.GetSearchAutocompleteLastModifiedAsync(market); var cachedResponse = CheckConditionalRetrieve(Request, lastModified); if (cachedResponse != null) return cachedResponse; var autocompleteResult = await service.SearchAutocompleteAsync(market, (string) parameters.searchString); var response = Response.AsJson(autocompleteResult).WithLastModified(lastModified).WithFastlyKey("autocomplete"); return response; }; Get["GetTrackRecommendations", "/{serviceType}/{region}/trackRecommendations", true] = async (parameters, cx) => { var service = GetService((string)parameters.serviceType); var region = (string) parameters.region; var market = GetApplicationByCountry(region); var searchTrackParameters = ReadSearchTracksParameters(Request); var tracks = await service.SearchTracksAsync(market, region, searchTrackParameters); var filteredTracks = Filter(Request, tracks, defaultLimit: 100); return Response.AsJson(filteredTracks).WithFastlyKey("trackRecommendations"); }; //Get["/{serviceType}/{region}/recommendations", true] = async (parameters, cx) => //{ // var service = GetService((string)parameters.serviceType); // var market = GetApplicationByCountry((string)parameters.region); // var tag = (string) Request.Query.tag; // var artist = (string) Request.Query.artist; // var recommendations = await service.GetRecommendationsAsync(market, tag, artist); // return Response.AsJson(recommendations); //}; Get["GetPromotedTracks", "/{serviceType}/{region}/promotedTracks", true] = async (parameters, cx) => { var service = GetService((string)parameters.serviceType); var market = GetApplicationByCountry((string)parameters.region); var lastModified = await service.GetPromotedTracksLastModifiedAsync(market); var cachedResponse = CheckConditionalRetrieve(Request, lastModified); if (cachedResponse != null) return cachedResponse; var promotedTracks = await service.GetPromotedTracksAsync(market); return Response.AsJson(promotedTracks).WithLastModified(lastModified).WithFastlyKey("promotedTracks"); }; Get["GetPlaylists", "/{serviceType}/{region}/playlists", true] = async (parameters, cx) => { //TODO: Detailed //TODO: Specify playlist IDs. var service = GetService((string)parameters.serviceType); var market = GetApplicationByCountry((string)parameters.region); var lastModified = await service.GetPlaylistLastModifiedDateAsync(market); var cachedResponse = CheckConditionalRetrieve(Request, lastModified); if (cachedResponse != null) return cachedResponse; var playlists = await service.GetPlaylistsAsync(market); var sortedPlaylists = Sort(Request, playlists); var playlistViewModel = Filter(Request, sortedPlaylists); return Response.AsJson(playlistViewModel).WithLastModified(lastModified).WithFastlyKey("playlist"); }; Get["GetPlaylist", "/{serviceType}/{region}/playlists/{playlistId}", true] = async (parameters, cx) => { var service = GetService((string)parameters.serviceType); var market = GetApplicationByCountry((string)parameters.region); var playlistId = (int) parameters.playlistId; var lastModified = await service.GetPlaylistLastModifiedDateAsync(market); var cachedResponse = CheckConditionalRetrieve(Request, lastModified); if (cachedResponse != null) return cachedResponse; var playlists = await service.GetPlaylistsAsync(market); var playlist = playlists.FirstOrDefault(p => p.Id == playlistId); if (playlist == null) { return new Response() { StatusCode = HttpStatusCode.NotFound, ReasonPhrase = "Could not find playlists." }; } return Response.AsJson(playlist).WithLastModified(lastModified).WithFastlyKey("playlist", $"p{playlistId}"); }; Get["GetPlaylistTracks", "/{serviceType}/{region}/playlists/{playlistId}/tracks", true] = async (parameters, cx) => { var service = GetService((string)parameters.serviceType); var market = GetApplicationByCountry((string)parameters.region); var playlistId = (string)parameters.playlistId; var playlistTracks = await service.GetPlaylistTracksAsync(playlistId); return Response.AsJson(playlistTracks).WithFastlyKey("playlist", $"p{playlistId}"); }; Get["GetPlaylistCategories", "/{serviceType}/{region}/playlistCategories", true] = async (parameters, cx) => { var service = GetService((string)parameters.serviceType); var market = GetApplicationByCountry((string)parameters.region); var lastModified = await _commonService.GetPlaylistCategoriesLastModifiedDateAsync(market); var cachedResponse = CheckConditionalRetrieve(Request, lastModified); if (cachedResponse != null) return cachedResponse; var categories = service.GetCategories(market); var categoriesViewModel = Filter(Request, categories); return Response.AsJson(categoriesViewModel).WithLastModified(lastModified).WithFastlyKey("playlistcategory"); }; Get["GetPlaylistCategory", "/{serviceType}/{region}/playlistCategories/{playlistCategoryId}", true] = async (parameters, cx) => { var service = GetService((string)parameters.serviceType); var market = GetApplicationByCountry((string)parameters.region); var playlistCategoryId = (int)parameters.playlistCategoryId; var lastModified = await _commonService.GetPlaylistCategoriesLastModifiedDateAsync(market); var cachedResponse = CheckConditionalRetrieve(Request, lastModified); if (cachedResponse != null) return cachedResponse; var categories = service.GetCategories(market); var category = categories.FirstOrDefault(c => c.ID == playlistCategoryId); if (category == null) { return new Response() { StatusCode = HttpStatusCode.NotFound, ReasonPhrase = "Could not find playlist category" }; } return Response.AsJson(category).WithLastModified(lastModified).WithFastlyKey("playlistcategory", $"pc{playlistCategoryId}"); }; Get["GetPlaylistsInCategory", "/{serviceType}/{region}/playlistCategories/{playlistCategoryId}/playlists", true] = async (parameters, cx) => { //TODO: Caching var service = GetService((string)parameters.serviceType); var market = GetApplicationByCountry((string)parameters.region); var playlistCategoryId = (int)parameters.playlistCategoryId; var playlistLastModified = await _commonService.GetPlaylistLastModifiedDateAsync(market); var categoryLastModified = await _commonService.GetPlaylistCategoriesLastModifiedDateAsync(market); var lastModified = new[] {playlistLastModified, categoryLastModified}.Max(); var cachedResponse = CheckConditionalRetrieve(Request, lastModified); if (cachedResponse != null) return cachedResponse; var categories = service.GetCategories(market); var category = categories.FirstOrDefault(c => c.ID == playlistCategoryId); if (category == null) return HttpStatusCode.NotFound; var playlists = await service.GetPlaylistsAsync(market); var playlistsInCategory = playlists.Where(p => p.Categories.Contains(playlistCategoryId)).ToList(); var playlistViewModels = Filter(Request, playlistsInCategory); return Response.AsJson(playlistViewModels).WithLastModified(lastModified).WithFastlyKey("playlistcategory", "playlist", $"pc{playlistCategoryId}"); }; Get["GetReleases", "/{serviceType}/{region}/releases", true] = async (parameters, cx) => { var service = GetService((string)parameters.serviceType); var region = (string) parameters.region; var market = GetApplicationByCountry(region); var lastModified = await service.GetNewReleasesLastModifiedDateAsync(market); var cachedResponse = CheckConditionalRetrieve(Request, lastModified); if (cachedResponse != null) return cachedResponse; var releases = await service.GetNewReleasesAsync(market, region); var releaseViewModels = Filter(Request, releases); return Response.AsJson(releaseViewModels).WithLastModified(lastModified).WithFastlyKey("release"); }; Get["SearchContent", "/{serviceType}/{region}/search/{searchString}", true] = async (parameters, cx) => { var service = GetService((string)parameters.serviceType); var region = (string)parameters.region; var market = GetApplicationByCountry(region); var searchString = (string)parameters.searchString; var searchResult = await service.SearchAsync(market, searchString); return Response.AsJson(searchResult).WithFastlyKey("search"); }; Get["GetAvailableRegions", "/{serviceType}/availableRegions", true] = async (parameters, cx) => { var service = GetService((string)parameters.serviceType); var lastModified = await service.GetAvailableRegionsLastModifiedAsync(); var cachedResponse = CheckConditionalRetrieve(Request, lastModified); if (cachedResponse != null) return cachedResponse; var availableRegions = await service.GetAvailableRegionsAsync(); return Response.AsJson(availableRegions).WithLastModified(lastModified).WithFastlyKey("regions"); }; } private Response CheckConditionalRetrieve(Request request, DateTime lastModified) { DateTime? ifModifiedSince = request.Headers.IfModifiedSince; if (ifModifiedSince.HasValue && lastModified != DateTime.MinValue) { long ticksDifference = lastModified.ToUniversalTime().Ticks - ifModifiedSince.Value.ToUniversalTime().Ticks; if (ticksDifference < TimeSpan.TicksPerSecond) { return new Response() { StatusCode = HttpStatusCode.NotModified }; } } return null; } private IEnumerable Sort(Request request, List playlists) { IEnumerable sortedPlaylists = playlists; PlaylistSortOrder sortOrder; if (Enum.TryParse((string)request.Query.sortOrder, out sortOrder)) { switch (sortOrder) { case PlaylistSortOrder.Popularity: sortedPlaylists = playlists.OrderByDescending(p => p.Popularity); break; case PlaylistSortOrder.Followers: sortedPlaylists = playlists.OrderByDescending(p => p.Followers); break; case PlaylistSortOrder.LatestUpdated: sortedPlaylists = playlists.OrderByDescending(p => p.TracksLastAdded); break; } } return sortedPlaylists; } private SearchTracksParameters ReadSearchTracksParameters(Request request) { var artist = (string) request.Query.artist; var tag = (string) request.Query.tag; var minTempo = (int?) request.Query.minTempo; var maxTempo = (int?) request.Query.maxTempo; var minDanceability = (int?) request.Query.minDanceability; var maxDanceability = (int?) request.Query.maxDanceability; var minEnergy = (int?) request.Query.minEnergy; var maxEnergy = (int?) request.Query.maxEnergy; var minHotness = (int?) request.Query.minHotness; var maxHotness = (int?) request.Query.maxHotness; var minDuration = (int?) request.Query.minDuration; var maxDuration = (int?) request.Query.maxDuration; string[] artists = null; if (!string.IsNullOrWhiteSpace(artist)) { artists = artist.Split(',').ToArray(); } var searchTrackParameters = new SearchTracksParameters() { Tag = tag, Artists = artists, MinTempo = minTempo, MaxTempo = maxTempo, MinDanceability = minDanceability, MaxDanceability = maxDanceability, MinEnergy = minEnergy, MaxEnergy = maxEnergy, MinHotness = minHotness, MaxHotness = maxHotness, MinDuration = minDuration, MaxDuration = maxDuration, }; return searchTrackParameters; } private PaginatedContent Filter(Request request, IEnumerable objectsToFilter, int? defaultLimit = null) { var limit = (int?)request.Query.limit ?? defaultLimit; var offset = (int?)request.Query.offset ?? 0; var toFilter = objectsToFilter as IList ?? objectsToFilter.ToList(); var total = toFilter.Count(); IEnumerable filteredObjects = toFilter; if (offset != 0) { filteredObjects = filteredObjects.Skip(offset); } if (limit.HasValue) { filteredObjects = filteredObjects.Take(limit.Value); } return new PaginatedContent() { Items = filteredObjects.ToList(), Pagination = new Pagination() { Total = total, Limit = limit, Offset = offset } }; } private Application GetApplicationByCountry(string countryCode) { return _applicationInstanceManager.GetRegularApplicationByCountrySpecialFallback(countryCode); } private IService3 GetService(string service) { if (service != null && service.Equals("spotify", StringComparison.InvariantCultureIgnoreCase)) return _spotifyService; if (service != null && service.Equals("deezer", StringComparison.InvariantCultureIgnoreCase)) return _deezerService; throw new NancyStatusException(HttpStatusCode.NotFound); } } }