diff --git a/AsbCloudApp/Data/Manuals/CatalogItemManualDto.cs b/AsbCloudApp/Data/Manuals/CatalogItemManualDto.cs new file mode 100644 index 00000000..d4a766d1 --- /dev/null +++ b/AsbCloudApp/Data/Manuals/CatalogItemManualDto.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using System.Linq; + +namespace AsbCloudApp.Data.Manuals; + +/// +/// Элемент каталога инструкций +/// +public class CatalogItemManualDto +{ + /// + /// DTO категории + /// + public FileCategoryDto Category { get; set; } = null!; + + /// + /// DTO инструкций хранящиеся без папки + /// + public IEnumerable ManualsWithoutFolder { get; set; } = Enumerable.Empty(); + + /// + /// DTO папок с инструкциями + /// + public IEnumerable Folders { get; set; } = Enumerable.Empty(); +} \ No newline at end of file diff --git a/AsbCloudApp/Data/Manuals/ManualDto.cs b/AsbCloudApp/Data/Manuals/ManualDto.cs new file mode 100644 index 00000000..8c01f66c --- /dev/null +++ b/AsbCloudApp/Data/Manuals/ManualDto.cs @@ -0,0 +1,32 @@ +using System; + +namespace AsbCloudApp.Data.Manuals; + +/// +/// DTO инструкции +/// +public class ManualDto : IId +{ + /// + public int Id { get; set; } + + /// + /// Название + /// + public string Name { get; set; } = null!; + + /// + /// Дата загрузки + /// + public DateTime DateDownload { get; set; } + + /// + /// Id папки + /// + public int? IdFolder { get; set; } + + /// + /// Id категории файла + /// + public int? IdCategory { get; set; } +} \ No newline at end of file diff --git a/AsbCloudApp/Data/Manuals/ManualFolderDto.cs b/AsbCloudApp/Data/Manuals/ManualFolderDto.cs new file mode 100644 index 00000000..8eef923d --- /dev/null +++ b/AsbCloudApp/Data/Manuals/ManualFolderDto.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using System.Linq; + +namespace AsbCloudApp.Data.Manuals; + +/// +/// DTO папки для хранения инструкций +/// +public class ManualFolderDto : IId +{ + /// + public int Id { get; set; } + + /// + /// Название + /// + public string Name { get; set; } = null!; + + /// + /// Id родительской папки + /// + public int? IdParent { get; set; } + + /// + /// Id категории + /// + public int IdCategory { get; set; } + + /// + /// Вложенные папки + /// + public IEnumerable Children { get; set; } = Enumerable.Empty(); + + /// + /// Хранимые инструкции + /// + public IEnumerable Manuals { get; set; } = Enumerable.Empty(); +} \ No newline at end of file diff --git a/AsbCloudApp/Repositories/IFileCategoryRepository.cs b/AsbCloudApp/Repositories/IFileCategoryRepository.cs new file mode 100644 index 00000000..7e64b024 --- /dev/null +++ b/AsbCloudApp/Repositories/IFileCategoryRepository.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using AsbCloudApp.Data; +using AsbCloudApp.Services; + +namespace AsbCloudApp.Repositories; + +/// +/// Репозиторий для работы с категориями файлов +/// +public interface IFileCategoryRepository : ICrudRepository +{ + /// + /// Получение всех записей по идентификатору типа + /// + /// + /// + /// + Task> GetAllAsync(int idType, CancellationToken cancellationToken); +} \ No newline at end of file diff --git a/AsbCloudApp/Repositories/IFileStorageRepository.cs b/AsbCloudApp/Repositories/IFileStorageRepository.cs index b8b7b482..37ebb125 100644 --- a/AsbCloudApp/Repositories/IFileStorageRepository.cs +++ b/AsbCloudApp/Repositories/IFileStorageRepository.cs @@ -47,6 +47,12 @@ namespace AsbCloudApp.Repositories /// void DeleteFile(string fileName); + /// + /// Удаление директории + /// + /// + void DeleteDirectory(string path); + /// /// Удаление всех файлов с диска о которых нет информации в базе /// diff --git a/AsbCloudApp/Repositories/IManualFolderRepository.cs b/AsbCloudApp/Repositories/IManualFolderRepository.cs new file mode 100644 index 00000000..d7b86182 --- /dev/null +++ b/AsbCloudApp/Repositories/IManualFolderRepository.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using AsbCloudApp.Data.Manuals; +using AsbCloudApp.Services; + +namespace AsbCloudApp.Repositories; + +/// +/// Репозиторий для работы с папки хранящими инструкциями +/// +public interface IManualFolderRepository : ICrudRepository +{ + /// + /// Получение дерева каталога папок + /// + /// + /// + /// + Task> GetTreeAsync(int idCategory, CancellationToken cancellationToken); + + /// + /// Получение одной папки по параметрам + /// + /// + /// + /// + /// + /// + Task GetOrDefaultAsync(string name, int? idParent, int idCategory, CancellationToken cancellationToken); +} \ No newline at end of file diff --git a/AsbCloudApp/Repositories/IManualRepository.cs b/AsbCloudApp/Repositories/IManualRepository.cs new file mode 100644 index 00000000..d5d98c04 --- /dev/null +++ b/AsbCloudApp/Repositories/IManualRepository.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using AsbCloudApp.Data.Manuals; +using AsbCloudApp.Services; + +namespace AsbCloudApp.Repositories; + +/// +/// Репозиторий для инструкций +/// +public interface IManualRepository : ICrudRepository +{ + /// + /// Получение инструкций, которые не добавлены в папку + /// + /// + /// + /// + Task> GetManualsWithoutFolderAsync(int idCategory, CancellationToken cancellationToken); +} \ No newline at end of file diff --git a/AsbCloudApp/Services/IManualCatalogService.cs b/AsbCloudApp/Services/IManualCatalogService.cs new file mode 100644 index 00000000..598de63e --- /dev/null +++ b/AsbCloudApp/Services/IManualCatalogService.cs @@ -0,0 +1,76 @@ +using System.Collections.Generic; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using AsbCloudApp.Data.Manuals; + +namespace AsbCloudApp.Services; + +/// +/// Сервис для работы c каталогом инструкций +/// +public interface IManualCatalogService +{ + /// + /// Сохранение файла + /// + /// + /// + /// + /// + /// + /// + Task SaveFileAsync(int? idCategory, int? idFolder, string name, Stream stream, + CancellationToken cancellationToken); + + /// + /// Добавление новой папки + /// + /// + /// + /// + /// + /// + Task AddFolderAsync(string name, int? idParent, int idCategory, + CancellationToken cancellationToken); + + /// + /// Обновление папки + /// + /// + /// + /// + /// + Task UpdateFolderAsync(int id, string name, CancellationToken cancellationToken); + + /// + /// Удаление папки + /// + /// + /// + /// + Task DeleteFolderAsync(int id, CancellationToken cancellationToken); + + /// + /// Удаление файла + /// + /// + /// + /// + Task DeleteFileAsync(int id, CancellationToken cancellationToken); + + /// + /// Получение файла + /// + /// + /// + /// + Task<(Stream stream, string fileName)?> GetFileAsync(int id, CancellationToken cancellationToken); + + /// + /// Получение каталога + /// + /// + /// + Task> GetCatalogAsync(CancellationToken cancellationToken); +} \ No newline at end of file diff --git a/AsbCloudInfrastructure/DependencyInjection.cs b/AsbCloudInfrastructure/DependencyInjection.cs index 601d05e7..6d2a43be 100644 --- a/AsbCloudInfrastructure/DependencyInjection.cs +++ b/AsbCloudInfrastructure/DependencyInjection.cs @@ -221,6 +221,11 @@ namespace AsbCloudInfrastructure services.AddTransient(); services.AddTransient(); + + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); return services; } diff --git a/AsbCloudInfrastructure/Repository/FileCategoryRepository.cs b/AsbCloudInfrastructure/Repository/FileCategoryRepository.cs new file mode 100644 index 00000000..703817e4 --- /dev/null +++ b/AsbCloudInfrastructure/Repository/FileCategoryRepository.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using AsbCloudApp.Data; +using AsbCloudApp.Repositories; +using AsbCloudDb.Model; +using Mapster; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Caching.Memory; + +namespace AsbCloudInfrastructure.Repository; + +public class FileCategoryRepository : CrudCacheRepositoryBase, IFileCategoryRepository +{ + public FileCategoryRepository(IAsbCloudDbContext dbContext, IMemoryCache memoryCache) : base(dbContext, memoryCache) + { + } + + public FileCategoryRepository(IAsbCloudDbContext dbContext, IMemoryCache memoryCache, + Func, IQueryable> makeQuery) : base(dbContext, memoryCache, makeQuery) + { + } + + public async Task> GetAllAsync(int idType, CancellationToken cancellationToken) + { + return await dbContext.FileCategories.Where(f => f.IdType == idType) + .Select(f => f.Adapt()) + .ToArrayAsync(cancellationToken); + } +} \ No newline at end of file diff --git a/AsbCloudInfrastructure/Repository/FileStorageRepository.cs b/AsbCloudInfrastructure/Repository/FileStorageRepository.cs index 4af6aa09..ea6268eb 100644 --- a/AsbCloudInfrastructure/Repository/FileStorageRepository.cs +++ b/AsbCloudInfrastructure/Repository/FileStorageRepository.cs @@ -33,6 +33,21 @@ public class FileStorageRepository : IFileStorageRepository DeleteFile(fileName); } } + + public void DeleteDirectory(string path) + { + var fullPath = Path.Combine(RootPath, path); + + if (!Directory.Exists(fullPath)) + return; + + foreach (var file in Directory.GetFiles(fullPath)) + { + File.Delete(file); + } + + Directory.Delete(fullPath, true); + } public void DeleteFile(string fileName) { diff --git a/AsbCloudInfrastructure/Repository/ManualFolderRepository.cs b/AsbCloudInfrastructure/Repository/ManualFolderRepository.cs new file mode 100644 index 00000000..a567195d --- /dev/null +++ b/AsbCloudInfrastructure/Repository/ManualFolderRepository.cs @@ -0,0 +1,62 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using AsbCloudApp.Data.Manuals; +using AsbCloudApp.Repositories; +using AsbCloudDb.Model; +using AsbCloudDb.Model.Manuals; +using Mapster; +using Microsoft.EntityFrameworkCore; + +namespace AsbCloudInfrastructure.Repository; + +public class ManualFolderRepository : CrudRepositoryBase, IManualFolderRepository +{ + public ManualFolderRepository(IAsbCloudDbContext context) : base(context) + { + } + + public async Task> GetTreeAsync(int idCategory, + CancellationToken cancellationToken) + { + var folders = await dbContext.ManualFolders + .Where(m => m.IdCategory == idCategory) + .AsNoTracking() + .Include(m => m.Manuals) + .Include(m => m.Parent) + .ToArrayAsync(cancellationToken); + + return BuildTree(folders).Select(x => x.Adapt()); + } + + public async Task GetOrDefaultAsync(string name, int? idParent, int idCategory, + CancellationToken cancellationToken) + { + var entity = await dbContext.ManualFolders + .FirstOrDefaultAsync(m => m.Name == name && + m.IdCategory == idCategory && + m.IdParent == idParent, cancellationToken); + + if (entity is null) + return null; + + return Convert(entity); + } + + private IEnumerable BuildTree(IEnumerable folders) + { + var folderDict = folders.ToDictionary(f => f.Id); + + foreach (var folder in folders) + { + if (folder.IdParent.HasValue && folderDict.TryGetValue(folder.IdParent.Value, out var parent)) + { + parent.Children ??= new List(); + parent.Children.Add(folder); + } + } + + return folders.Where(f => f.IdParent == null); + } +} \ No newline at end of file diff --git a/AsbCloudInfrastructure/Repository/ManualRepository.cs b/AsbCloudInfrastructure/Repository/ManualRepository.cs new file mode 100644 index 00000000..a23d8714 --- /dev/null +++ b/AsbCloudInfrastructure/Repository/ManualRepository.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using AsbCloudApp.Data.Manuals; +using AsbCloudApp.Repositories; +using AsbCloudDb.Model; +using AsbCloudDb.Model.Manuals; +using Mapster; +using Microsoft.EntityFrameworkCore; + +namespace AsbCloudInfrastructure.Repository; + +public class ManualRepository : CrudRepositoryBase, IManualRepository +{ + public ManualRepository(IAsbCloudDbContext context) : base(context) + { + } + + public ManualRepository(IAsbCloudDbContext context, Func, IQueryable> makeQuery) + : base(context, makeQuery) + { + } + + public async Task> GetManualsWithoutFolderAsync(int idCategory, CancellationToken cancellationToken) + { + return await dbContext.Manuals.Where(m => m.IdCategory == idCategory && + m.IdFolder == null) + .Select(m => m.Adapt()) + .ToArrayAsync(cancellationToken); + } +} \ No newline at end of file diff --git a/AsbCloudInfrastructure/Services/ManualCatalogService.cs b/AsbCloudInfrastructure/Services/ManualCatalogService.cs new file mode 100644 index 00000000..545a114e --- /dev/null +++ b/AsbCloudInfrastructure/Services/ManualCatalogService.cs @@ -0,0 +1,202 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using AsbCloudApp.Data.Manuals; +using AsbCloudApp.Exceptions; +using AsbCloudApp.Repositories; +using AsbCloudApp.Services; +using AsbCloudDb.Model; +using Microsoft.Extensions.Configuration; + +namespace AsbCloudInfrastructure.Services; + +public class ManualCatalogService : IManualCatalogService +{ + private readonly IEnumerable validExtensions = new[] + { + ".pdf", + ".mp4" + }; + + private readonly string directoryFiles; + private readonly IFileStorageRepository fileStorageRepository; + private readonly IManualFolderRepository manualFolderRepository; + private readonly IManualRepository manualRepository; + private readonly IFileCategoryRepository fileCategoryRepository; + + public ManualCatalogService(IFileStorageRepository fileStorageRepository, + IManualFolderRepository manualFolderRepository, + IManualRepository manualRepository, + IFileCategoryRepository fileCategoryRepository, + IConfiguration configuration) + { + this.fileStorageRepository = fileStorageRepository; + this.manualFolderRepository = manualFolderRepository; + this.manualRepository = manualRepository; + this.fileCategoryRepository = fileCategoryRepository; + directoryFiles = configuration.GetValue("DirectoryManualFiles"); + + if (string.IsNullOrWhiteSpace(directoryFiles)) + directoryFiles = "manuals"; + } + + public async Task SaveFileAsync(int? idCategory, int? idFolder, string name, Stream stream, + CancellationToken cancellationToken) + { + var extension = Path.GetExtension(name); + + if (!validExtensions.Contains(extension)) + throw new ArgumentInvalidException( + $"Невозможно загрузить файл с расширением '{extension}'. Допустимые форматы файлов: {string.Join(", ", validExtensions)}", + extension); + + var path = await BuildFilePathAsync(idCategory, idFolder, name, cancellationToken); + + await fileStorageRepository.SaveFileAsync(path, + stream, + cancellationToken); + + var manual = new ManualDto + { + Name = name, + DateDownload = DateTime.UtcNow, + IdFolder = idFolder, + IdCategory = idCategory + }; + + return await manualRepository.InsertAsync(manual, cancellationToken); + } + + public async Task AddFolderAsync(string name, int? idParent, int idCategory, + CancellationToken cancellationToken) + { + if (idParent.HasValue) + { + var parent = await manualFolderRepository.GetOrDefaultAsync(idParent.Value, cancellationToken) + ?? throw new ArgumentInvalidException("Родительской папки не существует", nameof(idParent)); + + if (parent.IdCategory != idCategory) + throw new ArgumentInvalidException("Категория родительской папки не соответствует текущей категории", + nameof(idCategory)); + } + + var manualFolder = new ManualFolderDto + { + Name = name, + IdParent = idParent, + IdCategory = idCategory, + }; + + if (await IsExistFolderAsync(manualFolder, cancellationToken)) + throw new ArgumentInvalidException("Папка с таким названием уже существует", name); + + return await manualFolderRepository.InsertAsync(manualFolder, cancellationToken); + } + + public async Task UpdateFolderAsync(int id, string name, CancellationToken cancellationToken) + { + var folder = await manualFolderRepository.GetOrDefaultAsync(id, cancellationToken) + ?? throw new ArgumentInvalidException($"Папки с Id: {id} не сущесвует", nameof(id)); + + folder.Name = name; + + if (await IsExistFolderAsync(folder, cancellationToken)) + throw new ArgumentInvalidException("Папка с таким названием уже существует", name); + + await manualFolderRepository.UpdateAsync(folder, cancellationToken); + } + + + public async Task DeleteFolderAsync(int id, CancellationToken cancellationToken) + { + var folder = await manualFolderRepository.GetOrDefaultAsync(id, cancellationToken); + + if (folder is null) + return 0; + + var path = Path.Combine(directoryFiles, folder.IdCategory.ToString(), folder.Id.ToString()); + fileStorageRepository.DeleteDirectory(path); + + return await manualFolderRepository.DeleteAsync(folder.Id, cancellationToken); + } + + public async Task DeleteFileAsync(int id, CancellationToken cancellationToken) + { + var manual = await manualRepository.GetOrDefaultAsync(id, cancellationToken); + + if (manual is null) + return 0; + + var filePath = await BuildFilePathAsync(manual.IdCategory, manual.IdFolder, manual.Name, + cancellationToken); + + fileStorageRepository.DeleteFile(filePath); + + return await manualRepository.DeleteAsync(manual.Id, cancellationToken); + } + + public async Task<(Stream stream, string fileName)?> GetFileAsync(int id, CancellationToken cancellationToken) + { + var manual = await manualRepository.GetOrDefaultAsync(id, cancellationToken); + + if (manual is null) + return null; + + var path = await BuildFilePathAsync(manual.IdCategory, manual.IdFolder, manual.Name, cancellationToken); + + var fileStream = new FileStream(Path.GetFullPath(path), FileMode.Open); + + return (fileStream, manual.Name); + } + + public async Task> GetCatalogAsync(CancellationToken cancellationToken) + { + var catalogItems = new List(); + + var categories = await fileCategoryRepository.GetAllAsync(FileCategory.IdFileCategoryTypeManuals, + cancellationToken); + + foreach (var category in categories) + { + catalogItems.Add(new CatalogItemManualDto() + { + Category = category, + ManualsWithoutFolder = await manualRepository.GetManualsWithoutFolderAsync(category.Id, cancellationToken), + Folders = await manualFolderRepository.GetTreeAsync(category.Id, cancellationToken) + }); + } + + return catalogItems; + } + + private async Task IsExistFolderAsync(ManualFolderDto folder, CancellationToken cancellationToken) + { + var existingFolder = await manualFolderRepository.GetOrDefaultAsync(folder.Name, folder.IdParent, + folder.IdCategory, + cancellationToken); + + return existingFolder is not null && folder.Id != existingFolder.Id; + } + + private async Task BuildFilePathAsync(int? idCategory, int? idFolder, string name, + CancellationToken cancellationToken) + { + if (idFolder.HasValue) + { + var folder = await manualFolderRepository.GetOrDefaultAsync(idFolder.Value, cancellationToken) + ?? throw new ArgumentInvalidException($"Папки с Id: {idFolder} не сущесвует", nameof(idFolder)); + + return fileStorageRepository.MakeFilePath(directoryFiles, Path.Combine(folder.IdCategory.ToString(), + folder.IdParent.ToString() ?? string.Empty, + folder.Id.ToString()), name); + } + + if (!idCategory.HasValue) + throw new ArgumentInvalidException("Не указан идентификатор категории", nameof(idCategory)); + + return fileStorageRepository.MakeFilePath(directoryFiles, idCategory.Value.ToString(), name); + } +} \ No newline at end of file diff --git a/AsbCloudWebApi/Controllers/ManualController.cs b/AsbCloudWebApi/Controllers/ManualController.cs new file mode 100644 index 00000000..1a05655d --- /dev/null +++ b/AsbCloudWebApi/Controllers/ManualController.cs @@ -0,0 +1,120 @@ +using System.ComponentModel.DataAnnotations; +using System.Threading; +using System.Threading.Tasks; +using AsbCloudApp.Repositories; +using AsbCloudApp.Services; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace AsbCloudWebApi.Controllers; + +[ApiController] +[Route("api/[controller]")] +public class ManualController : ControllerBase +{ + private readonly IManualCatalogService manualCatalogService; + private readonly IUserRepository userRepository; + + public ManualController(IManualCatalogService manualCatalogService, + IUserRepository userRepository) + { + this.manualCatalogService = manualCatalogService; + this.userRepository = userRepository; + } + + /// + /// Сохранение файла + /// + /// Необязательный параметр. 30000 - АСУ ТП, 30001 - Технология бурения + /// Необязательный параметр. Id папки + /// Загружаемый файл + /// + /// + [HttpPost] + [Permission] + [ProducesResponseType(typeof(int), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + public async Task SaveFileAsync( + [Range(minimum: 30000, maximum: 30001, ErrorMessage = "Категория файла недопустима. Допустимые: 30000, 30001")] + int? idCategory, + int? idFolder, + [Required] IFormFile file, + CancellationToken cancellationToken) + { + if(!CanUserAccess("Manual.edit")) + return Forbid(); + + using var fileStream = file.OpenReadStream(); + + var id = await manualCatalogService.SaveFileAsync(idCategory, idFolder, file.FileName, fileStream, cancellationToken); + + return Ok(id); + } + + /// + /// Получение файла + /// + /// Id инструкции + /// + /// + [HttpGet("{id:int}")] + [Permission] + [ProducesResponseType(typeof(PhysicalFileResult), StatusCodes.Status200OK, "application/octet-stream")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + public async Task GetFileAsync(int id, CancellationToken cancellationToken) + { + if(!CanUserAccess("Manual.view")) + return Forbid(); + + var file = await manualCatalogService.GetFileAsync(id, cancellationToken); + + if (!file.HasValue) + return NoContent(); + + return File(file.Value.stream, "application/octet-stream", file.Value.fileName); + } + + /// + /// Удаление файла + /// + /// Id инструкции + /// + /// + [HttpDelete] + [Permission] + [ProducesResponseType(typeof(int), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + public async Task DeleteFileAsync(int id, CancellationToken cancellationToken) + { + if(!CanUserAccess("Manual.edit")) + return Forbid(); + + return Ok(await manualCatalogService.DeleteFileAsync(id, cancellationToken)); + } + + /// + /// Получение каталога с инструкциями + /// + /// + /// + [HttpGet] + [Permission] + [ProducesResponseType(typeof(int), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + public async Task GetCatalogAsync(CancellationToken cancellationToken) + { + if(!CanUserAccess("Manual.view")) + return Forbid(); + + return Ok(await manualCatalogService.GetCatalogAsync(cancellationToken)); + } + + private bool CanUserAccess(string permission) + { + var idUser = User.GetUserId(); + + return idUser.HasValue && userRepository.HasPermission(idUser.Value, permission); + } +} \ No newline at end of file diff --git a/AsbCloudWebApi/Controllers/ManualFolderController.cs b/AsbCloudWebApi/Controllers/ManualFolderController.cs new file mode 100644 index 00000000..076abe3c --- /dev/null +++ b/AsbCloudWebApi/Controllers/ManualFolderController.cs @@ -0,0 +1,94 @@ +using System.ComponentModel.DataAnnotations; +using System.Threading; +using System.Threading.Tasks; +using AsbCloudApp.Repositories; +using AsbCloudApp.Services; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +namespace AsbCloudWebApi.Controllers; + +[ApiController] +[Route("api/[controller]")] +public class ManualFolderController : ControllerBase +{ + private readonly IManualCatalogService manualCatalogService; + private readonly IUserRepository userRepository; + + public ManualFolderController(IManualCatalogService manualCatalogService, + IUserRepository userRepository) + { + this.manualCatalogService = manualCatalogService; + this.userRepository = userRepository; + } + + /// + /// Создание папки + /// + /// Название + /// Необязательный параметр. Id родительской папки + /// Id категории. 30000 - АСУ ТП, 30001 - Технология бурения + /// + /// + [HttpPost] + [Permission] + [ProducesResponseType(typeof(int), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + public async Task AddFolderAsync(string name, int? idParent, + [Required(ErrorMessage = "Обязательный параметр")] + [Range(minimum: 30000, maximum: 30001, ErrorMessage = "Категория файла недопустима. Допустимые: 30000, 30001")] + int idCategory, + CancellationToken cancellationToken) + { + if (!CanUserAccess()) + Forbid(); + + return Ok(await manualCatalogService.AddFolderAsync(name, idParent, idCategory, cancellationToken)); + } + + /// + /// Обновление папки + /// + /// + /// Новое название папки + /// + /// + [HttpPut] + [Permission] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + public async Task UpdateFolderAsync(int id, string name, CancellationToken cancellationToken) + { + if (!CanUserAccess()) + Forbid(); + + await manualCatalogService.UpdateFolderAsync(id, name, cancellationToken); + + return Ok(); + } + + /// + /// Удаление папки + /// + /// + /// + /// + [HttpDelete] + [Permission] + [ProducesResponseType(typeof(int), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + public async Task DeleteFolderAsync(int id, CancellationToken cancellationToken) + { + if (!CanUserAccess()) + Forbid(); + + return Ok(await manualCatalogService.DeleteFolderAsync(id, cancellationToken)); + } + + private bool CanUserAccess() + { + var idUser = User.GetUserId(); + + return idUser.HasValue && userRepository.HasPermission(idUser.Value, "Manual.edit"); + } +} \ No newline at end of file diff --git a/AsbCloudWebApi/appsettings.json b/AsbCloudWebApi/appsettings.json index 683f7929..6801ff38 100644 --- a/AsbCloudWebApi/appsettings.json +++ b/AsbCloudWebApi/appsettings.json @@ -27,6 +27,7 @@ "supportMail": "support@digitaldrilling.ru" }, "DirectoryNameHelpPageFiles": "helpPages", + "DirectoryManualFiles": "manuals", "Urls": "http://0.0.0.0:5000" //;https://0.0.0.0:5001" //, // See https man: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/servers/kestrel/endpoints?view=aspnetcore-6.0 //"Kestrel": {