Compare commits
16 Commits
6288312c01
...
feat/profi
| Author | SHA1 | Date | |
|---|---|---|---|
| c10f3c1b58 | |||
| b71e451121 | |||
| d4ea68fc0e | |||
| 142a1a4faa | |||
| 66fe515518 | |||
| f54329ecd3 | |||
| 9b7475bb56 | |||
| 1d0ded0e84 | |||
| 9332a9161d | |||
| 6a04f36388 | |||
| 581bd22c24 | |||
| eafdc17b70 | |||
| c952df5bb4 | |||
| 8c6202d7c0 | |||
| 288f8f98bd | |||
| 659a402555 |
12
src/WorkFlow.API/Base64File.cs
Normal file
12
src/WorkFlow.API/Base64File.cs
Normal file
File diff suppressed because one or more lines are too long
57
src/WorkFlow.API/Controllers/FileController.cs
Normal file
57
src/WorkFlow.API/Controllers/FileController.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Web;
|
||||
|
||||
namespace WorkFlow.API.Controllers;
|
||||
|
||||
[Route("api/[controller]")]
|
||||
[ApiController]
|
||||
[Authorize]
|
||||
public class FileController : ControllerBase
|
||||
{
|
||||
[HttpGet("{path}")]
|
||||
public IActionResult GetFile([FromRoute] string path, [FromQuery] bool icon = false)
|
||||
{
|
||||
string dPath = HttpUtility.UrlDecode(path);
|
||||
|
||||
byte[]? fileBytes = null;
|
||||
string? contentType = null;
|
||||
|
||||
if (dPath == "docs/doc1.pdf" && !icon)
|
||||
{
|
||||
fileBytes = Convert.FromBase64String(Base64File.Doc1Base64);
|
||||
contentType = "application/pdf";
|
||||
}
|
||||
else if (dPath == "icons/icon1.png" && icon)
|
||||
{
|
||||
fileBytes = Convert.FromBase64String(Base64File.Icon1Base64);
|
||||
contentType = "image/png";
|
||||
}
|
||||
else
|
||||
{
|
||||
string fullPath = Path.Combine(AppContext.BaseDirectory, "files", dPath);
|
||||
if (!System.IO.File.Exists(fullPath))
|
||||
return NotFound();
|
||||
|
||||
fileBytes = System.IO.File.ReadAllBytes(fullPath);
|
||||
contentType = GetContentType(fullPath);
|
||||
}
|
||||
|
||||
return File(fileBytes, contentType ?? "application/octet-stream");
|
||||
}
|
||||
|
||||
private string GetContentType(string path)
|
||||
{
|
||||
var ext = Path.GetExtension(path).ToLowerInvariant();
|
||||
return ext switch
|
||||
{
|
||||
".pdf" => "application/pdf",
|
||||
".png" => "image/png",
|
||||
".jpg" => "image/jpeg",
|
||||
".jpeg" => "image/jpeg",
|
||||
".txt" => "text/plain",
|
||||
".docx" => "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||
_ => "application/octet-stream",
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -47,7 +47,11 @@ try
|
||||
?? throw new InvalidOperationException(
|
||||
"The 'MediatRLicense' configuration value is missing or empty." +
|
||||
"Please ensure it is properly set in the configuration source.");
|
||||
builder.Services.AddWorkFlowServices(opt => opt.MediatRLicense = mediatRLicense).AddWorkFlowRepositories();
|
||||
builder.Services.AddWorkFlowServices(opt =>
|
||||
{
|
||||
opt.MediatRLicense = mediatRLicense;
|
||||
opt.ConfigMapping(config.GetSection("MappingOptions"));
|
||||
}).AddWorkFlowRepositories();
|
||||
|
||||
builder.Services.AddUserManager<WFDBContext>();
|
||||
|
||||
|
||||
@@ -5,12 +5,12 @@
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<PackageId>WorkFlow.API</PackageId>
|
||||
<Version>1.1.0</Version>
|
||||
<Version>1.2.1</Version>
|
||||
<Company>Digital Data GmbH</Company>
|
||||
<Product>WorkFlow.API</Product>
|
||||
<Title>WorkFlow.API</Title>
|
||||
<AssemblyVersion>1.1.0</AssemblyVersion>
|
||||
<FileVersion>1.1.0</FileVersion>
|
||||
<AssemblyVersion>1.2.1</AssemblyVersion>
|
||||
<FileVersion>1.2.1</FileVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
17
src/WorkFlow.API/appsettings.Mapping.json
Normal file
17
src/WorkFlow.API/appsettings.Mapping.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"MappingOptions": {
|
||||
"TfFileUri": {
|
||||
"Scheme": "https",
|
||||
"Host": "dd-gan.digitaldata.works",
|
||||
"Port": 8443,
|
||||
"Path": "api/file"
|
||||
},
|
||||
"TfFileIconUri": {
|
||||
"Scheme": "https",
|
||||
"Host": "dd-gan.digitaldata.works",
|
||||
"Port": 8443,
|
||||
"Path": "api/file",
|
||||
"Query": "icon=true"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using WorkFlow.Application.Mapping;
|
||||
|
||||
namespace WorkFlow.Application;
|
||||
@@ -7,26 +8,45 @@ public static class DependencyInjection
|
||||
{
|
||||
public static IServiceCollection AddWorkFlowServices(this IServiceCollection services, Action<WorkFlowServiceOptions>? options = null)
|
||||
{
|
||||
WorkFlowServiceOptions diOptions = new();
|
||||
options?.Invoke(diOptions);
|
||||
WorkFlowServiceOptions sOptions = new(services);
|
||||
options?.Invoke(sOptions);
|
||||
|
||||
services.AddAutoMapper(typeof(MappingProfile).Assembly);
|
||||
services.AddMediatR(cfg =>
|
||||
{
|
||||
cfg.RegisterServicesFromAssembly(typeof(DependencyInjection).Assembly);
|
||||
cfg.LicenseKey = diOptions.MediatRLicense;
|
||||
cfg.LicenseKey = sOptions.MediatRLicense;
|
||||
});
|
||||
|
||||
services.AddSingleton(diOptions.UriBuilderFactory());
|
||||
services.AddTransient<UriBuilderResolver>();
|
||||
if(!sOptions.IsMappingConfigured)
|
||||
services.Configure<MappingOptions>(_ => { });
|
||||
|
||||
services.AddTransient<TfFileUriResolver>();
|
||||
services.AddTransient<TfFileIconUriResolver>();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public class WorkFlowServiceOptions
|
||||
{
|
||||
public string MediatRLicense { get; set; } = string.Empty;
|
||||
private readonly IServiceCollection _services;
|
||||
|
||||
public Func<UriBuilder> UriBuilderFactory { get; set; } = () => new UriBuilder();
|
||||
internal bool IsMappingConfigured { get; private set; } = false;
|
||||
|
||||
public WorkFlowServiceOptions(IServiceCollection services) => _services = services;
|
||||
|
||||
private void EnsureSingleMappingConfiguration(Action action)
|
||||
{
|
||||
if (IsMappingConfigured)
|
||||
throw new InvalidOperationException("Mapping configuration has already been set.");
|
||||
action();
|
||||
IsMappingConfigured = true;
|
||||
}
|
||||
|
||||
public void ConfigMapping(IConfiguration config) => EnsureSingleMappingConfiguration(() => _services.Configure<MappingOptions>(config));
|
||||
|
||||
public void ConfigMapping(Action<MappingOptions> options) => EnsureSingleMappingConfiguration(() => _services.Configure(options));
|
||||
|
||||
public string? MediatRLicense { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -11,4 +11,6 @@ public class ObjectDto
|
||||
public ObjectStateDto? State { get; set; }
|
||||
|
||||
public IEnumerable<ObjectStateHistDto> StateHistories { get; set; } = Array.Empty<ObjectStateHistDto>();
|
||||
|
||||
public IEnumerable<PControlsUpdateDto>? ControlsUpdates { get; set; } = Array.Empty<PControlsUpdateDto>();
|
||||
}
|
||||
|
||||
12
src/WorkFlow.Application/Dto/PControlsUpdateDto.cs
Normal file
12
src/WorkFlow.Application/Dto/PControlsUpdateDto.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace WorkFlow.Application.Dto;
|
||||
|
||||
public record PControlsUpdateDto
|
||||
{
|
||||
public string? AttrName { get; set; }
|
||||
|
||||
public string? AttrValue { get; set; }
|
||||
|
||||
public string? AddedWho { get; set; }
|
||||
|
||||
public DateTime? AddedWhen { get; set; }
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
public class TfFileDto
|
||||
{
|
||||
public UriBuilder Url { get; set; } = null!;
|
||||
public string? Url { get; set; }
|
||||
|
||||
public string? Headline { get; set; }
|
||||
|
||||
@@ -10,5 +10,5 @@ public class TfFileDto
|
||||
|
||||
public string? Comment { get; set; }
|
||||
|
||||
public string? Icon { get; set; }
|
||||
public string? IconUrl { get; set; }
|
||||
}
|
||||
52
src/WorkFlow.Application/Mapping/MappingOptions.cs
Normal file
52
src/WorkFlow.Application/Mapping/MappingOptions.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
namespace WorkFlow.Application.Mapping;
|
||||
|
||||
public class MappingOptions
|
||||
{
|
||||
public UriBuilderOptions TfFileUri { get; set; } = new();
|
||||
|
||||
public UriBuilderOptions TfFileIconUri { get; set; } = new();
|
||||
|
||||
public class UriBuilderOptions
|
||||
{
|
||||
public string? Scheme { get; set; }
|
||||
|
||||
public string? Host { get; set; }
|
||||
|
||||
public int? Port { get; set; }
|
||||
|
||||
private string _path = "/";
|
||||
|
||||
public string Path
|
||||
{
|
||||
get => _path;
|
||||
set => _path = value.Trim('/');
|
||||
}
|
||||
|
||||
private string _query = string.Empty;
|
||||
|
||||
public string Query
|
||||
{
|
||||
get => _query;
|
||||
set => _query = value.TrimStart('?');
|
||||
}
|
||||
|
||||
public UriBuilder ToBuilder
|
||||
{
|
||||
get
|
||||
{
|
||||
var uriBuilder = new UriBuilder()
|
||||
{
|
||||
Scheme = Scheme ?? "http",
|
||||
Host = Host ?? "localhost",
|
||||
Path = Path,
|
||||
Query = Query,
|
||||
};
|
||||
|
||||
if (Port is int port)
|
||||
uriBuilder.Port = port;
|
||||
|
||||
return uriBuilder;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,9 @@ public class MappingProfile : AutoMapper.Profile
|
||||
.ForMember(dest => dest.Others, opt => opt.MapFrom(src => new string?[] { src.State2, src.State3, src.State4 }));
|
||||
|
||||
CreateMap<TfFile, TfFileDto>()
|
||||
.ForMember(dest => dest.Url, opt => opt.MapFrom<UriBuilderResolver>());
|
||||
.ForMember(dest => dest.Url, opt => opt.MapFrom<TfFileUriResolver>())
|
||||
.ForMember(dest => dest.IconUrl, opt => opt.MapFrom<TfFileIconUriResolver>());
|
||||
|
||||
CreateMap<PControlsUpdate, PControlsUpdateDto>();
|
||||
}
|
||||
}
|
||||
24
src/WorkFlow.Application/Mapping/TfFileIconUriResolver.cs
Normal file
24
src/WorkFlow.Application/Mapping/TfFileIconUriResolver.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using AutoMapper;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System.Web;
|
||||
using WorkFlow.Application.Dto;
|
||||
using WorkFlow.Domain.Entities;
|
||||
|
||||
namespace WorkFlow.Application.Mapping;
|
||||
|
||||
public class TfFileIconUriResolver : IValueResolver<TfFile, TfFileDto, string?>
|
||||
{
|
||||
private readonly Func<UriBuilder> _uriBuilderFactory;
|
||||
|
||||
public TfFileIconUriResolver(IOptions<MappingOptions> options) => _uriBuilderFactory = () => options.Value.TfFileIconUri.ToBuilder;
|
||||
|
||||
public string? Resolve(TfFile source, TfFileDto destination, string? destMember, ResolutionContext context)
|
||||
{
|
||||
if(string.IsNullOrEmpty(source.Icon))
|
||||
return null;
|
||||
|
||||
var builder = _uriBuilderFactory();
|
||||
builder.AppendPath(source.Icon);
|
||||
return builder.ToString();
|
||||
}
|
||||
}
|
||||
23
src/WorkFlow.Application/Mapping/TfFileUriResolver.cs
Normal file
23
src/WorkFlow.Application/Mapping/TfFileUriResolver.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using AutoMapper;
|
||||
using Microsoft.Extensions.Options;
|
||||
using WorkFlow.Application.Dto;
|
||||
using WorkFlow.Domain.Entities;
|
||||
|
||||
namespace WorkFlow.Application.Mapping;
|
||||
|
||||
public class TfFileUriResolver : IValueResolver<TfFile, TfFileDto, string?>
|
||||
{
|
||||
private readonly Func<UriBuilder> _uriBuilderFactory;
|
||||
|
||||
public TfFileUriResolver(IOptions<MappingOptions> options) => _uriBuilderFactory = () => options.Value.TfFileUri.ToBuilder;
|
||||
|
||||
public string? Resolve(TfFile source, TfFileDto destination, string? destMember, ResolutionContext context)
|
||||
{
|
||||
if (string.IsNullOrEmpty(source.Path))
|
||||
return null;
|
||||
|
||||
var builder = _uriBuilderFactory();
|
||||
builder.AppendPath(source.Path);
|
||||
return builder.ToString();
|
||||
}
|
||||
}
|
||||
14
src/WorkFlow.Application/Mapping/UriBuilderExtensions.cs
Normal file
14
src/WorkFlow.Application/Mapping/UriBuilderExtensions.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System.Web;
|
||||
|
||||
namespace WorkFlow.Application.Mapping;
|
||||
|
||||
internal static class UriBuilderExtensions
|
||||
{
|
||||
public static void AppendPath(this UriBuilder uriBuilder, string path, bool encode = true)
|
||||
{
|
||||
if(encode)
|
||||
path = HttpUtility.UrlEncode(path);
|
||||
|
||||
uriBuilder.Path = $"{uriBuilder.Path.TrimEnd('/', '\\')}/{path.Trim('/', '\\')}";
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
using AutoMapper;
|
||||
using System.Web;
|
||||
using WorkFlow.Application.Dto;
|
||||
using WorkFlow.Domain.Entities;
|
||||
|
||||
namespace WorkFlow.Application.Mapping;
|
||||
|
||||
public class UriBuilderResolver : IValueResolver<TfFile, TfFileDto, UriBuilder>
|
||||
{
|
||||
private readonly UriBuilder _uriBuilder;
|
||||
|
||||
public UriBuilderResolver(UriBuilder uriBuilder) => _uriBuilder = uriBuilder;
|
||||
|
||||
public UriBuilder Resolve(TfFile source, TfFileDto destination, UriBuilder destMember, ResolutionContext context)
|
||||
{
|
||||
var builder = new UriBuilder(_uriBuilder.Uri)
|
||||
{
|
||||
Path = HttpUtility.UrlEncode(source.Path)
|
||||
};
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
69
src/WorkFlow.Domain/Entities/PControlsUpdate.cs
Normal file
69
src/WorkFlow.Domain/Entities/PControlsUpdate.cs
Normal file
@@ -0,0 +1,69 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace WorkFlow.Domain.Entities;
|
||||
|
||||
[Table("TBMWF_PROFILE_CONTROLS_UPDATE")]
|
||||
public class PControlsUpdate
|
||||
{
|
||||
[Column("GUID", TypeName = "bigint")]
|
||||
public long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Although this field is marked as <c>nullable</c> in the database schema to allow
|
||||
/// for greater flexibility during database-level operations or migrations, it is
|
||||
/// treated as <c>required</c> and <c>not null</c> within the application logic.
|
||||
/// Validation should be enforced at the application level to ensure a value is provided.
|
||||
/// </summary>
|
||||
[Column("MWF_PROFILE_ID", TypeName = "int")]
|
||||
public int ProfileId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Although this field is marked as <c>nullable</c> in the database schema to allow
|
||||
/// for greater flexibility during database-level operations or migrations, it is
|
||||
/// treated as <c>required</c> and <c>not null</c> within the application logic.
|
||||
/// Validation should be enforced at the application level to ensure a value is provided.
|
||||
/// </summary>
|
||||
[Column("USR_ID", TypeName = "int")]
|
||||
public int? UsrId { get; set; }
|
||||
|
||||
[Required]
|
||||
[Column("OBJ_ID", TypeName = "bigint")]
|
||||
public long ObjId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Although this field is marked as <c>nullable</c> in the database schema to allow
|
||||
/// for greater flexibility during database-level operations or migrations, it is
|
||||
/// treated as <c>required</c> and <c>not null</c> within the application logic.
|
||||
/// Validation should be enforced at the application level to ensure a value is provided.
|
||||
/// </summary>
|
||||
[Column("ATTR_NAME", TypeName = "nvarchar(100)")]
|
||||
public string? AttrName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Although this field is marked as <c>nullable</c> in the database schema to allow
|
||||
/// for greater flexibility during database-level operations or migrations, it is
|
||||
/// treated as <c>required</c> and <c>not null</c> within the application logic.
|
||||
/// Validation should be enforced at the application level to ensure a value is provided.
|
||||
/// </summary>
|
||||
[Column("ATTR_VALUE", TypeName = "nvarchar(3000)")]
|
||||
public string? AttrValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Although this field is marked as <c>nullable</c> in the database schema to allow
|
||||
/// for greater flexibility during database-level operations or migrations, it is
|
||||
/// treated as <c>required</c> and <c>not null</c> within the application logic.
|
||||
/// Validation should be enforced at the application level to ensure a value is provided.
|
||||
/// </summary>
|
||||
[Column("ADDED_WHO", TypeName = "nvarchar(30)")]
|
||||
public string? AddedWho { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Although this field is marked as <c>nullable</c> in the database schema to allow
|
||||
/// for greater flexibility during database-level operations or migrations, it is
|
||||
/// treated as <c>required</c> and <c>not null</c> within the application logic.
|
||||
/// Validation should be enforced at the application level to ensure a value is provided.
|
||||
/// </summary>
|
||||
[Column("ADDED_WHEN", TypeName = "datetime")]
|
||||
public DateTime? AddedWhen { get; set; }
|
||||
}
|
||||
@@ -29,4 +29,6 @@ public class PObject
|
||||
public virtual PObjectState? State { get; set; }
|
||||
|
||||
public virtual IEnumerable<PObjectStateHist>? StateHistories { get; set; }
|
||||
|
||||
public virtual IEnumerable<PControlsUpdate>? ControlsUpdates { get; set; }
|
||||
}
|
||||
@@ -39,5 +39,6 @@ public class PObjectRepository : IProfileObjRepository
|
||||
.Include(obj => obj.State).ThenInclude(objState => objState != null ? objState.TFControls : null)
|
||||
.Include(obj => obj.State).ThenInclude(objState => objState != null ? objState.TfFiles : null)
|
||||
.Include(obj => obj.StateHistories!).ThenInclude(hist => hist != null ? hist.State1 : null)
|
||||
.Include(obj => obj.ControlsUpdates)
|
||||
.ToListAsync(cancel);
|
||||
}
|
||||
@@ -43,7 +43,6 @@ public class WFDBContext : DbContext, IUserManagerDbContext
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
//configure model builder for user manager tables
|
||||
modelBuilder.ConfigureUserManager();
|
||||
|
||||
modelBuilder.Entity<PObjectState>()
|
||||
@@ -62,6 +61,12 @@ public class WFDBContext : DbContext, IUserManagerDbContext
|
||||
.WithOne()
|
||||
.HasForeignKey(hist => hist.ObjectId);
|
||||
|
||||
modelBuilder.Entity<PObject>()
|
||||
.HasMany(obj => obj.ControlsUpdates)
|
||||
.WithOne()
|
||||
.HasForeignKey(cu => cu.ObjId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
base.OnModelCreating(modelBuilder);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user