Cleaned up the startup sequence.
This commit is contained in:
@@ -1,55 +1,19 @@
|
||||
using System.Reflection;
|
||||
using System.Text.Json;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using MoonCore.Extended.Abstractions;
|
||||
using MoonCore.Extended.Extensions;
|
||||
using MoonCore.Extended.Helpers;
|
||||
using MoonCore.Extended.OAuth2.ApiServer;
|
||||
using MoonCore.Extensions;
|
||||
using MoonCore.Helpers;
|
||||
using MoonCore.Models;
|
||||
using MoonCore.PluginFramework.Extensions;
|
||||
using MoonCore.PluginFramework.Services;
|
||||
using MoonCore.Services;
|
||||
using Moonlight.ApiServer;
|
||||
using Moonlight.ApiServer.Configuration;
|
||||
using Moonlight.ApiServer.Database;
|
||||
using Moonlight.ApiServer.Database.Entities;
|
||||
using Moonlight.ApiServer.Helpers;
|
||||
using Moonlight.ApiServer.Helpers.Authentication;
|
||||
using Moonlight.ApiServer.Http.Middleware;
|
||||
using Moonlight.ApiServer.Implementations.OAuth2;
|
||||
using Moonlight.ApiServer.Interfaces.Auth;
|
||||
using Moonlight.ApiServer.Interfaces.OAuth2;
|
||||
using Moonlight.ApiServer.Interfaces.Startup;
|
||||
|
||||
// Prepare file system
|
||||
Directory.CreateDirectory(PathBuilder.Dir("storage"));
|
||||
Directory.CreateDirectory(PathBuilder.Dir("storage", "plugins"));
|
||||
Directory.CreateDirectory(PathBuilder.Dir("storage", "clientPlugins"));
|
||||
Directory.CreateDirectory(PathBuilder.Dir("storage", "logs"));
|
||||
|
||||
// Configuration
|
||||
var configService = new ConfigService<AppConfiguration>(
|
||||
PathBuilder.File("storage", "config.json")
|
||||
);
|
||||
|
||||
var config = configService.Get();
|
||||
|
||||
ApplicationStateHelper.SetConfiguration(configService);
|
||||
|
||||
// Build pre run logger
|
||||
var providers = LoggerBuildHelper.BuildFromConfiguration(configuration =>
|
||||
{
|
||||
configuration.Console.Enable = true;
|
||||
configuration.Console.EnableAnsiMode = true;
|
||||
|
||||
configuration.FileLogging.Enable = true;
|
||||
configuration.FileLogging.Path = PathBuilder.File("storage", "logs", "moonlight.log");
|
||||
configuration.FileLogging.EnableLogRotation = true;
|
||||
configuration.FileLogging.RotateLogNameTemplate = PathBuilder.File("storage", "logs", "moonlight.log.{0}");
|
||||
});
|
||||
|
||||
using var loggerFactory = new LoggerFactory(providers);
|
||||
var logger = loggerFactory.CreateLogger("Startup");
|
||||
// Cry about it
|
||||
#pragma warning disable ASP0000
|
||||
|
||||
// Fancy start console output... yes very fancy :>
|
||||
var rainbow = new Crayon.Rainbow(0.5);
|
||||
@@ -65,144 +29,105 @@ foreach (var c in "Moonlight")
|
||||
|
||||
Console.WriteLine();
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
// Storage i guess
|
||||
Directory.CreateDirectory(PathBuilder.Dir("storage"));
|
||||
|
||||
// Configure application logging
|
||||
builder.Logging.ClearProviders();
|
||||
builder.Logging.AddProviders(providers);
|
||||
|
||||
// Logging levels
|
||||
var logConfigPath = PathBuilder.File("storage", "logConfig.json");
|
||||
|
||||
// Ensure logging config, add a default one is missing
|
||||
if (!File.Exists(logConfigPath))
|
||||
{
|
||||
await File.WriteAllTextAsync(logConfigPath,
|
||||
"{\"LogLevel\":{\"Default\":\"Information\",\"Microsoft.AspNetCore\":\"Warning\",\"MoonCore.Extended.Helpers.JwtHelper\": \"Error\"}}");
|
||||
}
|
||||
|
||||
builder.Logging.AddConfiguration(await File.ReadAllTextAsync(logConfigPath));
|
||||
|
||||
builder.Services.AddControllers();
|
||||
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
builder.Services.AddSwaggerGen();
|
||||
|
||||
builder.Services.AddSingleton(configService);
|
||||
|
||||
builder.Services.AutoAddServices<Program>();
|
||||
|
||||
// OAuth2
|
||||
builder.Services.AddSingleton<TokenHelper>();
|
||||
|
||||
builder.Services.AddHttpClient();
|
||||
builder.Services.AddOAuth2Consumer(configuration =>
|
||||
{
|
||||
configuration.ClientId = config.Authentication.OAuth2.ClientId;
|
||||
configuration.ClientSecret = config.Authentication.OAuth2.ClientSecret;
|
||||
configuration.AuthorizationRedirect =
|
||||
config.Authentication.OAuth2.AuthorizationRedirect ?? $"{config.PublicUrl}/auth";
|
||||
|
||||
configuration.AccessEndpoint = config.Authentication.OAuth2.AccessEndpoint ?? $"{config.PublicUrl}/oauth2/access";
|
||||
configuration.RefreshEndpoint = config.Authentication.OAuth2.RefreshEndpoint ?? $"{config.PublicUrl}/oauth2/refresh";
|
||||
|
||||
if (config.Authentication.UseLocalOAuth2)
|
||||
{
|
||||
configuration.AuthorizationEndpoint = config.Authentication.OAuth2.AuthorizationRedirect ?? $"{config.PublicUrl}/oauth2/authorize";
|
||||
}
|
||||
else
|
||||
{
|
||||
if(config.Authentication.OAuth2.AuthorizationUri == null)
|
||||
logger.LogWarning("The 'AuthorizationUri' for the oauth2 client is not set. If you want to use an external oauth2 provider, you need to specify this url. If you want to use the local oauth2 service, set 'UseLocalOAuth2Service' to true");
|
||||
|
||||
configuration.AuthorizationEndpoint = config.Authentication.OAuth2.AuthorizationUri!;
|
||||
}
|
||||
});
|
||||
|
||||
if (config.Authentication.UseLocalOAuth2)
|
||||
{
|
||||
logger.LogInformation("Using local oauth2 provider");
|
||||
|
||||
builder.Services.AddOAuth2Provider(configuration =>
|
||||
{
|
||||
configuration.AccessSecret = config.Authentication.LocalOAuth2.AccessSecret;
|
||||
configuration.RefreshSecret = config.Authentication.LocalOAuth2.RefreshSecret;
|
||||
|
||||
configuration.ClientId = config.Authentication.OAuth2.ClientId;
|
||||
configuration.ClientSecret = config.Authentication.OAuth2.ClientSecret;
|
||||
configuration.CodeSecret = config.Authentication.LocalOAuth2.CodeSecret;
|
||||
configuration.AuthorizationRedirect =
|
||||
config.Authentication.OAuth2.AuthorizationRedirect ?? $"{config.PublicUrl}/auth";
|
||||
configuration.AccessTokenDuration = 60;
|
||||
configuration.RefreshTokenDuration = 3600;
|
||||
});
|
||||
}
|
||||
|
||||
builder.Services.AddTokenAuthentication(configuration =>
|
||||
{
|
||||
configuration.AccessSecret = config.Authentication.AccessSecret;
|
||||
configuration.DataLoader = async (data, provider, context) =>
|
||||
{
|
||||
if (!data.TryGetValue("userId", out var userIdStr) || !userIdStr.TryGetInt32(out var userId))
|
||||
return false;
|
||||
|
||||
var userRepo = provider.GetRequiredService<DatabaseRepository<User>>();
|
||||
var user = userRepo.Get().FirstOrDefault(x => x.Id == userId);
|
||||
|
||||
if (user == null)
|
||||
return false;
|
||||
|
||||
// Load permissions, handle empty values
|
||||
var permissions = JsonSerializer.Deserialize<string[]>(
|
||||
string.IsNullOrEmpty(user.PermissionsJson) ? "[]" : user.PermissionsJson
|
||||
) ?? [];
|
||||
|
||||
// Save permission state
|
||||
context.User = new PermClaimsPrinciple(permissions, user);
|
||||
|
||||
return true;
|
||||
};
|
||||
});
|
||||
|
||||
// Database
|
||||
var databaseHelper = new DatabaseHelper(
|
||||
loggerFactory.CreateLogger<DatabaseHelper>()
|
||||
// Configuration
|
||||
var configService = new ConfigService<AppConfiguration>(
|
||||
PathBuilder.File("storage", "config.json")
|
||||
);
|
||||
|
||||
builder.Services.AddSingleton(databaseHelper);
|
||||
builder.Services.AddScoped(typeof(DatabaseRepository<>));
|
||||
builder.Services.AddScoped(typeof(CrudHelper<,>));
|
||||
var config = configService.Get();
|
||||
|
||||
builder.Services.AddDbContext<CoreDataContext>();
|
||||
databaseHelper.AddDbContext<CoreDataContext>();
|
||||
ApplicationStateHelper.SetConfiguration(configService);
|
||||
|
||||
databaseHelper.GenerateMappings();
|
||||
// TODO: Load plugin/module assemblies
|
||||
|
||||
// API Docs
|
||||
if (configService.Get().Development.EnableApiDocs)
|
||||
// Configure startup logger
|
||||
var startupLoggerFactory = new LoggerFactory();
|
||||
|
||||
// TODO: Add direct extension method
|
||||
var providers = LoggerBuildHelper.BuildFromConfiguration(configuration =>
|
||||
{
|
||||
// Configure swagger api specification generator and set the document title for the api docs to use
|
||||
builder.Services.AddSwaggerGen(options => options.SwaggerDoc("main", new OpenApiInfo()
|
||||
configuration.Console.Enable = true;
|
||||
configuration.Console.EnableAnsiMode = true;
|
||||
configuration.FileLogging.Enable = false;
|
||||
});
|
||||
|
||||
startupLoggerFactory.AddProviders(providers);
|
||||
|
||||
var startupLogger = startupLoggerFactory.CreateLogger("Startup");
|
||||
|
||||
// Configure startup interfaces
|
||||
var startupServiceCollection = new ServiceCollection();
|
||||
startupServiceCollection.AddSingleton(configService);
|
||||
|
||||
startupServiceCollection.AddLogging(loggingBuilder => { loggingBuilder.AddProviders(providers); });
|
||||
|
||||
startupServiceCollection.AddPlugins(configuration =>
|
||||
{
|
||||
// Configure startup interfaces
|
||||
configuration.AddInterface<IAppStartup>();
|
||||
configuration.AddInterface<IDatabaseStartup>();
|
||||
configuration.AddInterface<IEndpointStartup>();
|
||||
|
||||
// Configure assemblies to scan
|
||||
configuration.AddAssembly(Assembly.GetEntryAssembly()!);
|
||||
}, startupLogger);
|
||||
|
||||
|
||||
var startupServiceProvider = startupServiceCollection.BuildServiceProvider();
|
||||
var appStartupInterfaces = startupServiceProvider.GetRequiredService<IAppStartup[]>();
|
||||
|
||||
// Start the actual app
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
await Startup.ConfigureLogging(builder);
|
||||
|
||||
await Startup.ConfigureDatabase(
|
||||
builder,
|
||||
startupLoggerFactory,
|
||||
startupServiceProvider.GetRequiredService<IDatabaseStartup[]>()
|
||||
);
|
||||
|
||||
// Call interfaces
|
||||
foreach (var startupInterface in appStartupInterfaces)
|
||||
{
|
||||
try
|
||||
{
|
||||
Title = "Moonlight API"
|
||||
}));
|
||||
await startupInterface.BuildApp(builder);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
startupLogger.LogCritical(
|
||||
"An unhandled error occured while processing BuildApp call for interface '{interfaceName}': {e}",
|
||||
startupInterface.GetType().FullName,
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
builder.Services.AddControllers();
|
||||
builder.Services.AddSingleton(configService);
|
||||
builder.Services.AutoAddServices<Program>();
|
||||
builder.Services.AddSingleton<TokenHelper>();
|
||||
builder.Services.AddHttpClient();
|
||||
|
||||
await Startup.ConfigureTokenAuthentication(builder, config);
|
||||
await Startup.ConfigureOAuth2(builder, startupLogger, config);
|
||||
|
||||
// Implementation service
|
||||
builder.Services.AddPlugins(configuration =>
|
||||
{
|
||||
configuration.AddInterface<IOAuth2Provider>();
|
||||
configuration.AddInterface<IAuthInterceptor>();
|
||||
|
||||
|
||||
configuration.AddAssembly(Assembly.GetEntryAssembly()!);
|
||||
}, logger);
|
||||
}, startupLogger);
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
using (var scope = app.Services.CreateScope())
|
||||
{
|
||||
await databaseHelper.EnsureMigrated(scope.ServiceProvider);
|
||||
}
|
||||
await Startup.PrepareDatabase(app);
|
||||
|
||||
if (app.Environment.IsDevelopment())
|
||||
app.UseWebAssemblyDebugging();
|
||||
@@ -214,16 +139,47 @@ app.UseRouting();
|
||||
|
||||
app.UseMiddleware<ApiErrorMiddleware>();
|
||||
|
||||
app.UseTokenAuthentication();
|
||||
await Startup.UseTokenAuthentication(app);
|
||||
|
||||
// Call interfaces
|
||||
foreach (var startupInterface in appStartupInterfaces)
|
||||
{
|
||||
try
|
||||
{
|
||||
await startupInterface.ConfigureApp(app);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
startupLogger.LogCritical(
|
||||
"An unhandled error occured while processing ConfigureApp call for interface '{interfaceName}': {e}",
|
||||
startupInterface.GetType().FullName,
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
app.UseMiddleware<AuthorizationMiddleware>();
|
||||
|
||||
app.MapControllers();
|
||||
// Call interfaces
|
||||
var endpointStartupInterfaces = startupServiceProvider.GetRequiredService<IEndpointStartup[]>();
|
||||
|
||||
foreach (var endpointStartup in endpointStartupInterfaces)
|
||||
{
|
||||
try
|
||||
{
|
||||
await endpointStartup.ConfigureEndpoints(app);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
startupLogger.LogCritical(
|
||||
"An unhandled error occured while processing ConfigureEndpoints call for interface '{interfaceName}': {e}",
|
||||
endpointStartup.GetType().FullName,
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
app.MapControllers();
|
||||
app.MapFallbackToFile("index.html");
|
||||
|
||||
// API Docs
|
||||
if (configService.Get().Development.EnableApiDocs)
|
||||
app.MapSwagger("/api/swagger/{documentName}");
|
||||
|
||||
app.Run();
|
||||
Reference in New Issue
Block a user