diff --git a/OptixServe.Api/Configuration/AppSettings.cs b/OptixServe.Api/Configuration/AppSettings.cs new file mode 100644 index 0000000..751b9eb --- /dev/null +++ b/OptixServe.Api/Configuration/AppSettings.cs @@ -0,0 +1,25 @@ +namespace OptixServe.Api.Configuration; + +public record OptixServeSettings +{ + public ApiSettings? Api { get; set; } = new(); + public DatabaseSettings? Database { get; set; } = new(); +} + +public record ApiSettings +{ + public string? Listen { get; set; } = "127.0.0.1"; + public int? Port { get; set; } = 10086; +} + +public enum DatabaseType +{ + Sqlite, + MySQL +} + +public record DatabaseSettings +{ + public DatabaseType Type { get; set; } = DatabaseType.Sqlite; + public string? Host { get; set; } +} \ No newline at end of file diff --git a/OptixServe.Api/Configuration/ConfigurationHelper.cs b/OptixServe.Api/Configuration/ConfigurationHelper.cs new file mode 100644 index 0000000..4c222e3 --- /dev/null +++ b/OptixServe.Api/Configuration/ConfigurationHelper.cs @@ -0,0 +1,27 @@ +using System; + +namespace OptixServe.Api.Configuration; + +public static class ConfigurationHelper +{ + public static IConfigurationBuilder CreateDefaultBuilder() + { + var aspEnv = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"); + var netEnv = Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT"); + // Console.WriteLine($"ASPNETCORE_ENVIRONMENT: {aspEnv}, DOTNET_ENVIRONMENT: {netEnv}"); + var env = aspEnv ?? netEnv ?? null; + + var builder = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", optional: true) + .AddJsonFile("config.json", optional: true); + + if (env != null) + { + builder.AddJsonFile($"appsettings.{env}.json", optional: true) + .AddJsonFile($"config.{env}.json", optional: true); + } + + return builder; + } +} \ No newline at end of file diff --git a/OptixServe.Api/Dtos/Error.cs b/OptixServe.Api/Dtos/Error.cs new file mode 100644 index 0000000..eb1d505 --- /dev/null +++ b/OptixServe.Api/Dtos/Error.cs @@ -0,0 +1,6 @@ +namespace OptixServe.Api.Dtos; + +public record CommonErrorDto +{ + public string? Message { get; set; } +} \ No newline at end of file diff --git a/OptixServe.Api/Endpoints/VersionEndpoint.cs b/OptixServe.Api/Endpoints/VersionEndpoint.cs new file mode 100644 index 0000000..6eb01ec --- /dev/null +++ b/OptixServe.Api/Endpoints/VersionEndpoint.cs @@ -0,0 +1,35 @@ +using System.Text.Json.Serialization; +using Microsoft.Extensions.Options; +using OptixServe.Api.Configuration; +using OptixServe.Api.Dtos; + +namespace OptixServe.Api.Endpoints; + + +[JsonSerializable(typeof(string))] +[JsonSerializable(typeof(CommonErrorDto))] +public partial class VersionJsonContext : JsonSerializerContext { } + + +/// +/// This is a endpoint ONLY FOR TEST! +/// Should not expect ANY stable behavior on it! +/// +public static class VersionEndpoint +{ + public static void Register(RouteGroupBuilder parentGroup) + { + var group = parentGroup.MapGroup("/version"); + + group.MapGet("/", () => "v1"); + group.MapGet("/test/dbconfig", (IOptions appSettings) => + { + var dbType = appSettings.Value.Database?.Type; + var dbHost = appSettings.Value.Database?.Host; + return Results.Ok(new CommonErrorDto + { + Message = $"Set up {dbType} database on {dbHost}" + }); + }); + } +} \ No newline at end of file diff --git a/OptixServe.Api/OptixServe.Api.csproj b/OptixServe.Api/OptixServe.Api.csproj index 3815467..f69bfae 100644 --- a/OptixServe.Api/OptixServe.Api.csproj +++ b/OptixServe.Api/OptixServe.Api.csproj @@ -1,11 +1,16 @@ - - + + - - + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + @@ -13,7 +18,7 @@ enable enable true - true + false diff --git a/OptixServe.Api/Program.cs b/OptixServe.Api/Program.cs index 4952dd1..8da4d29 100644 --- a/OptixServe.Api/Program.cs +++ b/OptixServe.Api/Program.cs @@ -1,6 +1,9 @@ using System.CommandLine; +using OptixServe.Api.Configuration; using OptixServe.Api.Endpoints; +using OptixServe.Core.Data; using OptixServe.Core.Services; +using OptixServe.Api.Utilites; class Program { @@ -46,8 +49,15 @@ class Program builder.RegiserJsonContext(); var app = builder.Build(); + + using (var scope = app.Services.CreateScope()) + { + var initializer = scope.ServiceProvider.GetRequiredService(); + initializer.Initialize(); + } + var apiGroup = app.MapGroup("api/v1"); - ExtensionMethods.RegisterEndpoints(apiGroup); + StartupHelper.RegisterEndpoints(apiGroup); app.Run(); }); @@ -62,7 +72,7 @@ class Program /// /// Contains extension methods for WebApplicationBuilder and WebApplication /// -static class ExtensionMethods +static class StartupHelper { /// /// Adds configuration sources to the application builder @@ -88,6 +98,37 @@ static class ExtensionMethods builder.Configuration.AddConfiguration(configurationBuilder.Build()); } + /// + /// Configures DbContext services + /// + /// + /// + /// + public static IServiceCollection AddAppDatabase(this IServiceCollection services, DatabaseSettings dbSettings) + { + services.AddDbContext(options => DatabaseHelper.ConfigureDbContext(options, dbSettings)); + return services; + } + + /// + /// Configures services for DI + /// + /// WebApplicationBuilder instance + public static void RegisterServices(this WebApplicationBuilder builder) + { + // Add configuration class + var optixSettigns = builder.Configuration.GetSection("OptixServe"); + var onConfigSettings = optixSettigns.Get(); + builder.Services.Configure(optixSettigns); + + // Add DBContext class + builder.Services.AddAppDatabase(onConfigSettings?.Database!); + builder.Services.AddScoped(); + + // Application services + builder.Services.AddScoped(); + } + /// /// Configures JSON serialization options with custom context /// @@ -97,26 +138,18 @@ static class ExtensionMethods builder.Services.ConfigureHttpJsonOptions(options => { options.SerializerOptions.TypeInfoResolverChain.Add(UserJsonContext.Default); + options.SerializerOptions.TypeInfoResolverChain.Add(VersionJsonContext.Default); }); } - /// - /// Configures services for DI - /// - /// - public static void RegisterServices(this WebApplicationBuilder builder) - { - // Application services - builder.Services.AddScoped(); - } - /// /// Registers all API endpoints /// - /// WebApplication instance + /// Root RouteGroupBuilder instance public static void RegisterEndpoints(RouteGroupBuilder rootGroup) { UserEndpoint.Register(rootGroup); + VersionEndpoint.Register(rootGroup); } } \ No newline at end of file diff --git a/OptixServe.Api/Utilites/DatabaseHelper.cs b/OptixServe.Api/Utilites/DatabaseHelper.cs new file mode 100644 index 0000000..3107d66 --- /dev/null +++ b/OptixServe.Api/Utilites/DatabaseHelper.cs @@ -0,0 +1,34 @@ +using Microsoft.EntityFrameworkCore; +using OptixServe.Api.Configuration; +using OptixServe.Core.Data; + +namespace OptixServe.Api.Utilites; + +public static class DatabaseHelper +{ + public static string BuildConnectionString(DatabaseSettings dbSettings) + { + return dbSettings.Type switch + { + DatabaseType.Sqlite => $"Data Source={dbSettings.Host ?? "optixserve.db"}", + DatabaseType.MySQL => throw new NotSupportedException("MySQL connection is not yet implemented"), + _ => throw new NotSupportedException($"Database type {dbSettings.Type} is not supported") + }; + } + + public static void ConfigureDbContext(DbContextOptionsBuilder options, DatabaseSettings dbSettings) + { + if (dbSettings?.Type == DatabaseType.Sqlite) + { + var dbPath = dbSettings.Host ?? "optixserve.db"; + var connectionString = $"Data Source={dbPath}"; + + options.UseSqlite(connectionString, b => b.MigrationsAssembly("OptixServe.Api")); + + } + else + { + throw new NotImplementedException("Only SQLite database is currently supported"); + } + } +} \ No newline at end of file diff --git a/OptixServe.Api/Utilites/DesignTimeDbContextFactory.cs b/OptixServe.Api/Utilites/DesignTimeDbContextFactory.cs new file mode 100644 index 0000000..f9d01d8 --- /dev/null +++ b/OptixServe.Api/Utilites/DesignTimeDbContextFactory.cs @@ -0,0 +1,20 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Design; +using OptixServe.Api.Configuration; +using OptixServe.Core.Data; + +namespace OptixServe.Api.Utilites; + +public class DesignTimeDbContextFactory : IDesignTimeDbContextFactory +{ + public AppDbContext CreateDbContext(string[] args) + { + var configuration = ConfigurationHelper.CreateDefaultBuilder().Build(); + + var dbSettings = configuration.GetSection("OptixServe:Database").Get()!; + var optionsBuilder = new DbContextOptionsBuilder(); + DatabaseHelper.ConfigureDbContext(optionsBuilder, dbSettings); + + return new AppDbContext(optionsBuilder.Options); + } +} \ No newline at end of file diff --git a/OptixServe.Api/appsettings.json b/OptixServe.Api/appsettings.json index 4d56694..0d75ce5 100644 --- a/OptixServe.Api/appsettings.json +++ b/OptixServe.Api/appsettings.json @@ -5,5 +5,15 @@ "Microsoft.AspNetCore": "Warning" } }, - "AllowedHosts": "*" + "AllowedHosts": "*", + "OptixServe": { + "Api": { + "Listen": "0.0.0.0", + "Port": "54321" + }, + "Database": { + "Type": "Sqlite", + "Host": "optixserve.db" + } + } } diff --git a/OptixServe.Core/Data/AppDbContext.cs b/OptixServe.Core/Data/AppDbContext.cs new file mode 100644 index 0000000..2116fc3 --- /dev/null +++ b/OptixServe.Core/Data/AppDbContext.cs @@ -0,0 +1,21 @@ +using Microsoft.EntityFrameworkCore; +using OptixServe.Core.Models; + +namespace OptixServe.Core.Data; + +public class AppDbContext(DbContextOptions options) : DbContext(options) +{ + public DbSet Users { get; set; } = null!; + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity(user => + { + user.HasKey(u => u.Id); + }); + + modelBuilder.Entity().HasData([ + new() {Id = "1", UserName = "admin", Password = "admin12345"} + ]); + } +} \ No newline at end of file diff --git a/OptixServe.Core/Data/DbInitializer.cs b/OptixServe.Core/Data/DbInitializer.cs new file mode 100644 index 0000000..bec11cc --- /dev/null +++ b/OptixServe.Core/Data/DbInitializer.cs @@ -0,0 +1,11 @@ +namespace OptixServe.Core.Data; + +public class DbInitializer(AppDbContext dbContext) +{ + private readonly AppDbContext _context = dbContext; + + public void Initialize() + { + _context.Database.EnsureCreated(); + } +} \ No newline at end of file diff --git a/OptixServe.Core/Models/User.cs b/OptixServe.Core/Models/User.cs index 671e88e..3dbd0b4 100644 --- a/OptixServe.Core/Models/User.cs +++ b/OptixServe.Core/Models/User.cs @@ -1,8 +1,15 @@ namespace OptixServe.Core.Models; +public enum PrivilegeGroup +{ + Admin, + User, +} + public record User { public required string Id { get; set; } public required string UserName { get; set; } - public required string Password { get; set; } + public string? Password { get; set; } + public PrivilegeGroup PrivilegeGroup { get; set; } = PrivilegeGroup.User; } \ No newline at end of file diff --git a/OptixServe.Core/OptixServe.Core.csproj b/OptixServe.Core/OptixServe.Core.csproj index 125f4c9..354caa7 100644 --- a/OptixServe.Core/OptixServe.Core.csproj +++ b/OptixServe.Core/OptixServe.Core.csproj @@ -6,4 +6,8 @@ enable + + + + diff --git a/OptixServe.Core/Services/UserService.cs b/OptixServe.Core/Services/UserService.cs index 8d9d292..01c4d7d 100644 --- a/OptixServe.Core/Services/UserService.cs +++ b/OptixServe.Core/Services/UserService.cs @@ -1,3 +1,4 @@ +using OptixServe.Core.Data; using OptixServe.Core.Models; namespace OptixServe.Core.Services; @@ -5,21 +6,20 @@ namespace OptixServe.Core.Services; public interface IUserService { IEnumerable GetUsers(); - User? GetUserById(string Id); + User? GetUserById(string id); } -public class UserService : IUserService +public class UserService(AppDbContext dbContext) : IUserService { - public User? GetUserById(string Id) + private readonly AppDbContext _dbContext = dbContext; + + public User? GetUserById(string id) { - throw new NotImplementedException(); + return _dbContext.Users.FirstOrDefault(u => u.Id == id); } public IEnumerable GetUsers() { - return [ - new() { Id = "1234", UserName = "xxx", Password = "pass1" }, - new() { Id = "5678", UserName = "yyy", Password = "pass2" } - ]; + return _dbContext.Users.AsEnumerable(); } } \ No newline at end of file