file uploads for media management (not persisted yet)

This commit is contained in:
2021-11-25 16:01:38 +01:00
parent f3b006eb36
commit d81992d2cd
12 changed files with 352 additions and 487 deletions
@@ -0,0 +1,250 @@
using Raven.Client.Documents;
using Raven.Client.Documents.Linq;
using Raven.Client.Documents.Session;
namespace zero.Backoffice.Modules;
public class MediaTreeService : IMediaTreeService
{
protected IMediaStore Media { get; private set; }
protected PageOptions PageOptions { get; set; }
public PageTreeService(IPagesStore pages, IZeroOptions options, IRoutes routes)
{
Media = pages;
Routes = routes;
PageOptions = options.For<PageOptions>();
}
/// <inheritdoc />
/// FOR MEDIA
public virtual async Task<Paged<MediaListItem>> Load(MediaListItemQuery query)
{
bool hasSearch = !query.Search.IsNullOrWhiteSpace();
bool isRoot = query.FolderId.IsNullOrWhiteSpace();
query.SearchFor(entity => entity.Name);
query.OrderQuery = q => q
.OrderByDescending(x => x.IsFolder)
.ThenBy(query.OrderBy, query.OrderIsDescending, query.OrderType == ListQueryOrderType.String ? OrderingType.String : OrderingType.Double);
IRavenQueryable<MediaListItem> dbQuery = Session.Query<MediaListItem, Media_ByParent>().ProjectInto<MediaListItem>();
if (!hasSearch || !query.SearchIsGlobal)
{
dbQuery = dbQuery.WhereIf(x => x.ParentId == query.FolderId, !query.FolderId.IsNullOrEmpty(), x => x.ParentId == null);
}
Paged<MediaListItem> result = await dbQuery.ToQueriedListAsyncX(query);
string[] ids = result.Items.Where(x => x.IsFolder).Select(x => x.Id).ToArray();
List<Media_ByChildren.Result> children = await Session.Query<Media_ByChildren.Result, Media_ByChildren>()
.Where(x => x.ParentId.In(ids))
.ToListAsync();
foreach (MediaListItem item in result.Items)
{
item.Children = children.FirstOrDefault(x => x.ParentId == item.Id)?.ChildrenCount ?? 0;
}
return result;
}
/// <inheritdoc />
/// FOR MEDIA FOLDER
public async Task<List<TreeItem>> LoadAsTree(string parentId = null, string activeId = null)
{
List<TreeItem> items = new();
string[] openIds = Array.Empty<string>();
IList<MediaFolder> folders = await Session.Query<MediaFolder>()
.WhereIf(x => x.ParentId == parentId, !parentId.IsNullOrEmpty(), x => x.ParentId == null)
.OrderByDescending(x => x.CreatedDate).ThenBy(x => x.Name)
.ToListAsync();
// get hierarchy so we know if we should set the folder to open
if (!activeId.IsNullOrEmpty())
{
MediaFolder_ByHierarchy.Result result = await Session.Query<MediaFolder_ByHierarchy.Result, MediaFolder_ByHierarchy>()
.ProjectInto<MediaFolder_ByHierarchy.Result>()
.Include<MediaFolder_ByHierarchy.Result, MediaFolder>(x => x.Path.Select(p => p.Id))
.FirstOrDefaultAsync(x => x.Id == activeId);
if (result != null)
{
openIds = result.Path.Select(x => x.Id).ToArray();
}
}
// get children for all folders
string[] folderIds = folders.Select(x => x.Id).ToArray();
IList<MediaFolders_WithChildren.Result> children = await Session.Query<MediaFolders_WithChildren.Result, MediaFolders_WithChildren>()
.ProjectInto<MediaFolders_WithChildren.Result>()
.Where(x => x.Id.In(folderIds))
.ToListAsync();
foreach (MediaFolder folder in folders)
{
int childCount = children.Count(x => x.Id == folder.Id);
items.Add(new TreeItem()
{
Id = folder.Id,
Name = folder.Name,
HasChildren = childCount > 0,
ChildCount = childCount,
ParentId = folder.ParentId,
Sort = folder.Sort,
Icon = "fth-folder",
IsOpen = openIds.Contains(folder.Id),
IsInactive = !folder.IsActive,
HasActions = true,
Modifier = !folder.IsActive ? new TreeItemModifier()
{
Icon = "fth-minus-circle color-yellow",
Name = "Inactive"
} : null
});
}
return items;
}
/// <inheritdoc />
public async Task<IList<TreeItem>> GetChildren(string parentId = null, string activeId = null, string search = null)
{
IList<TreeItem> items = new List<TreeItem>();
IReadOnlyCollection<PageType> pageTypes = PageOptions.GetAllItems();
string[] openIds = new string[0] { };
Paged<Page> pages = null;
IList<Pages_WithChildren.Result> children = null;
bool isSearch = !search.IsNullOrWhiteSpace();
if (isSearch)
{
pages = await Media.Load(1, Int32.MaxValue, q => q.SearchIf(x => x.Name, search, "*").OrderBy(x => x.Sort, OrderingType.Long));
var urls = await Routes.GetUrls(pages.Items.ToArray());
foreach (Page page in pages.Items)
{
if (urls.TryGetValue(page, out string url))
{
page.Url = url;
}
}
}
else
{
pages = await Media.Load(1, Int32.MaxValue, q => q
.WhereIf(x => x.ParentId == parentId, !parentId.IsNullOrEmpty(), x => x.ParentId == null)
.OrderBy(x => x.Sort, OrderingType.Long));
// get hierarchy so we know if we should set the page to open
if (!activeId.IsNullOrEmpty())
{
Pages_ByHierarchy.Result result = await Media.Session.Query<Pages_ByHierarchy.Result, Pages_ByHierarchy>()
.ProjectInto<Pages_ByHierarchy.Result>()
.Include<Pages_ByHierarchy.Result, Page>(x => x.Path.Select(p => p.Id))
.FirstOrDefaultAsync(x => x.Id == activeId);
if (result != null)
{
openIds = result.Path.Select(x => x.Id).ToArray(); // .Union(new string[1] { activeId })
}
}
// get children for all pages
string[] pageIds = pages.Items.Select(x => x.Id).ToArray();
children = await Media.Session.Query<Pages_WithChildren.Result, Pages_WithChildren>()
.ProjectInto<Pages_WithChildren.Result>()
.Where(x => x.Id.In(pageIds))
.ToListAsync();
}
// function to get modifier icon
TreeItemModifier GetModifier(Page page)
{
if (page.PublishDate > DateTimeOffset.Now || page.UnpublishDate > DateTimeOffset.Now)
{
return new TreeItemModifier("@page.schedule.scheduled", "fth-clock");
}
if (!page.IsActive)
{
return new TreeItemModifier("@ui.inactive", "fth-minus-circle color-red");
}
return null;
}
// build tree
foreach (Page page in pages.Items)
{
PageType pageType = pageTypes.FirstOrDefault(x => x.Alias == page.PageTypeAlias);
if (pageType == null)
{
continue;
// TODO the page type does not exist anymore
}
int childCount = isSearch ? 0 : children.Count(x => x.Id == page.Id);
items.Add(new TreeItem()
{
Id = page.Id,
Name = page.Name,
HasChildren = childCount > 0,
ChildCount = childCount,
ParentId = page.ParentId,
Sort = page.Sort,
Icon = pageType.Icon,
IsOpen = openIds.Contains(page.Id),
IsInactive = !page.IsActive,
HasActions = true,
Modifier = GetModifier(page),
Description = isSearch ? page.Url : null
});
}
if (parentId.IsNullOrEmpty())
{
items.Add(new TreeItem()
{
Id = "recyclebin",
ParentId = null,
Sort = 99999,
Name = "@recyclebin.name",
Icon = "fth-trash",
HasChildren = false,
HasActions = true
});
}
return items;
}
}
public interface IMediaTreeService
{
/// <summary>
/// Get media children as tree items
/// </summary>
Task<IList<TreeItem>> GetChildren(string parentId = null, string activeId = null, string search = null);
}
@@ -18,6 +18,7 @@ public class PageTreeService : IPageTreeService
Pages = pages;
Routes = routes;
PageOptions = options.For<PageOptions>();
IMediaManagement media;
}
-275
View File
@@ -1,275 +0,0 @@
using FluentValidation;
using Microsoft.AspNetCore.Http;
using Raven.Client.Documents;
using Raven.Client.Documents.Linq;
using Raven.Client.Documents.Session;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using zero.Core.Database.Indexes;
namespace zero.Core.Collections
{
public class MediaCollection : EntityStore<Media>, IMediaCollection
{
/// <inheritdoc />
public IMediaFolderCollection Folders { get; protected set; }
public MediaCollection(IStoreContext<Media> context, IMediaFolderCollection folders, IPaths paths) : base(context)
{
Options = new(true);
Folders = folders;
//PreSave = model => model.IsActive = true;
Paths = paths;
}
protected const char PATH_SEPARATOR = '/';
protected const string PATH_PREFIX = "/uploads";
protected const string THUMB_EXTENSION = ".thumb";
protected const string PREVIEW_EXTENSION = ".preview";
protected string[] ImageExtensions = new[] { ".jpg", ".jpeg", ".png", ".bmp", ".webp", ".jfif", ".gif" };
protected IPaths Paths { get; set; }
/// <inheritdoc />
public override Task<EntityResult<Media>> Save(Media model)
{
model.IsActive = true;
return base.Save(model);
}
/// <inheritdoc />
public virtual async Task<string> GetSource(string id, MediaSourceSize size = MediaSourceSize.Original)
{
Media media = await Session.LoadAsync<Media>(id);
if (media == null)
{
return null;
}
if (size == MediaSourceSize.Thumbnail && media.ThumbnailSource.HasValue())
{
return media.ThumbnailSource;
}
if (size == MediaSourceSize.Preview && media.PreviewSource.HasValue())
{
return media.PreviewSource;
}
return media.Source;
}
/// <inheritdoc />
public virtual async Task<Paged<Media>> Load(MediaListQuery query)
{
query.SearchFor(entity => entity.Name);
return await Session.Query<Media>()
.WhereIf(x => x.FolderId == query.FolderId, !query.FolderId.IsNullOrEmpty(), x => x.FolderId == null)
.ToQueriedListAsync(query);
}
/// <inheritdoc />
public virtual async Task<EntityResult<Media>> Move(string id, string parentId)
{
Media model = await Load(id);
MediaFolder parent = await Session.LoadAsync<MediaFolder>(parentId);
if (model == null || (!parentId.IsNullOrEmpty() && parent == null))
{
return EntityResult<Media>.Fail("@errors.idnotfound");
}
model.FolderId = parent?.Id;
return await Save(model);
}
/// <inheritdoc />
public virtual async Task<Media> Upload(IFormFile file, string folderId, CancellationToken cancellationToken = default)
{
Media media = await Empty();
// generate file id which is used as the folder name on disk
media.FileId = Guid.NewGuid().ToString();
media.FolderId = folderId;
// generate file name
media.Name = Safenames.File(file.FileName);
// build folder and full file path
string folderPath = Path.Combine(Paths.Media, media.FileId);
string filePath = Path.Combine(folderPath, media.Name);
string extension = Path.GetExtension(filePath);
// find media type
media.Type = ImageExtensions.Contains(extension, StringComparer.InvariantCultureIgnoreCase) ? MediaType.Image : MediaType.File;
Paths.Create(folderPath);
// write media file to disk
using (var stream = File.Create(filePath))
{
await file.CopyToAsync(stream, cancellationToken);
}
// set new properties
media.Source = Path.Combine(PATH_PREFIX, media.FileId, media.Name).Replace(Path.DirectorySeparatorChar, PATH_SEPARATOR);
media.Size = file.Length;
// write additional image data + thumbnail
if (media.Type == MediaType.Image)
{
using Image<Rgba32> image = Image.Load<Rgba32>(filePath);
media.ImageMeta = GetImageMeta(image);
Image<Rgba32> imageFrame = media.ImageMeta.Frames > 1 ? image.Frames.CloneFrame(0) : image;
media.PreviewSource = SaveThumbnail(media, imageFrame, PREVIEW_EXTENSION, new ResizeOptions()
{
Size = new Size(210, 210),
Mode = ResizeMode.Min
});
media.ThumbnailSource = SaveThumbnail(media, imageFrame, THUMB_EXTENSION, new ResizeOptions()
{
Size = new Size(100, 100),
Mode = ResizeMode.Max
});
}
return media;
}
/// <inheritdoc />
public virtual async Task<Paged<MediaListItem>> Load(MediaListItemQuery query)
{
bool hasSearch = !query.Search.IsNullOrWhiteSpace();
bool isRoot = query.FolderId.IsNullOrWhiteSpace();
query.SearchFor(entity => entity.Name);
query.OrderQuery = q => q
.OrderByDescending(x => x.IsFolder)
.ThenBy(query.OrderBy, query.OrderIsDescending, query.OrderType == ListQueryOrderType.String ? OrderingType.String : OrderingType.Double);
IRavenQueryable<MediaListItem> dbQuery = Session.Query<MediaListItem, Media_ByParent>().ProjectInto<MediaListItem>();
if (!hasSearch || !query.SearchIsGlobal)
{
dbQuery = dbQuery.WhereIf(x => x.ParentId == query.FolderId, !query.FolderId.IsNullOrEmpty(), x => x.ParentId == null);
}
Paged<MediaListItem> result = await dbQuery.ToQueriedListAsyncX(query);
string[] ids = result.Items.Where(x => x.IsFolder).Select(x => x.Id).ToArray();
List<Media_ByChildren.Result> children = await Session.Query<Media_ByChildren.Result, Media_ByChildren>()
.Where(x => x.ParentId.In(ids))
.ToListAsync();
foreach (MediaListItem item in result.Items)
{
item.Children = children.FirstOrDefault(x => x.ParentId == item.Id)?.ChildrenCount ?? 0;
}
return result;
}
/// <inheritdoc />
protected override void ValidationRules(ZeroValidator<Media> validator)
{
validator.RuleFor(x => x.Name).Length(2, 80);
validator.RuleFor(x => x.IsActive).Equal(true);
}
/// <summary>
/// Saves a thumbnail of an image
/// </summary>
protected virtual string SaveThumbnail(Media media, Image<Rgba32> image, string extensionPrefix, ResizeOptions resizeOptions)
{
string extension = Path.GetExtension(media.Source);
image.Mutate(x => x.Resize(resizeOptions));
string thumbFileName = media.Name.TrimEnd(extension) + extensionPrefix + extension;
image.Save(Path.Combine(Paths.Media, media.FileId, thumbFileName));
return Path.Combine(PATH_PREFIX, media.FileId, thumbFileName).Replace(Path.DirectorySeparatorChar, PATH_SEPARATOR);
}
/// <summary>
/// Create image data if available
/// </summary>
protected virtual MediaImageMetadata GetImageMeta(Image<Rgba32> image)
{
var pngMetadata = image.Metadata.GetPngMetadata();
return new MediaImageMetadata()
{
Width = image.Width,
Height = image.Height,
ImageTakenDate = new DateTimeOffset(image.Metadata.IccProfile?.Header?.CreationDate ?? DateTime.Now),
DPI = image.Metadata.HorizontalResolution,
ColorSpace = image.Metadata.IccProfile?.Header?.DataColorSpace.ToString(),
HasTransparency = pngMetadata?.HasTransparency ?? false,
Frames = image.Frames.Count
};
}
}
public interface IMediaCollection : IEntityStore<Media>
{
/// <summary>
/// Media folder collection
/// </summary>
IMediaFolderCollection Folders { get; }
/// <summary>
/// Get media source by Id
/// </summary>
Task<string> GetSource(string id, MediaSourceSize size = MediaSourceSize.Original);
/// <summary>
/// Get all available media items with query
/// </summary>
Task<Paged<Media>> Load(MediaListQuery query);
/// <summary>
/// Get all available media items (including folders) with query
/// </summary>
Task<Paged<MediaListItem>> Load(MediaListItemQuery query);
/// <summary>
/// Move a file to a new parent
/// </summary>
Task<EntityResult<Media>> Move(string id, string parentId);
/// <summary>
/// Uploads a file to the media folder
/// </summary>
Task<Media> Upload(IFormFile file, string folderId, CancellationToken cancellationToken = default);
}
}
+4 -4
View File
@@ -23,7 +23,7 @@ public class MediaCreator : IMediaCreator
/// <inheritdoc />
public async Task<Media> UploadFile(Stream fileStream, string filename, string folderId = null, CancellationToken cancellationToken = default)
public async Task<EntityResult<Media>> UploadFile(Stream fileStream, string filename, string folderId = null, CancellationToken cancellationToken = default)
{
string fileExtension = Path.GetExtension(filename);
string normalizedFilename = Safenames.File(filename);
@@ -34,7 +34,7 @@ public class MediaCreator : IMediaCreator
if (!isImage && !isDocument)
{
// TODO error
return null;
return EntityResult<Media>.Fail("ERROR");
}
Media model = await Store.Empty();
@@ -67,7 +67,7 @@ public class MediaCreator : IMediaCreator
// TODO save thumbnails
}
return model;
return EntityResult<Media>.Success(model);
}
@@ -162,5 +162,5 @@ public interface IMediaCreator
/// Uploads a file by using the attached file system
/// </summary>
/// <returns>A temporary media file which can be persisted in a store</returns>
Task<Media> UploadFile(Stream fileStream, string filename, string folderId = null, CancellationToken cancellationToken = default);
Task<EntityResult<Media>> UploadFile(Stream fileStream, string filename, string folderId = null, CancellationToken cancellationToken = default);
}
-171
View File
@@ -1,171 +0,0 @@
using FluentValidation;
using Raven.Client.Documents;
using Raven.Client.Documents.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using zero.Core.Database.Indexes;
namespace zero.Core.Collections
{
public class MediaFolderCollection : EntityStore<MediaFolder>, IMediaFolderCollection
{
public MediaFolderCollection(IStoreContext<MediaFolder> context) : base(context)
{
Options = new(true);
}
/// <inheritdoc />
public override Task<EntityResult<MediaFolder>> Save(MediaFolder model)
{
model.IsActive = true;
return base.Save(model);
}
/// <inheritdoc />
public async Task<List<MediaFolder>> LoadByParent(string parentId = null)
{
return await Session.Query<MediaFolder>()
.WhereIf(x => x.ParentId == parentId, !parentId.IsNullOrEmpty(), x => x.ParentId == null)
.OrderByDescending(x => x.Name)
.ToListAsync();
}
/// <inheritdoc />
public async Task<EntityResult<MediaFolder>> Move(string id, string parentId)
{
MediaFolder model = await Load(id);
MediaFolder parent = await Load(parentId);
if (model == null || (!parentId.IsNullOrEmpty() && parent == null))
{
return EntityResult<MediaFolder>.Fail("@errors.idnotfound");
}
model.ParentId = parent?.Id;
return await Save(model);
}
/// <inheritdoc />
public async Task<List<TreeItem>> LoadAsTree(string parentId = null, string activeId = null)
{
List<TreeItem> items = new();
string[] openIds = Array.Empty<string>();
IList<MediaFolder> folders = await Session.Query<MediaFolder>()
.WhereIf(x => x.ParentId == parentId, !parentId.IsNullOrEmpty(), x => x.ParentId == null)
.OrderByDescending(x => x.CreatedDate).ThenBy(x => x.Name)
.ToListAsync();
// get hierarchy so we know if we should set the folder to open
if (!activeId.IsNullOrEmpty())
{
MediaFolder_ByHierarchy.Result result = await Session.Query<MediaFolder_ByHierarchy.Result, MediaFolder_ByHierarchy>()
.ProjectInto<MediaFolder_ByHierarchy.Result>()
.Include<MediaFolder_ByHierarchy.Result, MediaFolder>(x => x.Path.Select(p => p.Id))
.FirstOrDefaultAsync(x => x.Id == activeId);
if (result != null)
{
openIds = result.Path.Select(x => x.Id).ToArray();
}
}
// get children for all folders
string[] folderIds = folders.Select(x => x.Id).ToArray();
IList<MediaFolders_WithChildren.Result> children = await Session.Query<MediaFolders_WithChildren.Result, MediaFolders_WithChildren>()
.ProjectInto<MediaFolders_WithChildren.Result>()
.Where(x => x.Id.In(folderIds))
.ToListAsync();
foreach (MediaFolder folder in folders)
{
int childCount = children.Count(x => x.Id == folder.Id);
items.Add(new TreeItem()
{
Id = folder.Id,
Name = folder.Name,
HasChildren = childCount > 0,
ChildCount = childCount,
ParentId = folder.ParentId,
Sort = folder.Sort,
Icon = "fth-folder",
IsOpen = openIds.Contains(folder.Id),
IsInactive = !folder.IsActive,
HasActions = true,
Modifier = !folder.IsActive ? new TreeItemModifier()
{
Icon = "fth-minus-circle color-yellow",
Name = "Inactive"
} : null
});
}
return items;
}
/// <inheritdoc />
public async Task<List<MediaFolder>> GetHierarchy(string id)
{
MediaFolder_ByHierarchy.Result result = await Session.Query<MediaFolder_ByHierarchy.Result, MediaFolder_ByHierarchy>()
.ProjectInto<MediaFolder_ByHierarchy.Result>()
.Include<MediaFolder_ByHierarchy.Result, MediaFolder>(x => x.Path.Select(p => p.Id))
.FirstOrDefaultAsync(x => x.Id == id);
if (result == null)
{
return new List<MediaFolder>();
}
List<string> ids = result.Path.Select(x => x.Id).ToList();
ids.Add(id);
return (await Session.LoadAsync<MediaFolder>(ids)).Select(x => x.Value).ToList();
}
/// <inheritdoc />
protected override void ValidationRules(ZeroValidator<MediaFolder> validator)
{
validator.RuleFor(x => x.Name).Length(2, 80);
validator.RuleFor(x => x.IsActive).Equal(true);
validator.RuleFor(x => x.ParentId).Exists(Context.Store);
}
}
public interface IMediaFolderCollection : IEntityStore<MediaFolder>
{
/// <summary>
/// Get hierarchy for a folder
/// </summary>
Task<List<MediaFolder>> GetHierarchy(string id);
/// <summary>
/// Get all folders with the specified parent or on root
/// </summary>
Task<List<MediaFolder>> LoadByParent(string parentId = null);
/// <summary>
/// Get all folders with the specified parent or on root for tree output
/// </summary>
Task<List<TreeItem>> LoadAsTree(string parentId = null, string activeId = null);
/// <summary>
/// Move a folder to a new parent
/// </summary>
Task<EntityResult<MediaFolder>> Move(string id, string parentId);
}
}
+46 -2
View File
@@ -1,4 +1,6 @@
namespace zero.Media;
using System.IO;
namespace zero.Media;
public class MediaManagement : IMediaManagement
{
@@ -6,11 +8,34 @@ public class MediaManagement : IMediaManagement
protected IMediaStore Store { get; set; }
protected IMediaCreator Creator { get; set; }
public MediaManagement(IMediaFileSystem fileSystem, IMediaStore store)
public MediaManagement(IMediaFileSystem fileSystem, IMediaStore store, IMediaCreator creator)
{
FileSystem = fileSystem;
Store = store;
Creator = creator;
}
/// <inheritdoc />
public virtual string GetPublicFilePath(Media file, string thumbnailKey = null)
{
string path = file?.Path;
if (!thumbnailKey.IsNullOrWhiteSpace())
{
path = file?.Thumbnails?.GetValueOrDefault(thumbnailKey);
}
if (path.IsNullOrEmpty())
{
return null;
}
return FileSystem.MapToPublicPath(path);
}
@@ -38,6 +63,13 @@ public class MediaManagement : IMediaManagement
}
/// <inheritdoc />
public virtual async Task<EntityResult<Media>> UploadFile(Stream fileStream, string filename, string folderId = null, CancellationToken cancellationToken = default)
{
return await Creator.UploadFile(fileStream, filename, folderId, cancellationToken);
}
/// <inheritdoc />
public virtual async Task<Media> GetFolder(string id)
{
@@ -83,6 +115,18 @@ public class MediaManagement : IMediaManagement
public interface IMediaManagement
{
/// <summary>
/// Get publicly accessible file path for a media file
/// </summary>
/// <param name="file">The media file</param>
/// <param name="thumbnailKey">An optional thumbnail key which returns the path to a generated thumbnail</param>
string GetPublicFilePath(Media file, string thumbnailKey = null);
/// <summary>
/// Uploads a file and persists it
/// </summary>
Task<EntityResult<Media>> UploadFile(Stream fileStream, string filename, string folderId = null, CancellationToken cancellationToken = default);
/// <summary>
/// Get a media folder by id
/// </summary>
+26 -3
View File
@@ -1,7 +1,30 @@
namespace zero.Media
using Microsoft.AspNetCore.Http;
using System.IO;
namespace zero.Media
{
public static class MediaManagementExtensions
{
/// <summary>
/// Uploads a file and persists it
/// </summary>
public static async Task<EntityResult<Media>> UploadFile(this IMediaManagement media, IFormFile formFile, string folderId = null, CancellationToken cancellationToken = default)
{
using Stream stream = formFile.OpenReadStream();
return await media.UploadFile(stream, formFile.FileName, folderId, cancellationToken);
}
/// <summary>
/// Uploads a file and persists it
/// </summary>
public static async Task<EntityResult<Media>> UploadFile(this IMediaManagement media, byte[] fileBytes, string filename, string folderId = null, CancellationToken cancellationToken = default)
{
using Stream stream = new MemoryStream(fileBytes);
return await media.UploadFile(stream, filename, folderId, cancellationToken);
}
/// <summary>
/// Rename and store a media folder
/// </summary>
@@ -55,12 +78,12 @@
/// <summary>
/// Deletes a folder by id
/// </summary>
public static async Task<EntityResult<Media>> DeleteFolder(this IMediaManagement media, string folderId)
public static async Task<EntityResult<string[]>> DeleteFolder(this IMediaManagement media, string folderId)
{
Media folder = await media.GetFolder(folderId);
if (folder == null)
{
return EntityResult<Media>.Fail("@errors.idnotfound");
return EntityResult<string[]>.Fail("@errors.idnotfound");
}
return await media.DeleteFolder(folder);
+16 -1
View File
@@ -1,4 +1,6 @@
namespace zero.Media;
using FluentValidation;
namespace zero.Media;
public class MediaStore : TreeEntityStore<Media>, IMediaStore
{
@@ -22,6 +24,19 @@ public class MediaStore : TreeEntityStore<Media>, IMediaStore
Media parent = await Load(parentId);
return parent != null && parent.Type == MediaType.Folder;
}
/// <inheritdoc />
protected override void ValidationRules(ZeroValidator<Media> validator)
{
validator.RuleFor(x => x.Name).Length(2, 120);
validator.RuleFor(x => x.IsActive).Equal(true);
validator.When(x => x.Type == MediaType.Folder, () =>
{
validator.RuleFor(x => x.ParentId).Exists(Context.Store);
});
}
}
+5
View File
@@ -6,6 +6,11 @@
[RavenCollection("Media")]
public class Media : ZeroEntity, IZeroTreeEntity
{
public Media()
{
IsActive = true;
}
/// <summary>
/// Id/name of the phyiscal folder which is stored on disk/cloud
/// </summary>
-13
View File
@@ -1,13 +0,0 @@
namespace zero.Media;
/// <summary>
/// A media folder contains media and other folders
/// </summary>
[RavenCollection("MediaFolders")]
public class MediaFolder : ZeroEntity, IZeroTreeEntity
{
/// <summary>
/// Parent folder id
/// </summary>
public string ParentId { get; set; }
}
-14
View File
@@ -1,14 +0,0 @@
namespace zero.Media;
public class MediaListQuery : ListQuery<Media>
{
public string FolderId { get; set; }
}
public class MediaListItemQuery : ListQuery<MediaListItem>
{
public string FolderId { get; set; }
public bool SearchIsGlobal { get; set; }
}
@@ -11,10 +11,6 @@ internal static class ServiceCollectionExtensions
{
public static IServiceCollection AddZeroMedia(this IServiceCollection services, IConfiguration config)
{
services.AddScoped<IMediaStore, MediaStore>();
services.AddScoped<IMediaCreator, MediaCreator>();
services.AddScoped<IMediaManagement, MediaManagement>();
services.AddSingleton<IMediaFileSystem, MediaFileSystem>(svc =>
{
IOptions<MediaOptions> options = svc.GetRequiredService<IOptions<MediaOptions>>();
@@ -22,6 +18,10 @@ internal static class ServiceCollectionExtensions
return new(Path.Combine(env.WebRootPath, options.Value.FolderPath), options.Value.PublicPathPrefix);
});
services.AddScoped<IMediaStore, MediaStore>();
services.AddScoped<IMediaCreator, MediaCreator>();
services.AddScoped<IMediaManagement, MediaManagement>();
services.AddOptions<MediaOptions>().Bind(config.GetSection("Zero:Media")).Configure(opts =>
{
opts.FolderPath = "uploads";