using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Newtonsoft.Json; using RMuseum.Models.Auth.Memory; using RMuseum.Models.GanjoorAudio; using RMuseum.Models.GanjoorAudio.ViewModels; using RMuseum.Models.UploadSession; using RMuseum.Models.UploadSession.ViewModels; using RMuseum.Services; using RSecurityBackend.Models.Generic; using RSecurityBackend.Services; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Threading.Tasks; namespace RMuseum.Controllers { [Produces("application/json")] [Route("api/audio")] public class RecitationController : Controller { /// /// Gets audio narrations, user must have narration::moderate permission to be able to see all users narrations /// /// /// default: false, user must have narration::moderate permission to be able to see all users narrations /// default: -1, unfiltered /// [HttpGet] [Authorize] [ProducesResponseType((int)HttpStatusCode.OK, Type = typeof(IEnumerable))] [ProducesResponseType((int)HttpStatusCode.BadRequest, Type = typeof(string))] [ProducesResponseType((int)HttpStatusCode.Forbidden, Type = typeof(string))] public async Task Get([FromQuery] PagingParameterModel paging, bool allUsers = false, AudioReviewStatus status = AudioReviewStatus.All ) { Guid loggedOnUserId = new Guid(User.Claims.FirstOrDefault(c => c.Type == "UserId").Value); Guid sessionId = new Guid(User.Claims.FirstOrDefault(c => c.Type == "SessionId").Value); if (allUsers) { RServiceResult canView = await _userPermissionChecker.Check ( loggedOnUserId, sessionId, RMuseumSecurableItem.AudioNarrationEntityShortName, RMuseumSecurableItem.ModerateOperationShortName ); if (!string.IsNullOrEmpty(canView.ExceptionString)) return BadRequest(canView.ExceptionString); if (!canView.Result) return StatusCode((int)HttpStatusCode.Forbidden); } var res = await _audioService.GetAll(paging, allUsers ? Guid.Empty : loggedOnUserId, status); if(!string.IsNullOrEmpty(res.ExceptionString)) return BadRequest(res.ExceptionString); // Paging Header HttpContext.Response.Headers.Add("paging-headers", JsonConvert.SerializeObject(res.Result.PagingMeta)); return Ok(res.Result.Items); } /// /// get the corresponding mp3 file for the narration /// /// /// it could be protected (Authorized), but I guess I would have problems with available client components support, /// so I preferred it to be anonymous, as it does not harm anybody I guess /// /// /// [HttpGet("file/{id}.mp3")] [AllowAnonymous] [ProducesResponseType((int)HttpStatusCode.OK, Type = typeof(FileStreamResult))] [ProducesResponseType((int)HttpStatusCode.BadRequest, Type = typeof(string))] public async Task GetMp3File(int id) { RServiceResult narration = await _audioService.Get(id); if (!string.IsNullOrEmpty(narration.ExceptionString)) return BadRequest(narration.ExceptionString); if (narration.Result == null) return NotFound(); Response.GetTypedHeaders().LastModified = narration.Result.UploadDate;//TODO: Add a FileLastUpdated field to narrations to indicate the last time the mp3/xml files have been updated var requestHeaders = Request.GetTypedHeaders(); if (requestHeaders.IfModifiedSince.HasValue && requestHeaders.IfModifiedSince.Value >= narration.Result.UploadDate) { return StatusCode(StatusCodes.Status304NotModified); } return new FileStreamResult(new FileStream(narration.Result.LocalMp3FilePath, FileMode.Open, FileAccess.Read), "audio/mpeg"); } /// /// get the corresponding xml file contemnts (xml) for the narration /// /// /// it could be protected (Authorized), but I guess I would have problems with available client components support, /// so I preferred it to be anonymous, as it does not harm anybody I guess /// /// /// [HttpGet("xml/{id}")] [AllowAnonymous] [ProducesResponseType((int)HttpStatusCode.OK, Type = typeof(string))] [ProducesResponseType((int)HttpStatusCode.BadRequest, Type = typeof(string))] public async Task GetXMLFile(int id) { RServiceResult narration = await _audioService.Get(id); if (!string.IsNullOrEmpty(narration.ExceptionString)) return BadRequest(narration.ExceptionString); if (narration.Result == null) return NotFound(); Response.GetTypedHeaders().LastModified = narration.Result.UploadDate;//TODO: Add a FileLastUpdated field to narrations to indicate the last time the mp3/xml files have been updated var requestHeaders = Request.GetTypedHeaders(); if (requestHeaders.IfModifiedSince.HasValue && requestHeaders.IfModifiedSince.Value >= narration.Result.UploadDate) { return StatusCode(StatusCodes.Status304NotModified); } return Ok(System.IO.File.ReadAllText(narration.Result.LocalXmlFilePath)); } /// /// Gets Verse Sync Information /// /// narration id /// [HttpGet("verses/{id}")] [AllowAnonymous] [ProducesResponseType((int)HttpStatusCode.OK, Type = typeof(string))] [ProducesResponseType((int)HttpStatusCode.BadRequest, Type = typeof(string))] public async Task GetPoemNarrationVerseSyncArray(int id) { RServiceResult verses = await _audioService.GetPoemNarrationVerseSyncArray(id); if (!string.IsNullOrEmpty(verses.ExceptionString)) return BadRequest(verses.ExceptionString); return Ok(verses.Result); } /// /// updates narration metadata /// /// /// /// /// reviewstatus cannot be set to Approved or Rejected using this method, use moderate method instead /// only these set of fields are updatable: AudioTitle, AudioArtist, AudioArtistUrl, AudioSrc, AudioSrcUrl, ReviewStatus (Draft to Pending and vice versa and Approved/Rejected to Pending) /// only narrator or a moderator can update the narration /// /// [HttpPut("{id}")] [Authorize] [ProducesResponseType((int)HttpStatusCode.OK, Type = typeof(RecitationViewModel))] [ProducesResponseType((int)HttpStatusCode.BadRequest, Type = typeof(string))] [ProducesResponseType((int)HttpStatusCode.Forbidden, Type = typeof(string))] public async Task UpdatePoemNarration(int id, [FromBody] RecitationViewModel metadata) { Guid loggedOnUserId = new Guid(User.Claims.FirstOrDefault(c => c.Type == "UserId").Value); var narration = await _audioService.Get(id); if (!string.IsNullOrEmpty(narration.ExceptionString)) { return BadRequest(narration.ExceptionString); } if (narration.Result == null) return NotFound(); if(narration.Result.Owner.Id != loggedOnUserId) { Guid sessionId = new Guid(User.Claims.FirstOrDefault(c => c.Type == "SessionId").Value); RServiceResult serviceResult = await _userPermissionChecker.Check ( loggedOnUserId, sessionId, RMuseumSecurableItem.AudioNarrationEntityShortName, RMuseumSecurableItem.ModerateOperationShortName ); if (!string.IsNullOrEmpty(serviceResult.ExceptionString)) return BadRequest(serviceResult.ExceptionString); if (!serviceResult.Result) return StatusCode((int)HttpStatusCode.Forbidden); } if(narration.Result.ReviewStatus != metadata.ReviewStatus) { if (metadata.ReviewStatus == AudioReviewStatus.Approved || metadata.ReviewStatus == AudioReviewStatus.Rejected) { return StatusCode((int)HttpStatusCode.Forbidden); } } var res = await _audioService.UpdatePoemNarration(id, metadata); if (!string.IsNullOrEmpty(res.ExceptionString)) return BadRequest(res.ExceptionString); return Ok(res.Result); } /// /// Moderate pending narration /// /// /// /// [HttpPut("moderate/{id}")] [Authorize(Policy = RMuseumSecurableItem.AudioNarrationEntityShortName + ":" + RMuseumSecurableItem.ModerateOperationShortName)] [ProducesResponseType((int)HttpStatusCode.OK, Type = typeof(RecitationViewModel))] [ProducesResponseType((int)HttpStatusCode.BadRequest, Type = typeof(string))] [ProducesResponseType((int)HttpStatusCode.Forbidden, Type = typeof(string))] public async Task ModeratePoemNarration(int id, [FromBody] RecitationModerateViewModel model) { Guid loggedOnUserId = new Guid(User.Claims.FirstOrDefault(c => c.Type == "UserId").Value); var res = await _audioService.ModeratePoemNarration(id, loggedOnUserId, model); if (!string.IsNullOrEmpty(res.ExceptionString)) { if(res.ExceptionString == "404") { return NotFound(); } return BadRequest(res.ExceptionString); } return Ok(res.Result); } /// /// Delete a recitation /// /// /// [HttpDelete("{id}")] [Authorize] [ProducesResponseType((int)HttpStatusCode.OK, Type = typeof(bool))] [ProducesResponseType((int)HttpStatusCode.BadRequest, Type = typeof(string))] [ProducesResponseType((int)HttpStatusCode.Forbidden)] public async Task DeleteRecitation(int id) { Guid loggedOnUserId = new Guid(User.Claims.FirstOrDefault(c => c.Type == "UserId").Value); RServiceResult res = await _audioService.Delete(id, loggedOnUserId); if (!string.IsNullOrEmpty(res.ExceptionString)) { return BadRequest(res.ExceptionString); } return Ok(res.Result); } /// /// Get User Uploads /// /// /// default: false, user must have narration::moderate permission to be able to see all users uploads /// [HttpGet("uploads")] [Authorize] [ProducesResponseType((int)HttpStatusCode.OK, Type = typeof(IEnumerable))] [ProducesResponseType((int)HttpStatusCode.BadRequest, Type = typeof(string))] [ProducesResponseType((int)HttpStatusCode.Forbidden, Type = typeof(string))] public async Task GetUploads([FromQuery] PagingParameterModel paging, bool allUsers = false) { Guid loggedOnUserId = new Guid(User.Claims.FirstOrDefault(c => c.Type == "UserId").Value); Guid sessionId = new Guid(User.Claims.FirstOrDefault(c => c.Type == "SessionId").Value); if (allUsers) { RServiceResult canView = await _userPermissionChecker.Check ( loggedOnUserId, sessionId, RMuseumSecurableItem.AudioNarrationEntityShortName, RMuseumSecurableItem.ModerateOperationShortName ); if (!string.IsNullOrEmpty(canView.ExceptionString)) return BadRequest(canView.ExceptionString); if (!canView.Result) return StatusCode((int)HttpStatusCode.Forbidden); } var res = await _audioService.GetUploads(paging, allUsers ? Guid.Empty : loggedOnUserId); if (!string.IsNullOrEmpty(res.ExceptionString)) return BadRequest(res.ExceptionString); // Paging Header HttpContext.Response.Headers.Add("paging-headers", JsonConvert.SerializeObject(res.Result.PagingMeta)); return Ok(res.Result.Items); } /// /// Reciation Upload /// /// /// if you send true to replace parameter, if there is an existing recitation for the poem from the user with the same Audio Artist name /// corresponding mp3+xml files are replaced an no other changes is applied (no new post, preserving recitation position) /// /// [HttpPost] [Authorize] [ProducesResponseType((int)HttpStatusCode.OK, Type = typeof(UploadSession))] [ProducesResponseType((int)HttpStatusCode.BadRequest, Type = typeof(string))] [ProducesResponseType((int)HttpStatusCode.Unauthorized)] [ProducesResponseType((int)HttpStatusCode.Forbidden, Type = typeof(string))] public async Task UploadReciation(bool replace = false) { try { Guid loggedOnUserId = new Guid(User.Claims.FirstOrDefault(c => c.Type == "UserId").Value); RServiceResult resSession = await _audioService.InitiateNewUploadSession(loggedOnUserId, replace); if (!string.IsNullOrEmpty(resSession.ExceptionString)) return BadRequest(resSession.ExceptionString); List files = new List(); foreach(IFormFile file in Request.Form.Files) { RServiceResult rsFileResult = await _audioService.SaveUploadedFile(file); if(!string.IsNullOrEmpty(rsFileResult.ExceptionString)) { return BadRequest(rsFileResult.ExceptionString); } files.Add(rsFileResult.Result); } resSession = await _audioService.FinalizeNewUploadSession(resSession.Result, files.ToArray()); return Ok(resSession.Result); } catch (Exception exp) { return BadRequest(exp.ToString()); } } /// /// imports data from ganjoor MySql database /// /// [HttpPost] [Route("mysqlonetimeimport")] [Authorize(Policy = RMuseumSecurableItem.AudioNarrationEntityShortName + ":" + RMuseumSecurableItem.ImportOperationShortName)] [ProducesResponseType((int)HttpStatusCode.OK, Type = typeof(bool))] [ProducesResponseType((int)HttpStatusCode.BadRequest, Type = typeof(string))] public async Task MysqlonetimeImport() { Guid loggedOnUserId = new Guid(User.Claims.FirstOrDefault(c => c.Type == "UserId").Value); RServiceResult res = await _audioService.OneTimeImport(loggedOnUserId); if (res.Result) return Ok(); return BadRequest(res.ExceptionString); } /// /// retry publish unpublished narrations /// /// [HttpPost] [Route("retrypublish")] [Authorize(Policy = RMuseumSecurableItem.AudioNarrationEntityShortName + ":" + RMuseumSecurableItem.ModerateOperationShortName)] [ProducesResponseType((int)HttpStatusCode.OK, Type = typeof(bool))] [ProducesResponseType((int)HttpStatusCode.BadRequest, Type = typeof(string))] public IActionResult RetryPublish() { try { _audioService.RetryPublish(); return Ok(); } catch(Exception exp) { return BadRequest(exp.ToString()); } } /// /// Get User Profiles /// /// [HttpGet("profile")] [Authorize] [ProducesResponseType((int)HttpStatusCode.OK, Type = typeof(IEnumerable))] [ProducesResponseType((int)HttpStatusCode.BadRequest, Type = typeof(string))] [ProducesResponseType((int)HttpStatusCode.Forbidden, Type = typeof(string))] public async Task GetUserNarrationProfiles() { Guid loggedOnUserId = new Guid(User.Claims.FirstOrDefault(c => c.Type == "UserId").Value); var res = await _audioService.GetUserNarrationProfiles(loggedOnUserId); if (!string.IsNullOrEmpty(res.ExceptionString)) return BadRequest(res.ExceptionString); return Ok(res.Result); } /// /// Get User Default Profile /// /// [HttpGet("profile/def")] [Authorize] [ProducesResponseType((int)HttpStatusCode.OK, Type = typeof(UserRecitationProfileViewModel))] [ProducesResponseType((int)HttpStatusCode.BadRequest, Type = typeof(string))] [ProducesResponseType((int)HttpStatusCode.Forbidden, Type = typeof(string))] public async Task GetUserDefProfile() { Guid loggedOnUserId = new Guid(User.Claims.FirstOrDefault(c => c.Type == "UserId").Value); var res = await _audioService.GetUserDefProfile(loggedOnUserId); if (!string.IsNullOrEmpty(res.ExceptionString)) return BadRequest(res.ExceptionString); return Ok(res.Result); } /// /// Add a narration profile /// /// /// [HttpPost("profile")] [Authorize] [ProducesResponseType((int)HttpStatusCode.OK, Type = typeof(UserRecitationProfileViewModel))] [ProducesResponseType((int)HttpStatusCode.BadRequest, Type = typeof(string))] [ProducesResponseType((int)HttpStatusCode.Forbidden, Type = typeof(string))] public async Task AddUserNarrationProfiles([FromBody]UserRecitationProfileViewModel profile) { Guid loggedOnUserId = new Guid(User.Claims.FirstOrDefault(c => c.Type == "UserId").Value); profile.UserId = loggedOnUserId; var res = await _audioService.AddUserNarrationProfiles(profile); if (!string.IsNullOrEmpty(res.ExceptionString)) return BadRequest(res.ExceptionString); return Ok(res.Result); } /// /// Update a narration profile /// /// /// [HttpPut("profile")] [Authorize] [ProducesResponseType((int)HttpStatusCode.OK, Type = typeof(UserRecitationProfileViewModel))] [ProducesResponseType((int)HttpStatusCode.BadRequest, Type = typeof(string))] [ProducesResponseType((int)HttpStatusCode.Forbidden, Type = typeof(string))] public async Task UpdateUserNarrationProfiles([FromBody] UserRecitationProfileViewModel profile) { Guid loggedOnUserId = new Guid(User.Claims.FirstOrDefault(c => c.Type == "UserId").Value); profile.UserId = loggedOnUserId; var res = await _audioService.UpdateUserNarrationProfiles(profile); if (!string.IsNullOrEmpty(res.ExceptionString)) return BadRequest(res.ExceptionString); return Ok(res.Result); } /// /// Delete a narration profile /// /// /// [HttpDelete("profile/{id}")] [Authorize] [ProducesResponseType((int)HttpStatusCode.OK, Type = typeof(bool))] [ProducesResponseType((int)HttpStatusCode.BadRequest, Type = typeof(string))] [ProducesResponseType((int)HttpStatusCode.Forbidden)] public async Task DeleteUserNarrationProfiles(Guid id) { Guid loggedOnUserId = new Guid(User.Claims.FirstOrDefault(c => c.Type == "UserId").Value); RServiceResult res = await _audioService.DeleteUserNarrationProfiles(id, loggedOnUserId); if (!string.IsNullOrEmpty(res.ExceptionString)) { return BadRequest(res.ExceptionString); } return Ok(res.Result); } /// /// publishing tracker data /// /// TODO: Poem Narration Data is not purified yet (includes sensitive information), so it needs a very special permission /// /// /// /// [HttpGet("publishqueue")] [Authorize(Policy = RMuseumSecurableItem.AudioNarrationEntityShortName + ":" + RMuseumSecurableItem.ImportOperationShortName)] [ProducesResponseType((int)HttpStatusCode.OK, Type = typeof(IEnumerable))] [ProducesResponseType((int)HttpStatusCode.BadRequest, Type = typeof(string))] [ProducesResponseType((int)HttpStatusCode.Forbidden, Type = typeof(string))] public async Task GetPublishingQueueStatus([FromQuery] PagingParameterModel paging, bool inProgress = true, bool finished = true) { var res = await _audioService.GetPublishingQueueStatus(paging, inProgress, finished); if (!string.IsNullOrEmpty(res.ExceptionString)) return BadRequest(res.ExceptionString); // Paging Header HttpContext.Response.Headers.Add("paging-headers", JsonConvert.SerializeObject(res.Result.PagingMeta)); return Ok(res.Result.Items); } /// /// Transfer Recitations Ownership /// /// /// /// number of transfered items [HttpPut("chown")] [Authorize(Policy = RMuseumSecurableItem.AudioNarrationEntityShortName + ":" + RMuseumSecurableItem.ImportOperationShortName)] [ProducesResponseType((int)HttpStatusCode.OK, Type = typeof(int))] [ProducesResponseType((int)HttpStatusCode.BadRequest, Type = typeof(string))] [ProducesResponseType((int)HttpStatusCode.NotFound, Type = typeof(string))] public async Task TransferRecitationsOwnership(string targetEmailAddress, string artistName) { Guid loggedOnUserId = new Guid(User.Claims.FirstOrDefault(c => c.Type == "UserId").Value); var userSearchResult = await _appUserService.FindUserByEmail(targetEmailAddress); if (!string.IsNullOrEmpty(userSearchResult.ExceptionString)) return BadRequest(userSearchResult); if (userSearchResult.Result == null) return NotFound(); var resExec = await _audioService.TransferRecitationsOwnership(loggedOnUserId, (Guid)userSearchResult.Result.Id, artistName); if (!string.IsNullOrEmpty(resExec.ExceptionString)) return BadRequest(resExec.ExceptionString); return Ok(resExec.Result); } /// /// constructor /// /// /// /// public RecitationController(IRecitationService audioService, IUserPermissionChecker userPermissionChecker, IAppUserService appUserService) { _audioService = audioService; _userPermissionChecker = userPermissionChecker; _appUserService = appUserService; } /// /// Artifact Service /// protected readonly IRecitationService _audioService; /// /// IUserPermissionChecker instance /// protected IUserPermissionChecker _userPermissionChecker; /// /// IAppUserService instance /// protected IAppUserService _appUserService; } }