From f06a9fcaead95e73e9f9ef9a7300be2e2bb0e37d Mon Sep 17 00:00:00 2001 From: Tudor Stanciu Date: Sat, 6 Jun 2020 18:29:20 +0300 Subject: [PATCH] infrastructure --- .../BasicAuthenticationHandler.cs | 61 ++++++++++ Chatbot.Api/Chatbot.Api.csproj | 20 +++ Chatbot.Api/Controllers/SystemController.cs | 35 ++++++ .../Controllers/WeatherForecastController.cs | 39 ------ Chatbot.Api/Program.cs | 62 +++++++++- Chatbot.Api/Startup.cs | 57 +++++++-- Chatbot.Api/Swagger/DtoSchemaFilter.cs | 36 ++++++ .../Swagger/PathParamsOperationFilter.cs | 34 ++++++ Chatbot.Api/Swagger/SwaggerExtensions.cs | 114 ++++++++++++++++++ Chatbot.Api/WeatherForecast.cs | 15 --- Chatbot.Api/appsettings.json | 10 +- .../Chatbot.Api.Application.csproj | 13 ++ Chatbot.Application/Class1.cs | 8 -- Chatbot.Application/Commands/Command.cs | 18 +++ Chatbot.Application/Commands/Metadata.cs | 25 ++++ .../DependencyInjectionExtensions.cs | 15 +++ .../Mappings/MappingProfile.cs | 14 +++ Chatbot.Application/Queries/GetBots.cs | 45 +++++++ Chatbot.Application/Queries/Query.cs | 8 ++ Chatbot.Application/Services/ParamProvider.cs | 18 +++ Chatbot.Application/Services/UserService.cs | 34 ++++++ .../Chatbot.Api.Domain.Data.csproj | 8 ++ Chatbot.Domain.Data/Class1.cs | 8 -- .../DbContexts/BotDbContext.cs | 25 ++++ .../DbContexts/ChatDbContext.cs | 16 +++ .../DependencyInjectionExtensions.cs | 26 ++++ .../BotConfiguration.cs | 14 +++ .../Repositories/BotRepository.cs | 23 ++++ .../Scripts/01.Chatbot tables.sql | 70 +++++++++++ .../Scripts/02.Insert into MessageSource.sql | 13 ++ Chatbot.Domain/Class1.cs | 8 -- Chatbot.Domain/Entities/Bot.cs | 12 ++ Chatbot.Domain/Entities/User.cs | 9 ++ Chatbot.Domain/Models/Settings/Credentials.cs | 8 ++ Chatbot.Domain/Repositories/IBotRepository.cs | 10 ++ Chatbot.Domain/Services/IParamProvider.cs | 9 ++ Chatbot.sln | 1 + Notes.txt | 1 + 38 files changed, 848 insertions(+), 94 deletions(-) create mode 100644 Chatbot.Api/Authentication/BasicAuthenticationHandler.cs create mode 100644 Chatbot.Api/Controllers/SystemController.cs delete mode 100644 Chatbot.Api/Controllers/WeatherForecastController.cs create mode 100644 Chatbot.Api/Swagger/DtoSchemaFilter.cs create mode 100644 Chatbot.Api/Swagger/PathParamsOperationFilter.cs create mode 100644 Chatbot.Api/Swagger/SwaggerExtensions.cs delete mode 100644 Chatbot.Api/WeatherForecast.cs delete mode 100644 Chatbot.Application/Class1.cs create mode 100644 Chatbot.Application/Commands/Command.cs create mode 100644 Chatbot.Application/Commands/Metadata.cs create mode 100644 Chatbot.Application/DependencyInjectionExtensions.cs create mode 100644 Chatbot.Application/Mappings/MappingProfile.cs create mode 100644 Chatbot.Application/Queries/GetBots.cs create mode 100644 Chatbot.Application/Queries/Query.cs create mode 100644 Chatbot.Application/Services/ParamProvider.cs create mode 100644 Chatbot.Application/Services/UserService.cs delete mode 100644 Chatbot.Domain.Data/Class1.cs create mode 100644 Chatbot.Domain.Data/DbContexts/BotDbContext.cs create mode 100644 Chatbot.Domain.Data/DbContexts/ChatDbContext.cs create mode 100644 Chatbot.Domain.Data/DependencyInjectionExtensions.cs create mode 100644 Chatbot.Domain.Data/EntityTypeConfiguration/BotConfiguration.cs create mode 100644 Chatbot.Domain.Data/Repositories/BotRepository.cs create mode 100644 Chatbot.Domain.Data/Scripts/01.Chatbot tables.sql create mode 100644 Chatbot.Domain.Data/Scripts/02.Insert into MessageSource.sql delete mode 100644 Chatbot.Domain/Class1.cs create mode 100644 Chatbot.Domain/Entities/Bot.cs create mode 100644 Chatbot.Domain/Entities/User.cs create mode 100644 Chatbot.Domain/Models/Settings/Credentials.cs create mode 100644 Chatbot.Domain/Repositories/IBotRepository.cs create mode 100644 Chatbot.Domain/Services/IParamProvider.cs create mode 100644 Notes.txt diff --git a/Chatbot.Api/Authentication/BasicAuthenticationHandler.cs b/Chatbot.Api/Authentication/BasicAuthenticationHandler.cs new file mode 100644 index 0000000..d53b066 --- /dev/null +++ b/Chatbot.Api/Authentication/BasicAuthenticationHandler.cs @@ -0,0 +1,61 @@ +using Chatbot.Api.Application.Services; +using Microsoft.AspNetCore.Authentication; +using System; +using System.Linq; +using System.Threading.Tasks; +using System.Net.Http.Headers; +using System.Security.Claims; +using System.Text.Encodings.Web; +using System.Text; +using Chatbot.Api.Domain.Entities; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Chatbot.Api.Authentication +{ + public class BasicAuthenticationHandler : AuthenticationHandler + { + private readonly IUserService _userService; + + public BasicAuthenticationHandler(IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock, IUserService userService) + : base(options, logger, encoder, clock) + { + _userService = userService; + } + + protected override async Task HandleAuthenticateAsync() + { + if (!Request.Headers.ContainsKey("Authorization")) + return AuthenticateResult.Fail("Missing Authorization Header"); + + User user; + try + { + var authHeader = AuthenticationHeaderValue.Parse(Request.Headers["Authorization"]); + var credentialBytes = Convert.FromBase64String(authHeader.Parameter); + var credentials = Encoding.UTF8.GetString(credentialBytes).Split(':'); + var username = credentials.First(); + var password = credentials.Last(); + user = await _userService.Authenticate(username, password); + } + catch + { + return AuthenticateResult.Fail("Invalid Authorization Header"); + } + + if (user == null) + return AuthenticateResult.Fail("Invalid Username or Password"); + + var claims = new[] { + new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()), + new Claim(ClaimTypes.Name, user.UserName), + }; + + var identity = new ClaimsIdentity(claims, Scheme.Name); + var principal = new ClaimsPrincipal(identity); + var ticket = new AuthenticationTicket(principal, Scheme.Name); + + return AuthenticateResult.Success(ticket); + } + } +} \ No newline at end of file diff --git a/Chatbot.Api/Chatbot.Api.csproj b/Chatbot.Api/Chatbot.Api.csproj index d12c450..2a9c58c 100644 --- a/Chatbot.Api/Chatbot.Api.csproj +++ b/Chatbot.Api/Chatbot.Api.csproj @@ -4,5 +4,25 @@ netcoreapp3.1 + + + + + + + + + + + + + + + + + + + + diff --git a/Chatbot.Api/Controllers/SystemController.cs b/Chatbot.Api/Controllers/SystemController.cs new file mode 100644 index 0000000..2dc2e4b --- /dev/null +++ b/Chatbot.Api/Controllers/SystemController.cs @@ -0,0 +1,35 @@ +using Chatbot.Api.Application.Queries; +using MediatR; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using System.Threading.Tasks; + +namespace Chatbot.Api.Controllers +{ + [Authorize] + [ApiController] + [Route("system")] + public class SystemController : ControllerBase + { + private readonly IMediator _mediator; + + public SystemController(IMediator mediator) + { + _mediator = mediator; + } + + [AllowAnonymous] + [HttpGet("ping")] + public IActionResult Ping() + { + return Ok("Chatbot api ping success."); + } + + [HttpGet("bots")] + public async Task GetReleaseNotes([FromRoute] GetBots.Query query) + { + var result = await _mediator.Send(query); + return Ok(result); + } + } +} diff --git a/Chatbot.Api/Controllers/WeatherForecastController.cs b/Chatbot.Api/Controllers/WeatherForecastController.cs deleted file mode 100644 index f264e78..0000000 --- a/Chatbot.Api/Controllers/WeatherForecastController.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; - -namespace Chatbot.Api.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] - public IEnumerable Get() - { - var rng = new Random(); - return Enumerable.Range(1, 5).Select(index => new WeatherForecast - { - Date = DateTime.Now.AddDays(index), - TemperatureC = rng.Next(-20, 55), - Summary = Summaries[rng.Next(Summaries.Length)] - }) - .ToArray(); - } - } -} diff --git a/Chatbot.Api/Program.cs b/Chatbot.Api/Program.cs index 380f5a5..10faabe 100644 --- a/Chatbot.Api/Program.cs +++ b/Chatbot.Api/Program.cs @@ -1,11 +1,14 @@ using System; -using System.Collections.Generic; +using System.Diagnostics; +using System.IO; using System.Linq; -using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; +using Serilog; +using Serilog.Core; +using Serilog.Events; +using Serilog.Sinks.MSSqlServer; namespace Chatbot.Api { @@ -13,14 +16,61 @@ namespace Chatbot.Api { public static void Main(string[] args) { - CreateHostBuilder(args).Build().Run(); + if (!args.Contains("--docker")) + { + var pathToExe = Process.GetCurrentProcess().MainModule.FileName; + var pathToContentRoot = Path.GetDirectoryName(pathToExe); + Directory.SetCurrentDirectory(pathToContentRoot); + } + + var configuration = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) + .Build(); + + var connectionString = configuration.GetConnectionString("DatabaseConnection"); + var loggingLevelParam = configuration.GetValue("Logging:LogLevel:Default"); + + Enum.TryParse(loggingLevelParam, out LogEventLevel loggingLevel); + var loggingLevelSwitch = new LoggingLevelSwitch(loggingLevel); + + var columnOptions = new ColumnOptions(); + columnOptions.Store.Remove(StandardColumn.Properties); + columnOptions.Store.Remove(StandardColumn.MessageTemplate); + columnOptions.Store.Add(StandardColumn.LogEvent); + Log.Logger = new LoggerConfiguration() + .MinimumLevel.ControlledBy(loggingLevelSwitch) + .MinimumLevel.Override("Microsoft", LogEventLevel.Warning) + .Enrich.FromLogContext() + .WriteTo.Console() + .WriteTo.MSSqlServer(connectionString, "__Logs_Api", autoCreateSqlTable: true, columnOptions: columnOptions) + .CreateLogger(); + + try + { + var urls = configuration.GetValue("urls"); + Log.Information("Starting chatbot API..."); + Log.Information($"API listening on {urls}"); + Console.WriteLine("Application started. Press Ctrl+C to shut down."); + CreateHostBuilder(args, configuration).Build().Run(); + } + catch (Exception ex) + { + Log.Fatal(ex, "Chatbot API host terminated unexpectedly"); + } + finally + { + Log.CloseAndFlush(); + } } - public static IHostBuilder CreateHostBuilder(string[] args) => + public static IHostBuilder CreateHostBuilder(string[] args, IConfiguration configuration) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { - webBuilder.UseStartup(); + webBuilder.UseStartup() + .UseConfiguration(configuration) + .UseSerilog(); }); } } diff --git a/Chatbot.Api/Startup.cs b/Chatbot.Api/Startup.cs index 82cde74..2c59b38 100644 --- a/Chatbot.Api/Startup.cs +++ b/Chatbot.Api/Startup.cs @@ -1,14 +1,18 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; +using AutoMapper; +using Chatbot.Api.Authentication; +using Chatbot.Api.Domain.Data; +using Chatbot.Api.Swagger; +using Chatbot.Application; +using MediatR; +using MediatR.Pipeline; +using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using System.Reflection; namespace Chatbot.Api { @@ -24,25 +28,60 @@ namespace Chatbot.Api // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { - services.AddControllers(); + services.AddControllers() + .AddNewtonsoftJson(o => o.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Utc); + + // configure basic authentication + services.AddAuthentication("BasicAuthentication") + .AddScheme("BasicAuthentication", null); + + // MediatR + services.AddMediatR(GetMediatRAssemblies()); + services.AddScoped(typeof(IPipelineBehavior<,>), typeof(RequestPreProcessorBehavior<,>)); + services.AddScoped(typeof(IPipelineBehavior<,>), typeof(RequestPostProcessorBehavior<,>)); + + // AutoMapper + services.AddAutoMapper( + typeof(Application.Mappings.MappingProfile).Assembly); + + // Swagger + services.AddSwagger(); + + // Application + services.AddApplicationServices(); + + // DataAccess + services.AddDataAccess(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { + // global cors policy + app.UseCors(x => x + .AllowAnyOrigin() + .AllowAnyMethod() + .AllowAnyHeader()); + if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseRouting(); - + app.UseAuthentication(); app.UseAuthorization(); - app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); + app.ConfigureSwagger(); + } + + private Assembly[] GetMediatRAssemblies() + { + var assembly = typeof(Application.Queries.GetBots).Assembly; + return new Assembly[] { assembly }; } } } diff --git a/Chatbot.Api/Swagger/DtoSchemaFilter.cs b/Chatbot.Api/Swagger/DtoSchemaFilter.cs new file mode 100644 index 0000000..9dad03b --- /dev/null +++ b/Chatbot.Api/Swagger/DtoSchemaFilter.cs @@ -0,0 +1,36 @@ +using Chatbot.Api.Application.Commands; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; +using System.Linq; + +namespace Chatbot.Api.Swagger +{ + public class DtoSchemaFilter : ISchemaFilter + { + public void Apply(OpenApiSchema schema, SchemaFilterContext context) + { + var targetType = context.Type; + while (targetType != null) + { + if (typeof(ICommand).IsAssignableFrom(targetType)) + { + foreach (var property in schema.Properties.ToList()) + { + property.Value.ReadOnly = false; + + switch (property.Key) + { + case "metadata": + schema.Properties.Remove(property.Key); + break; + default: + break; + } + } + } + + targetType = targetType.DeclaringType; + } + } + } +} diff --git a/Chatbot.Api/Swagger/PathParamsOperationFilter.cs b/Chatbot.Api/Swagger/PathParamsOperationFilter.cs new file mode 100644 index 0000000..df43634 --- /dev/null +++ b/Chatbot.Api/Swagger/PathParamsOperationFilter.cs @@ -0,0 +1,34 @@ +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; +using System; +using System.Linq; +using System.Text.RegularExpressions; + +namespace Chatbot.Api.Swagger +{ + public class PathParamsOperationFilter : IOperationFilter + { + public void Apply(OpenApiOperation operation, OperationFilterContext context) + { + const string paramCaptureGroup = "param"; + + var openApiPathParameters = operation.Parameters.Where(param => param.In == ParameterLocation.Path).ToList(); + var pathParamRegEx = $@"\{{(?<{paramCaptureGroup}>[^\}}]+)\}}"; + + if (openApiPathParameters.Any()) + { + var pathParameterMatches = Regex.Matches(context.ApiDescription.RelativePath, pathParamRegEx, RegexOptions.Compiled); + var pathParameters = pathParameterMatches.Select(x => x.Groups[paramCaptureGroup].Value); + + foreach (var openApiPathParameter in openApiPathParameters) + { + var correspondingPathParameter = pathParameters.FirstOrDefault(x => + string.Equals(x, openApiPathParameter.Name, StringComparison.InvariantCultureIgnoreCase)); + + if (correspondingPathParameter != null) + openApiPathParameter.Name = correspondingPathParameter; + } + } + } + } +} diff --git a/Chatbot.Api/Swagger/SwaggerExtensions.cs b/Chatbot.Api/Swagger/SwaggerExtensions.cs new file mode 100644 index 0000000..d9c5ed1 --- /dev/null +++ b/Chatbot.Api/Swagger/SwaggerExtensions.cs @@ -0,0 +1,114 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OpenApi.Models; +using System.Collections.Generic; +using System.Linq; + +namespace Chatbot.Api.Swagger +{ + public static class SwaggerExtensions + { + public static IServiceCollection AddSwagger(this IServiceCollection services) + { + services.AddSwaggerGen(c => + { + c.SwaggerDoc("v1", + new OpenApiInfo + { + Title = "Chatbot API", + Version = "v1" + }); + + c.AddSecurityDefinition("Basic", + new OpenApiSecurityScheme + { + In = ParameterLocation.Header, + Description = @"JWT Authorization header using the Basic scheme. Enter 'Basic' [space] and then your token in the text input below. Example: 'Basic 12345abcdef'", + Name = "Authorization", + Scheme = "Basic", + Type = SecuritySchemeType.ApiKey + }); + + c.AddSecurityRequirement(new OpenApiSecurityRequirement() + { + { + new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Type = ReferenceType.SecurityScheme, + Id = "Basic" + }, + Scheme = "Basic", + Name = "Authorization", + In = ParameterLocation.Header + }, + new List() + } + }); + + c.OperationFilter(); + c.SchemaFilter(); + c.CustomSchemaIds(type => type.ToString()); + }); + + return services; + } + + public static IApplicationBuilder ConfigureSwagger(this IApplicationBuilder applicationBuilder) + { + applicationBuilder.UseSwagger(c => + { + c.PreSerializeFilters.Add((swagger, httpRequest) => + { + var (host, basePath, scheme) = GetUrlComponents(httpRequest); + + swagger.Servers = new List + { + new OpenApiServer {Url = $"{scheme}://{host}{basePath}"} + }; + }); + c.RouteTemplate = "swagger/{documentName}/swagger.json"; + }); + + applicationBuilder.UseSwaggerUI(c => + { + c.SwaggerEndpoint("v1/swagger.json", "Chatbot API"); + c.RoutePrefix = $"swagger"; + }); + + return applicationBuilder; + } + + private static (string host, string basePath, string scheme) GetUrlComponents(HttpRequest request) + { + var host = ExtractHost(request); + var basePath = ExtractBasePath(request); + var scheme = ExtractScheme(request); + + return (host, basePath, scheme); + } + + private static string ExtractHost(HttpRequest request) + { + if (request.Headers.ContainsKey("X-Forwarded-Host")) + return request.Headers["X-Forwarded-Host"].First(); + + return request.Host.Value; + } + + private static string ExtractBasePath(HttpRequest request) + { + if (request.Headers.ContainsKey("X-Forwarded-PathBase")) + return request.Headers["X-Forwarded-PathBase"].First(); + + return string.Empty; + } + + private static string ExtractScheme(HttpRequest request) + { + return request.Headers["X-Forwarded-Proto"].FirstOrDefault() ?? request.Scheme; + } + } +} \ No newline at end of file diff --git a/Chatbot.Api/WeatherForecast.cs b/Chatbot.Api/WeatherForecast.cs deleted file mode 100644 index 50db138..0000000 --- a/Chatbot.Api/WeatherForecast.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; - -namespace Chatbot.Api -{ - public class WeatherForecast - { - public DateTime Date { get; set; } - - public int TemperatureC { get; set; } - - public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); - - public string Summary { get; set; } - } -} diff --git a/Chatbot.Api/appsettings.json b/Chatbot.Api/appsettings.json index d9d9a9b..2ea9fa6 100644 --- a/Chatbot.Api/appsettings.json +++ b/Chatbot.Api/appsettings.json @@ -1,4 +1,8 @@ { + "urls": "http://*:5061", + "ConnectionStrings": { + "DatabaseConnection": "***REMOVED***" + }, "Logging": { "LogLevel": { "Default": "Information", @@ -6,5 +10,9 @@ "Microsoft.Hosting.Lifetime": "Information" } }, - "AllowedHosts": "*" + "AllowedHosts": "*", + "Credentials": { + "UserName": "***REMOVED***", + "Password": "***REMOVED***" + } } diff --git a/Chatbot.Application/Chatbot.Api.Application.csproj b/Chatbot.Application/Chatbot.Api.Application.csproj index 9f5c4f4..edf00d3 100644 --- a/Chatbot.Application/Chatbot.Api.Application.csproj +++ b/Chatbot.Application/Chatbot.Api.Application.csproj @@ -4,4 +4,17 @@ netstandard2.0 + + + + + + + + + + + + + diff --git a/Chatbot.Application/Class1.cs b/Chatbot.Application/Class1.cs deleted file mode 100644 index 3133ea8..0000000 --- a/Chatbot.Application/Class1.cs +++ /dev/null @@ -1,8 +0,0 @@ -using System; - -namespace Chatbot.Application -{ - public class Class1 - { - } -} diff --git a/Chatbot.Application/Commands/Command.cs b/Chatbot.Application/Commands/Command.cs new file mode 100644 index 0000000..f747213 --- /dev/null +++ b/Chatbot.Application/Commands/Command.cs @@ -0,0 +1,18 @@ +using MediatR; + +namespace Chatbot.Api.Application.Commands +{ + public abstract class Command : ICommand, IRequest + { + public Metadata Metadata { get; } + + protected Command(Metadata metadata) + { + Metadata = metadata; + } + } + + public interface ICommand + { + } +} diff --git a/Chatbot.Application/Commands/Metadata.cs b/Chatbot.Application/Commands/Metadata.cs new file mode 100644 index 0000000..57d8cf7 --- /dev/null +++ b/Chatbot.Application/Commands/Metadata.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; + +namespace Chatbot.Api.Application.Commands +{ + public class Metadata : Dictionary + { + public const string CorrelationIdKey = "CorrelationId"; + + public Guid CorrelationId + { + get + { + return Guid.Parse(this[CorrelationIdKey]); + } + set + { + if (ContainsKey(CorrelationIdKey)) + this[CorrelationIdKey] = value.ToString(); + else + Add(CorrelationIdKey, value.ToString()); + } + } + } +} diff --git a/Chatbot.Application/DependencyInjectionExtensions.cs b/Chatbot.Application/DependencyInjectionExtensions.cs new file mode 100644 index 0000000..938efd2 --- /dev/null +++ b/Chatbot.Application/DependencyInjectionExtensions.cs @@ -0,0 +1,15 @@ +using Chatbot.Api.Application.Services; +using Chatbot.Api.Domain.Services; +using Microsoft.Extensions.DependencyInjection; + +namespace Chatbot.Application +{ + public static class DependencyInjectionExtensions + { + public static void AddApplicationServices(this IServiceCollection services) + { + services.AddSingleton(); + services.AddScoped(); + } + } +} diff --git a/Chatbot.Application/Mappings/MappingProfile.cs b/Chatbot.Application/Mappings/MappingProfile.cs new file mode 100644 index 0000000..8de78c9 --- /dev/null +++ b/Chatbot.Application/Mappings/MappingProfile.cs @@ -0,0 +1,14 @@ +using AutoMapper; +using Chatbot.Api.Application.Queries; +using Chatbot.Api.Domain.Entities; + +namespace Chatbot.Api.Application.Mappings +{ + public class MappingProfile : Profile + { + public MappingProfile() + { + CreateMap(); + } + } +} \ No newline at end of file diff --git a/Chatbot.Application/Queries/GetBots.cs b/Chatbot.Application/Queries/GetBots.cs new file mode 100644 index 0000000..c52e0cc --- /dev/null +++ b/Chatbot.Application/Queries/GetBots.cs @@ -0,0 +1,45 @@ +using AutoMapper; +using Chatbot.Api.Domain.Repositories; +using MediatR; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Chatbot.Api.Application.Queries +{ + public class GetBots + { + public class Query : Query + { + public Query() { } + } + + public class Model + { + public Guid BotId { get; set; } + public string BotCode { get; set; } + public string BotName { get; set; } + public DateTime CreationDate { get; set; } + } + + public class QueryHandler : IRequestHandler + { + private readonly IBotRepository _botRepository; + private readonly IMapper _mapper; + + public QueryHandler(IBotRepository botRepository, IMapper mapper) + { + _botRepository = botRepository; + _mapper = mapper; + } + + public async Task Handle(Query request, CancellationToken cancellationToken) + { + var bots = await _botRepository.GetBots(); + var result = _mapper.Map(bots); + + return result; + } + } + } +} diff --git a/Chatbot.Application/Queries/Query.cs b/Chatbot.Application/Queries/Query.cs new file mode 100644 index 0000000..e712ec3 --- /dev/null +++ b/Chatbot.Application/Queries/Query.cs @@ -0,0 +1,8 @@ +using MediatR; + +namespace Chatbot.Api.Application.Queries +{ + public abstract class Query : IRequest, IBaseRequest + { + } +} diff --git a/Chatbot.Application/Services/ParamProvider.cs b/Chatbot.Application/Services/ParamProvider.cs new file mode 100644 index 0000000..b1f45c2 --- /dev/null +++ b/Chatbot.Application/Services/ParamProvider.cs @@ -0,0 +1,18 @@ +using Chatbot.Api.Domain.Models.Settings; +using Chatbot.Api.Domain.Services; +using Microsoft.Extensions.Configuration; + +namespace Chatbot.Api.Application.Services +{ + public class ParamProvider : IParamProvider + { + private readonly IConfiguration _configuration; + + public ParamProvider(IConfiguration configuration) + { + _configuration = configuration; + } + + public Credentials Credentials => _configuration.GetSection("Credentials").Get(); + } +} \ No newline at end of file diff --git a/Chatbot.Application/Services/UserService.cs b/Chatbot.Application/Services/UserService.cs new file mode 100644 index 0000000..bab6992 --- /dev/null +++ b/Chatbot.Application/Services/UserService.cs @@ -0,0 +1,34 @@ +using Chatbot.Api.Domain.Entities; +using Chatbot.Api.Domain.Services; +using System.Threading.Tasks; + +namespace Chatbot.Api.Application.Services +{ + public interface IUserService + { + Task Authenticate(string username, string password); + } + + public class UserService : IUserService + { + private readonly IParamProvider _paramProvider; + + public UserService(IParamProvider paramProvider) + { + _paramProvider = paramProvider; + } + + public async Task Authenticate(string username, string password) + { + return await Task.Run(() => CheckCredentials(username, password)); + } + + private User CheckCredentials(string username, string password) + { + if (_paramProvider.Credentials.UserName == username && _paramProvider.Credentials.Password == password) + return new User() { UserName = username, Id = 1 }; + else + return null; + } + } +} diff --git a/Chatbot.Domain.Data/Chatbot.Api.Domain.Data.csproj b/Chatbot.Domain.Data/Chatbot.Api.Domain.Data.csproj index 9f5c4f4..4c4501c 100644 --- a/Chatbot.Domain.Data/Chatbot.Api.Domain.Data.csproj +++ b/Chatbot.Domain.Data/Chatbot.Api.Domain.Data.csproj @@ -4,4 +4,12 @@ netstandard2.0 + + + + + + + + diff --git a/Chatbot.Domain.Data/Class1.cs b/Chatbot.Domain.Data/Class1.cs deleted file mode 100644 index 96def15..0000000 --- a/Chatbot.Domain.Data/Class1.cs +++ /dev/null @@ -1,8 +0,0 @@ -using System; - -namespace Chatbot.Domain.Data -{ - public class Class1 - { - } -} diff --git a/Chatbot.Domain.Data/DbContexts/BotDbContext.cs b/Chatbot.Domain.Data/DbContexts/BotDbContext.cs new file mode 100644 index 0000000..9fe551a --- /dev/null +++ b/Chatbot.Domain.Data/DbContexts/BotDbContext.cs @@ -0,0 +1,25 @@ +using Chatbot.Api.Domain.Data.EntityTypeConfiguration; +using Chatbot.Api.Domain.Entities; +using Microsoft.EntityFrameworkCore; + +namespace Chatbot.Api.Domain.Data.DbContexts +{ + public class BotDbContext : DbContext + { + public DbSet Bots { get; set; } + + public BotDbContext(DbContextOptions options) + : base(options) + { + base.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.TrackAll; + base.ChangeTracker.AutoDetectChangesEnabled = true; + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.ApplyConfiguration(new BotConfiguration()); + } + } +} diff --git a/Chatbot.Domain.Data/DbContexts/ChatDbContext.cs b/Chatbot.Domain.Data/DbContexts/ChatDbContext.cs new file mode 100644 index 0000000..10d3aef --- /dev/null +++ b/Chatbot.Domain.Data/DbContexts/ChatDbContext.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore; + +namespace Chatbot.Api.Domain.Data.DbContexts +{ + public class ChatDbContext : DbContext + { + public ChatDbContext(DbContextOptions options) + : base(options) + { + base.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.TrackAll; + base.ChangeTracker.AutoDetectChangesEnabled = true; + } + + + } +} diff --git a/Chatbot.Domain.Data/DependencyInjectionExtensions.cs b/Chatbot.Domain.Data/DependencyInjectionExtensions.cs new file mode 100644 index 0000000..3d8f14e --- /dev/null +++ b/Chatbot.Domain.Data/DependencyInjectionExtensions.cs @@ -0,0 +1,26 @@ +using Chatbot.Api.Domain.Data.DbContexts; +using Chatbot.Api.Domain.Data.Repositories; +using Chatbot.Api.Domain.Repositories; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.EntityFrameworkCore; + +namespace Chatbot.Api.Domain.Data +{ + public static class DependencyInjectionExtensions + { + public static void AddDataAccess(this IServiceCollection services) + { + services.AddScoped(); + + services + .AddDbContextPool( + (serviceProvider, options) => + { + var configuration = serviceProvider.GetService(); + var connectionString = configuration.GetConnectionString("DatabaseConnection"); + options.UseSqlServer(connectionString); + }); + } + } +} diff --git a/Chatbot.Domain.Data/EntityTypeConfiguration/BotConfiguration.cs b/Chatbot.Domain.Data/EntityTypeConfiguration/BotConfiguration.cs new file mode 100644 index 0000000..1971e99 --- /dev/null +++ b/Chatbot.Domain.Data/EntityTypeConfiguration/BotConfiguration.cs @@ -0,0 +1,14 @@ +using Chatbot.Api.Domain.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Chatbot.Api.Domain.Data.EntityTypeConfiguration +{ + class BotConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Bot").HasKey(key => key.BotId); + } + } +} diff --git a/Chatbot.Domain.Data/Repositories/BotRepository.cs b/Chatbot.Domain.Data/Repositories/BotRepository.cs new file mode 100644 index 0000000..6fe9333 --- /dev/null +++ b/Chatbot.Domain.Data/Repositories/BotRepository.cs @@ -0,0 +1,23 @@ +using Chatbot.Api.Domain.Data.DbContexts; +using Chatbot.Api.Domain.Entities; +using Chatbot.Api.Domain.Repositories; +using Microsoft.EntityFrameworkCore; +using System.Threading.Tasks; + +namespace Chatbot.Api.Domain.Data.Repositories +{ + class BotRepository : IBotRepository + { + private readonly BotDbContext _dbContext; + + public BotRepository(BotDbContext dbContext) + { + _dbContext = dbContext; + } + + public async Task GetBots() + { + return await _dbContext.Bots.ToArrayAsync(); + } + } +} diff --git a/Chatbot.Domain.Data/Scripts/01.Chatbot tables.sql b/Chatbot.Domain.Data/Scripts/01.Chatbot tables.sql new file mode 100644 index 0000000..0c994fb --- /dev/null +++ b/Chatbot.Domain.Data/Scripts/01.Chatbot tables.sql @@ -0,0 +1,70 @@ +if not exists (select top 1 1 from sys.objects where name = 'Bot' and type = 'U') +begin + create table Bot + ( + BotId uniqueidentifier constraint DF_Bot_BotId default newid() not null, + BotCode varchar(100) not null, + BotName varchar(255) not null, + CreationDate datetime constraint DF_Bot_CreationDate default getdate(), + constraint PK_Bot primary key (BotId) + ) +end +go + +if not exists (select top 1 1 from sys.objects where name = 'BotSession' and type = 'U') +begin + create table BotSession + ( + SessionId uniqueidentifier constraint DF_BotSession_SessionId default newid() not null, + StartDate datetime constraint DF_BotSession_StartDate default getdate(), + BotId uniqueidentifier not null, + ExternalId uniqueidentifier not null, + ClientApplication varchar(100) not null, + UserKey varchar(100) not null, + constraint PK_BotSession primary key (SessionId), + constraint FK_BotSession_Bot foreign key (BotId) references Bot(BotId) + ) +end +go + +if not exists (select top 1 1 from sys.objects where name = 'Chat' and type = 'U') +begin + create table Chat + ( + ChatId uniqueidentifier constraint DF_Chat_ChatId default newid() not null, + SessionId uniqueidentifier not null, + StartDate datetime constraint DF_Chat_StartDate default getdate(), + StopDate datetime, + constraint PK_Chat primary key (ChatId), + constraint FK_Chat_BotSession foreign key (ChatId) references BotSession(SessionId) + ) +end +go + +if not exists (select top 1 1 from sys.objects where name = 'MessageSource' and type = 'U') +begin + create table MessageSource + ( + MessageSourceId int not null, + MessageSourceCode varchar(20) not null, + MessageSourceName varchar(100) not null, + constraint PK_MessageSource primary key (MessageSourceId) + ) +end +go + +if not exists (select top 1 1 from sys.objects where name = 'ChatMessage' and type = 'U') +begin + create table ChatMessage + ( + MessageId int identity(0, 1), + ChatId uniqueidentifier not null, + MessageSourceId int not null, + MessageDate datetime constraint DF_ChatMessage_MessageDate default getdate(), + MessageContent varchar(max) + constraint PK_ChatMessage primary key (MessageId), + constraint FK_ChatMessage_Chat foreign key (ChatId) references Chat(ChatId), + constraint FK_ChatMessage_MessageSource foreign key (MessageSourceId) references MessageSource(MessageSourceId) + ) +end +go \ No newline at end of file diff --git a/Chatbot.Domain.Data/Scripts/02.Insert into MessageSource.sql b/Chatbot.Domain.Data/Scripts/02.Insert into MessageSource.sql new file mode 100644 index 0000000..98533f7 --- /dev/null +++ b/Chatbot.Domain.Data/Scripts/02.Insert into MessageSource.sql @@ -0,0 +1,13 @@ +if not exists (select top 1 1 from MessageSource where MessageSourceCode like 'BOT') +begin + insert into MessageSource (MessageSourceId, MessageSourceCode, MessageSourceName) + values (1, 'BOT', 'Bot') +end +go + +if not exists (select top 1 1 from MessageSource where MessageSourceCode like 'USER') +begin + insert into MessageSource (MessageSourceId, MessageSourceCode, MessageSourceName) + values (2, 'USER', 'User') +end +go \ No newline at end of file diff --git a/Chatbot.Domain/Class1.cs b/Chatbot.Domain/Class1.cs deleted file mode 100644 index 8a45aed..0000000 --- a/Chatbot.Domain/Class1.cs +++ /dev/null @@ -1,8 +0,0 @@ -using System; - -namespace Chatbot.Domain -{ - public class Class1 - { - } -} diff --git a/Chatbot.Domain/Entities/Bot.cs b/Chatbot.Domain/Entities/Bot.cs new file mode 100644 index 0000000..ddbddde --- /dev/null +++ b/Chatbot.Domain/Entities/Bot.cs @@ -0,0 +1,12 @@ +using System; + +namespace Chatbot.Api.Domain.Entities +{ + public class Bot + { + public Guid BotId { get; set; } + public string BotCode { get; set; } + public string BotName { get; set; } + public DateTime CreationDate { get; set; } + } +} diff --git a/Chatbot.Domain/Entities/User.cs b/Chatbot.Domain/Entities/User.cs new file mode 100644 index 0000000..078c7f1 --- /dev/null +++ b/Chatbot.Domain/Entities/User.cs @@ -0,0 +1,9 @@ +namespace Chatbot.Api.Domain.Entities +{ + public class User + { + public int Id { get; set; } + public string UserName { get; set; } + public string Password { get; set; } + } +} diff --git a/Chatbot.Domain/Models/Settings/Credentials.cs b/Chatbot.Domain/Models/Settings/Credentials.cs new file mode 100644 index 0000000..2b3eb4c --- /dev/null +++ b/Chatbot.Domain/Models/Settings/Credentials.cs @@ -0,0 +1,8 @@ +namespace Chatbot.Api.Domain.Models.Settings +{ + public class Credentials + { + public string UserName { get; set; } + public string Password { get; set; } + } +} diff --git a/Chatbot.Domain/Repositories/IBotRepository.cs b/Chatbot.Domain/Repositories/IBotRepository.cs new file mode 100644 index 0000000..8b22867 --- /dev/null +++ b/Chatbot.Domain/Repositories/IBotRepository.cs @@ -0,0 +1,10 @@ +using Chatbot.Api.Domain.Entities; +using System.Threading.Tasks; + +namespace Chatbot.Api.Domain.Repositories +{ + public interface IBotRepository + { + Task GetBots(); + } +} diff --git a/Chatbot.Domain/Services/IParamProvider.cs b/Chatbot.Domain/Services/IParamProvider.cs new file mode 100644 index 0000000..135af48 --- /dev/null +++ b/Chatbot.Domain/Services/IParamProvider.cs @@ -0,0 +1,9 @@ +using Chatbot.Api.Domain.Models.Settings; + +namespace Chatbot.Api.Domain.Services +{ + public interface IParamProvider + { + Credentials Credentials { get; } + } +} diff --git a/Chatbot.sln b/Chatbot.sln index 5976501..5a74588 100644 --- a/Chatbot.sln +++ b/Chatbot.sln @@ -11,6 +11,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution .gitignore = .gitignore dependencies.props = dependencies.props Directory.Build.props = Directory.Build.props + Notes.txt = Notes.txt EndProjectSection EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Chatbot.Api.Application", "Chatbot.Application\Chatbot.Api.Application.csproj", "{3759725E-8E75-44F4-86A3-34CC1EAC1D6C}" diff --git a/Notes.txt b/Notes.txt new file mode 100644 index 0000000..550aca0 --- /dev/null +++ b/Notes.txt @@ -0,0 +1 @@ +Basic ***REMOVED*** \ No newline at end of file