diff --git a/Endpoint/ConfigureSwaggerOptions.cs b/Endpoint/ConfigureSwaggerOptions.cs new file mode 100644 index 0000000..9b53140 --- /dev/null +++ b/Endpoint/ConfigureSwaggerOptions.cs @@ -0,0 +1,36 @@ +using Microsoft.AspNetCore.Mvc.ApiExplorer; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; +using System; + +namespace Mirea.Api.Endpoint; + +public class ConfigureSwaggerOptions(IApiVersionDescriptionProvider provider) : IConfigureOptions +{ + public void Configure(SwaggerGenOptions options) + { + foreach (var description in provider.ApiVersionDescriptions) + { + options.SwaggerDoc(description.GroupName, CreateInfoForApiVersion(description)); + } + } + + private static OpenApiInfo CreateInfoForApiVersion(ApiVersionDescription description) + { + var info = new OpenApiInfo() + { + Title = "MIREA Schedule Web API", + Version = description.ApiVersion.ToString(), + Description = "This API provides a convenient interface for retrieving data stored in the database. Special attention was paid to the lightweight and easy transfer of all necessary data. Made by the Winsomnia team.", + Contact = new OpenApiContact { Name = "Author name", Email = "support@winsomnia.net" }, + License = new OpenApiLicense { Name = "MIT", Url = new Uri("https://opensource.org/licenses/MIT") } + }; + + if (description.IsDeprecated) + info.Description += " This API version has been deprecated."; + + return info; + } +} \ No newline at end of file diff --git a/Endpoint/Controllers/WeatherForecastController.cs b/Endpoint/Controllers/WeatherForecastController.cs deleted file mode 100644 index 80c457d..0000000 --- a/Endpoint/Controllers/WeatherForecastController.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; - -namespace Mirea.Api.Endpoint.Controllers; - -[ApiController] -[Route("[controller]")] -public class WeatherForecastController : ControllerBase -{ - private static readonly string[] Summaries = new[] - { - "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" - }; - - private readonly ILogger _logger; - - public WeatherForecastController(ILogger logger) - { - _logger = logger; - } - - [HttpGet(Name = "GetWeatherForecast")] - public IEnumerable Get() - { - return Enumerable.Range(1, 5).Select(index => new WeatherForecast - { - Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), - TemperatureC = Random.Shared.Next(-20, 55), - Summary = Summaries[Random.Shared.Next(Summaries.Length)] - }) - .ToArray(); - } -} \ No newline at end of file diff --git a/Endpoint/Endpoint.csproj b/Endpoint/Endpoint.csproj index 077a905..84dcfc2 100644 --- a/Endpoint/Endpoint.csproj +++ b/Endpoint/Endpoint.csproj @@ -11,15 +11,25 @@ Mirea.Api.Endpoint $(AssemblyName) Exe - true + false 65cea060-88bf-4e35-9cfb-18fc996a8f05 Linux . + False + True + docs.xml + $(NoWarn);1591 + + + + + + \ No newline at end of file diff --git a/Endpoint/EnvironmentManager.cs b/Endpoint/EnvironmentManager.cs new file mode 100644 index 0000000..3cbe859 --- /dev/null +++ b/Endpoint/EnvironmentManager.cs @@ -0,0 +1,24 @@ +using System; +using System.IO; + +namespace Mirea.Api.Endpoint; + +internal static class EnvironmentManager +{ + public static void LoadEnvironment(string filePath) + { + if (!File.Exists(filePath)) return; + + foreach (var line in File.ReadAllLines(filePath)) + { + var parts = line.Split( + '=', + StringSplitOptions.RemoveEmptyEntries); + + if (parts.Length != 2) + continue; + + Environment.SetEnvironmentVariable(parts[0].Trim(), parts[1][..(parts[1].Contains('#') ? parts[1].IndexOf('#') : parts[1].Length)].Trim()); + } + } +} \ No newline at end of file diff --git a/Endpoint/Program.cs b/Endpoint/Program.cs index c864599..920ce15 100644 --- a/Endpoint/Program.cs +++ b/Endpoint/Program.cs @@ -1,29 +1,114 @@ using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ApiExplorer; +using Microsoft.AspNetCore.Mvc.Versioning; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Options; +using Mirea.Api.DataAccess.Application; +using Mirea.Api.DataAccess.Persistence; +using Mirea.Api.Endpoint.Properties; +using Swashbuckle.AspNetCore.SwaggerGen; +using System; +using System.Collections; +using System.IO; +using System.Linq; namespace Mirea.Api.Endpoint; public class Program { + private static IConfigurationRoot ConfigureEnvironment() + { + EnvironmentManager.LoadEnvironment(".env"); + var environmentVariables = Environment.GetEnvironmentVariables() + .OfType() + .ToDictionary( + entry => entry.Key.ToString() ?? string.Empty, + entry => entry.Value?.ToString() ?? string.Empty + ); + + var result = new ConfigurationBuilder().AddInMemoryCollection(environmentVariables!); + + return result.Build(); + } + public static void Main(string[] args) { + Directory.SetCurrentDirectory(AppDomain.CurrentDomain.BaseDirectory); + var builder = WebApplication.CreateBuilder(args); + builder.Configuration.AddConfiguration(ConfigureEnvironment()); + builder.Configuration.AddJsonFile(Settings.FilePath, optional: true, reloadOnChange: true); - // Add services to the container. - + builder.Services.AddApplication(); + builder.Services.AddPersistence(builder.Configuration); builder.Services.AddControllers(); - // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle + + builder.Services.AddCors(options => + { + options.AddPolicy("AllowAll", policy => + { + policy.AllowAnyHeader(); + policy.AllowAnyMethod(); + policy.AllowAnyOrigin(); + }); + }); + + builder.Services.AddApiVersioning(options => + { + options.DefaultApiVersion = new ApiVersion(1, 0); + options.AssumeDefaultVersionWhenUnspecified = true; + options.ReportApiVersions = true; + options.ApiVersionReader = new UrlSegmentApiVersionReader(); + }); + + builder.Services.AddVersionedApiExplorer(options => + { + options.GroupNameFormat = "'v'VVV"; + options.SubstituteApiVersionInUrl = true; + }); + builder.Services.AddEndpointsApiExplorer(); - builder.Services.AddSwaggerGen(); + + builder.Services.AddSwaggerGen(options => + { + options.OperationFilter(); + var basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory); + + var xmlPath = Path.Combine(basePath, "docs.xml"); + options.IncludeXmlComments(xmlPath); + }); + + builder.Services.AddTransient, ConfigureSwaggerOptions>(); var app = builder.Build(); +#if DEBUG + // Write configurations + foreach (var item in app.Configuration.AsEnumerable()) + Console.WriteLine($"{item.Key}:{item.Value}"); +#endif + + var uber = app.Services.CreateScope().ServiceProvider.GetService(); + DbInitializer.Initialize(uber!); + // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseSwagger(); - app.UseSwaggerUI(); + app.UseSwaggerUI(options => + { + var provider = app.Services.GetService(); + + foreach (var description in provider!.ApiVersionDescriptions) + { + var url = $"/swagger/{description.GroupName}/swagger.json"; + var name = description.GroupName.ToUpperInvariant(); + options.SwaggerEndpoint(url, name); + } + }); } app.UseHttpsRedirection(); diff --git a/Endpoint/Properties/Settings.cs b/Endpoint/Properties/Settings.cs new file mode 100644 index 0000000..1bee892 --- /dev/null +++ b/Endpoint/Properties/Settings.cs @@ -0,0 +1,36 @@ +using Mirea.Api.DataAccess.Persistence.Properties; + +namespace Mirea.Api.Endpoint.Properties; + +public class EmailSettings +{ + public string? Server { get; set; } + public string? User { get; set; } + public string? Password { get; set; } + public string? From { get; set; } + public int? Port { get; set; } + public bool? Ssl { get; set; } +} + +public class LogSettings +{ + public bool EnableLogToFile { get; set; } + public string? LogFilePath { get; set; } + public string? LogFileName { get; set; } +} + +public class ScheduleSettings +{ + // Every 6 hours + public string CronUpdateSchedule { get; set; } = "0 0 0/6 * * *"; +} + +public class Settings +{ + public const string FilePath = "Settings.json"; + + public EmailSettings? EmailSettings { get; set; } + public LogSettings? LogSettings { get; set; } + public DbSettings? DbSettings { get; set; } + public ScheduleSettings? ScheduleSettings { get; set; } +} \ No newline at end of file diff --git a/Endpoint/SwaggerDefaultValues.cs b/Endpoint/SwaggerDefaultValues.cs new file mode 100644 index 0000000..d47c591 --- /dev/null +++ b/Endpoint/SwaggerDefaultValues.cs @@ -0,0 +1,55 @@ +using Microsoft.AspNetCore.Mvc.ApiExplorer; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; +using System; +using System.Linq; +using System.Text.Json; + +namespace Mirea.Api.Endpoint; + +public class SwaggerDefaultValues : IOperationFilter +{ + public void Apply(OpenApiOperation operation, OperationFilterContext context) + { + var apiDescription = context.ApiDescription; + operation.Deprecated |= apiDescription.IsDeprecated(); + + foreach (var responseType in context.ApiDescription.SupportedResponseTypes) + { + var responseKey = responseType.IsDefaultResponse ? "default" : responseType.StatusCode.ToString(); + var response = operation.Responses[responseKey]; + + foreach (var contentType in response.Content.Keys) + { + if (responseType.ApiResponseFormats.All(x => x.MediaType != contentType)) + { + response.Content.Remove(contentType); + } + } + } + + if (operation.Parameters == null) + { + return; + } + + foreach (var parameter in operation.Parameters) + { + var description = apiDescription.ParameterDescriptions.First(p => p.Name == parameter.Name); + + parameter.Description ??= description.ModelMetadata?.Description; + + if (parameter.Schema.Default == null && + description.DefaultValue != null && + description.DefaultValue is not DBNull && + description.ModelMetadata is ModelMetadata modelMetadata) + { + var json = JsonSerializer.Serialize(description.DefaultValue, modelMetadata.ModelType); + parameter.Schema.Default = OpenApiAnyFactory.CreateFromJson(json); + } + + parameter.Required |= description.IsRequired; + } + } +} \ No newline at end of file diff --git a/Endpoint/WeatherForecast.cs b/Endpoint/WeatherForecast.cs deleted file mode 100644 index 794bf3b..0000000 --- a/Endpoint/WeatherForecast.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; - -namespace Mirea.Api.Endpoint; - -public class WeatherForecast -{ - public DateOnly Date { get; set; } - - public int TemperatureC { get; set; } - - public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); - - public string? Summary { get; set; } -} \ No newline at end of file diff --git a/Persistence/DbInitializer.cs b/Persistence/DbInitializer.cs new file mode 100644 index 0000000..87f3a9b --- /dev/null +++ b/Persistence/DbInitializer.cs @@ -0,0 +1,11 @@ +using Microsoft.EntityFrameworkCore; + +namespace Mirea.Api.DataAccess.Persistence; + +public static class DbInitializer +{ + public static void Initialize(DbContext dbContext) + { + dbContext.Database.EnsureCreated(); + } +} \ No newline at end of file diff --git a/Persistence/DependencyInjection.cs b/Persistence/DependencyInjection.cs new file mode 100644 index 0000000..9ec71f1 --- /dev/null +++ b/Persistence/DependencyInjection.cs @@ -0,0 +1,64 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule; +using Mirea.Api.DataAccess.Persistence.Contexts.Schedule; +using Mirea.Api.DataAccess.Persistence.Properties; +using System; +using System.Collections.Generic; + +namespace Mirea.Api.DataAccess.Persistence; + +public static class DependencyInjection +{ + public static IServiceCollection AddPersistence(this IServiceCollection services, IConfiguration configuration) + { + var settings = configuration.GetSection(nameof(DbSettings)).Get(); + var connection = settings?.ConnectionStringSql; + + Dictionary> dbConfigurations = new() + { + { + DatabaseEnum.Mysql, + options => options.UseMySql(connection, ServerVersion.AutoDetect(connection)) + }, + { + DatabaseEnum.Sqlite, + options => options.UseSqlite(connection) + }, + { + DatabaseEnum.PostgresSql, + options => options.UseNpgsql(connection) + } + }; + + if (dbConfigurations.TryGetValue((DatabaseEnum)settings?.TypeDatabase!, out var dbConfig)) + { + services.AddDbContext(dbConfig); + services.AddDbContext(dbConfig); + services.AddDbContext(dbConfig); + services.AddDbContext(dbConfig); + services.AddDbContext(dbConfig); + services.AddDbContext(dbConfig); + services.AddDbContext(dbConfig); + services.AddDbContext(dbConfig); + services.AddDbContext(dbConfig); + + services.AddDbContext(dbConfig); + } + else + throw new NotSupportedException("Unsupported database type"); + + services.AddScoped(provider => provider.GetService()!); + services.AddScoped(provider => provider.GetService()!); + services.AddScoped(provider => provider.GetService()!); + services.AddScoped(provider => provider.GetService()!); + services.AddScoped(provider => provider.GetService()!); + services.AddScoped(provider => provider.GetService()!); + services.AddScoped(provider => provider.GetService()!); + services.AddScoped(provider => provider.GetService()!); + services.AddScoped(provider => provider.GetService()!); + + return services; + } +} \ No newline at end of file diff --git a/Persistence/Persistence.csproj b/Persistence/Persistence.csproj index 9cdfc62..37f0a7f 100644 --- a/Persistence/Persistence.csproj +++ b/Persistence/Persistence.csproj @@ -13,8 +13,9 @@ - - + + + @@ -24,4 +25,8 @@ + + + + \ No newline at end of file diff --git a/Persistence/Properties/DbSettings.cs b/Persistence/Properties/DbSettings.cs new file mode 100644 index 0000000..7ee06d9 --- /dev/null +++ b/Persistence/Properties/DbSettings.cs @@ -0,0 +1,15 @@ +namespace Mirea.Api.DataAccess.Persistence.Properties; + +public enum DatabaseEnum +{ + Mysql, + Sqlite, + PostgresSql +} +public class DbSettings +{ + public bool IsDoneConfiguration { get; set; } + public DatabaseEnum TypeDatabase { get; set; } + public required string ConnectionStringSql { get; set; } + public DatabaseEnum? MigrateTo { get; set; } +} \ No newline at end of file