infrastructure

master
Tudor Stanciu 2020-06-06 18:29:20 +03:00
parent e0b7ab93ba
commit f06a9fcaea
38 changed files with 848 additions and 94 deletions

View File

@ -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<AuthenticationSchemeOptions>
{
private readonly IUserService _userService;
public BasicAuthenticationHandler(IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock, IUserService userService)
: base(options, logger, encoder, clock)
{
_userService = userService;
}
protected override async Task<AuthenticateResult> 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);
}
}
}

View File

@ -4,5 +4,25 @@
<TargetFramework>netcoreapp3.1</TargetFramework> <TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.4" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="$(MicrosoftExtensionsPackageVersion)" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="$(MicrosoftExtensionsPackageVersion)" />
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="$(MicrosoftExtensionsPackageVersion)" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="$(MicrosoftExtensionsPackageVersion)" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.9.5" />
<PackageReference Include="Serilog.AspNetCore" Version="$(SerilogPackageVersion)" />
<PackageReference Include="Serilog.Extensions.Logging" Version="$(SerilogExtensionsPackageVersion)" />
<PackageReference Include="Serilog.Sinks.Console" Version="$(SerilogSinksConsolePackageVersion)" />
<PackageReference Include="Serilog.Sinks.MSSqlServer" Version="$(SerilogSinksMSSqlServerPackageVersion)" />
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="$(AutoMapperExtensionsPackageVersion)" />
<PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="$(MediatRPackageVersion)" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="$(SwashbucklePackageVersion)" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Chatbot.Application\Chatbot.Api.Application.csproj" />
<ProjectReference Include="..\Chatbot.Domain.Data\Chatbot.Api.Domain.Data.csproj" />
</ItemGroup>
</Project> </Project>

View File

@ -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<IActionResult> GetReleaseNotes([FromRoute] GetBots.Query query)
{
var result = await _mediator.Send(query);
return Ok(result);
}
}
}

View File

@ -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<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
[HttpGet]
public IEnumerable<WeatherForecast> 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();
}
}
}

View File

@ -1,11 +1,14 @@
using System; using System;
using System.Collections.Generic; using System.Diagnostics;
using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Serilog;
using Serilog.Core;
using Serilog.Events;
using Serilog.Sinks.MSSqlServer;
namespace Chatbot.Api namespace Chatbot.Api
{ {
@ -13,14 +16,61 @@ namespace Chatbot.Api
{ {
public static void Main(string[] args) 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);
} }
public static IHostBuilder CreateHostBuilder(string[] args) => var configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.Build();
var connectionString = configuration.GetConnectionString("DatabaseConnection");
var loggingLevelParam = configuration.GetValue<string>("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<string>("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, IConfiguration configuration) =>
Host.CreateDefaultBuilder(args) Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder => .ConfigureWebHostDefaults(webBuilder =>
{ {
webBuilder.UseStartup<Startup>(); webBuilder.UseStartup<Startup>()
.UseConfiguration(configuration)
.UseSerilog();
}); });
} }
} }

View File

@ -1,14 +1,18 @@
using System; using AutoMapper;
using System.Collections.Generic; using Chatbot.Api.Authentication;
using System.Linq; using Chatbot.Api.Domain.Data;
using System.Threading.Tasks; using Chatbot.Api.Swagger;
using Chatbot.Application;
using MediatR;
using MediatR.Pipeline;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Newtonsoft.Json;
using System.Reflection;
namespace Chatbot.Api 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. // This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services) public void ConfigureServices(IServiceCollection services)
{ {
services.AddControllers(); services.AddControllers()
.AddNewtonsoftJson(o => o.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Utc);
// configure basic authentication
services.AddAuthentication("BasicAuthentication")
.AddScheme<AuthenticationSchemeOptions, BasicAuthenticationHandler>("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. // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{ {
// global cors policy
app.UseCors(x => x
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader());
if (env.IsDevelopment()) if (env.IsDevelopment())
{ {
app.UseDeveloperExceptionPage(); app.UseDeveloperExceptionPage();
} }
app.UseRouting(); app.UseRouting();
app.UseAuthentication();
app.UseAuthorization(); app.UseAuthorization();
app.UseEndpoints(endpoints => app.UseEndpoints(endpoints =>
{ {
endpoints.MapControllers(); endpoints.MapControllers();
}); });
app.ConfigureSwagger();
}
private Assembly[] GetMediatRAssemblies()
{
var assembly = typeof(Application.Queries.GetBots).Assembly;
return new Assembly[] { assembly };
} }
} }
} }

View File

@ -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;
}
}
}
}

View File

@ -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;
}
}
}
}
}

View File

@ -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<string>()
}
});
c.OperationFilter<PathParamsOperationFilter>();
c.SchemaFilter<DtoSchemaFilter>();
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<OpenApiServer>
{
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;
}
}
}

View File

@ -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; }
}
}

View File

@ -1,4 +1,8 @@
{ {
"urls": "http://*:5061",
"ConnectionStrings": {
"DatabaseConnection": "***REMOVED***"
},
"Logging": { "Logging": {
"LogLevel": { "LogLevel": {
"Default": "Information", "Default": "Information",
@ -6,5 +10,9 @@
"Microsoft.Hosting.Lifetime": "Information" "Microsoft.Hosting.Lifetime": "Information"
} }
}, },
"AllowedHosts": "*" "AllowedHosts": "*",
"Credentials": {
"UserName": "***REMOVED***",
"Password": "***REMOVED***"
}
} }

View File

@ -4,4 +4,17 @@
<TargetFramework>netstandard2.0</TargetFramework> <TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<PackageReference Include="AutoMapper" Version="$(AutoMapperPackageVersion)" />
<PackageReference Include="MediatR" Version="$(MediatRPackageVersion)" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="$(MicrosoftExtensionsPackageVersion)" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="$(MicrosoftExtensionsPackageVersion)" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="$(MicrosoftExtensionsPackageVersion)" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="$(MicrosoftExtensionsPackageVersion)" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Chatbot.Domain\Chatbot.Api.Domain.csproj" />
</ItemGroup>
</Project> </Project>

View File

@ -1,8 +0,0 @@
using System;
namespace Chatbot.Application
{
public class Class1
{
}
}

View File

@ -0,0 +1,18 @@
using MediatR;
namespace Chatbot.Api.Application.Commands
{
public abstract class Command<TResponse> : ICommand, IRequest<TResponse>
{
public Metadata Metadata { get; }
protected Command(Metadata metadata)
{
Metadata = metadata;
}
}
public interface ICommand
{
}
}

View File

@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
namespace Chatbot.Api.Application.Commands
{
public class Metadata : Dictionary<string, string>
{
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());
}
}
}
}

View File

@ -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<IParamProvider, ParamProvider>();
services.AddScoped<IUserService, UserService>();
}
}
}

View File

@ -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<Bot, GetBots.Model>();
}
}
}

View File

@ -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<Model[]>
{
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<Query, Model[]>
{
private readonly IBotRepository _botRepository;
private readonly IMapper _mapper;
public QueryHandler(IBotRepository botRepository, IMapper mapper)
{
_botRepository = botRepository;
_mapper = mapper;
}
public async Task<Model[]> Handle(Query request, CancellationToken cancellationToken)
{
var bots = await _botRepository.GetBots();
var result = _mapper.Map<Model[]>(bots);
return result;
}
}
}
}

View File

@ -0,0 +1,8 @@
using MediatR;
namespace Chatbot.Api.Application.Queries
{
public abstract class Query<TResponse> : IRequest<TResponse>, IBaseRequest
{
}
}

View File

@ -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<Credentials>();
}
}

View File

@ -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<User> Authenticate(string username, string password);
}
public class UserService : IUserService
{
private readonly IParamProvider _paramProvider;
public UserService(IParamProvider paramProvider)
{
_paramProvider = paramProvider;
}
public async Task<User> 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;
}
}
}

View File

@ -4,4 +4,12 @@
<TargetFramework>netstandard2.0</TargetFramework> <TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="$(EntityFrameworkCorePackageVersion)" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Chatbot.Domain\Chatbot.Api.Domain.csproj" />
</ItemGroup>
</Project> </Project>

View File

@ -1,8 +0,0 @@
using System;
namespace Chatbot.Domain.Data
{
public class Class1
{
}
}

View File

@ -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<Bot> Bots { get; set; }
public BotDbContext(DbContextOptions<BotDbContext> 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());
}
}
}

View File

@ -0,0 +1,16 @@
using Microsoft.EntityFrameworkCore;
namespace Chatbot.Api.Domain.Data.DbContexts
{
public class ChatDbContext : DbContext
{
public ChatDbContext(DbContextOptions<ChatDbContext> options)
: base(options)
{
base.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.TrackAll;
base.ChangeTracker.AutoDetectChangesEnabled = true;
}
}
}

View File

@ -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<IBotRepository, BotRepository>();
services
.AddDbContextPool<BotDbContext>(
(serviceProvider, options) =>
{
var configuration = serviceProvider.GetService<IConfiguration>();
var connectionString = configuration.GetConnectionString("DatabaseConnection");
options.UseSqlServer(connectionString);
});
}
}
}

View File

@ -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<Bot>
{
public void Configure(EntityTypeBuilder<Bot> builder)
{
builder.ToTable("Bot").HasKey(key => key.BotId);
}
}
}

View File

@ -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<Bot[]> GetBots()
{
return await _dbContext.Bots.ToArrayAsync();
}
}
}

View File

@ -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

View File

@ -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

View File

@ -1,8 +0,0 @@
using System;
namespace Chatbot.Domain
{
public class Class1
{
}
}

View File

@ -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; }
}
}

View File

@ -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; }
}
}

View File

@ -0,0 +1,8 @@
namespace Chatbot.Api.Domain.Models.Settings
{
public class Credentials
{
public string UserName { get; set; }
public string Password { get; set; }
}
}

View File

@ -0,0 +1,10 @@
using Chatbot.Api.Domain.Entities;
using System.Threading.Tasks;
namespace Chatbot.Api.Domain.Repositories
{
public interface IBotRepository
{
Task<Bot[]> GetBots();
}
}

View File

@ -0,0 +1,9 @@
using Chatbot.Api.Domain.Models.Settings;
namespace Chatbot.Api.Domain.Services
{
public interface IParamProvider
{
Credentials Credentials { get; }
}
}

View File

@ -11,6 +11,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
.gitignore = .gitignore .gitignore = .gitignore
dependencies.props = dependencies.props dependencies.props = dependencies.props
Directory.Build.props = Directory.Build.props Directory.Build.props = Directory.Build.props
Notes.txt = Notes.txt
EndProjectSection EndProjectSection
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Chatbot.Api.Application", "Chatbot.Application\Chatbot.Api.Application.csproj", "{3759725E-8E75-44F4-86A3-34CC1EAC1D6C}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Chatbot.Api.Application", "Chatbot.Application\Chatbot.Api.Application.csproj", "{3759725E-8E75-44F4-86A3-34CC1EAC1D6C}"

1
Notes.txt Normal file
View File

@ -0,0 +1 @@
Basic ***REMOVED***