Initial version

This commit is contained in:
2026-06-09 22:27:38 +02:00
parent 5afbf3b01c
commit 5ff2ac8941
57 changed files with 2343 additions and 98 deletions
+9
View File
@@ -0,0 +1,9 @@
namespace EonaCat.gRPC.Api.Helpers;
public class AppSettings
{
public string Secret { get; set; } = null!;
public int Validity { get; set; }
public string Issuer { get; set; } = null!;
public string Audience { get; set; } = null!;
}
@@ -0,0 +1,10 @@
namespace EonaCat.gRPC.Api.Helpers
{
public class CustomException : Exception
{
public CustomException(string message) : base(message)
{
}
}
}
+34
View File
@@ -0,0 +1,34 @@
namespace EonaCat.gRPC.Api.Helpers;
public static class CustomMapper
{
/// <summary>
/// Proto to Entity/DTO And Reverse
/// </summary>
/// <typeparam name="TSource"></typeparam>
/// <typeparam name="TDestination"></typeparam>
/// <param name="src"></param>
/// <returns>TDestination</returns>
public static TDestination Map<TSource, TDestination>(TSource src) where TDestination : new()
{
try
{
var tDest = (TDestination)Activator.CreateInstance(typeof(TDestination))!;
if (src == null)
return tDest;
var srcClassType = src.GetType();
var srcProperties = srcClassType.GetProperties();
foreach (var srcProperty in srcProperties)
{
var destPropertyInfo = tDest.GetType().GetProperty(srcProperty.Name);
if (srcProperty.GetType() == destPropertyInfo?.GetType())
destPropertyInfo.SetValue(tDest, srcProperty.GetValue(src, null), null);
}
return tDest;
}
catch (Exception e)
{
throw new Exception($"Unsupported mapping.\n{e.Message}");
}
}
}
@@ -0,0 +1,54 @@
using ProtoBuf.Grpc;
namespace EonaCat.gRPC.Api.Helpers;
public static class ExceptionHelpers
{
public static RpcException Handle<T>(this Exception exception, ServerCallContext context, ILogger<T> logger, Guid correlationId) =>
exception switch
{
TimeoutException timeoutException => HandleTimeoutException(timeoutException, context, logger, correlationId),
SqlException sqlException => HandleSqlException(sqlException, context, logger, correlationId),
RpcException rpcException => HandleRpcException(rpcException, logger, correlationId),
_ => HandleDefault(exception, context, logger, correlationId)
};
private static RpcException HandleTimeoutException<T>(TimeoutException exception, ServerCallContext context, ILogger<T> logger, Guid correlationId)
{
logger.LogError(exception, $"CorrelationId: {correlationId} - A timeout occurred");
var status = new Status(StatusCode.Internal, "An external resource did not answer within the time limit");
return new RpcException(status, CreateTrailers(correlationId));
}
private static RpcException HandleSqlException<T>(SqlException exception, ServerCallContext context, ILogger<T> logger, Guid correlationId)
{
logger.LogError(exception, $"CorrelationId: {correlationId} - An SQL error occurred");
Status status;
if (exception.Number == -2)
status = new Status(StatusCode.DeadlineExceeded, "SQL timeout");
else
status = new Status(StatusCode.Internal, "SQL error");
return new RpcException(status, CreateTrailers(correlationId));
}
private static RpcException HandleRpcException<T>(RpcException exception, ILogger<T> logger, Guid correlationId)
{
logger.LogError(exception, $"CorrelationId: {correlationId} - An error occurred");
//var trailers = exception.Trailers;
//var d = CreateTrailers(correlationId);
//trailers.Add(d[0]);
return new RpcException(new Status(exception.StatusCode, exception.Message), CreateTrailers(correlationId));
}
private static RpcException HandleDefault<T>(Exception exception, ServerCallContext context, ILogger<T> logger, Guid correlationId)
{
logger.LogError(exception, $"CorrelationId: {correlationId} - An error occurred");
return new RpcException(new Status(StatusCode.Internal, exception.Message), CreateTrailers(correlationId));
}
private static Metadata CreateTrailers(Guid correlationId)
{
var trailers = new Metadata { { "CorrelationId", correlationId.ToString() } };
return trailers;
}
}
+104
View File
@@ -0,0 +1,104 @@
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using EonaCat.gRPC.Core.Interfaces.Services;
using EonaCat.gRPC.Repository;
using EonaCat.gRPC.Repository.Base;
using EonaCat.gRPC.Repository.DatabaseContext;
using EonaCat.gRPC.Service;
using System.Text;
using EonaCat.LogStack.Extensions;
using EonaCat.Mapper;
namespace EonaCat.gRPC.Api.Helpers;
public static class Extension
{
public static void AddInfrastructureServices(this WebApplicationBuilder builder)
{
RegisterSwagger(builder);
RegisterLogger(builder);
RegisterDatabaseContext(builder);
RegisterAuthentication(builder);
}
public static void AddBusinessServices(this WebApplicationBuilder builder)
{
RegisterRepositoryDependencies(builder.Services);
RegisterServiceDependencies(builder);
}
public static void RegisterServiceDependencies(WebApplicationBuilder builder)
{
builder.Services.Configure<AppSettings>(builder.Configuration.GetSection("Jwt"));
builder.Services.AddTransient<IUserService, UserService>();
}
private static void RegisterRepositoryDependencies(IServiceCollection services)
{
services.AddScoped(typeof(IBaseRepository<>), typeof(BaseRepository<>));
services.AddScoped<IUserRepository, UserRepository>();
}
private static void RegisterAuthentication(WebApplicationBuilder builder)
{
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.RequireHttpsMetadata = true;
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(builder.Configuration.GetSection("Jwt").GetSection("Secret").Value)),
ValidateIssuer = false,
ValidateAudience = false,
};
});
}
private static void RegisterDatabaseContext(WebApplicationBuilder builder)
{
builder.Services.AddDbContext<AppDbContext>((provider, options) =>
{
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"));
});
}
private static void RegisterLogger(WebApplicationBuilder builder)
{
builder.AddEonaCatLogging((x) => x
.AddFlow(new LogStack.Flows.ConsoleFlow())
.AddFlow(new LogStack.Flows.FileFlow("./logs")));
}
private static void RegisterSwagger(WebApplicationBuilder builder)
{
builder.Services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo
{
Title = "EonaCat gRPC API",
Version = "v1",
Description = "EonaCat gRPC API"
});
});
}
public static void AppUseSwagger(this WebApplication app)
{
app.UseSwagger();
app.UseSwaggerUI(options =>
{
options.SwaggerEndpoint("/swagger/v1/swagger.json", "EonaCat gRPC API");
options.RoutePrefix = string.Empty;
});
}
public static void MapGrpcServices(this WebApplication app)
{
app.MapGrpcService<UserHandler>();
app.MapGrpcService<AuthenticationHandler>();
}
}
@@ -0,0 +1,40 @@
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using EonaCat.gRPC.Proto;
namespace EonaCat.gRPC.Api.Helpers;
public static class JwtAuthenticationManager
{
public static AuthenticationResponse Authenticate(IOptions<AppSettings> appSettings, AuthenticationRequest authenticationRequest)
{
// Implement DbCheck ----
if (authenticationRequest.UserName != "admin" || authenticationRequest.Password != "admin")
throw new RpcException(new Status(StatusCode.Unauthenticated, "Invalid ProtoUserResponse Credentials"));
var jwtSecurityTokenHandler = new JwtSecurityTokenHandler();
var tokenKey = Encoding.ASCII.GetBytes(appSettings.Value.Secret);
var tokenExpiryDateTime = DateTime.UtcNow.AddMinutes(appSettings.Value.Validity);
var securityTokenDescriptor = new SecurityTokenDescriptor()
{
Subject = new ClaimsIdentity(new List<Claim>
{
new(ClaimTypes.Name, authenticationRequest.UserName),
new(ClaimTypes.Role, "Administrator")
}),
Expires = tokenExpiryDateTime,
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(tokenKey), SecurityAlgorithms.HmacSha256Signature)
};
var securityToken = jwtSecurityTokenHandler.CreateToken(securityTokenDescriptor);
var token = jwtSecurityTokenHandler.WriteToken(securityToken);
return new AuthenticationResponse
{
AccessToken = token,
ExpiresIn = (int)tokenExpiryDateTime.Subtract(DateTime.UtcNow).TotalSeconds
};
}
}
@@ -0,0 +1,34 @@
using EonaCat.Json.Converters;
using EonaCat.Json.Serialization;
using System.Reflection;
namespace EonaCat.gRPC.Api.Helpers;
public class TimeStampContractResolver : DefaultContractResolver
{
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
if (property.PropertyType == typeof(Google.Protobuf.WellKnownTypes.Timestamp))
{
property.Converter = new TimeStampConverter();
}
return property;
}
}
public class TimeStampConverter : DateTimeConverterBase
{
public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
JsonSerializer serializer)
{
var date = DateTime.Parse(reader.Value?.ToString());
date = DateTime.SpecifyKind(date, DateTimeKind.Utc);
return Google.Protobuf.WellKnownTypes.Timestamp.FromDateTime(date);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteValue(((Google.Protobuf.WellKnownTypes.Timestamp)value).ToString());
}
}