Merge branch 'master' of https://git.nejcraft.cz/Nejcraft/NejCommon.NET
This commit is contained in:
commit
d7e858eea4
28
AutoScan.cs
28
AutoScan.cs
|
|
@ -1,3 +1,7 @@
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using NejAccountingAPI.Models;
|
||||||
|
using Quartz;
|
||||||
|
|
||||||
namespace NejCommon;
|
namespace NejCommon;
|
||||||
|
|
||||||
public interface IScopedService
|
public interface IScopedService
|
||||||
|
|
@ -15,6 +19,11 @@ public interface ITransientService
|
||||||
public interface IBackgroundService
|
public interface IBackgroundService
|
||||||
{
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
public interface IRecurringService : IJob
|
||||||
|
{
|
||||||
|
public static virtual JobKey? JobKey { get; }
|
||||||
|
public static abstract string Cron { get; }
|
||||||
}
|
}
|
||||||
public interface ISettings
|
public interface ISettings
|
||||||
{
|
{
|
||||||
|
|
@ -23,6 +32,25 @@ public interface ISettings
|
||||||
|
|
||||||
public static class AutoScan
|
public static class AutoScan
|
||||||
{
|
{
|
||||||
|
public static void RegisterJobs(this IServiceCollection collection, IServiceCollectionQuartzConfigurator conf)
|
||||||
|
{
|
||||||
|
var jobs = typeof(IRecurringService).Assembly.GetTypes().Where(x => x.IsAssignableTo(typeof(IRecurringService)) && x != typeof(IRecurringService)).ToList();
|
||||||
|
|
||||||
|
foreach (var job in jobs)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Testing job: " + job.Name);
|
||||||
|
var jobKey = job.GetProperty("JobKey")?.GetValue(null) as JobKey ?? new JobKey(job.FullName);
|
||||||
|
var cron = job.GetProperty("Cron")!.GetValue(null) as string;
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(cron) || jobKey == null)
|
||||||
|
{
|
||||||
|
throw new Exception("Could not register job: " + job.Name);
|
||||||
|
}
|
||||||
|
Console.WriteLine("Adding job: " + jobKey.Name + " with cron: " + cron);
|
||||||
|
|
||||||
|
conf.AddJob(job, jobKey).AddTrigger(x => x.ForJob(jobKey).WithCronSchedule(cron).StartNow());
|
||||||
|
}
|
||||||
|
}
|
||||||
public static void RegisterServices(this IServiceCollection collection)
|
public static void RegisterServices(this IServiceCollection collection)
|
||||||
{
|
{
|
||||||
collection.Scan(scan => scan
|
collection.Scan(scan => scan
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,8 @@ namespace NejCommon.Controllers
|
||||||
{
|
{
|
||||||
public abstract class AutoChildController<TOwner, TType, TRequest, TResponse> : AutoChildController<TOwner, TType, TResponse, TRequest, TResponse>
|
public abstract class AutoChildController<TOwner, TType, TRequest, TResponse> : AutoChildController<TOwner, TType, TResponse, TRequest, TResponse>
|
||||||
where TType : class, new()
|
where TType : class, new()
|
||||||
where TRequest : IAutomappedAttribute<TType, TRequest>, new()
|
where TRequest : class, IAutomappedAttribute<TType, TRequest>, new()
|
||||||
where TResponse : IAutomappedAttribute<TType, TResponse>, new()
|
where TResponse : class, IAutomappedAttribute<TType, TResponse>, new()
|
||||||
{
|
{
|
||||||
public AutoChildController(CommonDbContext appDb, IServiceProvider providers) : base(appDb, providers)
|
public AutoChildController(CommonDbContext appDb, IServiceProvider providers) : base(appDb, providers)
|
||||||
{
|
{
|
||||||
|
|
@ -34,9 +34,9 @@ namespace NejCommon.Controllers
|
||||||
[ApiController]
|
[ApiController]
|
||||||
public abstract class AutoChildController<TOwner, TType, TGetResponse, TUpdateRequest, TUpdateResponse> : ControllerBase
|
public abstract class AutoChildController<TOwner, TType, TGetResponse, TUpdateRequest, TUpdateResponse> : ControllerBase
|
||||||
where TType : class, new()
|
where TType : class, new()
|
||||||
where TGetResponse : IAutomappedAttribute<TType, TGetResponse>, new()
|
where TGetResponse : class, IAutomappedAttribute<TType, TGetResponse>, new()
|
||||||
where TUpdateRequest : IAutomappedAttribute<TType, TUpdateRequest>, new()
|
where TUpdateRequest : class, IAutomappedAttribute<TType, TUpdateRequest>, new()
|
||||||
where TUpdateResponse : IAutomappedAttribute<TType, TUpdateResponse>, new()
|
where TUpdateResponse : class, IAutomappedAttribute<TType, TUpdateResponse>, new()
|
||||||
{
|
{
|
||||||
protected readonly CommonDbContext db;
|
protected readonly CommonDbContext db;
|
||||||
protected readonly IServiceProvider providers;
|
protected readonly IServiceProvider providers;
|
||||||
|
|
@ -100,8 +100,6 @@ namespace NejCommon.Controllers
|
||||||
}*/
|
}*/
|
||||||
body.ApplyTo(providers, entity);
|
body.ApplyTo(providers, entity);
|
||||||
|
|
||||||
var dat = new TUpdateResponse().ApplyFrom(providers, entity);
|
|
||||||
|
|
||||||
var res = await db.ApiSaveChangesAsyncOk<TType, TUpdateResponse>(providers, entity);
|
var res = await db.ApiSaveChangesAsyncOk<TType, TUpdateResponse>(providers, entity);
|
||||||
|
|
||||||
//use the private constructor thru reflection
|
//use the private constructor thru reflection
|
||||||
|
|
|
||||||
|
|
@ -17,9 +17,9 @@ namespace NejCommon.Controllers
|
||||||
{
|
{
|
||||||
|
|
||||||
public abstract class AutoController<TOwner, TType, TRequest, TResponse> : AutoController<TOwner, TType, TResponse, TRequest, TResponse, TResponse, TRequest, TResponse>
|
public abstract class AutoController<TOwner, TType, TRequest, TResponse> : AutoController<TOwner, TType, TResponse, TRequest, TResponse, TResponse, TRequest, TResponse>
|
||||||
where TType : class, new()
|
where TType : class
|
||||||
where TRequest : IAutomappedAttribute<TType, TRequest>, new()
|
where TRequest : class, IAutomappedAttribute<TType, TRequest>, new()
|
||||||
where TResponse : IAutomappedAttribute<TType, TResponse>, new()
|
where TResponse : class, IAutomappedAttribute<TType, TResponse>, new()
|
||||||
{
|
{
|
||||||
public AutoController(CommonDbContext appDb, IServiceProvider providers) : base(appDb, providers)
|
public AutoController(CommonDbContext appDb, IServiceProvider providers) : base(appDb, providers)
|
||||||
{
|
{
|
||||||
|
|
@ -34,13 +34,13 @@ namespace NejCommon.Controllers
|
||||||
/// <typeparam name="TResponse">The response type</typeparam>
|
/// <typeparam name="TResponse">The response type</typeparam>
|
||||||
[ApiController]
|
[ApiController]
|
||||||
public abstract partial class AutoController<TOwner, TType, TGetAllResponse, TCreateRequest, TCreateResponse, TGetResponse, TUpdateRequest, TUpdateResponse> : AutoGetterController<TOwner, TType, TGetAllResponse, TGetResponse>
|
public abstract partial class AutoController<TOwner, TType, TGetAllResponse, TCreateRequest, TCreateResponse, TGetResponse, TUpdateRequest, TUpdateResponse> : AutoGetterController<TOwner, TType, TGetAllResponse, TGetResponse>
|
||||||
where TType : class, new()
|
where TType : class
|
||||||
where TGetAllResponse : IAutomappedAttribute<TType, TGetAllResponse>, new()
|
where TGetAllResponse : class, IAutomappedAttribute<TType, TGetAllResponse>, new()
|
||||||
where TCreateRequest : IAutomappedAttribute<TType, TCreateRequest>, new()
|
where TCreateRequest : class, IAutomappedAttribute<TType, TCreateRequest>, new()
|
||||||
where TCreateResponse : IAutomappedAttribute<TType, TCreateResponse>, new()
|
where TCreateResponse : class, IAutomappedAttribute<TType, TCreateResponse>, new()
|
||||||
where TGetResponse : IAutomappedAttribute<TType, TGetResponse>, new()
|
where TGetResponse : class, IAutomappedAttribute<TType, TGetResponse>, new()
|
||||||
where TUpdateRequest : IAutomappedAttribute<TType, TUpdateRequest>, new()
|
where TUpdateRequest : class, IAutomappedAttribute<TType, TUpdateRequest>, new()
|
||||||
where TUpdateResponse : IAutomappedAttribute<TType, TUpdateResponse>, new()
|
where TUpdateResponse : class, IAutomappedAttribute<TType, TUpdateResponse>, new()
|
||||||
{
|
{
|
||||||
|
|
||||||
public AutoController(CommonDbContext appDb, IServiceProvider providers) : base(appDb, providers)
|
public AutoController(CommonDbContext appDb, IServiceProvider providers) : base(appDb, providers)
|
||||||
|
|
@ -59,6 +59,12 @@ namespace NejCommon.Controllers
|
||||||
props.SetValue(entity, comp);
|
props.SetValue(entity, comp);
|
||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
|
protected override IAutomappedAttribute GetResponseType(RequestedResponseType type, TType entity) => type switch
|
||||||
|
{
|
||||||
|
RequestedResponseType.Create => new TCreateResponse(),
|
||||||
|
RequestedResponseType.Update => new TUpdateResponse(),
|
||||||
|
_ => base.GetResponseType(type, entity),
|
||||||
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates the <typeparamref name="TType"/>
|
/// Creates the <typeparamref name="TType"/>
|
||||||
|
|
@ -69,15 +75,16 @@ namespace NejCommon.Controllers
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public virtual async Task<Results<BadRequest<Error>, CreatedAtRoute<TCreateResponse>>> Create([FromServices] TOwner company, [FromBody] TCreateRequest body)
|
public virtual async Task<Results<BadRequest<Error>, CreatedAtRoute<TCreateResponse>>> Create([FromServices] TOwner company, [FromBody] TCreateRequest body)
|
||||||
{
|
{
|
||||||
var entity = db.Create<TType>();
|
var type = body.GetSourceType();
|
||||||
|
var entity = (TType)db.Create(type);
|
||||||
|
|
||||||
entity = AssociateWithParent(entity, company);
|
entity = AssociateWithParent(entity, company);
|
||||||
|
|
||||||
await db.AddAsync(entity);
|
await db.AddAsync(entity);
|
||||||
|
|
||||||
body.ApplyTo(providers, entity);
|
body.ApplyTo(providers, (object)entity);
|
||||||
|
|
||||||
return await db.ApiSaveChangesAsyncCreate<TType, TCreateResponse>(providers, entity);
|
return await db.ApiSaveChangesAsyncCreate<TType, TCreateResponse>(providers, entity, true, (TCreateResponse)GetResponseType(RequestedResponseType.Create, entity));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -106,11 +113,11 @@ namespace NejCommon.Controllers
|
||||||
[Route("{id}/")]
|
[Route("{id}/")]
|
||||||
public virtual async Task<Results<NotFound, BadRequest<Error>, Ok<TUpdateResponse>>> Update([FromServices][ModelBinder(Name = "id")] TType entity, [FromBody] TUpdateRequest body)
|
public virtual async Task<Results<NotFound, BadRequest<Error>, Ok<TUpdateResponse>>> Update([FromServices][ModelBinder(Name = "id")] TType entity, [FromBody] TUpdateRequest body)
|
||||||
{
|
{
|
||||||
body.ApplyTo(providers, entity);
|
body.ApplyTo(providers, (object)entity);
|
||||||
|
|
||||||
var dat = new TUpdateResponse().ApplyFrom(providers, entity);
|
var dat = new TUpdateResponse().ApplyFrom(providers, entity);
|
||||||
|
|
||||||
var res = await db.ApiSaveChangesAsyncOk<TType, TUpdateResponse>(providers, entity);
|
var res = await db.ApiSaveChangesAsyncOk<TType, TUpdateResponse>(providers, entity, true, (TUpdateResponse)GetResponseType(RequestedResponseType.Update, entity));
|
||||||
|
|
||||||
//use the private constructor thru reflection
|
//use the private constructor thru reflection
|
||||||
var ctor = typeof(Results<NotFound, BadRequest<Error>, Ok<TUpdateResponse>>).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance)[0];
|
var ctor = typeof(Results<NotFound, BadRequest<Error>, Ok<TUpdateResponse>>).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance)[0];
|
||||||
|
|
@ -119,7 +126,7 @@ namespace NejCommon.Controllers
|
||||||
}
|
}
|
||||||
[ApiController]
|
[ApiController]
|
||||||
public abstract class AutoGetterController<TOwner, TType, TGetAllResponse, TGetResponse> : ControllerBase
|
public abstract class AutoGetterController<TOwner, TType, TGetAllResponse, TGetResponse> : ControllerBase
|
||||||
where TType : class, new()
|
where TType : class
|
||||||
where TGetAllResponse : IAutomappedAttribute<TType, TGetAllResponse>, new()
|
where TGetAllResponse : IAutomappedAttribute<TType, TGetAllResponse>, new()
|
||||||
where TGetResponse : IAutomappedAttribute<TType, TGetResponse>, new()
|
where TGetResponse : IAutomappedAttribute<TType, TGetResponse>, new()
|
||||||
{
|
{
|
||||||
|
|
@ -134,6 +141,18 @@ namespace NejCommon.Controllers
|
||||||
|
|
||||||
protected abstract IQueryable<TType> GetQuery(TOwner comp);
|
protected abstract IQueryable<TType> GetQuery(TOwner comp);
|
||||||
|
|
||||||
|
public enum RequestedResponseType
|
||||||
|
{
|
||||||
|
Get,
|
||||||
|
Create,
|
||||||
|
Update
|
||||||
|
}
|
||||||
|
protected virtual IAutomappedAttribute GetResponseType(RequestedResponseType type, TType entity) => type switch
|
||||||
|
{
|
||||||
|
RequestedResponseType.Get => new TGetResponse(),
|
||||||
|
_ => throw new InvalidOperationException("Not implemented"),
|
||||||
|
};
|
||||||
|
|
||||||
protected virtual IQueryable<TType> ApplyDefaultOrdering(IQueryable<TType> query)
|
protected virtual IQueryable<TType> ApplyDefaultOrdering(IQueryable<TType> query)
|
||||||
{
|
{
|
||||||
return query;
|
return query;
|
||||||
|
|
@ -169,8 +188,8 @@ namespace NejCommon.Controllers
|
||||||
[Route("{id}/")]
|
[Route("{id}/")]
|
||||||
public virtual async Task<Results<NotFound, Ok<TGetResponse>>> Get([FromServices][ModelBinder(Name = "id")] TType entity)
|
public virtual async Task<Results<NotFound, Ok<TGetResponse>>> Get([FromServices][ModelBinder(Name = "id")] TType entity)
|
||||||
{
|
{
|
||||||
var dat = new TGetResponse().ApplyFrom(providers, entity);
|
var dat = GetResponseType(RequestedResponseType.Get, entity).ApplyFrom(providers, entity);
|
||||||
return TypedResults.Ok(dat);
|
return TypedResults.Ok((TGetResponse)dat);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ namespace NejCommon.Controllers
|
||||||
var key = context.SchemaRepository.Schemas.FirstOrDefault(k => k.Value == schema).Key;
|
var key = context.SchemaRepository.Schemas.FirstOrDefault(k => k.Value == schema).Key;
|
||||||
if (string.IsNullOrWhiteSpace(key))
|
if (string.IsNullOrWhiteSpace(key))
|
||||||
continue;
|
continue;
|
||||||
//Console.WriteLine($"Removing schema {key}");
|
Console.WriteLine($"Removing schema {key}");
|
||||||
context.SchemaRepository.Schemas.Remove(key);
|
context.SchemaRepository.Schemas.Remove(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -86,7 +86,7 @@ namespace NejCommon.Controllers
|
||||||
|
|
||||||
var orphanedSchemas = swaggerDoc.Components.Schemas.Where(x => !currentSchemas.ContainsKey(x.Key)).ToDictionary(x => x.Key, x => x.Value);
|
var orphanedSchemas = swaggerDoc.Components.Schemas.Where(x => !currentSchemas.ContainsKey(x.Key)).ToDictionary(x => x.Key, x => x.Value);
|
||||||
|
|
||||||
//Console.WriteLine($"Unused schemas: {string.Join(", ", orphanedSchemas.Keys)}");
|
Console.WriteLine($"Unused schemas: {string.Join(", ", orphanedSchemas.Keys)}");
|
||||||
|
|
||||||
swaggerDoc.Components.Schemas = swaggerDoc.Components.Schemas.Where(x => currentSchemas.ContainsKey(x.Key)).ToDictionary(x => x.Key, x => x.Value);
|
swaggerDoc.Components.Schemas = swaggerDoc.Components.Schemas.Where(x => currentSchemas.ContainsKey(x.Key)).ToDictionary(x => x.Key, x => x.Value);
|
||||||
}
|
}
|
||||||
|
|
@ -113,6 +113,18 @@ namespace NejCommon.Controllers
|
||||||
{
|
{
|
||||||
ExpandSchemas(repo, currentSchemas, sch);
|
ExpandSchemas(repo, currentSchemas, sch);
|
||||||
}
|
}
|
||||||
|
foreach(var sch in schemaToExpand.OneOf)
|
||||||
|
{
|
||||||
|
ExpandSchemas(repo, currentSchemas, sch);
|
||||||
|
}
|
||||||
|
foreach(var sch in schemaToExpand.AnyOf)
|
||||||
|
{
|
||||||
|
ExpandSchemas(repo, currentSchemas, sch);
|
||||||
|
}
|
||||||
|
foreach(var sch in schemaToExpand.AllOf)
|
||||||
|
{
|
||||||
|
ExpandSchemas(repo, currentSchemas, sch);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,7 +1,14 @@
|
||||||
|
|
||||||
using System.Diagnostics.Eventing.Reader;
|
using System.Diagnostics.Eventing.Reader;
|
||||||
|
using System.Reflection;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
using Microsoft.AspNetCore.Http.HttpResults;
|
using Microsoft.AspNetCore.Http.HttpResults;
|
||||||
|
using Microsoft.AspNetCore.Http.Metadata;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Controllers;
|
||||||
|
using Microsoft.AspNetCore.WebUtilities;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
using Microsoft.OpenApi.Models;
|
using Microsoft.OpenApi.Models;
|
||||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||||
|
|
||||||
|
|
@ -9,100 +16,144 @@ namespace NejCommon.Controllers
|
||||||
{
|
{
|
||||||
public class TypedResultsMetadataProvider : IOperationFilter
|
public class TypedResultsMetadataProvider : IOperationFilter
|
||||||
{
|
{
|
||||||
public void Apply(OpenApiOperation operation, OperationFilterContext context)
|
private readonly Lazy<string[]> _contentTypes;
|
||||||
{
|
|
||||||
var responseType = context.MethodInfo.ReturnType;
|
|
||||||
//Console.WriteLine(context.MethodInfo.DeclaringType.Name);
|
|
||||||
//Console.WriteLine(context.MethodInfo.Name);
|
|
||||||
//Console.WriteLine(responseType);
|
|
||||||
var t = IsSubclassOfRawGeneric(typeof(Microsoft.AspNetCore.Http.HttpResults.Results<,>), responseType);
|
|
||||||
if (t == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var parArg = t.GetGenericArguments();
|
/// <summary>
|
||||||
if (operation.Responses.ContainsKey("200"))
|
/// Constructor to inject services
|
||||||
operation.Responses.Remove("200");
|
/// </summary>
|
||||||
|
/// <param name="mvc">MVC options to define response content types</param>
|
||||||
foreach (var arg in parArg)
|
public TypedResultsMetadataProvider(IOptions<MvcOptions> mvc)
|
||||||
{
|
{
|
||||||
if (arg == typeof(NotFound))
|
_contentTypes = new Lazy<string[]>(() =>
|
||||||
{
|
{
|
||||||
operation.Responses.Add("404", new OpenApiResponse { Description = "Not found" });
|
var apiResponseTypes = new List<string>();
|
||||||
}
|
if (mvc.Value == null)
|
||||||
else if (arg == typeof(Ok))
|
|
||||||
{
|
{
|
||||||
operation.Responses.Add("200", new OpenApiResponse { Description = "Success" });
|
apiResponseTypes.Add("application/json");
|
||||||
}
|
|
||||||
else if (IsSubclassOfRawGeneric(typeof(Ok<>), arg) != null)
|
|
||||||
{
|
|
||||||
|
|
||||||
var okArg = IsSubclassOfRawGeneric(typeof(Ok<>), arg).GetGenericArguments()[0];
|
|
||||||
// Console.WriteLine("Adding: " + okArg);
|
|
||||||
|
|
||||||
//get or generate the schema
|
|
||||||
var schema = context.SchemaGenerator.GenerateSchema(okArg, context.SchemaRepository);
|
|
||||||
operation.Responses.Add("200", new OpenApiResponse { Description = "Success", Content = { { "application/json", new OpenApiMediaType { Schema = schema } } } });
|
|
||||||
|
|
||||||
}
|
|
||||||
else if (arg == typeof(CreatedAtRoute))
|
|
||||||
{
|
|
||||||
operation.Responses.Add("201", new OpenApiResponse { Description = "Success" });
|
|
||||||
}
|
|
||||||
else if (IsSubclassOfRawGeneric(typeof(CreatedAtRoute<>), arg) != null)
|
|
||||||
{
|
|
||||||
if (operation.Responses.ContainsKey("201"))
|
|
||||||
operation.Responses.Remove("201");
|
|
||||||
|
|
||||||
var okArg = IsSubclassOfRawGeneric(typeof(CreatedAtRoute<>), arg).GetGenericArguments()[0];
|
|
||||||
// Console.WriteLine("Adding: " + okArg);
|
|
||||||
|
|
||||||
//get or generate the schema
|
|
||||||
var schema = context.SchemaGenerator.GenerateSchema(okArg, context.SchemaRepository);
|
|
||||||
operation.Responses.Add("201", new OpenApiResponse { Description = "Success", Content = { { "application/json", new OpenApiMediaType { Schema = schema } } } });
|
|
||||||
}
|
|
||||||
else if (arg == typeof(BadRequest))
|
|
||||||
{
|
|
||||||
operation.Responses.Add("400", new OpenApiResponse { Description = "There was an error" });
|
|
||||||
}
|
|
||||||
else if (IsSubclassOfRawGeneric(typeof(BadRequest<>), arg) != null)
|
|
||||||
{
|
|
||||||
if (operation.Responses.ContainsKey("400"))
|
|
||||||
operation.Responses.Remove("400");
|
|
||||||
|
|
||||||
var okArg = IsSubclassOfRawGeneric(typeof(BadRequest<>), arg).GetGenericArguments()[0];
|
|
||||||
// Console.WriteLine("Adding: " + okArg);
|
|
||||||
|
|
||||||
//get or generate the schema
|
|
||||||
var schema = context.SchemaGenerator.GenerateSchema(okArg, context.SchemaRepository);
|
|
||||||
operation.Responses.Add("400", new OpenApiResponse { Description = "There was an error", Content = { { "application/json", new OpenApiMediaType { Schema = schema } } } });
|
|
||||||
}
|
|
||||||
else if (arg == typeof(FileStreamHttpResult)){
|
|
||||||
operation.Responses.Add("200", new OpenApiResponse { Description = "Success", Content = { { "application/octet-stream", new OpenApiMediaType { Schema = new OpenApiSchema { Type = "string", Format = "binary" } } } } });
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Console.WriteLine("Unknown type: " + arg);
|
var jsonApplicationType = mvc.Value.FormatterMappings.GetMediaTypeMappingForFormat("json");
|
||||||
|
if (jsonApplicationType != null)
|
||||||
|
apiResponseTypes.Add(jsonApplicationType);
|
||||||
|
var xmlApplicationType = mvc.Value.FormatterMappings.GetMediaTypeMappingForFormat("xml");
|
||||||
|
if (xmlApplicationType != null)
|
||||||
|
apiResponseTypes.Add(xmlApplicationType);
|
||||||
|
}
|
||||||
|
return apiResponseTypes.ToArray();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void IOperationFilter.Apply(OpenApiOperation operation, OperationFilterContext context)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (!IsControllerAction(context)) return;
|
||||||
|
|
||||||
|
var actionReturnType = UnwrapTask(context.MethodInfo.ReturnType);
|
||||||
|
if (!IsHttpResults(actionReturnType)) return;
|
||||||
|
|
||||||
|
if (typeof(IEndpointMetadataProvider).IsAssignableFrom(actionReturnType))
|
||||||
|
{
|
||||||
|
var populateMetadataMethod = actionReturnType.GetMethod("Microsoft.AspNetCore.Http.Metadata.IEndpointMetadataProvider.PopulateMetadata", BindingFlags.Static | BindingFlags.NonPublic);
|
||||||
|
if (populateMetadataMethod == null) return;
|
||||||
|
|
||||||
|
var endpointBuilder = new MetadataEndpointBuilder();
|
||||||
|
populateMetadataMethod.Invoke(null, new object[] { context.MethodInfo, endpointBuilder });
|
||||||
|
|
||||||
|
var responseTypes = endpointBuilder.Metadata.Cast<IProducesResponseTypeMetadata>().ToList();
|
||||||
|
if (!responseTypes.Any()) return;
|
||||||
|
operation.Responses.Clear();
|
||||||
|
foreach (var responseType in responseTypes)
|
||||||
|
{
|
||||||
|
var statusCode = responseType.StatusCode.ToString();
|
||||||
|
var oar = new OpenApiResponse { Description = GetResponseDescription(statusCode) };
|
||||||
|
|
||||||
|
if (responseType.Type != null && responseType.Type != typeof(void))
|
||||||
|
{
|
||||||
|
var schema = context.SchemaGenerator.GenerateSchema(responseType.Type, context.SchemaRepository);
|
||||||
|
foreach (var contentType in _contentTypes.Value)
|
||||||
|
{
|
||||||
|
oar.Content.Add(contentType, new OpenApiMediaType { Schema = schema });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
operation.Responses.Add(statusCode, oar);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (actionReturnType == typeof(UnauthorizedHttpResult))
|
||||||
|
{
|
||||||
|
operation.Responses.Clear();
|
||||||
|
operation.Responses.Add("401", new OpenApiResponse { Description = ReasonPhrases.GetReasonPhrase(401) });
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var generics = actionReturnType.GetGenericArguments();
|
||||||
|
foreach (var generic in generics)
|
||||||
|
{
|
||||||
|
if (generic == typeof(FileStreamHttpResult))
|
||||||
|
{
|
||||||
|
var statusCode = "200";
|
||||||
|
var oar = new OpenApiResponse { Description = GetResponseDescription(statusCode) };
|
||||||
|
oar.Content.Add( "application/octet-stream", new OpenApiMediaType { Schema = new OpenApiSchema { Type = "string", Format = "binary" } });
|
||||||
|
operation.Responses.Add(statusCode, oar);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static Type? IsSubclassOfRawGeneric(Type generic, Type toCheck)
|
private static bool IsControllerAction(OperationFilterContext context)
|
||||||
|
=> context.ApiDescription.ActionDescriptor is ControllerActionDescriptor;
|
||||||
|
|
||||||
|
private static bool IsHttpResults(Type type)
|
||||||
|
=> type.Namespace == "Microsoft.AspNetCore.Http.HttpResults";
|
||||||
|
|
||||||
|
private static Type UnwrapTask(Type type)
|
||||||
{
|
{
|
||||||
while (toCheck != null && toCheck != typeof(object))
|
if (type.IsGenericType)
|
||||||
{
|
{
|
||||||
//if Task is used, we need to check the underlying type
|
var genericType = type.GetGenericTypeDefinition();
|
||||||
var realTypeNoTask = toCheck.IsGenericType && toCheck.GetGenericTypeDefinition() == typeof(Task<>) ? toCheck.GetGenericArguments()[0] : toCheck;
|
if (genericType == typeof(Task<>) || genericType == typeof(ValueTask<>))
|
||||||
var cur = realTypeNoTask.IsGenericType ? realTypeNoTask.GetGenericTypeDefinition() : realTypeNoTask;
|
|
||||||
//Console.WriteLine(cur);
|
|
||||||
if (generic == cur)
|
|
||||||
{
|
{
|
||||||
return realTypeNoTask;
|
return type.GetGenericArguments()[0];
|
||||||
}
|
}
|
||||||
toCheck = toCheck.BaseType;
|
|
||||||
}
|
}
|
||||||
return null;
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string? GetResponseDescription(string statusCode)
|
||||||
|
=> ResponseDescriptionMap
|
||||||
|
.FirstOrDefault(entry => Regex.IsMatch(statusCode, entry.Key))
|
||||||
|
.Value;
|
||||||
|
|
||||||
|
private static readonly IReadOnlyCollection<KeyValuePair<string, string>> ResponseDescriptionMap = new[]
|
||||||
|
{
|
||||||
|
new KeyValuePair<string, string>("1\\d{2}", "Information"),
|
||||||
|
|
||||||
|
new KeyValuePair<string, string>("201", "Created"),
|
||||||
|
new KeyValuePair<string, string>("202", "Accepted"),
|
||||||
|
new KeyValuePair<string, string>("204", "No Content"),
|
||||||
|
new KeyValuePair<string, string>("2\\d{2}", "Success"),
|
||||||
|
|
||||||
|
new KeyValuePair<string, string>("304", "Not Modified"),
|
||||||
|
new KeyValuePair<string, string>("3\\d{2}", "Redirect"),
|
||||||
|
|
||||||
|
new KeyValuePair<string, string>("400", "Bad Request"),
|
||||||
|
new KeyValuePair<string, string>("401", "Unauthorized"),
|
||||||
|
new KeyValuePair<string, string>("403", "Forbidden"),
|
||||||
|
new KeyValuePair<string, string>("404", "Not Found"),
|
||||||
|
new KeyValuePair<string, string>("405", "Method Not Allowed"),
|
||||||
|
new KeyValuePair<string, string>("406", "Not Acceptable"),
|
||||||
|
new KeyValuePair<string, string>("408", "Request Timeout"),
|
||||||
|
new KeyValuePair<string, string>("409", "Conflict"),
|
||||||
|
new KeyValuePair<string, string>("429", "Too Many Requests"),
|
||||||
|
new KeyValuePair<string, string>("4\\d{2}", "Client Error"),
|
||||||
|
|
||||||
|
new KeyValuePair<string, string>("5\\d{2}", "Server Error"),
|
||||||
|
new KeyValuePair<string, string>("default", "Error")
|
||||||
|
};
|
||||||
|
|
||||||
|
private sealed class MetadataEndpointBuilder : EndpointBuilder
|
||||||
|
{
|
||||||
|
public override Endpoint Build() => throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2,6 +2,7 @@ using BlazorTemplater;
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using NejCommon.Services.Email;
|
using NejCommon.Services.Email;
|
||||||
|
using System.Net.Mail;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace NejCommon.Emails
|
namespace NejCommon.Emails
|
||||||
|
|
@ -33,5 +34,7 @@ namespace NejCommon.Emails
|
||||||
{
|
{
|
||||||
return "DEV: " + this.ToString();
|
return "DEV: " + this.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public virtual List<Attachment> GetAttachments() => new List<Attachment>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,111 +1,3 @@
|
||||||
*, ::before, ::after {
|
|
||||||
--tw-border-spacing-x: 0;
|
|
||||||
--tw-border-spacing-y: 0;
|
|
||||||
--tw-translate-x: 0;
|
|
||||||
--tw-translate-y: 0;
|
|
||||||
--tw-rotate: 0;
|
|
||||||
--tw-skew-x: 0;
|
|
||||||
--tw-skew-y: 0;
|
|
||||||
--tw-scale-x: 1;
|
|
||||||
--tw-scale-y: 1;
|
|
||||||
--tw-pan-x: ;
|
|
||||||
--tw-pan-y: ;
|
|
||||||
--tw-pinch-zoom: ;
|
|
||||||
--tw-scroll-snap-strictness: proximity;
|
|
||||||
--tw-gradient-from-position: ;
|
|
||||||
--tw-gradient-via-position: ;
|
|
||||||
--tw-gradient-to-position: ;
|
|
||||||
--tw-ordinal: ;
|
|
||||||
--tw-slashed-zero: ;
|
|
||||||
--tw-numeric-figure: ;
|
|
||||||
--tw-numeric-spacing: ;
|
|
||||||
--tw-numeric-fraction: ;
|
|
||||||
--tw-ring-inset: ;
|
|
||||||
--tw-ring-offset-width: 0px;
|
|
||||||
--tw-ring-offset-color: #fff;
|
|
||||||
--tw-ring-color: rgb(59 130 246 / 0.5);
|
|
||||||
--tw-ring-offset-shadow: 0 0 #0000;
|
|
||||||
--tw-ring-shadow: 0 0 #0000;
|
|
||||||
--tw-shadow: 0 0 #0000;
|
|
||||||
--tw-shadow-colored: 0 0 #0000;
|
|
||||||
--tw-blur: ;
|
|
||||||
--tw-brightness: ;
|
|
||||||
--tw-contrast: ;
|
|
||||||
--tw-grayscale: ;
|
|
||||||
--tw-hue-rotate: ;
|
|
||||||
--tw-invert: ;
|
|
||||||
--tw-saturate: ;
|
|
||||||
--tw-sepia: ;
|
|
||||||
--tw-drop-shadow: ;
|
|
||||||
--tw-backdrop-blur: ;
|
|
||||||
--tw-backdrop-brightness: ;
|
|
||||||
--tw-backdrop-contrast: ;
|
|
||||||
--tw-backdrop-grayscale: ;
|
|
||||||
--tw-backdrop-hue-rotate: ;
|
|
||||||
--tw-backdrop-invert: ;
|
|
||||||
--tw-backdrop-opacity: ;
|
|
||||||
--tw-backdrop-saturate: ;
|
|
||||||
--tw-backdrop-sepia: ;
|
|
||||||
--tw-contain-size: ;
|
|
||||||
--tw-contain-layout: ;
|
|
||||||
--tw-contain-paint: ;
|
|
||||||
--tw-contain-style: ;
|
|
||||||
}
|
|
||||||
|
|
||||||
::backdrop {
|
|
||||||
--tw-border-spacing-x: 0;
|
|
||||||
--tw-border-spacing-y: 0;
|
|
||||||
--tw-translate-x: 0;
|
|
||||||
--tw-translate-y: 0;
|
|
||||||
--tw-rotate: 0;
|
|
||||||
--tw-skew-x: 0;
|
|
||||||
--tw-skew-y: 0;
|
|
||||||
--tw-scale-x: 1;
|
|
||||||
--tw-scale-y: 1;
|
|
||||||
--tw-pan-x: ;
|
|
||||||
--tw-pan-y: ;
|
|
||||||
--tw-pinch-zoom: ;
|
|
||||||
--tw-scroll-snap-strictness: proximity;
|
|
||||||
--tw-gradient-from-position: ;
|
|
||||||
--tw-gradient-via-position: ;
|
|
||||||
--tw-gradient-to-position: ;
|
|
||||||
--tw-ordinal: ;
|
|
||||||
--tw-slashed-zero: ;
|
|
||||||
--tw-numeric-figure: ;
|
|
||||||
--tw-numeric-spacing: ;
|
|
||||||
--tw-numeric-fraction: ;
|
|
||||||
--tw-ring-inset: ;
|
|
||||||
--tw-ring-offset-width: 0px;
|
|
||||||
--tw-ring-offset-color: #fff;
|
|
||||||
--tw-ring-color: rgb(59 130 246 / 0.5);
|
|
||||||
--tw-ring-offset-shadow: 0 0 #0000;
|
|
||||||
--tw-ring-shadow: 0 0 #0000;
|
|
||||||
--tw-shadow: 0 0 #0000;
|
|
||||||
--tw-shadow-colored: 0 0 #0000;
|
|
||||||
--tw-blur: ;
|
|
||||||
--tw-brightness: ;
|
|
||||||
--tw-contrast: ;
|
|
||||||
--tw-grayscale: ;
|
|
||||||
--tw-hue-rotate: ;
|
|
||||||
--tw-invert: ;
|
|
||||||
--tw-saturate: ;
|
|
||||||
--tw-sepia: ;
|
|
||||||
--tw-drop-shadow: ;
|
|
||||||
--tw-backdrop-blur: ;
|
|
||||||
--tw-backdrop-brightness: ;
|
|
||||||
--tw-backdrop-contrast: ;
|
|
||||||
--tw-backdrop-grayscale: ;
|
|
||||||
--tw-backdrop-hue-rotate: ;
|
|
||||||
--tw-backdrop-invert: ;
|
|
||||||
--tw-backdrop-opacity: ;
|
|
||||||
--tw-backdrop-saturate: ;
|
|
||||||
--tw-backdrop-sepia: ;
|
|
||||||
--tw-contain-size: ;
|
|
||||||
--tw-contain-layout: ;
|
|
||||||
--tw-contain-paint: ;
|
|
||||||
--tw-contain-style: ;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
! tailwindcss v3.4.15 | MIT License | https://tailwindcss.com
|
! tailwindcss v3.4.15 | MIT License | https://tailwindcss.com
|
||||||
*/
|
*/
|
||||||
|
|
@ -139,12 +31,9 @@
|
||||||
3. Use a more readable tab size.
|
3. Use a more readable tab size.
|
||||||
4. Use the user's configured `sans` font-family by default.
|
4. Use the user's configured `sans` font-family by default.
|
||||||
5. Use the user's configured `sans` font-feature-settings by default.
|
5. Use the user's configured `sans` font-feature-settings by default.
|
||||||
6. Use the user's configured `sans` font-variation-settings by default.
|
|
||||||
7. Disable tap highlights on iOS
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
html,
|
html {
|
||||||
:host {
|
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
/* 1 */
|
/* 1 */
|
||||||
-webkit-text-size-adjust: 100%;
|
-webkit-text-size-adjust: 100%;
|
||||||
|
|
@ -154,14 +43,10 @@ html,
|
||||||
-o-tab-size: 4;
|
-o-tab-size: 4;
|
||||||
tab-size: 4;
|
tab-size: 4;
|
||||||
/* 3 */
|
/* 3 */
|
||||||
font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
font-family: "Noto Sans", "Roboto", "Segoe UI", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||||
/* 4 */
|
/* 4 */
|
||||||
font-feature-settings: normal;
|
font-feature-settings: normal;
|
||||||
/* 5 */
|
/* 5 */
|
||||||
font-variation-settings: normal;
|
|
||||||
/* 6 */
|
|
||||||
-webkit-tap-highlight-color: transparent;
|
|
||||||
/* 7 */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -233,10 +118,8 @@ strong {
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
1. Use the user's configured `mono` font-family by default.
|
1. Use the user's configured `mono` font family by default.
|
||||||
2. Use the user's configured `mono` font-feature-settings by default.
|
2. Correct the odd `em` font sizing in all browsers.
|
||||||
3. Use the user's configured `mono` font-variation-settings by default.
|
|
||||||
4. Correct the odd `em` font sizing in all browsers.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
code,
|
code,
|
||||||
|
|
@ -245,12 +128,8 @@ samp,
|
||||||
pre {
|
pre {
|
||||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||||
/* 1 */
|
/* 1 */
|
||||||
font-feature-settings: normal;
|
|
||||||
/* 2 */
|
|
||||||
font-variation-settings: normal;
|
|
||||||
/* 3 */
|
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
/* 4 */
|
/* 2 */
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -309,18 +188,12 @@ select,
|
||||||
textarea {
|
textarea {
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
/* 1 */
|
/* 1 */
|
||||||
font-feature-settings: inherit;
|
|
||||||
/* 1 */
|
|
||||||
font-variation-settings: inherit;
|
|
||||||
/* 1 */
|
|
||||||
font-size: 100%;
|
font-size: 100%;
|
||||||
/* 1 */
|
/* 1 */
|
||||||
font-weight: inherit;
|
font-weight: inherit;
|
||||||
/* 1 */
|
/* 1 */
|
||||||
line-height: inherit;
|
line-height: inherit;
|
||||||
/* 1 */
|
/* 1 */
|
||||||
letter-spacing: inherit;
|
|
||||||
/* 1 */
|
|
||||||
color: inherit;
|
color: inherit;
|
||||||
/* 1 */
|
/* 1 */
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|
@ -344,9 +217,9 @@ select {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
button,
|
button,
|
||||||
input:where([type='button']),
|
[type='button'],
|
||||||
input:where([type='reset']),
|
[type='reset'],
|
||||||
input:where([type='submit']) {
|
[type='submit'] {
|
||||||
-webkit-appearance: button;
|
-webkit-appearance: button;
|
||||||
/* 1 */
|
/* 1 */
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
|
|
@ -465,14 +338,6 @@ menu {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
Reset default styling for dialogs.
|
|
||||||
*/
|
|
||||||
|
|
||||||
dialog {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Prevent resizing textareas horizontally by default.
|
Prevent resizing textareas horizontally by default.
|
||||||
*/
|
*/
|
||||||
|
|
@ -554,24 +419,306 @@ video {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.visible {
|
*, ::before, ::after {
|
||||||
visibility: visible;
|
--tw-border-spacing-x: 0;
|
||||||
|
--tw-border-spacing-y: 0;
|
||||||
|
--tw-translate-x: 0;
|
||||||
|
--tw-translate-y: 0;
|
||||||
|
--tw-rotate: 0;
|
||||||
|
--tw-skew-x: 0;
|
||||||
|
--tw-skew-y: 0;
|
||||||
|
--tw-scale-x: 1;
|
||||||
|
--tw-scale-y: 1;
|
||||||
|
--tw-pan-x: ;
|
||||||
|
--tw-pan-y: ;
|
||||||
|
--tw-pinch-zoom: ;
|
||||||
|
--tw-scroll-snap-strictness: proximity;
|
||||||
|
--tw-ordinal: ;
|
||||||
|
--tw-slashed-zero: ;
|
||||||
|
--tw-numeric-figure: ;
|
||||||
|
--tw-numeric-spacing: ;
|
||||||
|
--tw-numeric-fraction: ;
|
||||||
|
--tw-ring-inset: ;
|
||||||
|
--tw-ring-offset-width: 0px;
|
||||||
|
--tw-ring-offset-color: #fff;
|
||||||
|
--tw-ring-color: rgb(59 130 246 / 0.5);
|
||||||
|
--tw-ring-offset-shadow: 0 0 #0000;
|
||||||
|
--tw-ring-shadow: 0 0 #0000;
|
||||||
|
--tw-shadow: 0 0 #0000;
|
||||||
|
--tw-shadow-colored: 0 0 #0000;
|
||||||
|
--tw-blur: ;
|
||||||
|
--tw-brightness: ;
|
||||||
|
--tw-contrast: ;
|
||||||
|
--tw-grayscale: ;
|
||||||
|
--tw-hue-rotate: ;
|
||||||
|
--tw-invert: ;
|
||||||
|
--tw-saturate: ;
|
||||||
|
--tw-sepia: ;
|
||||||
|
--tw-drop-shadow: ;
|
||||||
|
--tw-backdrop-blur: ;
|
||||||
|
--tw-backdrop-brightness: ;
|
||||||
|
--tw-backdrop-contrast: ;
|
||||||
|
--tw-backdrop-grayscale: ;
|
||||||
|
--tw-backdrop-hue-rotate: ;
|
||||||
|
--tw-backdrop-invert: ;
|
||||||
|
--tw-backdrop-opacity: ;
|
||||||
|
--tw-backdrop-saturate: ;
|
||||||
|
--tw-backdrop-sepia: ;
|
||||||
|
}
|
||||||
|
|
||||||
|
::backdrop {
|
||||||
|
--tw-border-spacing-x: 0;
|
||||||
|
--tw-border-spacing-y: 0;
|
||||||
|
--tw-translate-x: 0;
|
||||||
|
--tw-translate-y: 0;
|
||||||
|
--tw-rotate: 0;
|
||||||
|
--tw-skew-x: 0;
|
||||||
|
--tw-skew-y: 0;
|
||||||
|
--tw-scale-x: 1;
|
||||||
|
--tw-scale-y: 1;
|
||||||
|
--tw-pan-x: ;
|
||||||
|
--tw-pan-y: ;
|
||||||
|
--tw-pinch-zoom: ;
|
||||||
|
--tw-scroll-snap-strictness: proximity;
|
||||||
|
--tw-ordinal: ;
|
||||||
|
--tw-slashed-zero: ;
|
||||||
|
--tw-numeric-figure: ;
|
||||||
|
--tw-numeric-spacing: ;
|
||||||
|
--tw-numeric-fraction: ;
|
||||||
|
--tw-ring-inset: ;
|
||||||
|
--tw-ring-offset-width: 0px;
|
||||||
|
--tw-ring-offset-color: #fff;
|
||||||
|
--tw-ring-color: rgb(59 130 246 / 0.5);
|
||||||
|
--tw-ring-offset-shadow: 0 0 #0000;
|
||||||
|
--tw-ring-shadow: 0 0 #0000;
|
||||||
|
--tw-shadow: 0 0 #0000;
|
||||||
|
--tw-shadow-colored: 0 0 #0000;
|
||||||
|
--tw-blur: ;
|
||||||
|
--tw-brightness: ;
|
||||||
|
--tw-contrast: ;
|
||||||
|
--tw-grayscale: ;
|
||||||
|
--tw-hue-rotate: ;
|
||||||
|
--tw-invert: ;
|
||||||
|
--tw-saturate: ;
|
||||||
|
--tw-sepia: ;
|
||||||
|
--tw-drop-shadow: ;
|
||||||
|
--tw-backdrop-blur: ;
|
||||||
|
--tw-backdrop-brightness: ;
|
||||||
|
--tw-backdrop-contrast: ;
|
||||||
|
--tw-backdrop-grayscale: ;
|
||||||
|
--tw-backdrop-hue-rotate: ;
|
||||||
|
--tw-backdrop-invert: ;
|
||||||
|
--tw-backdrop-opacity: ;
|
||||||
|
--tw-backdrop-saturate: ;
|
||||||
|
--tw-backdrop-sepia: ;
|
||||||
}
|
}
|
||||||
|
|
||||||
.static {
|
.static {
|
||||||
position: static;
|
position: static;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.absolute {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ml-2 {
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
.inline {
|
.inline {
|
||||||
display: inline;
|
display: inline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.flex {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
.table {
|
.table {
|
||||||
display: table;
|
display: table;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hidden {
|
.contents {
|
||||||
display: none;
|
display: contents;
|
||||||
|
}
|
||||||
|
|
||||||
|
.h-full {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.h-16 {
|
||||||
|
height: 4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.h-48 {
|
||||||
|
height: 12rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.w-full {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.w-16 {
|
||||||
|
width: 4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.w-48 {
|
||||||
|
width: 12rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-grow {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-col {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-wrap {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.items-end {
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.items-center {
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.justify-start {
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.justify-end {
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.justify-center {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.justify-between {
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.self-end {
|
||||||
|
align-self: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rounded-md {
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rounded {
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.border-4 {
|
||||||
|
border-width: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.border {
|
||||||
|
border-width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-primary {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(var(--primary) / var(--tw-bg-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
|
.py-2 {
|
||||||
|
padding-top: 0.5rem;
|
||||||
|
padding-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.py-4 {
|
||||||
|
padding-top: 1rem;
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.px-10 {
|
||||||
|
padding-left: 2.5rem;
|
||||||
|
padding-right: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pt-2 {
|
||||||
|
padding-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pb-2 {
|
||||||
|
padding-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pl-8 {
|
||||||
|
padding-left: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pr-8 {
|
||||||
|
padding-right: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pr-4 {
|
||||||
|
padding-right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pt-1 {
|
||||||
|
padding-top: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-left {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-sm {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
line-height: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-xs {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
line-height: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-lg {
|
||||||
|
font-size: 1.125rem;
|
||||||
|
line-height: 1.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-3xl {
|
||||||
|
font-size: 1.875rem;
|
||||||
|
line-height: 2.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-xl {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
line-height: 1.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-2xl {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
line-height: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.font-semibold {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.font-bold {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-secondary {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(var(--secondary-text) / var(--tw-text-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-primary {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(var(--primary-text) / var(--tw-text-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
|
.line-through {
|
||||||
|
text-decoration-line: line-through;
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter {
|
.filter {
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,8 @@ using Microsoft.EntityFrameworkCore;
|
||||||
namespace NejCommon.Models;
|
namespace NejCommon.Models;
|
||||||
|
|
||||||
|
|
||||||
public abstract class CommonDbContext : DbContext
|
public abstract class CommonDbContext : DbContext
|
||||||
{
|
{
|
||||||
public CommonDbContext() : base()
|
public CommonDbContext() : base()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
@ -52,27 +52,31 @@ namespace NejCommon.Models;
|
||||||
else
|
else
|
||||||
return SaveError;
|
return SaveError;
|
||||||
}
|
}
|
||||||
public async Task<Results<BadRequest<Error>, CreatedAtRoute<T2>>> ApiSaveChangesAsyncCreate<T1, T2>(IServiceProvider providers, T1 value, bool apply = true) where T2 : IAutomappedAttribute<T1, T2>, new()
|
public async Task<Results<BadRequest<Error>, CreatedAtRoute<T2>>> ApiSaveChangesAsyncCreate<T1, T2>(IServiceProvider providers, T1 value, bool apply = true, T2? response = null) where T2 : class, IAutomappedAttribute<T1, T2>, new()
|
||||||
{
|
{
|
||||||
|
if(response == null)
|
||||||
|
response = new T2();
|
||||||
if (!apply)
|
if (!apply)
|
||||||
return TypedResults.CreatedAtRoute(new T2().ApplyFrom(providers, value));
|
return TypedResults.CreatedAtRoute(response.ApplyFrom(providers, value));
|
||||||
|
|
||||||
var res = await ApiSaveChangesAsync();
|
var res = await ApiSaveChangesAsync();
|
||||||
|
|
||||||
if (res)
|
if (res)
|
||||||
return TypedResults.CreatedAtRoute(new T2().ApplyFrom(providers, value));
|
return TypedResults.CreatedAtRoute(response.ApplyFrom(providers, value));
|
||||||
else
|
else
|
||||||
return SaveError;
|
return SaveError;
|
||||||
}
|
}
|
||||||
public async Task<Results<BadRequest<Error>, Ok<T2>>> ApiSaveChangesAsyncOk<T1, T2>(IServiceProvider providers, T1 value, bool apply = true) where T2 : IAutomappedAttribute<T1, T2>, new()
|
public async Task<Results<BadRequest<Error>, Ok<T2>>> ApiSaveChangesAsyncOk<T1, T2>(IServiceProvider providers, T1 value, bool apply = true, T2? response = null) where T2 : class, IAutomappedAttribute<T1, T2>, new()
|
||||||
{
|
{
|
||||||
|
if(response == null)
|
||||||
|
response = new T2();
|
||||||
if (!apply)
|
if (!apply)
|
||||||
return TypedResults.Ok(new T2().ApplyFrom(providers, value));
|
return TypedResults.Ok(response.ApplyFrom(providers, value));
|
||||||
|
|
||||||
var res = await ApiSaveChangesAsync();
|
var res = await ApiSaveChangesAsync();
|
||||||
|
|
||||||
if (res)
|
if (res)
|
||||||
return TypedResults.Ok(new T2().ApplyFrom(providers, value));
|
return TypedResults.Ok(response.ApplyFrom(providers, value));
|
||||||
else
|
else
|
||||||
return SaveError;
|
return SaveError;
|
||||||
}
|
}
|
||||||
|
|
@ -157,6 +161,16 @@ namespace NejCommon.Models;
|
||||||
|
|
||||||
config?.Invoke(entity);
|
config?.Invoke(entity);
|
||||||
this.Add(entity);
|
this.Add(entity);
|
||||||
|
this.ChangeTracker.DetectChanges();
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
public object Create(Type entityType, Action<object> config = null, params object[] constructorArguments)
|
||||||
|
{
|
||||||
|
var entity = this.CreateProxy(entityType, constructorArguments);
|
||||||
|
|
||||||
|
config?.Invoke(entity);
|
||||||
|
this.Add(entity);
|
||||||
|
this.ChangeTracker.DetectChanges();
|
||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -164,4 +178,4 @@ namespace NejCommon.Models;
|
||||||
{
|
{
|
||||||
this.ChangeTracker.DetectChanges();
|
this.ChangeTracker.DetectChanges();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -14,10 +14,11 @@ public interface IEmailService
|
||||||
var obj = Activator.CreateInstance<T>();
|
var obj = Activator.CreateInstance<T>();
|
||||||
obj.Data = data;
|
obj.Data = data;
|
||||||
var subject = obj.GetSubject();
|
var subject = obj.GetSubject();
|
||||||
|
var atts = obj.GetAttachments();
|
||||||
|
|
||||||
//invoke the GetHTML function asynchronously and wait for the result
|
//invoke the GetHTML function asynchronously and wait for the result
|
||||||
var html = await (Task<string>)func.Invoke(null, new object[] { data });
|
var html = await (Task<string>)func.Invoke(null, new object[] { data });
|
||||||
|
|
||||||
await SendEmailAsync(recipient, subject, html, attachments);
|
await SendEmailAsync(recipient, subject, html, attachments == null ? atts : atts.Concat(attachments).ToList());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -38,6 +38,7 @@ public class SMTPService : IEmailService
|
||||||
|
|
||||||
using (var client = new MailKit.Net.Smtp.SmtpClient())
|
using (var client = new MailKit.Net.Smtp.SmtpClient())
|
||||||
{
|
{
|
||||||
|
await Task.Delay(100);
|
||||||
await client.ConnectAsync(_settings.Host, _settings.Port);
|
await client.ConnectAsync(_settings.Host, _settings.Port);
|
||||||
|
|
||||||
// Note: only needed if the SMTP server requires authentication
|
// Note: only needed if the SMTP server requires authentication
|
||||||
|
|
@ -46,6 +47,8 @@ public class SMTPService : IEmailService
|
||||||
await client.SendAsync(mess);
|
await client.SendAsync(mess);
|
||||||
await client.DisconnectAsync(true);
|
await client.DisconnectAsync(true);
|
||||||
|
|
||||||
|
await Task.Delay(100);
|
||||||
|
|
||||||
using (var imap = new MailKit.Net.Imap.ImapClient())
|
using (var imap = new MailKit.Net.Imap.ImapClient())
|
||||||
{
|
{
|
||||||
await imap.ConnectAsync(_settings.Host, _settings.ImapPort); // or ConnectSSL for SSL
|
await imap.ConnectAsync(_settings.Host, _settings.ImapPort); // or ConnectSSL for SSL
|
||||||
|
|
|
||||||
|
|
@ -17,13 +17,14 @@ public static class Extensions
|
||||||
query = query.Take(pag.Count);
|
query = query.Take(pag.Count);
|
||||||
return query;
|
return query;
|
||||||
}
|
}
|
||||||
|
public static async Task<PaginationResponse<TResponseType>> ApplyPaginationRes<TType, TResponseType>(this IQueryable<TType> query, IServiceProvider providers, Pagination pag, Expression<Func<TType, TResponseType>>? projector = null) where TResponseType : IAutomappedAttribute<TType, TResponseType>, new()
|
||||||
public static async Task<PaginationResponse<TResponseType>> ApplyPaginationRes<TType, TResponseType>(this IQueryable<TType> query, IServiceProvider providers, Pagination pag) where TResponseType : IAutomappedAttribute<TType, TResponseType>, new()
|
|
||||||
{
|
{
|
||||||
var totalCount = await query.CountAsync();
|
var totalCount = await query.CountAsync();
|
||||||
query = query.Skip(pag.Offset);
|
query = query.Skip(pag.Offset);
|
||||||
query = query.Take(pag.Count);
|
query = query.Take(pag.Count);
|
||||||
var projector = new TResponseType().GetProjectorFrom(providers);
|
|
||||||
|
if (projector == null)
|
||||||
|
projector = new TResponseType().GetProjectorFrom(providers);
|
||||||
|
|
||||||
return new PaginationResponse<TResponseType>
|
return new PaginationResponse<TResponseType>
|
||||||
{
|
{
|
||||||
|
|
@ -34,7 +35,7 @@ public static class Extensions
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<PaginationResponse<TResponseType>> ApplySearchPaginationRes<TType, TResponseType, TEntityBinder>(this IQueryable<TType> query, string? search, IServiceProvider providers, Pagination pag, List<Expression<Func<TType, string, bool>>> matchers)
|
public static async Task<PaginationResponse<TResponseType>> ApplySearchPaginationRes<TType, TResponseType, TEntityBinder>(this IQueryable<TType> query, string? search, IServiceProvider providers, Pagination pag, List<Expression<Func<TType, string, bool>>> matchers, Expression<Func<TType, TResponseType>>? projector = null)
|
||||||
where TType : class
|
where TType : class
|
||||||
where TResponseType : IAutomappedAttribute<TType, TResponseType>, new()
|
where TResponseType : IAutomappedAttribute<TType, TResponseType>, new()
|
||||||
where TEntityBinder : EntityBinder<TType>
|
where TEntityBinder : EntityBinder<TType>
|
||||||
|
|
@ -89,7 +90,7 @@ public static class Extensions
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return await query.ApplyPaginationRes<TType, TResponseType>(providers, pag);
|
return await query.ApplyPaginationRes<TType, TResponseType>(providers, pag, projector);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Projectable]
|
[Projectable]
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user