feat(admincli): add command for creating a user

This commit is contained in:
Stevan Freeborn
2026-02-03 04:38:40 -06:00
parent 0ce97cdec8
commit fcc1356d69
11 changed files with 114 additions and 14 deletions
+1
View File
@@ -11,5 +11,6 @@
</PackageVersion>
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="10.0.2" />
<PackageVersion Include="Spectre.Console" Version="0.54.0" />
</ItemGroup>
</Project>
+1
View File
@@ -1,3 +1,4 @@
[*.cs]
dotnet_diagnostic.CA1303.severity = none
dotnet_diagnostic.CA2007.severity = none
+52
View File
@@ -0,0 +1,52 @@
namespace FiscalOS.AdminCLI;
internal sealed class App(
IAnsiConsole console,
IPasswordHasher passwordHasher,
IServiceScopeFactory serviceScopeFactory
) : IHostedService
{
private readonly IAnsiConsole _console = console;
private readonly IServiceScopeFactory _serviceScopeFactory = serviceScopeFactory;
public async Task StartAsync(CancellationToken cancellationToken)
{
using var scope = _serviceScopeFactory.CreateScope();
var config = scope.ServiceProvider.GetRequiredService<IConfiguration>();
var appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
var selectionPrompt = new SelectionPrompt<string>()
.Title("What do you want to do?")
.AddChoices(Commands.All);
var command = await _console.PromptAsync(selectionPrompt, cancellationToken);
switch (command)
{
case Commands.CreateUser:
var username = await _console.AskAsync<string>("Enter the [green]username[/]:", cancellationToken);
var password = _console.Prompt(
new TextPrompt<string>("Enter the [green]password[/]:")
.PromptStyle("red")
.Secret()
);
var hashedPassword = passwordHasher.Hash(password);
var user = User.From(username, hashedPassword);
await appDbContext.Users.AddAsync(user, cancellationToken);
await appDbContext.SaveChangesAsync(cancellationToken);
_console.MarkupLine($"User [green]{username}[/] created successfully.");
break;
case Commands.Exit:
default:
break;
}
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
+9
View File
@@ -0,0 +1,9 @@
namespace FiscalOS.AdminCLI;
internal static class Commands
{
public const string CreateUser = "Create User";
public const string Exit = "Exit";
public static readonly string[] All = [CreateUser, Exit];
}
@@ -4,4 +4,20 @@
<OutputType>Exe</OutputType>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" />
<PackageReference Include="Spectre.Console" />
</ItemGroup>
<ItemGroup>
<None Update="appsettings*.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\FiscalOS.Infra\FiscalOS.Infra.csproj" />
<ProjectReference Include="..\FiscalOS.Core\FiscalOS.Core.csproj" />
</ItemGroup>
</Project>
+15 -11
View File
@@ -1,11 +1,15 @@
// TODO: We need a way to add
// a user to the system with
// a properly hashed password
// so need something like this:
//
// 1. Ask for username
// 2. Ask for user password
// 3. Hash user password
// 4. Save user to database
Console.WriteLine("Hello, World!");
await Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration(
static c => c.SetBasePath(AppContext.BaseDirectory)
.AddJsonFile("appsettings.json")
.AddEnvironmentVariables()
)
.ConfigureLogging(static c => c.ClearProviders())
.ConfigureServices(static (_, services) =>
{
services.AddSingleton(AnsiConsole.Console);
services.AddInfrastructure();
services.AddHostedService<App>();
})
.Build()
.StartAsync();
+12
View File
@@ -0,0 +1,12 @@
global using FiscalOS.AdminCLI;
global using FiscalOS.Core.Authentication;
global using FiscalOS.Core.Identity;
global using FiscalOS.Infra.Data;
global using FiscalOS.Infra.DependencyInjection;
global using Microsoft.Extensions.Configuration;
global using Microsoft.Extensions.DependencyInjection;
global using Microsoft.Extensions.Hosting;
global using Microsoft.Extensions.Logging;
global using Spectre.Console;
@@ -0,0 +1,5 @@
{
"AppDbContextOptions": {
"DatabaseFilePath": "DatabaseFilePath"
}
}
+1 -3
View File
@@ -51,9 +51,7 @@ public sealed class AppDbContext(IOptions<AppDbContextOptions> ctxOptions) : DbC
.OnDelete(DeleteBehavior.Cascade);
eb.Property(static u => u.Username);
eb.Property(static u => u.HashedPassword);
});
eb.Property(static u => u.HashedPassword); });
modelBuilder.Entity<RefreshToken>(static eb =>
{
eb.Property(static t => t.Id);
@@ -3,6 +3,7 @@ namespace FiscalOS.Infra.Data;
public sealed record AppDbContextOptions
{
public string DatabaseFilePath { get; init; } = string.Empty;
public string GetFullyQualifiedDatabasePath()
{
return Path.GetFullPath(DatabaseFilePath, AppContext.BaseDirectory);
@@ -5,6 +5,7 @@ public static class ServiceCollectionExtensions
public static IServiceCollection AddInfrastructure(this IServiceCollection services)
{
services.AddSingleton(TimeProvider.System);
services.ConfigureOptions<JwtOptionsSetup>();
services.AddSingleton<ITokenGenerator, TokenGenerator>(TokenGenerator.From);
services.AddSingleton<IPasswordHasher, PasswordHasher>(PasswordHasher.From);
services.ConfigureOptions<AppDbContextOptionsSetup>();