using FoodLabeling.Application.Contracts.Dtos.Common;
using FoodLabeling.Application.Contracts.Dtos.Training;
using FoodLabeling.Application.Contracts.IServices;
using FoodLabeling.Application.Helpers;
using FoodLabeling.Application.Services.DbModels;
using FoodLabeling.Domain.Shared.Enums;
using FoodLabeling.Domain.Shared.Helpers;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Hosting;
using SqlSugar;
using Volo.Abp;
using Volo.Abp.Application.Services;
using Yi.Framework.SqlSugarCore.Abstractions;
namespace FoodLabeling.Application.Services;
///
/// 培训 / 资料中心(管理端)
///
public class TrainingAppService : ApplicationService, ITrainingAppService
{
private const long MaxFileSizeBytes = 20 * 1024 * 1024;
private static readonly HashSet ImageExtensions = new(StringComparer.OrdinalIgnoreCase)
{
".jpg", ".jpeg", ".png", ".webp", ".gif", ".bmp"
};
private static readonly HashSet DocExtensions = new(StringComparer.OrdinalIgnoreCase)
{
".pdf", ".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx", ".txt", ".csv"
};
private static readonly HashSet AllowedExtensions = new(StringComparer.OrdinalIgnoreCase);
private readonly ISqlSugarDbContext _dbContext;
private readonly IHostEnvironment _hostEnvironment;
static TrainingAppService()
{
foreach (var ext in ImageExtensions)
{
AllowedExtensions.Add(ext);
}
foreach (var ext in DocExtensions)
{
AllowedExtensions.Add(ext);
}
}
public TrainingAppService(ISqlSugarDbContext dbContext, IHostEnvironment hostEnvironment)
{
_dbContext = dbContext;
_hostEnvironment = hostEnvironment;
}
///
/// 获取培训分类树(可选含文件;支持 keyword、locationId 筛选)
///
///
/// 一级分类 ParentId 为空;二级分类 ParentId 指向一级。文件仅挂在二级分类下。
///
/// 示例请求:
/// ```json
/// {
/// "keyword": "安全",
/// "locationId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
/// "includeFiles": true
/// }
/// ```
///
/// 参数说明:
/// - keyword: 匹配分类名或文件名
/// - locationId: 按门店权限过滤可见文件
/// - includeFiles: 是否返回文件列表
///
/// 查询条件
/// 分类树
/// 成功返回分类树
/// 参数无效
/// 服务器错误
public async Task> GetCategoryTreeAsync([FromQuery] TrainingCategoryTreeInputVo input)
{
var keyword = input.Keyword?.Trim();
TrainingFileScopeHelper.LocationScopeContext? scopeContext = null;
if (!string.IsNullOrWhiteSpace(input.LocationId))
{
scopeContext = await TrainingFileScopeHelper.ResolveLocationScopeContextAsync(
_dbContext.SqlSugarClient,
input.LocationId);
}
var categories = await _dbContext.SqlSugarClient.Queryable()
.Where(x => !x.IsDeleted)
.OrderByDescending(x => x.OrderNum)
.OrderByDescending(x => x.CreationTime)
.ToListAsync();
var level2Ids = categories
.Where(x => !string.IsNullOrWhiteSpace(x.ParentId))
.Select(x => x.Id)
.ToList();
var filesByCategory = new Dictionary>(StringComparer.Ordinal);
if (input.IncludeFiles && level2Ids.Count > 0)
{
var fileQuery = _dbContext.SqlSugarClient.Queryable()
.Where(x => !x.IsDeleted && level2Ids.Contains(x.CategoryId));
fileQuery = TrainingFileScopeHelper.ApplyLocationVisibilityFilter(fileQuery, scopeContext);
if (!string.IsNullOrWhiteSpace(keyword))
{
fileQuery = fileQuery.Where(x => x.FileName.Contains(keyword!));
}
var files = await fileQuery
.OrderByDescending(x => x.OrderNum)
.OrderByDescending(x => x.CreationTime)
.ToListAsync();
foreach (var group in files.GroupBy(x => x.CategoryId))
{
filesByCategory[group.Key] = group.ToList();
}
}
var allFileEntities = filesByCategory.Values.SelectMany(x => x).ToList();
var scopeDisplayMap = await TrainingFileScopeHelper.BuildScopeDisplayMapAsync(
_dbContext.SqlSugarClient,
allFileEntities);
var level1 = categories.Where(x => string.IsNullOrWhiteSpace(x.ParentId)).ToList();
var level2Map = categories
.Where(x => !string.IsNullOrWhiteSpace(x.ParentId))
.GroupBy(x => x.ParentId!.Trim(), StringComparer.Ordinal)
.ToDictionary(g => g.Key, g => g.ToList(), StringComparer.Ordinal);
var result = new List();
foreach (var l1 in level1)
{
var children = level2Map.TryGetValue(l1.Id, out var l2List)
? l2List.OrderByDescending(x => x.OrderNum).ThenByDescending(x => x.CreationTime).ToList()
: new List();
var childNodes = new List();
foreach (var l2 in children)
{
filesByCategory.TryGetValue(l2.Id, out var fileRows);
fileRows ??= new List();
var nameMatch = string.IsNullOrWhiteSpace(keyword)
|| l2.CategoryName.Contains(keyword!, StringComparison.OrdinalIgnoreCase);
var fileMatch = fileRows.Count > 0;
if (!string.IsNullOrWhiteSpace(keyword) && !nameMatch && !fileMatch)
{
continue;
}
if (scopeContext is not null && fileRows.Count == 0 && !nameMatch)
{
continue;
}
childNodes.Add(MapCategoryNode(l2, fileRows, scopeDisplayMap));
}
var l1NameMatch = string.IsNullOrWhiteSpace(keyword)
|| l1.CategoryName.Contains(keyword!, StringComparison.OrdinalIgnoreCase);
if (!string.IsNullOrWhiteSpace(keyword) && !l1NameMatch && childNodes.Count == 0)
{
continue;
}
if (scopeContext is not null && childNodes.Count == 0 && !l1NameMatch)
{
continue;
}
result.Add(new TrainingCategoryTreeNodeDto
{
Id = l1.Id,
CategoryName = l1.CategoryName,
ParentId = null,
OrderNum = l1.OrderNum,
Children = childNodes,
Files = new List()
});
}
return result;
}
///
/// 新增培训分类(一级或二级)
///
///
/// 示例请求:
/// ```json
/// {
/// "categoryName": "食品安全",
/// "parentId": null,
/// "orderNum": 100
/// }
/// ```
///
/// 分类信息
/// 新建分类
/// 创建成功
/// 参数无效或父级不存在
/// 服务器错误
public async Task CreateCategoryAsync(TrainingCategoryCreateInputVo input)
{
var name = input.CategoryName?.Trim();
if (string.IsNullOrWhiteSpace(name))
{
throw new UserFriendlyException("分类名称不能为空");
}
var parentId = string.IsNullOrWhiteSpace(input.ParentId) ? null : input.ParentId.Trim();
if (parentId is not null)
{
var parent = await _dbContext.SqlSugarClient.Queryable()
.FirstAsync(x => x.Id == parentId && !x.IsDeleted);
if (parent is null)
{
throw new UserFriendlyException("父级分类不存在");
}
if (!string.IsNullOrWhiteSpace(parent.ParentId))
{
throw new UserFriendlyException("仅支持两级分类,不能在二级分类下再建子级");
}
}
var duplicated = await _dbContext.SqlSugarClient.Queryable()
.AnyAsync(x => !x.IsDeleted && x.CategoryName == name && x.ParentId == parentId);
if (duplicated)
{
throw new UserFriendlyException("同级分类名称已存在");
}
var now = DateTime.Now;
var currentUserId = CurrentUser?.Id?.ToString();
var entity = new FlTrainingCategoryDbEntity
{
Id = YitIdHelper.NextId().ToString(),
CategoryName = name,
ParentId = parentId,
OrderNum = input.OrderNum,
IsDeleted = false,
CreationTime = now,
CreatorId = currentUserId,
LastModificationTime = now,
LastModifierId = currentUserId,
ConcurrencyStamp = YitIdHelper.NextId().ToString()
};
await _dbContext.SqlSugarClient.Insertable(entity).ExecuteCommandAsync();
return MapCategoryOutput(entity);
}
///
/// 编辑培训分类
///
///
/// 示例请求:
/// ```json
/// {
/// "categoryName": "食品安全(更新)",
/// "orderNum": 90
/// }
/// ```
///
/// 分类Id
/// 分类信息
/// 更新后的分类
/// 更新成功
/// 分类不存在或名称重复
/// 服务器错误
public async Task UpdateCategoryAsync(string id, TrainingCategoryUpdateInputVo input)
{
var entity = await GetCategoryOrThrowAsync(id);
var name = input.CategoryName?.Trim();
if (string.IsNullOrWhiteSpace(name))
{
throw new UserFriendlyException("分类名称不能为空");
}
var duplicated = await _dbContext.SqlSugarClient.Queryable()
.AnyAsync(x => !x.IsDeleted && x.Id != id && x.CategoryName == name && x.ParentId == entity.ParentId);
if (duplicated)
{
throw new UserFriendlyException("同级分类名称已存在");
}
entity.CategoryName = name;
entity.OrderNum = input.OrderNum;
entity.LastModificationTime = DateTime.Now;
entity.LastModifierId = CurrentUser?.Id?.ToString();
await _dbContext.SqlSugarClient.Updateable(entity).ExecuteCommandAsync();
return MapCategoryOutput(entity);
}
///
/// 删除培训分类(软删)
///
///
/// 一级分类存在二级子分类时不可删除;二级分类存在文件时不可删除。
///
/// 分类Id
/// 删除成功
/// 存在子分类或文件
/// 服务器错误
public async Task DeleteCategoryAsync(string id)
{
var entity = await GetCategoryOrThrowAsync(id);
if (string.IsNullOrWhiteSpace(entity.ParentId))
{
var hasChild = await _dbContext.SqlSugarClient.Queryable()
.AnyAsync(x => !x.IsDeleted && x.ParentId == id);
if (hasChild)
{
throw new UserFriendlyException("该一级分类下存在二级分类,无法删除");
}
}
else
{
var hasFile = await _dbContext.SqlSugarClient.Queryable()
.AnyAsync(x => !x.IsDeleted && x.CategoryId == id);
if (hasFile)
{
throw new UserFriendlyException("该二级分类下存在培训文件,无法删除");
}
}
entity.IsDeleted = true;
entity.LastModificationTime = DateTime.Now;
entity.LastModifierId = CurrentUser?.Id?.ToString();
await _dbContext.SqlSugarClient.Updateable(entity).ExecuteCommandAsync();
}
///
/// 上传培训文件到二级分类(可同时提交 Company / Region / Location 适用范围)
///
///
/// multipart/form-data:file、categoryId、orderNum,及可选 scope 字段。
/// 支持常见图片与 pdf/doc/docx/xlsx 等,单文件最大 20MB。
///
/// 示例 form 字段:
/// - file: 文件
/// - categoryId: 二级分类 Id
/// - appliedPartnerType: ALL / SPECIFIED
/// - partnerIds: 可重复传值或含 ALL
/// - appliedRegionType: ALL / SPECIFIED
/// - regionIds / groupIds: 可含 ALL
/// - availabilityType 或 appliedLocationType: ALL / SPECIFIED
/// - locationIds: 可含 ALL
///
/// 上传表单
/// 文件信息
/// 上传成功
/// 文件无效或分类不是二级
/// 服务器错误
[HttpPost]
[Consumes("multipart/form-data")]
[Route("/api/app/training/file/upload")]
public async Task UploadFileAsync([FromForm] TrainingFileUploadInputVo input)
{
if (input.File is null || input.File.Length <= 0)
{
throw new UserFriendlyException("请选择要上传的文件");
}
if (input.File.Length > MaxFileSizeBytes)
{
throw new UserFriendlyException("文件大小不能超过20MB");
}
var categoryId = input.CategoryId?.Trim();
if (string.IsNullOrWhiteSpace(categoryId))
{
throw new UserFriendlyException("二级分类Id不能为空");
}
var category = await GetCategoryOrThrowAsync(categoryId);
if (string.IsNullOrWhiteSpace(category.ParentId))
{
throw new UserFriendlyException("文件只能上传到二级分类");
}
var ext = Path.GetExtension(input.File.FileName ?? string.Empty);
if (string.IsNullOrWhiteSpace(ext) || !AllowedExtensions.Contains(ext))
{
throw new UserFriendlyException("不支持的文件格式");
}
var saveRoot = ResolveTrainingRoot();
Directory.CreateDirectory(saveRoot);
var storedName = $"{DateTime.Now:yyyyMMddHHmmss}_{YitIdHelper.NextId()}{ext.ToLowerInvariant()}";
var savePath = Path.Combine(saveRoot, storedName);
await using (var stream = new FileStream(savePath, FileMode.CreateNew, FileAccess.Write, FileShare.None))
{
await input.File.CopyToAsync(stream);
}
var now = DateTime.Now;
var currentUserId = CurrentUser?.Id?.ToString();
var hasScopeInput = HasScopeInput(input);
var scope = hasScopeInput
? await TrainingFileScopeHelper.ResolveScopeForSaveAsync(
_dbContext.SqlSugarClient,
input.AppliedPartnerType,
input.PartnerIds,
input.CompanyIds,
input.AppliedRegionType,
input.RegionIds,
input.GroupIds,
ResolveLocationTypeInput(input),
input.LocationIds)
: null;
var entity = new FlTrainingFileDbEntity
{
Id = YitIdHelper.NextId().ToString(),
CategoryId = categoryId,
FileName = Path.GetFileName(input.File.FileName ?? storedName),
FileUrl = BuildTrainingUrl(storedName),
FileType = ResolveFileType(ext),
FileSize = input.File.Length,
OrderNum = input.OrderNum,
AppliedPartnerType = scope?.AppliedPartnerType ?? AllScopeBindingHelper.ScopeAll,
AppliedRegionType = scope?.AppliedRegionType ?? AllScopeBindingHelper.ScopeAll,
AvailabilityType = scope?.AvailabilityType ?? AllScopeBindingHelper.ScopeAll,
IsDeleted = false,
CreationTime = now,
CreatorId = currentUserId,
LastModificationTime = now,
LastModifierId = currentUserId,
ConcurrencyStamp = YitIdHelper.NextId().ToString()
};
await _dbContext.SqlSugarClient.Insertable(entity).ExecuteCommandAsync();
if (scope is not null)
{
await TrainingFileScopeHelper.SaveScopeAsync(
_dbContext.SqlSugarClient,
entity.Id,
scope,
currentUserId,
now);
}
var display = await TrainingFileScopeHelper.BuildScopeDisplayAsync(_dbContext.SqlSugarClient, entity);
return MapFileDto(entity, display);
}
///
/// 编辑培训文件元数据及适用范围
///
///
/// 示例请求:
/// ```json
/// {
/// "fileName": "操作手册.pdf",
/// "orderNum": 100,
/// "appliedPartnerType": "SPECIFIED",
/// "partnerIds": ["p1"],
/// "appliedRegionType": "ALL",
/// "availabilityType": "SPECIFIED",
/// "locationIds": ["loc1"]
/// }
/// ```
///
/// 参数说明:
/// - fileName / orderNum: 文件元数据
/// - appliedPartnerType / partnerIds / companyIds: Company 范围,Id 可含 ALL
/// - appliedRegionType / regionIds / groupIds: Region 范围,Id 可含 ALL
/// - availabilityType 或 appliedLocationType / locationIds: Location 范围,Id 可含 ALL
///
/// 文件Id
/// 文件元数据
/// 更新后的文件
/// 更新成功
/// 文件不存在
/// 服务器错误
public async Task UpdateFileAsync(string id, TrainingFileUpdateInputVo input)
{
var entity = await GetFileOrThrowAsync(id);
var fileName = input.FileName?.Trim();
if (string.IsNullOrWhiteSpace(fileName))
{
throw new UserFriendlyException("文件名称不能为空");
}
entity.FileName = fileName;
entity.OrderNum = input.OrderNum;
entity.LastModificationTime = DateTime.Now;
entity.LastModifierId = CurrentUser?.Id?.ToString();
await _dbContext.SqlSugarClient.Updateable(entity).ExecuteCommandAsync();
if (HasScopeInput(input))
{
entity = await ApplyFileScopeAsync(entity, input);
}
var display = await TrainingFileScopeHelper.BuildScopeDisplayAsync(_dbContext.SqlSugarClient, entity);
return MapFileDto(entity, display);
}
///
/// 删除培训文件(软删)
///
/// 文件Id
/// 删除成功
/// 文件不存在
/// 服务器错误
public async Task DeleteFileAsync(string id)
{
var entity = await GetFileOrThrowAsync(id);
await TrainingFileScopeHelper.DeleteScopeRowsAsync(_dbContext.SqlSugarClient, entity.Id);
entity.IsDeleted = true;
entity.LastModificationTime = DateTime.Now;
entity.LastModifierId = CurrentUser?.Id?.ToString();
await _dbContext.SqlSugarClient.Updateable(entity).ExecuteCommandAsync();
}
///
/// 批量更新培训文件排序
///
///
/// 示例请求:
/// ```json
/// {
/// "items": [
/// { "id": "123", "orderNum": 100 },
/// { "id": "456", "orderNum": 90 }
/// ]
/// }
/// ```
///
/// 排序项
/// 排序成功
/// 存在无效文件Id
/// 服务器错误
public async Task SortFilesAsync(TrainingFileSortInputVo input)
{
if (input.Items is null || input.Items.Count == 0)
{
return;
}
var ids = input.Items.Select(x => x.Id?.Trim()).Where(x => !string.IsNullOrWhiteSpace(x)).Cast().ToList();
if (ids.Count == 0)
{
return;
}
var existing = await _dbContext.SqlSugarClient.Queryable()
.Where(x => !x.IsDeleted && ids.Contains(x.Id))
.ToListAsync();
var map = existing.ToDictionary(x => x.Id, StringComparer.Ordinal);
var now = DateTime.Now;
var userId = CurrentUser?.Id?.ToString();
foreach (var item in input.Items)
{
if (string.IsNullOrWhiteSpace(item.Id) || !map.TryGetValue(item.Id.Trim(), out var entity))
{
continue;
}
entity.OrderNum = item.OrderNum;
entity.LastModificationTime = now;
entity.LastModifierId = userId;
}
if (existing.Count > 0)
{
await _dbContext.SqlSugarClient.Updateable(existing).ExecuteCommandAsync();
}
}
///
/// 获取培训文件权限范围(兼容独立查询;主路径为 create/update 携带 scope)
///
/// 文件Id
/// 权限范围
/// 成功
/// 文件不存在
/// 服务器错误
[HttpGet]
[Route("/api/app/training/file-scope/{id}")]
public async Task GetFileScopeAsync(string id)
{
var entity = await GetFileOrThrowAsync(id);
var display = await TrainingFileScopeHelper.BuildScopeDisplayAsync(_dbContext.SqlSugarClient, entity);
return MapScopeOutput(display);
}
///
/// 设置培训文件权限范围(兼容独立编辑;主路径为 create/update 携带 scope)
///
///
/// 示例请求:
/// ```json
/// {
/// "appliedPartnerType": "SPECIFIED",
/// "partnerIds": ["p1"],
/// "appliedRegionType": "ALL",
/// "availabilityType": "SPECIFIED",
/// "locationIds": ["loc1"]
/// }
/// ```
///
/// 文件Id
/// 权限范围
/// 更新后的权限范围
/// 设置成功
/// 参数无效
/// 服务器错误
[HttpPut]
[Route("/api/app/training/file-scope/{id}")]
public async Task SetFileScopeAsync(string id, TrainingFileScopeInputVo input)
{
var entity = await GetFileOrThrowAsync(id);
entity = await ApplyFileScopeAsync(entity, input);
var display = await TrainingFileScopeHelper.BuildScopeDisplayAsync(_dbContext.SqlSugarClient, entity);
return MapScopeOutput(display);
}
private async Task ApplyFileScopeAsync(
FlTrainingFileDbEntity entity,
ITrainingFileScopeInput input)
{
var scope = await TrainingFileScopeHelper.ResolveScopeForSaveAsync(
_dbContext.SqlSugarClient,
input.AppliedPartnerType,
input.PartnerIds,
input.CompanyIds,
input.AppliedRegionType,
input.RegionIds,
input.GroupIds,
ResolveLocationTypeInput(input),
input.LocationIds);
entity.AppliedPartnerType = scope.AppliedPartnerType;
entity.AppliedRegionType = scope.AppliedRegionType;
entity.AvailabilityType = scope.AvailabilityType;
entity.LastModificationTime = DateTime.Now;
entity.LastModifierId = CurrentUser?.Id?.ToString();
await _dbContext.SqlSugarClient.Updateable(entity).ExecuteCommandAsync();
await TrainingFileScopeHelper.SaveScopeAsync(
_dbContext.SqlSugarClient,
entity.Id,
scope,
entity.LastModifierId,
entity.LastModificationTime ?? DateTime.Now);
return entity;
}
private static bool HasScopeInput(ITrainingFileScopeInput input) =>
!string.IsNullOrWhiteSpace(input.AppliedPartnerType)
|| input.PartnerIds is not null
|| input.CompanyIds is not null
|| !string.IsNullOrWhiteSpace(input.AppliedRegionType)
|| input.RegionIds is not null
|| input.GroupIds is not null
|| !string.IsNullOrWhiteSpace(input.AvailabilityType)
|| !string.IsNullOrWhiteSpace(input.AppliedLocationType)
|| input.LocationIds is not null;
private static string? ResolveLocationTypeInput(ITrainingFileScopeInput input) =>
string.IsNullOrWhiteSpace(input.AvailabilityType)
? input.AppliedLocationType
: input.AvailabilityType;
private async Task GetFileOrThrowAsync(string id)
{
var entity = await _dbContext.SqlSugarClient.Queryable()
.FirstAsync(x => x.Id == id && !x.IsDeleted);
if (entity is null)
{
throw new UserFriendlyException("培训文件不存在");
}
return entity;
}
private async Task GetCategoryOrThrowAsync(string id)
{
var entity = await _dbContext.SqlSugarClient.Queryable()
.FirstAsync(x => x.Id == id && !x.IsDeleted);
if (entity is null)
{
throw new UserFriendlyException("分类不存在");
}
return entity;
}
private static TrainingCategoryGetOutputDto MapCategoryOutput(FlTrainingCategoryDbEntity entity) =>
new()
{
Id = entity.Id,
CategoryName = entity.CategoryName,
ParentId = entity.ParentId,
OrderNum = entity.OrderNum,
CreationTime = entity.CreationTime,
LastModificationTime = entity.LastModificationTime
};
private static TrainingCategoryTreeNodeDto MapCategoryNode(
FlTrainingCategoryDbEntity entity,
List files,
IReadOnlyDictionary scopeDisplayMap) =>
new()
{
Id = entity.Id,
CategoryName = entity.CategoryName,
ParentId = entity.ParentId,
OrderNum = entity.OrderNum,
Children = new List(),
Files = files.Select(file =>
{
scopeDisplayMap.TryGetValue(file.Id, out var display);
return MapFileDto(file, display);
}).ToList()
};
private static TrainingFileDto MapFileDto(
FlTrainingFileDbEntity entity,
TrainingFileScopeHelper.TrainingFileScopeDisplay? display = null) =>
new()
{
Id = entity.Id,
CategoryId = entity.CategoryId,
FileName = entity.FileName,
FileUrl = entity.FileUrl,
FileType = entity.FileType,
FileSize = entity.FileSize,
OrderNum = entity.OrderNum,
AppliedPartnerType = display?.AppliedPartnerType ?? entity.AppliedPartnerType,
Company = display?.Company ?? string.Empty,
PartnerIds = display?.PartnerIds ?? new List(),
CompanyIds = display?.PartnerIds ?? new List(),
AppliedRegionType = display?.AppliedRegionType ?? entity.AppliedRegionType,
Region = display?.Region ?? string.Empty,
RegionIds = display?.RegionIds ?? new List(),
GroupIds = display?.RegionIds ?? new List(),
AvailabilityType = display?.AvailabilityType ?? entity.AvailabilityType,
Location = display?.Location ?? string.Empty,
LocationIds = display?.LocationIds ?? new List(),
CreationTime = entity.CreationTime,
LastModificationTime = entity.LastModificationTime
};
private static TrainingFileScopeOutputDto MapScopeOutput(TrainingFileScopeHelper.TrainingFileScopeDisplay display) =>
new()
{
AppliedPartnerType = display.AppliedPartnerType,
Company = display.Company,
PartnerIds = display.PartnerIds,
CompanyIds = display.PartnerIds,
AppliedRegionType = display.AppliedRegionType,
Region = display.Region,
RegionIds = display.RegionIds,
GroupIds = display.RegionIds,
AvailabilityType = display.AvailabilityType,
Location = display.Location,
LocationIds = display.LocationIds
};
private string ResolveTrainingRoot()
{
var linuxRoot = "/www/wwwroot/FoodLabelingManagementSAAS/training";
var webRoot = Path.Combine(_hostEnvironment.ContentRootPath, "wwwroot", "FoodLabelingManagementSAAS", "training");
return Directory.Exists(linuxRoot) ? linuxRoot : webRoot;
}
private static string BuildTrainingUrl(string fileName) => $"/training/{fileName}";
private static string ResolveFileType(string ext)
{
if (ImageExtensions.Contains(ext))
{
return TrainingFileType.Image.ToString().ToLowerInvariant();
}
if (DocExtensions.Contains(ext))
{
return TrainingFileType.Doc.ToString().ToLowerInvariant();
}
return TrainingFileType.Other.ToString().ToLowerInvariant();
}
}