Refactor: the project is now divided into a more clear structure, with **Infrastructure** and **Application** layers added. Refactor: configurations are split into sections for different layers. Fix: now EF Core related operations, such as migration, should be invoked in `OptixServe.Infrastructure`, with config file and data dir passed into `dotnet ef` command. See `OptixServe.Infrastructure/Utilites/DesignTimeDbContextFactory.cs` for details. Fix: EF migrations are ignored in gitignore on purpose in early development.
182 lines
6.7 KiB
C#
182 lines
6.7 KiB
C#
using System.CommandLine;
|
|
using System.Text;
|
|
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
|
using Microsoft.IdentityModel.Tokens;
|
|
using OptixServe.Api.Configuration;
|
|
using OptixServe.Api.Endpoints;
|
|
using OptixServe.Api.Services;
|
|
using OptixServe.Application.Services;
|
|
using OptixServe.Infrastructure.Configuration;
|
|
using OptixServe.Infrastructure.Data;
|
|
using OptixServe.Infrastructure.Utilites;
|
|
|
|
class Program
|
|
{
|
|
/// <summary>
|
|
/// Main method that configures and runs the application
|
|
/// </summary>
|
|
/// <param name="args">Command line arguments</param>
|
|
/// <returns>A Task representing the asynchronous operation</returns>
|
|
static async Task Main(string[] args)
|
|
{
|
|
var rootCommand = new RootCommand("OptixServe API");
|
|
|
|
// Configure the --config/-c command line option
|
|
var configOption = new Option<FileInfo?>("--config", "-c")
|
|
{
|
|
Description = "Config file path",
|
|
CustomParser = arg =>
|
|
{
|
|
if (!arg.Tokens.Any()) return null;
|
|
|
|
var filePath = arg.Tokens.Single().Value;
|
|
if (!File.Exists(filePath))
|
|
{
|
|
arg.AddError($"Configuration file not found: {filePath}");
|
|
return null;
|
|
}
|
|
return new FileInfo(filePath);
|
|
}
|
|
};
|
|
rootCommand.Add(configOption);
|
|
|
|
// Set handle for rootCommand
|
|
// The handle prototype and way of parsing arguments
|
|
// is changed in System.CommandLine 2.0.0-beta5
|
|
rootCommand.SetAction((ParseResult parseResult) =>
|
|
{
|
|
var builder = WebApplication.CreateSlimBuilder(args);
|
|
|
|
var configFile = parseResult.GetValue(configOption);
|
|
builder.AddConfigurationWithCommand(configFile);
|
|
|
|
builder.RegisterServices();
|
|
builder.RegiserJsonContext();
|
|
|
|
var app = builder.Build();
|
|
|
|
app.UseAuthentication();
|
|
app.UseAuthorization();
|
|
|
|
using (var scope = app.Services.CreateScope())
|
|
{
|
|
var initializer = scope.ServiceProvider.GetRequiredService<DbInitializer>();
|
|
initializer.Initialize();
|
|
}
|
|
|
|
var apiGroup = app.MapGroup("api/v1");
|
|
StartupHelper.RegisterEndpoints(apiGroup);
|
|
|
|
app.Run();
|
|
});
|
|
|
|
// Parse arguments then invoke the command
|
|
var parseResult = rootCommand.Parse(args);
|
|
await parseResult.InvokeAsync();
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Contains extension methods for WebApplicationBuilder and WebApplication
|
|
/// </summary>
|
|
static class StartupHelper
|
|
{
|
|
/// <summary>
|
|
/// Adds configuration sources to the application builder
|
|
/// </summary>
|
|
/// <param name="builder">WebApplicationBuilder instance</param>
|
|
/// <param name="file">Optional configuration file specified via command line</param>
|
|
public static void AddConfigurationWithCommand(this WebApplicationBuilder builder, FileInfo? file)
|
|
{
|
|
// Configure configuration sources in specified order
|
|
var configurationBuilder = ConfigurationHelper.CreateDefaultBuilder();
|
|
|
|
// Add command-line specified config file if provided
|
|
if (file != null)
|
|
{
|
|
configurationBuilder.AddJsonFile(file.FullName, optional: false);
|
|
}
|
|
|
|
builder.Configuration.AddConfiguration(configurationBuilder.Build());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Configures DbContext services
|
|
/// </summary>
|
|
/// <param name="services"></param>
|
|
/// <param name="configuration"></param>
|
|
/// <returns></returns>
|
|
public static IServiceCollection AddAppDatabase(this IServiceCollection services, DatabaseSettings dbSettings)
|
|
{
|
|
services.AddDbContext<AppDbContext>(options => DatabaseHelper.ConfigureDbContext(options, dbSettings));
|
|
return services;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Configures services for DI
|
|
/// </summary>
|
|
/// <param name="builder">WebApplicationBuilder instance</param>
|
|
public static void RegisterServices(this WebApplicationBuilder builder)
|
|
{
|
|
// Add configuration classes
|
|
var apiConfigSection = builder.Configuration.GetSection("OptixServe:Api");
|
|
builder.Services.Configure<ApiConfiguration>(apiConfigSection);
|
|
var apiConfig = apiConfigSection.Get<ApiConfiguration>();
|
|
|
|
var infraConfigSection = builder.Configuration.GetSection("OptixServe:Infrastructure");
|
|
builder.Services.Configure<InfrastructureConfiguration>(infraConfigSection);
|
|
var infraConfig = infraConfigSection.Get<InfrastructureConfiguration>();
|
|
|
|
// Add DBContext class
|
|
builder.Services.AddAppDatabase(infraConfig?.Database!);
|
|
builder.Services.AddScoped<DbInitializer>();
|
|
|
|
// Application services
|
|
builder.Services.AddScoped<IUserService, UserService>();
|
|
builder.Services.AddScoped<ITokenService, TokenService>();
|
|
|
|
// Add Authentication and Authorization
|
|
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
|
|
.AddJwtBearer(options =>
|
|
{
|
|
var jwtSettings = apiConfig?.Jwt ?? throw new ArgumentNullException(nameof(builder), "JWT settings are not configured.");
|
|
options.TokenValidationParameters = new TokenValidationParameters
|
|
{
|
|
ValidateIssuer = true,
|
|
ValidateAudience = true,
|
|
ValidateLifetime = true,
|
|
ValidateIssuerSigningKey = true,
|
|
ValidIssuer = jwtSettings.Issuer,
|
|
ValidAudience = jwtSettings.Audience,
|
|
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings.Secret))
|
|
};
|
|
});
|
|
builder.Services.AddAuthorization();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Configures JSON serialization options with custom context
|
|
/// </summary>
|
|
/// <param name="builder">WebApplicationBuilder instance</param>
|
|
public static void RegiserJsonContext(this WebApplicationBuilder builder)
|
|
{
|
|
builder.Services.ConfigureHttpJsonOptions(options =>
|
|
{
|
|
options.SerializerOptions.TypeInfoResolverChain.Add(UserJsonContext.Default);
|
|
options.SerializerOptions.TypeInfoResolverChain.Add(VersionJsonContext.Default);
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Registers all API endpoints
|
|
/// </summary>
|
|
/// <param name="rootGroup">Root RouteGroupBuilder instance</param>
|
|
public static void RegisterEndpoints(RouteGroupBuilder rootGroup)
|
|
{
|
|
UserEndpoint.Register(rootGroup);
|
|
VersionEndpoint.Register(rootGroup);
|
|
}
|
|
|
|
}
|