From 57bae97ad795442f03706c3d3eedbf113f8c4532 Mon Sep 17 00:00:00 2001 From: Chris Collins <1-cirx08@users.noreply.gitlab.cirx08.com> Date: Thu, 19 Dec 2024 22:44:38 +0000 Subject: [PATCH] Prerel 1.4.0 --- .../Helpers/JsonResponseHelper.cs | 23 ++ WeddingShare.UnitTests/Helpers/MockData.cs | 72 +++++ .../Controllers/GalleryControllerTests.cs | 275 ++++++++++++++++++ .../Tests/Controllers/HomeControllerTests.cs | 11 +- .../Tests/Helpers/ImageHelper.cs | 3 +- .../BackgroundWorkers/DirectoryScanner.cs | 17 +- WeddingShare/Controllers/AdminController.cs | 121 +++----- WeddingShare/Controllers/GalleryController.cs | 59 ++-- WeddingShare/Controllers/HomeController.cs | 2 +- .../Helpers/Database/SQLiteDatabaseHelper.cs | 7 +- WeddingShare/Helpers/Dbup/DbupHelper.cs | 7 +- WeddingShare/Helpers/DeviceDetector.cs | 2 +- WeddingShare/Helpers/FileHelper.cs | 106 +++++++ WeddingShare/Helpers/ImageHelper.cs | 6 +- .../Models/Database/GalleryItemModel.cs | 14 + WeddingShare/Models/FileUploader.cs | 2 +- WeddingShare/Models/PhotoGallery.cs | 11 +- WeddingShare/Startup.cs | 1 + WeddingShare/Views/Admin/Index.cshtml | 28 +- WeddingShare/Views/Gallery/Index.cshtml | 6 +- .../Views/Gallery/Modes/Default.cshtml | 5 +- .../Views/Gallery/Modes/Slideshow.cshtml | 14 +- WeddingShare/Views/Shared/_FileUpload.cshtml | 26 +- WeddingShare/Views/Shared/_Layout.cshtml | 154 ++++++---- WeddingShare/appsettings.json | 6 +- WeddingShare/wwwroot/css/site.css | 20 +- WeddingShare/wwwroot/css/template.css | 37 ++- WeddingShare/wwwroot/css/themes/dark.css | 86 ++++++ WeddingShare/wwwroot/js/gallery.js | 2 + WeddingShare/wwwroot/js/site.js | 95 ++++++ readme.md | 2 + 31 files changed, 981 insertions(+), 239 deletions(-) create mode 100644 WeddingShare.UnitTests/Helpers/JsonResponseHelper.cs create mode 100644 WeddingShare.UnitTests/Helpers/MockData.cs create mode 100644 WeddingShare.UnitTests/Tests/Controllers/GalleryControllerTests.cs create mode 100644 WeddingShare/Helpers/FileHelper.cs create mode 100644 WeddingShare/wwwroot/css/themes/dark.css diff --git a/WeddingShare.UnitTests/Helpers/JsonResponseHelper.cs b/WeddingShare.UnitTests/Helpers/JsonResponseHelper.cs new file mode 100644 index 0000000..e6b42e1 --- /dev/null +++ b/WeddingShare.UnitTests/Helpers/JsonResponseHelper.cs @@ -0,0 +1,23 @@ +namespace WeddingShare.UnitTests.Helpers +{ + internal class JsonResponseHelper + { + public static T GetPropertyValue(object? obj, string propertyName, T defaultValue) + { + if (obj != null) + { + try + { + var val = obj?.GetType()?.GetProperty(propertyName)?.GetValue(obj, null); + if (val != null) + { + return (T)val; + } + } + catch { } + } + + return defaultValue; + } + } +} \ No newline at end of file diff --git a/WeddingShare.UnitTests/Helpers/MockData.cs b/WeddingShare.UnitTests/Helpers/MockData.cs new file mode 100644 index 0000000..6c3b4d2 --- /dev/null +++ b/WeddingShare.UnitTests/Helpers/MockData.cs @@ -0,0 +1,72 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Primitives; +using WeddingShare.Enums; +using WeddingShare.Models.Database; + +namespace WeddingShare.UnitTests.Helpers +{ + internal class MockData + { + public static DefaultHttpContext MockHttpContext(Dictionary? form = null, IFormFileCollection? files = null) + { + var ctx = new DefaultHttpContext() + { + Session = new MockSession() + }; + + ctx.Request.Form = new FormCollection(form, files); + + return ctx; + } + + public static List MockGalleryItems(int count = 10, int? galleryId = null, GalleryItemState state = GalleryItemState.All) + { + var result = new List(); + + for (var i = 0; i < count; i++) + { + result.Add(MockGalleryItem(galleryId, state)); + } + + return result; + } + + public static GalleryItemModel MockGalleryItem(int? galleryId = null, GalleryItemState state = GalleryItemState.All) + { + var rand = new Random(); + + return new GalleryItemModel() + { + Id = rand.Next(), + GalleryId = galleryId != null ? (int)galleryId : rand.Next(), + Title = $"{Guid.NewGuid()}.{MockFileExtension()}", + UploadedBy = rand.Next(2) % 2 == 0 ? Guid.NewGuid().ToString() : null, + State = state == GalleryItemState.All ? (GalleryItemState)rand.Next(2) : state, + }; + } + + public static string MockFileExtension() + { + var rand = new Random(); + + string extension; + switch (rand.Next(4)) + { + case 0: + extension = "jpg"; + break; + case 1: + extension = "jpeg"; + break; + case 2: + extension = "png"; + break; + default: + extension = "ffff"; + break; + } + + return rand.Next(2) % 2 == 0 ? extension.ToUpper() : extension.ToLower(); + } + } +} \ No newline at end of file diff --git a/WeddingShare.UnitTests/Tests/Controllers/GalleryControllerTests.cs b/WeddingShare.UnitTests/Tests/Controllers/GalleryControllerTests.cs new file mode 100644 index 0000000..7a9ef75 --- /dev/null +++ b/WeddingShare.UnitTests/Tests/Controllers/GalleryControllerTests.cs @@ -0,0 +1,275 @@ +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Localization; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Primitives; +using System.Net; +using System.Text.Json; +using WeddingShare.Controllers; +using WeddingShare.Enums; +using WeddingShare.Helpers; +using WeddingShare.Helpers.Database; +using WeddingShare.Models; +using WeddingShare.Models.Database; +using WeddingShare.UnitTests.Helpers; + +namespace WeddingShare.UnitTests.Tests.Helpers +{ + public class GalleryControllerTests + { + private readonly IWebHostEnvironment _env = Substitute.For(); + private readonly IConfigHelper _config = Substitute.For(); + private readonly ISecretKeyHelper _secretKey = Substitute.For(); + private readonly IDatabaseHelper _database = Substitute.For(); + private readonly IFileHelper _file = Substitute.For(); + private readonly IDeviceDetector _deviceDetector = Substitute.For(); + private readonly IImageHelper _image = Substitute.For(); + private readonly ILogger _logger = Substitute.For>(); + private readonly IStringLocalizer _localizer = Substitute.For>(); + + public GalleryControllerTests() + { + } + + [SetUp] + public void Setup() + { + _env.WebRootPath.Returns("/app/wwwroot"); + + _database.GetGallery("default").Returns(Task.FromResult(new GalleryModel() + { + Id = 1, + Name = "default", + SecretKey = "password", + ApprovedItems = 32, + PendingItems = 50, + TotalItems = 72 + })); + _database.GetGallery("missing").Returns(Task.FromResult(null)); + _database.AddGallery(Arg.Any()).Returns(Task.FromResult(new GalleryModel() + { + Id = 101, + Name = "missing", + SecretKey = "123456", + ApprovedItems = 0, + PendingItems = 0, + TotalItems = 0 + })); + _database.AddGalleryItem(Arg.Any()).Returns(Task.FromResult(MockData.MockGalleryItem())); + + _database.GetAllGalleryItems(Arg.Any(), GalleryItemState.All).Returns(Task.FromResult(MockData.MockGalleryItems(10, 1, GalleryItemState.All))); + _database.GetAllGalleryItems(Arg.Any(), GalleryItemState.Pending).Returns(Task.FromResult(MockData.MockGalleryItems(10, 1, GalleryItemState.Pending))); + _database.GetAllGalleryItems(Arg.Any(), GalleryItemState.Approved).Returns(Task.FromResult(MockData.MockGalleryItems(10, 1, GalleryItemState.Approved))); + + _secretKey.GetGallerySecretKey(Arg.Any()).Returns("password"); + _secretKey.GetGallerySecretKey("missing").Returns("123456"); + + _config.GetOrDefault("Settings", "Allowed_File_Types", Arg.Any()).Returns(".jpg,.jpeg,.png"); + _config.GetOrDefault("Settings", "Default_Gallery_View", Arg.Any()).Returns((int)ViewMode.Default); + _config.GetOrDefault("Settings", "Require_Review", Arg.Any()).Returns(true); + _config.GetOrDefault("Settings", "Max_File_Size_Mb", Arg.Any()).Returns(10); + + _localizer[Arg.Any()].Returns(new LocalizedString("UnitTest", "UnitTest")); + } + + [TestCase(DeviceType.Desktop, "default", "password", ViewMode.Default, GalleryOrder.None)] + [TestCase(DeviceType.Mobile, "blaa", "123456", ViewMode.Presentation, GalleryOrder.UploadedAsc)] + [TestCase(DeviceType.Tablet, "missing", "123456", ViewMode.Slideshow, GalleryOrder.NameAsc)] + public async Task GalleryController_Index(DeviceType deviceType, string id, string? key, ViewMode? mode, GalleryOrder order) + { + _deviceDetector.ParseDeviceType(Arg.Any()).Returns(deviceType); + _config.GetOrDefault("Settings", "Single_Gallery_Mode", Arg.Any()).Returns(false); + + var controller = new GalleryController(_env, _config, _database, _file, _secretKey, _deviceDetector, _image, _logger, _localizer); + controller.ControllerContext.HttpContext = MockData.MockHttpContext(); + + ViewResult actual = (ViewResult)await controller.Index(id, key, mode, order); + Assert.That(actual, Is.TypeOf()); + Assert.That(actual?.Model, Is.Not.Null); + + PhotoGallery model = (PhotoGallery)actual.Model; + Assert.That(model?.GalleryId, Is.EqualTo(id)); + Assert.That(model.GalleryPath, Is.EqualTo($"/uploads/{id}")); + Assert.That(model.ThumbnailsPath, Is.EqualTo($"/thumbnails")); + Assert.That(model.ViewMode, Is.EqualTo(mode)); + Assert.That(model?.FileUploader?.GalleryId, Is.EqualTo(id)); + Assert.That(model?.FileUploader?.SecretKey, Is.EqualTo(key)); + Assert.That(model?.FileUploader?.UploadUrl, Is.EqualTo("/Gallery/UploadImage")); + } + + [TestCase(DeviceType.Desktop, ViewMode.Default, GalleryOrder.None)] + [TestCase(DeviceType.Mobile, ViewMode.Presentation, GalleryOrder.UploadedAsc)] + [TestCase(DeviceType.Tablet, ViewMode.Slideshow, GalleryOrder.NameAsc)] + public async Task GalleryController_Index_SingleGalleryMode(DeviceType deviceType, ViewMode? mode, GalleryOrder order) + { + _deviceDetector.ParseDeviceType(Arg.Any()).Returns(deviceType); + _config.GetOrDefault("Settings", "Single_Gallery_Mode", Arg.Any()).Returns(true); + + var controller = new GalleryController(_env, _config, _database, _file, _secretKey, _deviceDetector, _image, _logger, _localizer); + controller.ControllerContext.HttpContext = MockData.MockHttpContext(); + + ViewResult actual = (ViewResult)await controller.Index("default", "password", mode, order); + Assert.That(actual, Is.TypeOf()); + Assert.That(actual?.Model, Is.Not.Null); + + PhotoGallery model = (PhotoGallery)actual.Model; + Assert.That(model?.GalleryId, Is.EqualTo("default")); + Assert.That(model.GalleryPath, Is.EqualTo($"/uploads/default")); + Assert.That(model.ThumbnailsPath, Is.EqualTo($"/thumbnails")); + Assert.That(model.ViewMode, Is.EqualTo(mode)); + Assert.That(model?.FileUploader?.GalleryId, Is.EqualTo("default")); + Assert.That(model?.FileUploader?.SecretKey, Is.EqualTo("password")); + Assert.That(model?.FileUploader?.UploadUrl, Is.EqualTo("/Gallery/UploadImage")); + } + + [TestCase(true, 1, null)] + [TestCase(true, 3, "Bob")] + [TestCase(false, 1, "")] + [TestCase(false, 3, "Unit Testing")] + public async Task GalleryController_UploadImage(bool requiresReview, int fileCount, string? uploadedBy) + { + _config.GetOrDefault("Settings", "Require_Review", Arg.Any()).Returns(requiresReview); + + var files = new FormFileCollection(); + for (var i = 0; i < fileCount; i++) + { + files.Add(new FormFile(null, 0, 0, "TestFile_001", $"{Guid.NewGuid()}.jpg")); + } + + var controller = new GalleryController(_env, _config, _database, _file, _secretKey, _deviceDetector, _image, _logger, _localizer); + controller.ControllerContext.HttpContext = MockData.MockHttpContext( + form: new Dictionary + { + { "Id", "default" }, + { "SecretKey", "password" }, + { "UploadedBy", uploadedBy } + }, + files: files); + + JsonResult actual = (JsonResult)await controller.UploadImage(); + Assert.That(actual, Is.TypeOf()); + Assert.That(actual?.Value, Is.Not.Null); + Assert.That(JsonResponseHelper.GetPropertyValue(actual.Value, "success", false), Is.True); + Assert.That(JsonResponseHelper.GetPropertyValue(actual.Value, "uploaded", 0), Is.EqualTo(files.Count)); + Assert.That(JsonResponseHelper.GetPropertyValue(actual.Value, "uploadedBy", string.Empty), Is.EqualTo(!string.IsNullOrWhiteSpace(uploadedBy) ? uploadedBy : string.Empty)); + Assert.That(JsonResponseHelper.GetPropertyValue(actual.Value, "errors", new List()).Count, Is.EqualTo(0)); + } + + [TestCase(null)] + [TestCase("")] + public async Task GalleryController_UploadImage_InvalidGallery(string? id) + { + var controller = new GalleryController(_env, _config, _database, _file, _secretKey, _deviceDetector, _image, _logger, _localizer); + controller.ControllerContext.HttpContext = MockData.MockHttpContext(form: new Dictionary + { + { "Id", id } + }); + + JsonResult actual = (JsonResult)await controller.UploadImage(); + Assert.That(actual, Is.TypeOf()); + Assert.That(actual?.Value, Is.Not.Null); + Assert.That(JsonResponseHelper.GetPropertyValue(actual.Value, "success", false), Is.False); + Assert.That(JsonResponseHelper.GetPropertyValue(actual.Value, "uploaded", 0), Is.EqualTo(0)); + Assert.That(JsonResponseHelper.GetPropertyValue(actual.Value, "errors", new List()).Count, Is.GreaterThan(0)); + } + + [TestCase(null)] + [TestCase("")] + public async Task GalleryController_UploadImage_InvalidSecretKey(string? key) + { + var controller = new GalleryController(_env, _config, _database, _file, _secretKey, _deviceDetector, _image, _logger, _localizer); + controller.ControllerContext.HttpContext = MockData.MockHttpContext(form: new Dictionary + { + { "Id", "default" }, + { "SecretKey", key } + }); + + JsonResult actual = (JsonResult)await controller.UploadImage(); + Assert.That(actual, Is.TypeOf()); + Assert.That(actual?.Value, Is.Not.Null); + Assert.That(JsonResponseHelper.GetPropertyValue(actual.Value, "success", false), Is.False); + Assert.That(JsonResponseHelper.GetPropertyValue(actual.Value, "uploaded", 0), Is.EqualTo(0)); + Assert.That(JsonResponseHelper.GetPropertyValue(actual.Value, "errors", new List()).Count, Is.GreaterThan(0)); + } + + [TestCase()] + public async Task GalleryController_UploadImage_MissingGallery() + { + var controller = new GalleryController(_env, _config, _database, _file, _secretKey, _deviceDetector, _image, _logger, _localizer); + controller.ControllerContext.HttpContext = MockData.MockHttpContext(form: new Dictionary + { + { "Id", Guid.NewGuid().ToString() } + }); + + JsonResult actual = (JsonResult)await controller.UploadImage(); + Assert.That(actual, Is.TypeOf()); + Assert.That(actual?.Value, Is.Not.Null); + Assert.That(JsonResponseHelper.GetPropertyValue(actual.Value, "success", false), Is.False); + Assert.That(JsonResponseHelper.GetPropertyValue(actual.Value, "uploaded", 0), Is.EqualTo(0)); + Assert.That(JsonResponseHelper.GetPropertyValue(actual.Value, "errors", new List()).Count, Is.GreaterThan(0)); + } + + [TestCase()] + public async Task GalleryController_UploadImage_NoFiles() + { + var controller = new GalleryController(_env, _config, _database, _file, _secretKey, _deviceDetector, _image, _logger, _localizer); + controller.ControllerContext.HttpContext = MockData.MockHttpContext(form: new Dictionary + { + { "Id", "default" }, + { "SecretKey", "password" } + }); + + JsonResult actual = (JsonResult)await controller.UploadImage(); + Assert.That(actual, Is.TypeOf()); + Assert.That(actual?.Value, Is.Not.Null); + Assert.That(JsonResponseHelper.GetPropertyValue(actual.Value, "success", false), Is.False); + Assert.That(JsonResponseHelper.GetPropertyValue(actual.Value, "uploaded", 0), Is.EqualTo(0)); + Assert.That(JsonResponseHelper.GetPropertyValue(actual.Value, "errors", new List()).Count, Is.GreaterThan(0)); + } + + [TestCase()] + public async Task GalleryController_UploadImage_FileTooBig() + { + var controller = new GalleryController(_env, _config, _database, _file, _secretKey, _deviceDetector, _image, _logger, _localizer); + controller.ControllerContext.HttpContext = MockData.MockHttpContext( + form: new Dictionary + { + { "Id", "default" }, + { "SecretKey", "password" } + }, + files: new FormFileCollection() { + new FormFile(null, 0, int.MaxValue, "TestFile_001", $"{Guid.NewGuid()}.jpg") + }); + + JsonResult actual = (JsonResult)await controller.UploadImage(); + Assert.That(actual, Is.TypeOf()); + Assert.That(actual?.Value, Is.Not.Null); + Assert.That(JsonResponseHelper.GetPropertyValue(actual.Value, "success", false), Is.False); + Assert.That(JsonResponseHelper.GetPropertyValue(actual.Value, "uploaded", 0), Is.EqualTo(0)); + Assert.That(JsonResponseHelper.GetPropertyValue(actual.Value, "errors", new List()).Count, Is.GreaterThan(0)); + } + + [TestCase()] + public async Task GalleryController_UploadImage_InvalidFileType() + { + var controller = new GalleryController(_env, _config, _database, _file, _secretKey, _deviceDetector, _image, _logger, _localizer); + controller.ControllerContext.HttpContext = MockData.MockHttpContext( + form: new Dictionary + { + { "Id", "default" }, + { "SecretKey", "password" } + }, + files: new FormFileCollection() { + new FormFile(null, 0, int.MaxValue, "TestFile_001", $"{Guid.NewGuid()}.blaa") + }); + + JsonResult actual = (JsonResult)await controller.UploadImage(); + Assert.That(actual, Is.TypeOf()); + Assert.That(actual?.Value, Is.Not.Null); + Assert.That(JsonResponseHelper.GetPropertyValue(actual.Value, "success", false), Is.False); + Assert.That(JsonResponseHelper.GetPropertyValue(actual.Value, "uploaded", 0), Is.EqualTo(0)); + Assert.That(JsonResponseHelper.GetPropertyValue(actual.Value, "errors", new List()).Count, Is.GreaterThan(0)); + } + } +} \ No newline at end of file diff --git a/WeddingShare.UnitTests/Tests/Controllers/HomeControllerTests.cs b/WeddingShare.UnitTests/Tests/Controllers/HomeControllerTests.cs index 42fbe77..2169f74 100644 --- a/WeddingShare.UnitTests/Tests/Controllers/HomeControllerTests.cs +++ b/WeddingShare.UnitTests/Tests/Controllers/HomeControllerTests.cs @@ -32,7 +32,7 @@ public void Setup() [TestCase(DeviceType.Desktop, false, "Abc123!", false)] [TestCase(DeviceType.Mobile, true, "abc123!", false)] [TestCase(DeviceType.Mobile, false, "adsbsds", false)] - public async Task HomeController_Index_ViewResult(DeviceType deviceType, bool singleGalleryMode, string secretKey, bool isRedirect) + public async Task HomeController_Index(DeviceType deviceType, bool singleGalleryMode, string secretKey, bool isRedirect) { _deviceDetector.ParseDeviceType(Arg.Any()).Returns(deviceType); _config.GetOrDefault("Settings", "Single_Gallery_Mode", Arg.Any()).Returns(singleGalleryMode); @@ -44,15 +44,20 @@ public async Task HomeController_Index_ViewResult(DeviceType deviceType, bool si Session = new MockSession() }; - var actual = await controller.Index(); - if (!isRedirect) { + ViewResult actual = (ViewResult)await controller.Index(); Assert.That(actual, Is.TypeOf()); } else { + RedirectToActionResult actual = (RedirectToActionResult)await controller.Index(); Assert.That(actual, Is.TypeOf()); + Assert.That(actual.Permanent, Is.EqualTo(false)); + Assert.That(actual.ControllerName, Is.EqualTo("Gallery")); + Assert.That(actual.ActionName, Is.EqualTo("Index")); + Assert.That(actual.RouteValues, Is.Null); + Assert.That(actual.Fragment, Is.Null); } } } diff --git a/WeddingShare.UnitTests/Tests/Helpers/ImageHelper.cs b/WeddingShare.UnitTests/Tests/Helpers/ImageHelper.cs index 5461821..65b9608 100644 --- a/WeddingShare.UnitTests/Tests/Helpers/ImageHelper.cs +++ b/WeddingShare.UnitTests/Tests/Helpers/ImageHelper.cs @@ -8,6 +8,7 @@ namespace WeddingShare.UnitTests.Tests.Helpers { public class ImageHelperTests { + private readonly IFileHelper _fileHelper = Substitute.For(); private readonly ILogger _logger = Substitute.For>(); private readonly IDictionary _imageCollection; @@ -34,7 +35,7 @@ public void ImageHelper_GetOrientation(ImageOrientation orientation) var image = _imageCollection[orientation]; Assert.IsNotNull(image); - var actual = new ImageHelper(_logger).GetOrientation(image); + var actual = new ImageHelper(_fileHelper, _logger).GetOrientation(image); Assert.That(actual, Is.EqualTo(orientation)); } } diff --git a/WeddingShare/BackgroundWorkers/DirectoryScanner.cs b/WeddingShare/BackgroundWorkers/DirectoryScanner.cs index ef1d39e..9e692e2 100644 --- a/WeddingShare/BackgroundWorkers/DirectoryScanner.cs +++ b/WeddingShare/BackgroundWorkers/DirectoryScanner.cs @@ -8,7 +8,7 @@ namespace WeddingShare.BackgroundWorkers { - public sealed class DirectoryScanner(IWebHostEnvironment hostingEnvironment, IConfigHelper configHelper, IDatabaseHelper databaseHelper, IImageHelper imageHelper, ILogger logger) : BackgroundService + public sealed class DirectoryScanner(IWebHostEnvironment hostingEnvironment, IConfigHelper configHelper, IDatabaseHelper databaseHelper, IFileHelper fileHelper, IImageHelper imageHelper, ILogger logger) : BackgroundService { protected override async Task ExecuteAsync(CancellationToken stoppingToken) { @@ -36,16 +36,13 @@ await Task.Run(async () => var allowedFileTypes = configHelper.GetOrDefault("Settings", "Allowed_File_Types", ".jpg,.jpeg,.png").Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries); var thumbnailsDirectory = Path.Combine(hostingEnvironment.WebRootPath, "thumbnails"); - if (!Directory.Exists(thumbnailsDirectory)) - { - Directory.CreateDirectory(thumbnailsDirectory); - } + fileHelper.CreateDirectoryIfNotExists(thumbnailsDirectory); var uploadsDirectory = Path.Combine(hostingEnvironment.WebRootPath, "uploads"); - if (Directory.Exists(uploadsDirectory)) + if (fileHelper.DirectoryExists(uploadsDirectory)) { var searchPattern = !configHelper.GetOrDefault("Settings", "Single_Gallery_Mode", false) ? "*" : "default"; - var galleries = Directory.GetDirectories(uploadsDirectory, searchPattern, SearchOption.TopDirectoryOnly)?.Where(x => !Path.GetFileName(x).StartsWith(".")); + var galleries = fileHelper.GetDirectories(uploadsDirectory, searchPattern, SearchOption.TopDirectoryOnly)?.Where(x => !Path.GetFileName(x).StartsWith(".")); if (galleries != null) { foreach (var gallery in galleries) @@ -68,7 +65,7 @@ await Task.Run(async () => if (Path.Exists(gallery)) { - var approvedFiles = Directory.GetFiles(gallery, "*.*", SearchOption.TopDirectoryOnly).Where(x => allowedFileTypes.Any(y => string.Equals(Path.GetExtension(x).Trim('.'), y.Trim('.'), StringComparison.OrdinalIgnoreCase))); + var approvedFiles = fileHelper.GetFiles(gallery, "*.*", SearchOption.TopDirectoryOnly).Where(x => allowedFileTypes.Any(y => string.Equals(Path.GetExtension(x).Trim('.'), y.Trim('.'), StringComparison.OrdinalIgnoreCase))); if (approvedFiles != null) { foreach (var file in approvedFiles) @@ -87,7 +84,7 @@ await databaseHelper.AddGalleryItem(new GalleryItemModel() } var thumbnailPath = Path.Combine(thumbnailsDirectory, $"{Path.GetFileNameWithoutExtension(file)}.webp"); - if (!File.Exists(thumbnailPath)) + if (!fileHelper.FileExists(thumbnailPath)) { await imageHelper.GenerateThumbnail(file, thumbnailPath, configHelper.GetOrDefault("Settings", "Thumbnail_Size", 720)); } @@ -115,7 +112,7 @@ await databaseHelper.AddGalleryItem(new GalleryItemModel() if (Path.Exists(Path.Combine(gallery, "Pending"))) { - var pendingFiles = Directory.GetFiles(Path.Combine(gallery, "Pending"), "*.*", SearchOption.TopDirectoryOnly).Where(x => allowedFileTypes.Any(y => string.Equals(Path.GetExtension(x).Trim('.'), y.Trim('.'), StringComparison.OrdinalIgnoreCase))); + var pendingFiles = fileHelper.GetFiles(Path.Combine(gallery, "Pending"), "*.*", SearchOption.TopDirectoryOnly).Where(x => allowedFileTypes.Any(y => string.Equals(Path.GetExtension(x).Trim('.'), y.Trim('.'), StringComparison.OrdinalIgnoreCase))); if (pendingFiles != null) { foreach (var file in pendingFiles) diff --git a/WeddingShare/Controllers/AdminController.cs b/WeddingShare/Controllers/AdminController.cs index 25b9953..63f45af 100644 --- a/WeddingShare/Controllers/AdminController.cs +++ b/WeddingShare/Controllers/AdminController.cs @@ -22,6 +22,7 @@ public class AdminController : Controller private readonly IConfigHelper _config; private readonly IDatabaseHelper _database; private readonly IDeviceDetector _deviceDetector; + private readonly IFileHelper _fileHelper; private readonly IImageHelper _imageHelper; private readonly ILogger _logger; private readonly IStringLocalizer _localizer; @@ -29,12 +30,13 @@ public class AdminController : Controller private readonly string UploadsDirectory; private readonly string ThumbnailsDirectory; - public AdminController(IWebHostEnvironment hostingEnvironment, IConfigHelper config, IDatabaseHelper database, IDeviceDetector deviceDetector, IImageHelper imageHelper, ILogger logger, IStringLocalizer localizer) + public AdminController(IWebHostEnvironment hostingEnvironment, IConfigHelper config, IDatabaseHelper database, IDeviceDetector deviceDetector, IFileHelper fileHelper, IImageHelper imageHelper, ILogger logger, IStringLocalizer localizer) { _hostingEnvironment = hostingEnvironment; _config = config; _database = database; _deviceDetector = deviceDetector; + _fileHelper = fileHelper; _imageHelper = imageHelper; _logger = logger; _localizer = localizer; @@ -148,28 +150,18 @@ public async Task ReviewPhoto(int id, ReviewAction action) var reviewFile = Path.Combine(galleryDir, "Pending", review.Title); if (action == ReviewAction.APPROVED) { - if (!Directory.Exists(ThumbnailsDirectory)) - { - Directory.CreateDirectory(ThumbnailsDirectory); - } + _fileHelper.CreateDirectoryIfNotExists(ThumbnailsDirectory); await _imageHelper.GenerateThumbnail(reviewFile, Path.Combine(ThumbnailsDirectory, $"{Path.GetFileNameWithoutExtension(reviewFile)}.webp"), _config.GetOrDefault("Settings", "Thumbnail_Size", 720)); - if (System.IO.File.Exists(reviewFile)) - { - System.IO.File.Move(reviewFile, Path.Combine(galleryDir, review.Title)); - } + _fileHelper.MoveFileIfExists(reviewFile, Path.Combine(galleryDir, review.Title)); review.State = GalleryItemState.Approved; await _database.EditGalleryItem(review); } else if (action == ReviewAction.REJECTED) { - if (System.IO.File.Exists(reviewFile)) - { - System.IO.File.Delete(reviewFile); - } - + _fileHelper.DeleteFileIfExists(reviewFile); await _database.DeleteGalleryItem(review); } else if (action == ReviewAction.UNKNOWN) @@ -281,19 +273,16 @@ public async Task WipeGallery(int id) if (gallery != null) { var galleryDir = Path.Combine(UploadsDirectory, gallery.Name); - if (Directory.Exists(galleryDir)) + if (_fileHelper.DirectoryExists(galleryDir)) { - foreach (var photo in Directory.GetFiles(galleryDir, "*.*", SearchOption.AllDirectories)) + foreach (var photo in _fileHelper.GetFiles(galleryDir, "*.*", SearchOption.AllDirectories)) { var thumbnail = Path.Combine(ThumbnailsDirectory, $"{Path.GetFileNameWithoutExtension(photo)}.webp"); - if (System.IO.File.Exists(thumbnail)) - { - System.IO.File.Delete(thumbnail); - } + _fileHelper.DeleteFileIfExists(thumbnail); } - Directory.Delete(galleryDir, true); - Directory.CreateDirectory(galleryDir); + _fileHelper.DeleteDirectoryIfExists(galleryDir); + _fileHelper.CreateDirectoryIfNotExists(galleryDir); } return Json(new { success = await _database.WipeGallery(gallery) }); @@ -319,19 +308,19 @@ public async Task WipeAllGalleries() { try { - if (Directory.Exists(UploadsDirectory)) + if (_fileHelper.DirectoryExists(UploadsDirectory)) { - foreach (var gallery in Directory.GetDirectories(UploadsDirectory, "*", SearchOption.TopDirectoryOnly)) + foreach (var gallery in _fileHelper.GetDirectories(UploadsDirectory, "*", SearchOption.TopDirectoryOnly)) { - Directory.Delete(gallery, true); + _fileHelper.DeleteDirectoryIfExists(gallery); } - foreach (var thumbnail in Directory.GetFiles(ThumbnailsDirectory, "*.*", SearchOption.AllDirectories)) + foreach (var thumbnail in _fileHelper.GetFiles(ThumbnailsDirectory, "*.*", SearchOption.AllDirectories)) { - System.IO.File.Delete(thumbnail); + _fileHelper.DeleteFileIfExists(thumbnail); } - Directory.CreateDirectory(Path.Combine(UploadsDirectory, "default")); + _fileHelper.CreateDirectoryIfNotExists(Path.Combine(UploadsDirectory, "default")); } return Json(new { success = await _database.WipeAllGalleries() }); @@ -356,10 +345,7 @@ public async Task DeleteGallery(int id) if (gallery != null) { var galleryDir = Path.Combine(UploadsDirectory, gallery.Name); - if (Directory.Exists(galleryDir)) - { - Directory.Delete(galleryDir, true); - } + _fileHelper.DeleteDirectoryIfExists(galleryDir); return Json(new { success = await _database.DeleteGallery(gallery) }); } @@ -391,10 +377,7 @@ public async Task DeletePhoto(int id) if (gallery != null) { var photoPath = Path.Combine(UploadsDirectory, gallery.Name, photo.Title); - if (System.IO.File.Exists(photoPath)) - { - System.IO.File.Delete(photoPath); - } + _fileHelper.DeleteFileIfExists(photoPath); return Json(new { success = await _database.DeleteGalleryItem(photo) }); } @@ -424,19 +407,16 @@ public async Task DownloadGallery(int id) if (gallery != null) { var galleryDir = Path.Combine(UploadsDirectory, gallery.Name); - if (Directory.Exists(galleryDir)) + if (_fileHelper.DirectoryExists(galleryDir)) { var tempZipDir = $"Temp"; - if (!Directory.Exists(tempZipDir)) - { - Directory.CreateDirectory(tempZipDir); - } + _fileHelper.CreateDirectoryIfNotExists(tempZipDir); var tempZipFile = Path.Combine(tempZipDir, $"{gallery.Name}-{DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss")}.zip"); ZipFile.CreateFromDirectory(galleryDir, tempZipFile, CompressionLevel.Optimal, false); - byte[] bytes = System.IO.File.ReadAllBytes(tempZipFile); - System.IO.File.Delete(tempZipFile); + byte[] bytes = await _fileHelper.ReadAllBytes(tempZipFile); + _fileHelper.DeleteFileIfExists(tempZipFile); return Json(new { success = true, filename = Path.GetFileName(tempZipFile), content = Convert.ToBase64String(bytes, 0, bytes.Length) }); } @@ -465,19 +445,11 @@ public async Task ExportBackup() try { - if (Directory.Exists(UploadsDirectory)) + if (_fileHelper.DirectoryExists(UploadsDirectory)) { - if (!Directory.Exists(tempDir)) - { - Directory.CreateDirectory(tempDir); - } - - if (Directory.Exists(exportDir)) - { - Directory.Delete(exportDir, true); - } - - Directory.CreateDirectory(exportDir); + _fileHelper.CreateDirectoryIfNotExists(tempDir); + _fileHelper.DeleteDirectoryIfExists(exportDir); + _fileHelper.CreateDirectoryIfNotExists(exportDir); var dbExport = Path.Combine(exportDir, $"WeddingShare.bak"); var exported = await _database.Export($"Data Source={dbExport}"); @@ -490,18 +462,15 @@ public async Task ExportBackup() ZipFile.CreateFromDirectory(ThumbnailsDirectory, thumbnailsZip, CompressionLevel.Optimal, false); var exportZipFile = Path.Combine(tempDir, $"WeddingShare-{DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss")}.zip"); - if (System.IO.File.Exists(exportZipFile)) - { - System.IO.File.Delete(exportZipFile); - } + _fileHelper.DeleteFileIfExists(exportZipFile); ZipFile.CreateFromDirectory(exportDir, exportZipFile, CompressionLevel.Optimal, false); - System.IO.File.Delete(dbExport); - System.IO.File.Delete(uploadsZip); - System.IO.File.Delete(thumbnailsZip); + _fileHelper.DeleteFileIfExists(dbExport); + _fileHelper.DeleteFileIfExists(uploadsZip); + _fileHelper.DeleteFileIfExists(thumbnailsZip); - byte[] bytes = System.IO.File.ReadAllBytes(exportZipFile); - System.IO.File.Delete(exportZipFile); + byte[] bytes = await _fileHelper.ReadAllBytes(exportZipFile); + _fileHelper.DeleteFileIfExists(exportZipFile); return Json(new { success = true, filename = Path.GetFileName(exportZipFile), content = Convert.ToBase64String(bytes, 0, bytes.Length) }); } @@ -513,7 +482,7 @@ public async Task ExportBackup() } finally { - Directory.Delete(exportDir, true); + _fileHelper.DeleteDirectoryIfExists(exportDir); } } @@ -538,28 +507,18 @@ public async Task ImportBackup() var extension = Path.GetExtension(file.FileName)?.Trim('.'); if (string.Equals("zip", extension, StringComparison.OrdinalIgnoreCase)) { - if (!Directory.Exists(tempDir)) - { - Directory.CreateDirectory(tempDir); - } + _fileHelper.CreateDirectoryIfNotExists(tempDir); var filePath = Path.Combine(tempDir, "Import.zip"); if (!string.IsNullOrWhiteSpace(filePath)) { - using (var fs = new FileStream(filePath, FileMode.Create)) - { - await file.CopyToAsync(fs); - } - - if (Directory.Exists(importDir)) - { - Directory.Delete(importDir, true); - } + await _fileHelper.SaveFile(file, filePath, FileMode.Create); - Directory.CreateDirectory(importDir); + _fileHelper.DeleteDirectoryIfExists(importDir); + _fileHelper.CreateDirectoryIfNotExists(importDir); ZipFile.ExtractToDirectory(filePath, importDir, true); - System.IO.File.Delete(filePath); + _fileHelper.DeleteFileIfExists(filePath); var uploadsZip = Path.Combine(importDir, "Uploads.bak"); ZipFile.ExtractToDirectory(uploadsZip, UploadsDirectory, true); @@ -582,7 +541,7 @@ public async Task ImportBackup() } finally { - Directory.Delete(importDir, true); + _fileHelper.DeleteDirectoryIfExists(importDir); } } diff --git a/WeddingShare/Controllers/GalleryController.cs b/WeddingShare/Controllers/GalleryController.cs index 56b9d0c..69df375 100644 --- a/WeddingShare/Controllers/GalleryController.cs +++ b/WeddingShare/Controllers/GalleryController.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Localization; +using System.Net; using WeddingShare.Attributes; using WeddingShare.Enums; using WeddingShare.Extensions; @@ -17,6 +18,7 @@ public class GalleryController : Controller private readonly IWebHostEnvironment _hostingEnvironment; private readonly IConfigHelper _config; private readonly IDatabaseHelper _database; + private readonly IFileHelper _fileHelper; private readonly ISecretKeyHelper _secretKey; private readonly IDeviceDetector _deviceDetector; private readonly IImageHelper _imageHelper; @@ -26,11 +28,12 @@ public class GalleryController : Controller private readonly string UploadsDirectory; private readonly string ThumbnailsDirectory; - public GalleryController(IWebHostEnvironment hostingEnvironment, IConfigHelper config, IDatabaseHelper database, ISecretKeyHelper secretKey, IDeviceDetector deviceDetector, IImageHelper imageHelper, ILogger logger, IStringLocalizer localizer) + public GalleryController(IWebHostEnvironment hostingEnvironment, IConfigHelper config, IDatabaseHelper database, IFileHelper fileHelper, ISecretKeyHelper secretKey, IDeviceDetector deviceDetector, IImageHelper imageHelper, ILogger logger, IStringLocalizer localizer) { _hostingEnvironment = hostingEnvironment; _config = config; _database = database; + _fileHelper = fileHelper; _secretKey = secretKey; _deviceDetector = deviceDetector; _imageHelper = imageHelper; @@ -47,12 +50,8 @@ public GalleryController(IWebHostEnvironment hostingEnvironment, IConfigHelper c [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] public async Task Index(string id = "default", string? key = null, ViewMode? mode = null, GalleryOrder order = GalleryOrder.None) { - id = id.ToLower(); - if (string.IsNullOrWhiteSpace(id) || _config.GetOrDefault("Settings", "Single_Gallery_Mode", false)) - { - id = "default"; - } - + id = (!string.IsNullOrWhiteSpace(id) && !_config.GetOrDefault("Settings", "Single_Gallery_Mode", false)) ? id.ToLower() : "default"; + try { ViewBag.ViewMode = mode ?? (ViewMode)_config.GetOrDefault("Settings", "Default_Gallery_View", (int)ViewMode.Default); @@ -72,11 +71,8 @@ public async Task Index(string id = "default", string? key = null ViewBag.IsMobile = !string.Equals("Desktop", deviceType, StringComparison.OrdinalIgnoreCase); var galleryPath = Path.Combine(UploadsDirectory, id); - if (!Directory.Exists(galleryPath)) - { - Directory.CreateDirectory(galleryPath); - Directory.CreateDirectory(Path.Combine(galleryPath, "Pending")); - } + _fileHelper.CreateDirectoryIfNotExists(galleryPath); + _fileHelper.CreateDirectoryIfNotExists(Path.Combine(galleryPath, "Pending")); GalleryModel? gallery = await _database.GetGallery(id); if (gallery == null) @@ -114,14 +110,15 @@ public async Task Index(string id = "default", string? key = null break; } - var model = new PhotoGallery(_config.GetOrDefault("Settings", "Gallery_Columns", 4), (ViewMode)ViewBag.ViewMode) + var model = new PhotoGallery() { GalleryId = id, GalleryPath = $"/{galleryPath.Remove(_hostingEnvironment.WebRootPath).Replace('\\', '/').TrimStart('/')}", ThumbnailsPath = $"/{ThumbnailsDirectory.Remove(_hostingEnvironment.WebRootPath).Replace('\\', '/').TrimStart('/')}", - Images = images?.Select(x => new PhotoGalleryImage() { Id = x.Id, Name = Path.GetFileName(x.Title), Path = x.Title })?.ToList(), + Images = images?.Select(x => new PhotoGalleryImage() { Id = x.Id, Name = Path.GetFileName(x.Title), Path = x.Title, UploadedBy = x.UploadedBy })?.ToList(), PendingCount = gallery?.PendingItems ?? 0, - FileUploader = !_config.GetOrDefault("Settings", "Disable_Upload", false) || (User?.Identity != null && User.Identity.IsAuthenticated) ? new FileUploader(id, secretKey, "/Gallery/UploadImage") : null + FileUploader = !_config.GetOrDefault("Settings", "Disable_Upload", false) || (User?.Identity != null && User.Identity.IsAuthenticated) ? new FileUploader(id, secretKey, "/Gallery/UploadImage") : null, + ViewMode = (ViewMode)ViewBag.ViewMode }; return View(model); @@ -133,14 +130,16 @@ public async Task Index(string id = "default", string? key = null [HttpPost] public async Task UploadImage() { + Response.StatusCode = (int)HttpStatusCode.BadRequest; + try { string galleryId = (Request?.Form?.FirstOrDefault(x => string.Equals("Id", x.Key, StringComparison.OrdinalIgnoreCase)).Value)?.ToString()?.ToLower() ?? string.Empty; if (string.IsNullOrWhiteSpace(galleryId)) { - return Json(new { success = true, uploaded = 0, errors = new List() { _localizer["Invalid_Gallery_Id"].Value } }); + return Json(new { success = false, uploaded = 0, errors = new List() { _localizer["Invalid_Gallery_Id"].Value } }); } - + var gallery = await _database.GetGallery(galleryId); if (gallery != null) { @@ -148,9 +147,11 @@ public async Task UploadImage() string key = (Request?.Form?.FirstOrDefault(x => string.Equals("SecretKey", x.Key, StringComparison.OrdinalIgnoreCase)).Value)?.ToString() ?? string.Empty; if (!string.IsNullOrWhiteSpace(secretKey) && !string.Equals(secretKey, key)) { - return Json(new { success = true, uploaded = 0, errors = new List() { _localizer["Invalid_Secret_Key_Warning"].Value } }); + return Json(new { success = false, uploaded = 0, errors = new List() { _localizer["Invalid_Secret_Key_Warning"].Value } }); } + string uploadedBy = (Request?.Form?.FirstOrDefault(x => string.Equals("UploadedBy", x.Key, StringComparison.OrdinalIgnoreCase)).Value)?.ToString() ?? string.Empty; + var files = Request?.Form?.Files; if (files != null && files.Count > 0) { @@ -178,26 +179,17 @@ public async Task UploadImage() { var fileName = $"{Guid.NewGuid()}{Path.GetExtension(file.FileName)}"; var galleryPath = requiresReview ? Path.Combine(UploadsDirectory, gallery.Name, "Pending") : Path.Combine(UploadsDirectory, gallery.Name); - if (!Directory.Exists(galleryPath)) - { - Directory.CreateDirectory(galleryPath); - } + + _fileHelper.CreateDirectoryIfNotExists(galleryPath); var filePath = Path.Combine(galleryPath, fileName); if (!string.IsNullOrWhiteSpace(filePath)) { - using (var fs = new FileStream(filePath, FileMode.Create)) - { - await file.CopyToAsync(fs); - } + await _fileHelper.SaveFile(file, filePath, FileMode.Create); if (!requiresReview) { - if (!Directory.Exists(ThumbnailsDirectory)) - { - Directory.CreateDirectory(ThumbnailsDirectory); - } - + _fileHelper.CreateDirectoryIfNotExists(ThumbnailsDirectory); await _imageHelper.GenerateThumbnail(filePath, Path.Combine(ThumbnailsDirectory, $"{Path.GetFileNameWithoutExtension(filePath)}.webp"), _config.GetOrDefault("Settings", "Thumbnail_Size", 720)); } @@ -205,6 +197,7 @@ public async Task UploadImage() { GalleryId = gallery.Id, Title = fileName, + UploadedBy = uploadedBy, State = requiresReview ? GalleryItemState.Pending : GalleryItemState.Approved }); @@ -221,7 +214,9 @@ public async Task UploadImage() } } - return Json(new { success = true, uploaded, requiresReview, errors }); + Response.StatusCode = (int)HttpStatusCode.OK; + + return Json(new { success = uploaded > 0, uploaded, uploadedBy, requiresReview, errors }); } else { diff --git a/WeddingShare/Controllers/HomeController.cs b/WeddingShare/Controllers/HomeController.cs index f7c6e81..53329e5 100644 --- a/WeddingShare/Controllers/HomeController.cs +++ b/WeddingShare/Controllers/HomeController.cs @@ -35,7 +35,7 @@ public async Task Index() if (_config.GetOrDefault("Settings", "Single_Gallery_Mode", false)) { var key = await _secretKey.GetGallerySecretKey("default"); - if (string.IsNullOrEmpty(key)) + if (string.IsNullOrWhiteSpace(key)) { return RedirectToAction("Index", "Gallery"); } diff --git a/WeddingShare/Helpers/Database/SQLiteDatabaseHelper.cs b/WeddingShare/Helpers/Database/SQLiteDatabaseHelper.cs index 3997446..6510251 100644 --- a/WeddingShare/Helpers/Database/SQLiteDatabaseHelper.cs +++ b/WeddingShare/Helpers/Database/SQLiteDatabaseHelper.cs @@ -240,7 +240,6 @@ public async Task> GetAllGalleryItems(int galleryId, Gall cmd.Parameters.AddWithValue("Id", galleryId); cmd.Parameters.AddWithValue("State", state); - await conn.OpenAsync(); result = await ReadGalleryItems(await cmd.ExecuteReaderAsync()); await conn.CloseAsync(); @@ -329,11 +328,12 @@ public async Task GetPendingGalleryItemCount(int? galleryId = null) using (var conn = new SqliteConnection(_connString)) { - var cmd = new SqliteCommand($"INSERT INTO `gallery_items` (`gallery_id`, `title`, `state`) VALUES (@GalleryId, @Title, @State); SELECT * FROM `gallery_items` WHERE `id`=last_insert_rowid();", conn); + var cmd = new SqliteCommand($"INSERT INTO `gallery_items` (`gallery_id`, `title`, `state`, `uploaded_by`) VALUES (@GalleryId, @Title, @State, @UploadedBy); SELECT * FROM `gallery_items` WHERE `id`=last_insert_rowid();", conn); cmd.CommandType = CommandType.Text; cmd.Parameters.AddWithValue("GalleryId", model.GalleryId); cmd.Parameters.AddWithValue("Title", model.Title); cmd.Parameters.AddWithValue("State", (int)model.State); + cmd.Parameters.AddWithValue("UploadedBy", !string.IsNullOrWhiteSpace(model.UploadedBy) ? model.UploadedBy : DBNull.Value); await conn.OpenAsync(); var tran = await conn.BeginTransactionAsync(); @@ -360,11 +360,12 @@ public async Task GetPendingGalleryItemCount(int? galleryId = null) using (var conn = new SqliteConnection(_connString)) { - var cmd = new SqliteCommand($"UPDATE `gallery_items` SET `title`=@Title, `state`=@State WHERE `id`=@Id; SELECT * FROM `gallery_items` WHERE `id`=@Id;", conn); + var cmd = new SqliteCommand($"UPDATE `gallery_items` SET `title`=@Title, `state`=@State, `uploaded_by`=@UploadedBy WHERE `id`=@Id; SELECT * FROM `gallery_items` WHERE `id`=@Id;", conn); cmd.CommandType = CommandType.Text; cmd.Parameters.AddWithValue("Id", model.Id); cmd.Parameters.AddWithValue("Title", model.Title); cmd.Parameters.AddWithValue("State", (int)model.State); + cmd.Parameters.AddWithValue("UploadedBy", !string.IsNullOrWhiteSpace(model.UploadedBy) ? model.UploadedBy : DBNull.Value); await conn.OpenAsync(); var tran = await conn.BeginTransactionAsync(); diff --git a/WeddingShare/Helpers/Dbup/DbupHelper.cs b/WeddingShare/Helpers/Dbup/DbupHelper.cs index 2f72225..af6c118 100644 --- a/WeddingShare/Helpers/Dbup/DbupHelper.cs +++ b/WeddingShare/Helpers/Dbup/DbupHelper.cs @@ -5,7 +5,7 @@ namespace WeddingShare.Helpers.Dbup { - public sealed class DbupMigrator(IEnvironmentWrapper environment, IConfiguration configuration, ILoggerFactory loggerFactory) : BackgroundService + public sealed class DbupMigrator(IEnvironmentWrapper environment, IConfiguration configuration, IFileHelper fileHelper, ILoggerFactory loggerFactory) : BackgroundService { protected override async Task ExecuteAsync(CancellationToken stoppingToken) { @@ -13,10 +13,7 @@ await Task.Run(() => { var logger = loggerFactory.CreateLogger(); - if (!Directory.Exists("config")) - { - Directory.CreateDirectory("config"); - } + fileHelper.CreateDirectoryIfNotExists("config"); var config = new ConfigHelper(environment, configuration, loggerFactory.CreateLogger()); var connString = config.GetOrDefault("Database", "Connection_String", "Data Source=./config/wedding-share.db"); diff --git a/WeddingShare/Helpers/DeviceDetector.cs b/WeddingShare/Helpers/DeviceDetector.cs index 2c81478..dffba6e 100644 --- a/WeddingShare/Helpers/DeviceDetector.cs +++ b/WeddingShare/Helpers/DeviceDetector.cs @@ -14,7 +14,7 @@ public async Task ParseDeviceType(string userAgent) { return await Task.Run(() => { - if (string.IsNullOrEmpty(userAgent)) + if (string.IsNullOrWhiteSpace(userAgent)) { return DeviceType.Unknown; } diff --git a/WeddingShare/Helpers/FileHelper.cs b/WeddingShare/Helpers/FileHelper.cs new file mode 100644 index 0000000..6f3466f --- /dev/null +++ b/WeddingShare/Helpers/FileHelper.cs @@ -0,0 +1,106 @@ +using WeddingShare.Helpers.Database; + +namespace WeddingShare.Helpers +{ + public interface IFileHelper + { + bool DirectoryExists(string path); + bool CreateDirectoryIfNotExists(string path); + bool DeleteDirectoryIfExists(string path, bool recursive = true); + string[] GetDirectories(string path, string pattern = "*", SearchOption searchOption = SearchOption.AllDirectories); + string[] GetFiles(string path, string pattern = "*.*", SearchOption searchOption = SearchOption.AllDirectories); + bool FileExists(string path); + bool DeleteFileIfExists(string path); + bool MoveFileIfExists(string source, string destination); + Task ReadAllBytes(string path); + Task SaveFile(IFormFile file, string path, FileMode mode); + } + + public class FileHelper : IFileHelper + { + public FileHelper() + { + } + + public bool DirectoryExists(string path) + { + return Directory.Exists(path); + } + + public bool CreateDirectoryIfNotExists(string path) + { + if (!DirectoryExists(path)) + { + Directory.CreateDirectory(path); + + return true; + } + + return false; + } + + public bool DeleteDirectoryIfExists(string path, bool recursive = true) + { + if (DirectoryExists(path)) + { + Directory.Delete(path, recursive); + + return true; + } + + return false; + } + + public string[] GetDirectories(string path, string pattern = "*", SearchOption searchOption = SearchOption.AllDirectories) + { + return Directory.GetDirectories(path, pattern, searchOption); + } + + public string[] GetFiles(string path, string pattern = "*", SearchOption searchOption = SearchOption.AllDirectories) + { + return Directory.GetFiles(path, pattern, searchOption); + } + + public bool FileExists(string path) + { + return File.Exists(path); + } + + public bool DeleteFileIfExists(string path) + { + if (FileExists(path)) + { + File.Delete(path); + + return true; + } + + return false; + } + + public bool MoveFileIfExists(string source, string destination) + { + if (FileExists(source)) + { + File.Move(source, destination); + + return true; + } + + return false; + } + + public async Task ReadAllBytes(string path) + { + return await File.ReadAllBytesAsync(path); + } + + public async Task SaveFile(IFormFile file, string path, FileMode mode) + { + using (var fs = new FileStream(path, mode)) + { + await file.CopyToAsync(fs); + } + } + } +} \ No newline at end of file diff --git a/WeddingShare/Helpers/ImageHelper.cs b/WeddingShare/Helpers/ImageHelper.cs index 5c7719b..e9bc520 100644 --- a/WeddingShare/Helpers/ImageHelper.cs +++ b/WeddingShare/Helpers/ImageHelper.cs @@ -12,16 +12,18 @@ public interface IImageHelper public class ImageHelper : IImageHelper { + private readonly IFileHelper _fileHelper; private readonly ILogger _logger; - public ImageHelper(ILogger logger) + public ImageHelper(IFileHelper fileHelper, ILogger logger) { + _fileHelper = fileHelper; _logger = logger; } public async Task GenerateThumbnail(string imagePath, string savePath, int size = 720) { - if (File.Exists(imagePath)) + if (_fileHelper.FileExists(imagePath)) { try { diff --git a/WeddingShare/Models/Database/GalleryItemModel.cs b/WeddingShare/Models/Database/GalleryItemModel.cs index 32627a5..9db3d47 100644 --- a/WeddingShare/Models/Database/GalleryItemModel.cs +++ b/WeddingShare/Models/Database/GalleryItemModel.cs @@ -4,6 +4,20 @@ namespace WeddingShare.Models.Database { public class GalleryItemModel { + public GalleryItemModel() + : this(0, 0, string.Empty, null, GalleryItemState.Pending) + { + } + + public GalleryItemModel(int id, int galleryId, string title, string? uploadedBy, GalleryItemState state) + { + Id = id; + GalleryId = galleryId; + Title = title; + UploadedBy = uploadedBy; + State = state; + } + public int Id { get; set; } public int GalleryId { get; set; } public string Title { get; set; } diff --git a/WeddingShare/Models/FileUploader.cs b/WeddingShare/Models/FileUploader.cs index c9ed0a5..d83fbd2 100644 --- a/WeddingShare/Models/FileUploader.cs +++ b/WeddingShare/Models/FileUploader.cs @@ -2,7 +2,7 @@ { public class FileUploader { - public FileUploader(string id, string key, string url) + public FileUploader(string id, string? key, string url) { this.GalleryId = id; this.SecretKey = key; diff --git a/WeddingShare/Models/PhotoGallery.cs b/WeddingShare/Models/PhotoGallery.cs index 9ef7cd5..435a54f 100644 --- a/WeddingShare/Models/PhotoGallery.cs +++ b/WeddingShare/Models/PhotoGallery.cs @@ -5,22 +5,21 @@ namespace WeddingShare.Models public class PhotoGallery { public PhotoGallery() - : this(3, ViewMode.Default) + : this(ViewMode.Default) { } - public PhotoGallery(int columnCount, ViewMode viewMode) - : this("default", string.Empty, columnCount, string.Empty, string.Empty, viewMode, new List()) + public PhotoGallery(ViewMode viewMode) + : this("default", string.Empty, string.Empty, string.Empty, viewMode, new List()) { } - public PhotoGallery(string id, string secretKey, int columnCount, string galleryPath, string thumbnailPath, ViewMode viewMode, List images) + public PhotoGallery(string id, string secretKey, string galleryPath, string thumbnailPath, ViewMode viewMode, List images) { this.GalleryId = id; this.GalleryPath = galleryPath; this.ThumbnailsPath = thumbnailPath; this.ViewMode = viewMode; - this.ColumnCount = columnCount; this.PendingCount = 0; this.Images = images; this.FileUploader = new FileUploader(id, secretKey, "/Gallery/UploadImage"); @@ -30,7 +29,6 @@ public PhotoGallery(string id, string secretKey, int columnCount, string gallery public string? GalleryPath { get; set; } public string? ThumbnailsPath { get; set; } public ViewMode ViewMode { get; set; } - public int ColumnCount { get; set; } public int PendingCount { get; set; } public int ApprovedCount { @@ -59,5 +57,6 @@ public PhotoGalleryImage() public int Id { get; set; } public string? Path { get; set; } public string? Name { get; set; } + public string? UploadedBy { get; set; } } } \ No newline at end of file diff --git a/WeddingShare/Startup.cs b/WeddingShare/Startup.cs index 7a2a6f6..94c902d 100644 --- a/WeddingShare/Startup.cs +++ b/WeddingShare/Startup.cs @@ -29,6 +29,7 @@ public void ConfigureServices(IServiceCollection services) services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); var config = new ConfigHelper(new EnvironmentWrapper(), Configuration, _loggerFactory.CreateLogger()); switch (config.GetOrDefault("Database", "Database_Type", "sqlite")?.ToLower()) diff --git a/WeddingShare/Views/Admin/Index.cshtml b/WeddingShare/Views/Admin/Index.cshtml index 15ae6f6..505e90c 100644 --- a/WeddingShare/Views/Admin/Index.cshtml +++ b/WeddingShare/Views/Admin/Index.cshtml @@ -4,6 +4,10 @@ @inject WeddingShare.Helpers.IConfigHelper _config @inject WeddingShare.Helpers.ISecretKeyHelper _secretKey +@{ + var identityEnabled = _config.GetOrDefault("Settings", "Show_Identity_Request", false); +} +
@if (Model?.Galleries != null && Model.Galleries.Any()) @@ -93,12 +97,26 @@ {
-
-
- @review.Title.Split('.', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).FirstOrDefault() -
-
+
+
+ @if (identityEnabled) + { +
+ Gallery: @review.GalleryName +
+
+ Uploader: @(!string.IsNullOrWhiteSpace(review.UploadedBy) ? review.UploadedBy : "Anonymous") +
+ } + else + { +
+ Gallery: @review.GalleryName +
+ } +
+
diff --git a/WeddingShare/Views/Gallery/Index.cshtml b/WeddingShare/Views/Gallery/Index.cshtml index a6466e9..19abdce 100644 --- a/WeddingShare/Views/Gallery/Index.cshtml +++ b/WeddingShare/Views/Gallery/Index.cshtml @@ -6,11 +6,7 @@ @if (Model?.FileUploader != null && Model.ViewMode != ViewMode.Presentation && Model.ViewMode != ViewMode.Slideshow) { -
-
- -
-
+ }