using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.StaticFiles; using Microsoft.Net.Http.Headers; using Raven.Client.Documents; using Raven.Client.Documents.Linq; using System.IO; namespace zero.Api.Endpoints.Media; /// /// GET /empty /// GET / /// GET /{id} /// GET /{id}/hierarchy /// GET /{id}/{size}.tmp /// POS / /// PUT /{id} /// PUT /{id/move/{folderId} /// DEL /{id} /// /// GET /folders/empty /// GET /folders/{id} /// GET /folders/{id}/children /// GET /folders/{id}/hierarchy /// POS /folders/ /// PUT /folders/{id} /// PUT /folders/{id/move/{folderId} /// DEL /folders/{id} /// /// GET /bulk/descendants /// PUT /bulk/move /// DEL /bulk/delete /// public class MediaController : ZeroApiTreeEntityStoreController { IMediaManagement Media; public MediaController(IMediaStore store, IMediaManagement media) : base(store) { Media = media; } #region bulk operations [HttpPut("bulk/move")] [ZeroAuthorize(MediaPermissions.Update)] public virtual async Task>> BulkMove(MediaBulkMoveOperation operation) { List> results = new(); foreach (string id in operation.Ids) { Result result = await Store.Move(id, NormalizeParentId(operation.ParentId)); results.Add(result.ConvertTo(id)); } return results; } [HttpDelete("bulk/delete")] [ZeroAuthorize(MediaPermissions.Update)] public virtual async Task>> BulkDelete(MediaBulkDeleteOperation operation) { List> results = new(); foreach (string id in operation.Ids) { Result result = await Store.DeleteWithDescendants(id); results.Add(result.ConvertTo(id)); } return results; } [HttpGet("bulk/descendants")] [ZeroAuthorize(MediaPermissions.Update)] public virtual async Task> GetDescendantStatistics([FromQuery] string[] ids) { List> results = new(); Dictionary parents = await Store.Load(ids); int folderCount = parents.Count(x => x.Value != null && x.Value.IsFolder); int fileCount = parents.Count(x => x.Value != null && !x.Value.IsFolder); folderCount += await Store.Session.Query().CountAsync(x => x.Path.ContainsAny(ids) && x.IsFolder); fileCount += await Store.Session.Query().CountAsync(x => x.Path.ContainsAny(ids) && !x.IsFolder); return new MediaDescendantStatistics() { Files = fileCount, Folders = folderCount }; } #endregion #region folder operations [HttpGet("folders/empty")] [ZeroAuthorize(MediaPermissions.Create)] public virtual async Task> EmptyFolder(string flavor = null) => await EmptyModel(flavor, x => x.IsFolder = true); [HttpGet("folders")] [ZeroAuthorize(MediaPermissions.Read)] public virtual Task> GetFoldersByQuery([FromQuery] ListQuery query) { query.AdditionalQuery = q => q.Where(x => x.IsFolder); return GetModelsByIndex(query); } [HttpGet("folders/{id}")] [ZeroAuthorize(MediaPermissions.Read)] public virtual async Task> GetFolder(string id, string changeVector = null) => await GetModel(id, changeVector); [HttpGet("folders/{id}/children")] [ZeroAuthorize(MediaPermissions.Read)] public virtual async Task> GetFolderChildren(string id, [FromQuery] ListQuery query, [FromQuery] bool files = true) { id = NormalizeParentId(id); query.OrderQuery = q => q.OrderByDescending(x => x.IsFolder).ThenByDescending(x => x.CreatedDate); Paged result = await Store.LoadChildren(id, query.Page, query.PageSize, q => q.WhereIf(x => x.IsFolder, !files).Filter(query)); Paged mappedResult = Mapper.Map(result); // get children for all folders string[] folderIds = mappedResult.Items.Where(x => x.IsFolder).Select(x => x.Id).ToArray(); IList children = await Store.Session.Query() .ProjectInto() .Where(x => x.Id.In(folderIds)) .ToListAsync(); foreach (MediaBasic item in mappedResult.Items) { if (item.IsFolder) { zero_Api_Media_ChildCounts.Result childCounts = children.FirstOrDefault(x => x.Id == item.Id); if (childCounts != null) { item.Children = !files ? childCounts.ChildFolderCount : childCounts.ChildCount; } } } return mappedResult; } [HttpGet("folders/{id}/hierarchy")] [ZeroAuthorize(MediaPermissions.Read)] public virtual async Task> GetFolderHierarchy(string id) { return await Store.GetHierarchy(NormalizeParentId(id)); } [HttpPost("folders")] [ZeroAuthorize(MediaPermissions.Create)] public virtual Task> CreateFolder(zero.Media.Media saveModel) { saveModel.IsFolder = true; return CreateModel(saveModel); } [HttpPut("folders/{id}")] [ZeroAuthorize(MediaPermissions.Update)] public virtual Task> UpdateFolder(string id, zero.Media.Media updateModel, [FromQuery] string changeToken = null) => UpdateModel(id, updateModel, changeToken); [HttpPut("folders/{id}/move/{destinationId}")] [ZeroAuthorize(MediaPermissions.Read)] public virtual async Task> MoveFolder(string id, string destinationId) => await MoveModel(id, NormalizeParentId(destinationId)); [HttpDelete("folders/{id}")] [ZeroAuthorize(MediaPermissions.Delete)] public virtual Task>> DeleteFolder(string id) => DeleteModelWithDescendants(id); #endregion #region file operations [HttpGet("empty")] [ZeroAuthorize(MediaPermissions.Create)] public virtual async Task> Empty(string flavor = null) => await EmptyModel(flavor, x => x.IsFolder = false); [HttpGet("")] [ZeroAuthorize(MediaPermissions.Read)] public virtual Task> GetByQuery([FromQuery] ListQuery query) { query.AdditionalQuery = q => q.Where(x => !x.IsFolder); return GetModelsByIndex(query); } [HttpGet("{id}")] [ZeroAuthorize(MediaPermissions.Read)] public virtual async Task> Get(string id, string changeVector = null) => await GetModel(id, changeVector); [HttpGet("{id}/hierarchy")] [ZeroAuthorize(MediaPermissions.Read)] public virtual async Task> GetHierarchy(string id) => await Store.GetHierarchy(NormalizeParentId(id)); [HttpPost("")] [ZeroAuthorize(MediaPermissions.Create)] public virtual async Task> Create([FromForm] IFormFile file, [FromForm] string folderId = null) { Result result = await Media.UploadFile(file, folderId); bool minimalResponse = Hints.ResponsePreference == ApiResponsePreference.Minimal; if (result.IsSuccess) { return Created("/", minimalResponse ? null : result.Model); // TODO correct URL } return result.WithoutModel(); } [HttpPut("{id}")] [ZeroAuthorize(MediaPermissions.Update)] public virtual Task> Update(string id, zero.Media.Media updateModel, [FromQuery] string changeToken = null) => UpdateModel(id, updateModel, changeToken); [HttpPut("{id}/move/{destinationId}")] [ZeroAuthorize(MediaPermissions.Read)] public virtual async Task> Move(string id, string destinationId) => await MoveModel(id, NormalizeParentId(destinationId)); [HttpDelete("{id}")] [ZeroAuthorize(MediaPermissions.Delete)] public virtual Task> Delete(string id) => DeleteModel(id); [HttpGet("{id}/{size}.tmp")] public async Task GetThumbnail(string id, string size) { zero.Media.Media media = await Media.GetFile(id); if (media == null) { return NotFound(); } string path = Media.GetPublicFilePath(media); if (path == null) { return NotFound(); } if (path.StartsWith("url://")) { path = path.Substring(6); } FileExtensionContentTypeProvider provider = new(); string contentType; if (!provider.TryGetContentType(Path.GetFileName(path), out contentType)) { contentType = "application/octet-stream"; } try { return File(await Media.GetFileStream(media), contentType, DateTimeOffset.Now, EntityTagHeaderValue.Any); } catch (FileSystemException) { return NotFound(); } } #endregion }