using System.Linq; using System.Web.Hosting; using System.Collections.Generic; using Nancy; using Nancy.ModelBinding; using Sony.Filtr.Contracts.Abstractions; using Sony.Filtr.Contracts.Definitions; using Sony.Filtr.PlaylistSynchronization; using Sony.Filtr.AdminAPI.BusinessLogic; using System; using Sony.Filtr.PlaylistSynchronization.Data; using Sony.Filtr.Contracts.Entities; using Sony.Filtr.ErrorLogging; using Sony.Filtr.Utility; using Sony.Filtr.Utility.Extensions; namespace Sony.Filtr.AdminAPI.Modules { public class PlaylistSyncModule : BaseModule { private readonly PlaylistSynchronizationManager _playlistSynchronizationManager; private readonly ServiceAccountManager _serviceAccountManager; public PlaylistSyncModule(PlaylistSynchronizationManager playlistSynchronizationManager, ServiceAccountManager serviceAccountManager, IApplicationInstanceManager applicationInstanceManager, FlaggedChannelsManager whitelistingManager, PlaylistSynchronizationLogic playlistSynchronizationLogic) : base(applicationInstanceManager) { _playlistSynchronizationManager = playlistSynchronizationManager; _serviceAccountManager = serviceAccountManager; Get["GetAllPlaylistSyncs", "/PlaylistSync/playlists", true] = async (parameters, cx) => { var playlists = await playlistSynchronizationManager.GetPlaylistSynchronizationsWithLogsAsync(); return Response.AsJson(playlists.Select(s => ToViewModel(s)).ToList()); }; Get["GetPlaylistSyncsMarket", "/PlaylistSync/{countryCode}/playlists/", true] = async (parameters, cx) => { if (CurrentApplication == null) { return new Response() { StatusCode = HttpStatusCode.BadRequest, ReasonPhrase = $"No application with id {(string)parameters.playlistSyncId} found." }; } var playlists = await playlistSynchronizationManager.GetPlaylistSynchronizationsWithLogsAsync(CurrentApplication); return Response.AsJson(playlists.Select(s => ToViewModel(s)).ToList()); }; Get["GetSinglePlaylistSync", "/PlaylistSync/{countryCode}/playlists/{playlistSyncId}", true] = async (parameters, cx) => { if (CurrentApplication == null) { return new Response() { StatusCode = HttpStatusCode.BadRequest, ReasonPhrase = $"No application with id {(string)parameters.playlistSyncId} found." }; } var playlistSyncId = (int)parameters.playlistSyncId; var playlistSync = await playlistSynchronizationManager.GetPlaylistSynchronizationWithLatestLogAsync(playlistSyncId); if (playlistSync == null) { return new Response() { StatusCode = HttpStatusCode.BadRequest, ReasonPhrase = string.Format("No sync with id {0} found.", (string)parameters.playlistSyncId) }; } if (playlistSync.Synchronization.ApplicationId != CurrentApplication.ID) { return new Response() { StatusCode = HttpStatusCode.BadRequest, ReasonPhrase = string.Format("No sync with id {0} not available for application id {1}", (string)parameters.playlistSyncId, CurrentApplication.ID) }; } return Response.AsJson(ToViewModel(playlistSync)); }; Post["AddPlaylistSync", "/PlaylistSync/{countryCode}/playlists", true] = async (parameters, cx) => { var playlistSync = this.Bind(); var validation = ValidateSyncAdd(CurrentApplication, playlistSync, parameters); if (!validation.IsValid) { return new Response() { StatusCode = validation.StatusCode, ReasonPhrase = validation.ErrorMessage }; } playlistSync.CreatedAt = DateTime.UtcNow; playlistSync.LastUpdated = null; playlistSync.ApplicationId = CurrentApplication.ID; playlistSync.FromServiceType = ServiceType.Spotify; //TODO: Handle different cases? playlistSync.FromMusicServiceId = (int)MusicService.Spotify; //TODO: Handle different cases? await playlistSynchronizationLogic.GetSourceData(playlistSync); var addedPlaylistSync = playlistSynchronizationManager.AddPlaylistSynchronization(playlistSync); return Response.AsJson(addedPlaylistSync); }; Put["UpdatePlaylistSync", "/PlaylistSync/{countryCode}/playlists/{playlistSyncId}"] = parameters => { var playlistSyncId = (int)parameters.playlistSyncId; var playlistSync = this.Bind(); playlistSync.Id = playlistSyncId; var validation = ValidateSyncUpdate(CurrentApplication, playlistSync); playlistSync.ApplicationId = CurrentApplication.ID; if (!validation.IsValid) { return new Response() { StatusCode = validation.StatusCode, ReasonPhrase = validation.ErrorMessage }; } playlistSynchronizationManager.UpdatePlaylistSynchronization(playlistSync); return Response.AsJson(playlistSynchronizationManager.GetPlaylistSynchronization(playlistSync.Id)); }; Delete["DeletePlaylistSync", "/PlaylistSync/{countryCode}/playlists/{playlistSyncId}"] = parameters => { var syncId = (int)parameters.playlistSyncId; var existingPlaylistSync = playlistSynchronizationManager.GetPlaylistSynchronization(syncId); if (existingPlaylistSync == null) return HttpStatusCode.NotFound; if (CurrentApplication == null || CurrentApplication.ID != existingPlaylistSync.ApplicationId) { return new Response() { StatusCode = HttpStatusCode.BadRequest, ReasonPhrase = string.Format( "Application does not have access to playlist sync with id {0}", existingPlaylistSync.Id) }; } playlistSynchronizationManager.DeletePlaylistSynchronization(existingPlaylistSync); return HttpStatusCode.Accepted; }; Post["ExecutePlaylistSync", "/PlaylistSync/{countryCode}/playlists/{playlistSyncId}/execute"] = parameters => { var syncId = (int)parameters.playlistSyncId; var existingPlaylistSync = playlistSynchronizationManager.GetPlaylistSynchronization(syncId); if (existingPlaylistSync == null) return HttpStatusCode.NotFound; if (CurrentApplication == null || CurrentApplication.ID != existingPlaylistSync.ApplicationId) { return new Response() { StatusCode = HttpStatusCode.BadRequest, ReasonPhrase = string.Format("Application does not have access to playlist sync with id {0}", existingPlaylistSync.Id) }; } var serviceAccount = serviceAccountManager.GetServiceAccount(existingPlaylistSync.ToServiceAccountId); HostingEnvironment.QueueBackgroundWorkItem(async state=> { try { await playlistSynchronizationManager.ExecutePlaylistSyncAsync(existingPlaylistSync, serviceAccount, triggeredManually: true); } catch(Exception ex) { ErrorLoggingManager.Instance.LogError(ex); } }); return HttpStatusCode.OK; }; Get["GetLogForPlaylistSync", "/PlaylistSync/{countryCode}/playlists/{playlistSyncId}/log", true] = async (parameters, cx) => { var limit = Maybe.ToIntOrFallback((string)Request.Query.limit, 100); var offset = Maybe.ToIntOrFallback((string)Request.Query.offset, 0); if (CurrentApplication == null) { return new Response() { StatusCode = HttpStatusCode.BadRequest, ReasonPhrase = $"No application with id {(string)parameters.playlistSyncId} found." }; } var playlistSyncId = (int)parameters.playlistSyncId; var playlistSync = playlistSynchronizationManager.GetPlaylistSynchronization(playlistSyncId); if (playlistSync == null) { return new Response() { StatusCode = HttpStatusCode.BadRequest, ReasonPhrase = $"No sync with id {(string)parameters.playlistSyncId} found." }; } var logentries = await playlistSynchronizationManager.GetSyncLogAsync(playlistSync); var paginatedList = logentries.Paginate(limit, offset); return Response.AsJson(paginatedList); }; Get["GetWhitelistedYoutubeChannels", "/PlaylistSync/whitelistedchannels/"] = parameters => { var channels = whitelistingManager.GetWhitelistedChannels(); return Response.AsJson(channels); }; Post["AddWhitelistedYoutubeChannel", "/PlaylistSync/whitelistedchannels/{channel}"] = parameters => { var newChannel = (string)parameters.channel; whitelistingManager.AddWhitelistedChannel(newChannel); return HttpStatusCode.OK; }; Delete["DeleteWhitelistedYoutubeChannel", "/PlaylistSync/whitelistedchannels/{channel}"] = parameters => { var deleteChannel = (string)parameters.channel; whitelistingManager.DeleteWhitelistedChannel(deleteChannel); return HttpStatusCode.OK; }; Get["GetBlacklistedYoutubeChannels", "/PlaylistSync/blacklistedchannels/"] = parameters => { var channels = whitelistingManager.GetBlacklistedChannels(); return Response.AsJson(channels); }; Post["UpdateBlacklistedYoutubeChannel", "/PlaylistSync/blacklistedchannels/{channel}"] = parameters => { var newChannel = (string)parameters.channel; whitelistingManager.AddBlacklistedChannel(newChannel); return HttpStatusCode.OK; }; Delete["DeleteBlacklistedYoutubeChannel", "/PlaylistSync/blacklistedchannels/{channel}"] = parameters => { var deleteChannel = (string)parameters.channel; whitelistingManager.DeleteBlacklistedChannel(deleteChannel); return HttpStatusCode.OK; }; } private SyncValidation ValidateSyncAdd(Application currentApplication, PlaylistSynchronization.Data.PlaylistSynchronization playlistSync, dynamic parameters) { var validation = new SyncValidation() {IsValid = true}; if (currentApplication == null) { validation.IsValid = false; validation.StatusCode = HttpStatusCode.BadRequest; validation.ErrorMessage = string.Format("No application with id {0} found.", (string) parameters.playlistSyncId); } if (playlistSync == null || string.IsNullOrWhiteSpace(playlistSync.FromPlaylistId) || string.IsNullOrWhiteSpace(playlistSync.ToPlaylistId)) { validation.IsValid = false; validation.StatusCode = HttpStatusCode.BadRequest; return validation; } //TODO: Specify toServiceType when validating. var existingSyncForId = _playlistSynchronizationManager.GetPlaylistSynchronization(playlistSync.ToPlaylistId); if (existingSyncForId != null) { validation.IsValid = false; validation.ErrorMessage = string.Format("Playlist sync to playlist with id {0} already exists (Playlist sync ID: {1})", playlistSync.ToPlaylistId, existingSyncForId.Id); validation.StatusCode = HttpStatusCode.Conflict; return validation; } validation = ValidateServiceAccount(currentApplication, playlistSync); return validation; } private SyncValidation ValidateSyncUpdate(Application currentApplication, PlaylistSynchronization.Data.PlaylistSynchronization playlistSync) { var validation = new SyncValidation() { IsValid = true }; var existingPlaylistSync = _playlistSynchronizationManager.GetPlaylistSynchronization(playlistSync.Id); if (existingPlaylistSync == null) { validation.IsValid = false; validation.StatusCode = HttpStatusCode.NoContent; validation.ErrorMessage = string.Format("No playlist sync with id {0} found", playlistSync.Id); return validation; } var existingSyncForId = _playlistSynchronizationManager.GetPlaylistSynchronization(playlistSync.ToPlaylistId); if (existingSyncForId != null && playlistSync.Active && existingSyncForId.Active && existingSyncForId.Id != playlistSync.Id) { validation.IsValid = false; validation.StatusCode = HttpStatusCode.Conflict; validation.ErrorMessage = string.Format( "Active playlist sync to playlist with id {0} already exists (Playlist sync ID: {1})", playlistSync.ToPlaylistId, existingSyncForId.Id); return validation; } if (CurrentApplication == null || CurrentApplication.ID != existingPlaylistSync.ApplicationId) { validation.IsValid = false; validation.StatusCode = HttpStatusCode.Conflict; validation.ErrorMessage = string.Format("Application does not have access to playlist sync with id {0}", existingPlaylistSync.Id); return validation; } validation = ValidateServiceAccount(currentApplication, playlistSync); return validation; } private SyncValidation ValidateServiceAccount(Application currentApplication, PlaylistSynchronization.Data.PlaylistSynchronization playlistSync) { var validation = new SyncValidation() { IsValid = true }; var serviceAccount = _serviceAccountManager.GetServiceAccount(playlistSync.ToServiceAccountId); if (serviceAccount == null) { validation.IsValid = false; validation.ErrorMessage = string.Format("No service account with ID {0}", playlistSync.ToServiceAccountId); return validation; } if (serviceAccount.ApplicationId != currentApplication.ID) { validation.IsValid = false; validation.ErrorMessage = string.Format( "Service account not in requested market. Service account in market {0}, request in market {1}", serviceAccount.ApplicationId, currentApplication.ID); validation.StatusCode = HttpStatusCode.Forbidden; } return validation; } private static PlaylistSynchronizationViewModel ToViewModel(PlaylistSyncWithLog syncWithLog) { if (syncWithLog == null || syncWithLog.Synchronization == null) { return new PlaylistSynchronizationViewModel(); } return new PlaylistSynchronizationViewModel() { Active = syncWithLog.Synchronization.Active, ApplicationId = syncWithLog.Synchronization.ApplicationId, CreatedAt = syncWithLog.Synchronization.CreatedAt, Description = syncWithLog.Synchronization.Description, DescriptionCopyMode = syncWithLog.Synchronization.DescriptionCopyMode, FromMusicServiceId = syncWithLog.Synchronization.FromMusicServiceId, FromPlaylistId = syncWithLog.Synchronization.FromPlaylistId, FromServiceType = syncWithLog.Synchronization.FromServiceType, Id = syncWithLog.Synchronization.Id, LastUpdated = syncWithLog.Synchronization.LastUpdated.HasValue ? syncWithLog.Synchronization.LastUpdated.Value : DateTime.MinValue, SourceImage = syncWithLog.Synchronization.SourceImage, SourceTitle = syncWithLog.Synchronization.SourceTitle, SourceTrackCount = syncWithLog.Synchronization.SourceTrackCount, SynchronizedTrackCount = syncWithLog.Synchronization.SynchronizedTrackCount, Title = syncWithLog.Synchronization.Title, TitleCopyMode = syncWithLog.Synchronization.TitleCopyMode, ToPlaylistId = syncWithLog.Synchronization.ToPlaylistId, ToServiceAccountId = syncWithLog.Synchronization.ToServiceAccountId, TriggeredManually = syncWithLog.Log.TriggeredManually, AddedTracks = syncWithLog.Log.AddedTracks, Time = syncWithLog.Log.Time, SyncedTrackCount = syncWithLog.Log.SyncedTrackCount, DeletedDuplicates = syncWithLog.Log.DeletedDuplicates, DeletedTracks = syncWithLog.Log.DeletedTracks, ErrorText = syncWithLog.Log.ErrorText, MadeChange = syncWithLog.Log.MadeChange, ErrorType = syncWithLog.Log.Error.HasValue ? syncWithLog.Log.Error.ToString() : "", InsertMedia = syncWithLog.Synchronization.InsertMedia }; } } public class PlaylistSynchronizationViewModel { public int Id { get; set; } public int ApplicationId { get; set; } public string FromPlaylistId { get; set; } public ServiceType FromServiceType { get; set; } public int FromMusicServiceId { get; set; } public string ToPlaylistId { get; set; } public int ToServiceAccountId { get; set; } public string Title { get; set; } public string Description { get; set; } public DateTime LastUpdated { get; set; } public FieldCopyMode TitleCopyMode { get; set; } public FieldCopyMode DescriptionCopyMode { get; set; } public bool Active { get; set; } public DateTime CreatedAt { get; set; } public string SourceTitle { get; set; } public string SourceImage { get; set; } public int SourceTrackCount { get; set; } public int SynchronizedTrackCount { get; set; } public List InsertMedia { get; set; } public DateTime? Time { get; set; } public int SyncedTrackCount { get; set; } public int AddedTracks { get; set; } public int DeletedTracks { get; set; } public int DeletedDuplicates { get; set; } public bool MadeChange { get; set; } public string ErrorText { get; set; } public bool TriggeredManually { get; set; } public string ErrorType { get; set; } } public class SyncValidation { public bool IsValid { get; set; } public string ErrorMessage { get; set; } public HttpStatusCode StatusCode { get; set; } } }