diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3e16852 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +bin/ +obj/ +.vs/ \ No newline at end of file diff --git a/README.md b/README.md index b7dbd46..5fb998d 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,37 @@ -Oportunidade de trabalho na Minuto Seguros +Técnicas/Linguagem utilizada: =========================================== +> AspNetCore 2.1, C# +> SOLID +> Repository Pattern +> IoC -Venha trabalhar na área de tecnologia da Minuto Corretora de Seguros. Utilizamos métodos ágeis para criação de software e nosso clima é de extrema colaboração. - -Se você tem interesse em fazer parte de uma equipe multidisciplinar e que adora criar software com qualidade, siga os seguintes passos: - -Faça um fork desse projeto e faça um pull request com a resolução do seguinte problema: +Documentação: +=========================================== +> Swagger -Exercício de programação: As principais palavras -------------------------------------------------------- +Executando a aplicação +=========================================== +A aplicação trata-se de um web api RESTFULL -Você deverá criar um programa para obter automaticamente o conteúdo dos dez últimos tópicos publicados no blog da Minuto Seguros. Abaixo segue um link para auxiliá-lo nesse trabalho: +URL APIs: http://localhost:5000/swagger -http://www.minutoseguros.com.br/blog/feed/ -Caso tenha problemas em obter o feed, deixamos um arquivo feed.xml aqui no repositório. +Chamada via CURL +=========================================== +curl http://localhost:5000/api/v1/feeds/topics -O seu programa deverá avaliar quais as dez principais palavras abordadas nesses tópicos e qual o número de vezes que elas aparecem. Também deverá exibir a quantidade de palavras por tópico. Além disso, deverão ser removidos os artigos e preposições nessa análise. -A linguagem que mais utilizamos é C#. Porém, você poderá realizar o exercício na linguagem de programação de sua preferência. +EXTRA: Banco de dados Mongo +=========================================== +Ao consumir o serviço Baixar Feeds, um "espelho" do registros é criado em uma base de dados mongo. O objetivo é mostrar de forma simples como a aplicação escalaria de foma simples, caso fosse necessário manter uma base de dados local para outras análises. -Como trabalhamos aqui ----------- +Credenciais do banco de dados: +mongodb://msdbuser:ms#2018@ds135592.mlab.com:35592/minutosegurodb -Trabalhamos com uma boa infraestrutura, nosso hardware é muito bom e possuímos um ambiente de trabalho muito agradável. A empresa não possui hierarquias desnecessárias e você é convidado e desafiado a colaborar com todas as frentes de trabalho. Ou seja, aqui o “pitaco” é bem vindo! -Nós acreditamos muito nos números para tomada de decisões. Nós possuímos diversas métricas e isso inclui também medir o nosso código. Acompanhamos esses indicadores para construir códigos sólidos e de fácil manutenção. Afinal, queremos a cada dia mais flexibilidade e continuar animados a evoluir nossas aplicações. +Fontes para algumas informações: +=========================================== +https://www.normaculta.com.br/artigo-indefinido/ +https://www.normaculta.com.br/artigo-definido/ +https://www.normaculta.com.br/preposicao/ -Nosso trabalho é baseado em autogestão. Só existe uma regra de convivência na frente de TI: É proibido murmurar! Aqui as opiniões são discutidas, resolvidas e sempre chegamos a um consenso para melhorar a nossa convivência. Isso não foi descrito por um gerente de RH e sim por um desenvolvedor de software! diff --git a/src/.vs/Minuto.Seguros/v15/.suo b/src/.vs/Minuto.Seguros/v15/.suo new file mode 100644 index 0000000..183eb4a Binary files /dev/null and b/src/.vs/Minuto.Seguros/v15/.suo differ diff --git a/src/.vs/Minuto.Seguros/v15/Server/sqlite3/storage.ide b/src/.vs/Minuto.Seguros/v15/Server/sqlite3/storage.ide new file mode 100644 index 0000000..ba4fd15 Binary files /dev/null and b/src/.vs/Minuto.Seguros/v15/Server/sqlite3/storage.ide differ diff --git a/src/.vs/Minuto.Seguros/v15/Server/sqlite3/storage.ide-shm b/src/.vs/Minuto.Seguros/v15/Server/sqlite3/storage.ide-shm new file mode 100644 index 0000000..52b7087 Binary files /dev/null and b/src/.vs/Minuto.Seguros/v15/Server/sqlite3/storage.ide-shm differ diff --git a/src/.vs/Minuto.Seguros/v15/Server/sqlite3/storage.ide-wal b/src/.vs/Minuto.Seguros/v15/Server/sqlite3/storage.ide-wal new file mode 100644 index 0000000..11a7025 Binary files /dev/null and b/src/.vs/Minuto.Seguros/v15/Server/sqlite3/storage.ide-wal differ diff --git a/src/.vs/MinutoSeguroBlogFeed/DesignTimeBuild/.dtbcache b/src/.vs/MinutoSeguroBlogFeed/DesignTimeBuild/.dtbcache new file mode 100644 index 0000000..ebd38cd Binary files /dev/null and b/src/.vs/MinutoSeguroBlogFeed/DesignTimeBuild/.dtbcache differ diff --git a/src/.vs/config/applicationhost.config b/src/.vs/config/applicationhost.config new file mode 100644 index 0000000..0e578e6 --- /dev/null +++ b/src/.vs/config/applicationhost.config @@ -0,0 +1,1003 @@ + + + + + + +
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+ +
+
+ +
+
+ +
+
+
+ + +
+
+
+
+
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Minuto.Seguros.Application/Controllers/FeedsController.cs b/src/Minuto.Seguros.Application/Controllers/FeedsController.cs new file mode 100644 index 0000000..0cd604a --- /dev/null +++ b/src/Minuto.Seguros.Application/Controllers/FeedsController.cs @@ -0,0 +1,81 @@ +using System; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Minuto.Seguros.Service.Contracts; + +namespace Minuto.Seguros.Application.Controllers.V1 +{ + /// + /// Gestão de feeds + /// + [Authorize] + [ApiVersion("1")] + [Route("api/v1/feeds")] + [Produces("application/json")] + public class FeedsController : ControllerBase + { + private readonly IFeedService _feedService; + + /// + /// Construtor + /// + /// + public FeedsController( + IFeedService feedService + ) + { + _feedService = feedService; + } + + /// + /// Baixar Feeds + /// + [AllowAnonymous] + [HttpGet] + [ProducesResponseType(201)] + [ProducesResponseType(400)] + public IActionResult BaixarFeeds() + { + try + { + return new ObjectResult(_feedService.GetAllFeeds()); + } + catch (ArgumentNullException ex) + { + return NotFound(ex); + } + catch (Exception ex) + { + return BadRequest(ex); + } + } + + /// + /// Contabilizar TOP 10 palavras por Tópico + /// + /// eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... + /// Mensagem de confirmação de sucesso ou erro. + /// String com o token gerado + [AllowAnonymous] + [HttpGet] + [Route("topics")] + [ProducesResponseType(201)] + [ProducesResponseType(400)] + public IActionResult SincronizarFeeds() + { + try + { + var result = _feedService.GetFeeds(); + return new ObjectResult(result); + } + catch (ArgumentNullException ex) + { + return NotFound(ex); + } + catch (Exception ex) + { + return BadRequest(ex); + } + } + } +} \ No newline at end of file diff --git a/src/Minuto.Seguros.Application/Extensions/swagger/ActionDescriptorExtensions.cs b/src/Minuto.Seguros.Application/Extensions/swagger/ActionDescriptorExtensions.cs new file mode 100644 index 0000000..7f45267 --- /dev/null +++ b/src/Minuto.Seguros.Application/Extensions/swagger/ActionDescriptorExtensions.cs @@ -0,0 +1,27 @@ +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.Versioning; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Minuto.Seguros.Application.Extensions.swagger +{ + /// + /// Extensão para recuperar nome da api Swagger + /// + public static class ActionDescriptorExtensions + { + /// + /// Retora versão da api + /// + /// + /// + public static ApiVersionModel GetApiVersion(this ActionDescriptor actionDescriptor) + { + return actionDescriptor?.Properties + .Where((kvp) => ((Type)kvp.Key).Equals(typeof(ApiVersionModel))) + .Select(kvp => kvp.Value as ApiVersionModel).FirstOrDefault(); + } + } +} diff --git a/src/Minuto.Seguros.Application/Filters/swagger/ApiVersionOperationFilter.cs b/src/Minuto.Seguros.Application/Filters/swagger/ApiVersionOperationFilter.cs new file mode 100644 index 0000000..bb9b13d --- /dev/null +++ b/src/Minuto.Seguros.Application/Filters/swagger/ApiVersionOperationFilter.cs @@ -0,0 +1,43 @@ +using Minuto.Seguros.Application.Extensions.swagger; +using Swashbuckle.AspNetCore.Swagger; +using Swashbuckle.AspNetCore.SwaggerGen; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Minuto.Seguros.Application.Filters.swagger +{ + /// + /// Filtro de api swagger + /// + public class ApiVersionOperationFilter : IOperationFilter + { + /// + /// Executa filtro de acordo com a versão + /// + /// + /// + public void Apply(Operation operation, OperationFilterContext context) + { + var actionApiVersionModel = context.ApiDescription.ActionDescriptor?.GetApiVersion(); + if (actionApiVersionModel == null) + { + return; + } + + if (actionApiVersionModel.DeclaredApiVersions.Any()) + { + operation.Produces = operation.Produces + .SelectMany(p => actionApiVersionModel.DeclaredApiVersions + .Select(version => $"{p};v={version.ToString()}")).ToList(); + } + else + { + operation.Produces = operation.Produces + .SelectMany(p => actionApiVersionModel.ImplementedApiVersions.OrderByDescending(v => v) + .Select(version => $"{p};v={version.ToString()}")).ToList(); + } + } + } +} diff --git a/src/Minuto.Seguros.Application/Filters/swagger/VersionFilter.cs b/src/Minuto.Seguros.Application/Filters/swagger/VersionFilter.cs new file mode 100644 index 0000000..27bd9e9 --- /dev/null +++ b/src/Minuto.Seguros.Application/Filters/swagger/VersionFilter.cs @@ -0,0 +1,30 @@ +using Swashbuckle.AspNetCore.Swagger; +using Swashbuckle.AspNetCore.SwaggerGen; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Minuto.Seguros.Application.Filters.swagger +{ + /// + /// Filtro de versão swagger + /// + /// + public class VersionFilter: IDocumentFilter + { + /// + /// Aplica o filtro de acordo com a versão + /// + /// + /// + public void Apply(SwaggerDocument swaggerDoc, DocumentFilterContext context) + { + swaggerDoc.Paths = swaggerDoc.Paths + .ToDictionary( + path => path.Key.Replace("v{version}", swaggerDoc.Info.Version), + path => path.Value + ); + } + } +} diff --git a/src/Minuto.Seguros.Application/Minuto.Seguros.Application.csproj b/src/Minuto.Seguros.Application/Minuto.Seguros.Application.csproj new file mode 100644 index 0000000..5247488 --- /dev/null +++ b/src/Minuto.Seguros.Application/Minuto.Seguros.Application.csproj @@ -0,0 +1,51 @@ + + + + netcoreapp2.1 + + + + bin\Debug\netcoreapp2.1\Minuto.Seguro.Application.xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Never + + + + diff --git a/src/Minuto.Seguros.Application/Minuto.Seguros.Application.csproj.user b/src/Minuto.Seguros.Application/Minuto.Seguros.Application.csproj.user new file mode 100644 index 0000000..6c9d9b4 --- /dev/null +++ b/src/Minuto.Seguros.Application/Minuto.Seguros.Application.csproj.user @@ -0,0 +1,17 @@ + + + + ApiControllerWithActionsScaffolder + root/Controller + 600 + True + False + True + + False + Minuto.Seguro.Application + + + ProjectDebugger + + \ No newline at end of file diff --git a/src/Minuto.Seguros.Application/Minuto.Seguros.Application.xml b/src/Minuto.Seguros.Application/Minuto.Seguros.Application.xml new file mode 100644 index 0000000..2ba94f1 --- /dev/null +++ b/src/Minuto.Seguros.Application/Minuto.Seguros.Application.xml @@ -0,0 +1,20 @@ + + + + Saraiva.Connecta.Application + + + + + Gestão de feeds + + + + + Baixa todos os feeds disponibilizados pela Minuto Seguros + + Feeds disponibilizados pela Minuto Seguros. + Feeds disponibilizados pela Minuto Seguros + + + diff --git a/src/Minuto.Seguros.Application/Program.cs b/src/Minuto.Seguros.Application/Program.cs new file mode 100644 index 0000000..8973b9e --- /dev/null +++ b/src/Minuto.Seguros.Application/Program.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + +namespace Minuto.Seguros.Application +{ + public class Program + { + public static void Main(string[] args) + { + CreateWebHostBuilder(args).Build().Run(); + } + + public static IWebHostBuilder CreateWebHostBuilder(string[] args) => + WebHost.CreateDefaultBuilder(args) + .UseStartup(); + } +} diff --git a/src/Minuto.Seguros.Application/Properties/launchSettings.json b/src/Minuto.Seguros.Application/Properties/launchSettings.json new file mode 100644 index 0000000..416202e --- /dev/null +++ b/src/Minuto.Seguros.Application/Properties/launchSettings.json @@ -0,0 +1,31 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "launchUrl": "swagger", + "iisExpress": { + "applicationUrl": "http://localhost:5000", + "sslPort": 0 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Minuto.Seguro.Application": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} \ No newline at end of file diff --git a/src/Minuto.Seguros.Application/Resources/Swagger_Minuto_Seguros_index.html b/src/Minuto.Seguros.Application/Resources/Swagger_Minuto_Seguros_index.html new file mode 100644 index 0000000..955b7e0 --- /dev/null +++ b/src/Minuto.Seguros.Application/Resources/Swagger_Minuto_Seguros_index.html @@ -0,0 +1,122 @@ + + + + + + Minuto Seguro API + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + diff --git a/src/Minuto.Seguros.Application/Startup.cs b/src/Minuto.Seguros.Application/Startup.cs new file mode 100644 index 0000000..9b4ddc8 --- /dev/null +++ b/src/Minuto.Seguros.Application/Startup.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using AutoMapper; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http.Internal; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Minuto.Seguros.Application.Extensions; +using Minuto.Seguros.Domain.Entities; + +namespace Minuto.Seguros.Application +{ + /// + /// + /// + public class Startup + { + /// + /// + /// + public IConfigurationRoot Configuration { get; } + + /// + /// + /// + public IServiceProvider serviceProvider; + + /// + /// + /// + /// + public Startup(IHostingEnvironment env) + { + IConfigurationBuilder builder = new ConfigurationBuilder() + .SetBasePath(env.ContentRootPath) + .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true); + + builder.AddEnvironmentVariables(); + Configuration = builder.Build(); + + } + + /// + /// Configuração dos serviços utilizados pela Application + /// + /// Coleção de serviços + /// + public IServiceProvider ConfigureServices(IServiceCollection services) + { + services.AddCorsService(); + + var mappingConfig = new MapperConfiguration(mc => + { + mc.CreateMap(); + mc.CreateMap(); + mc.CreateMap(); + mc.CreateMap(); + }); + + IMapper mapper = mappingConfig.CreateMapper(); + services.AddSingleton(mapper); + + services.AddMvcService(); + services.AddApiVersioningService(); + services.AddJwtService(Configuration); + services.AddSwaggerService(); + services.AddHttpContextAccessor(); + services.AddSettingsService(Configuration); + + serviceProvider = services.RegisterServices(Configuration); + + return serviceProvider; + } + + /// + /// + /// + /// + /// + public void Configure(IApplicationBuilder app, IHostingEnvironment env) + { + app.Use(next => context => { context.Request.EnableRewind(); return next(context); }); + + app.UseSwagger(); + + app.UseSwaggerUI(c => + { + c.IndexStream = () => GetType().GetTypeInfo().Assembly.GetManifestResourceStream("Minuto.Seguros.Application.Resources.Swagger_Minuto_Seguros_index.html"); + c.SwaggerEndpoint("/swagger/v1/swagger.json", "Minuto Seguro API v1.0"); + }); + + app.UseCors(x => x + .AllowAnyOrigin() + .AllowAnyMethod() + .AllowAnyHeader() + .AllowCredentials()); + + app.UseAuthentication(); + + app.UseStaticFiles(); + app.UseMvc(); + } + } +} diff --git a/src/Minuto.Seguros.Application/Startup/StartupService.cs b/src/Minuto.Seguros.Application/Startup/StartupService.cs new file mode 100644 index 0000000..070e7dc --- /dev/null +++ b/src/Minuto.Seguros.Application/Startup/StartupService.cs @@ -0,0 +1,185 @@ +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc.Authorization; +using Microsoft.AspNetCore.Mvc.Versioning; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.IdentityModel.Tokens; +using Minuto.Seguros.Infra.CrossCutting.Configuration; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Swashbuckle.AspNetCore.Swagger; +using System.Threading.Tasks; +using Minuto.Seguros.Application.Filters.swagger; +using Autofac.Extensions.DependencyInjection; +using Minuto.Seguros.Infra.Data.Connection; +using Minuto.Seguros.Service; +using Autofac; +using Minuto.Seguros.Infra.Data; +using Minuto.Seguros.Application.Extensions.swagger; +using AutoMapper; +using System.IO; + +namespace Minuto.Seguros.Application +{ + /// + /// Classe para unificar configura~ções da startup.cs + /// + public static class StartupService + { + /// + /// Adicona configuração CORS no projeto + /// + /// + public static void AddCorsService(this IServiceCollection services) + { + services.AddCors(options => { + options.AddPolicy("AllowAllOrigins", + builder => builder.AllowAnyOrigin()); + }); + } + + /// + /// Adiciona configuração do MVC no projeto + /// + /// + public static void AddMvcService(this IServiceCollection services) + { + services.AddMvc(config => { + AuthorizationPolicy policy = new AuthorizationPolicyBuilder() + .RequireAuthenticatedUser() + .Build(); + config.Filters.Add(new AuthorizeFilter(policy)); + }) + .AddJsonOptions(options => options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore); + } + + /// + /// Adiciona configuração de versionamento de api ao projeto + /// + /// + public static void AddApiVersioningService(this IServiceCollection services) + { + services.AddApiVersioning(o => { + o.DefaultApiVersion = new Microsoft.AspNetCore.Mvc.ApiVersion(1, 0); // Versão padrão + o.AssumeDefaultVersionWhenUnspecified = true; // Assume a versão padrão quando não informado + o.ApiVersionReader = new MediaTypeApiVersionReader(); // Ler a versão via accept header + }); + } + + /// + /// Adiciona configuração JWT no projeto + /// + /// + /// + public static void AddJwtService(this IServiceCollection services, IConfigurationRoot configuration) + { + JwtConfig jwtConfig = configuration.GetSection("JwtConfig").Get(); + byte[] key = Encoding.ASCII.GetBytes(jwtConfig.Secret); + services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) + .AddJwtBearer(options => { + options.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuer = true, + ValidateAudience = true, + ValidateLifetime = true, + ValidateIssuerSigningKey = true, + ValidIssuer = jwtConfig.Issuer, + ValidAudience = jwtConfig.Issuer, + IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtConfig.Secret)) + }; + }); + } + + /// + /// Adiciona Swagger na aplicação + /// + /// + public static void AddSwaggerService(this IServiceCollection services) + { + services.AddSwaggerGen(c => { + c.SwaggerDoc("v1", + new Info { + Version = "v1" + , Title = "Minuto Seguro API" + }); + + c.AddSecurityDefinition("Bearer", new ApiKeyScheme + { + Description = "JWT Authorization header using the Bearer scheme. Example: \"Authorization: Bearer {token}\"", + Name = "Authorization", + In = "header", + Type = "apiKey" + }); + + c.AddSecurityRequirement(new Dictionary> { + { "Bearer", Enumerable.Empty() }, + }); + + c.DocInclusionPredicate((docName, apiDesc) => { + ApiVersionModel actionApiVersionModel = apiDesc.ActionDescriptor?.GetApiVersion(); + + if (actionApiVersionModel == null) + { + return true; + } + if (actionApiVersionModel.DeclaredApiVersions.Any()) + { + return actionApiVersionModel.DeclaredApiVersions.Any(v => $"v{v.ToString()}" == docName); + } + return actionApiVersionModel.ImplementedApiVersions.Any(v => $"v{v.ToString()}" == docName); + }); + c.DocumentFilter(); + c.OperationFilter(); + c.IncludeXmlComments(GetXmlCommentsPath()); + + }); + } + + public static string GetXmlCommentsPath() + { + return System.String.Format(@"{0}\Minuto.Seguro.Application.xml", System.AppDomain.CurrentDomain.BaseDirectory); + } + + /// + /// Adiciona suporte a configuração na aplicação + /// + /// + /// + public static void AddSettingsService(this IServiceCollection services, IConfigurationRoot Configuration) + { + services.Configure(Configuration.GetSection("AppSettings")); + services.Configure(Configuration.GetSection("ConfigEmail")); + services.Configure(Configuration.GetSection("JwtConfig")); + ConnectionStrings connectionStrings = Configuration.GetSection("ConnectionStrings").Get(); + } + + private static ConnectionStrings AddConnectionStringsService(this IServiceCollection services, IConfigurationRoot Configuration) + { + return Configuration.GetSection("ConnectionStrings").Get(); + } + + /// + /// + /// + /// + public static AutofacServiceProvider RegisterServices(this IServiceCollection services, IConfigurationRoot Configuration) + { + services.AddScoped(); + services.AddSingleton(Configuration); + services.AddScoped(); + + ContainerBuilder containerBuilder = new ContainerBuilder(); + containerBuilder.RegisterModule(new DataModule(AddConnectionStringsService(services, Configuration).MinutoSeguroConn, "minutosegurodb")); + containerBuilder.RegisterModule(); + + containerBuilder.RegisterType().As(); + + containerBuilder.Populate(services); + + return new AutofacServiceProvider(containerBuilder.Build()); + } + } +} diff --git a/src/Minuto.Seguros.Application/appsettings.Development.json b/src/Minuto.Seguros.Application/appsettings.Development.json new file mode 100644 index 0000000..e203e94 --- /dev/null +++ b/src/Minuto.Seguros.Application/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + } +} diff --git a/src/Minuto.Seguros.Application/appsettings.json b/src/Minuto.Seguros.Application/appsettings.json new file mode 100644 index 0000000..6d95ff9 --- /dev/null +++ b/src/Minuto.Seguros.Application/appsettings.json @@ -0,0 +1,13 @@ +{ + "AppSettings": { + "UriXmlMinutoSeguros": "https://www.minutoseguros.com.br/blog/feed/" + }, + "ConnectionStrings": { + "MinutoSeguroConn": "mongodb://msdbuser:ms#2018@ds135592.mlab.com:35592/minutosegurodb" + }, + "JwtConfig": { + "Secret": "1bb320b3904ad153ca8988ea7c283c07", + "Issuer": "http://127.0.0.1:5000/", + "LifeTimeMinutes": 60 + } +} \ No newline at end of file diff --git a/src/Minuto.Seguros.Domain/Entities/BaseEntity.cs b/src/Minuto.Seguros.Domain/Entities/BaseEntity.cs new file mode 100644 index 0000000..503d6e9 --- /dev/null +++ b/src/Minuto.Seguros.Domain/Entities/BaseEntity.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Minuto.Seguros.Domain.Entities +{ + public class BaseEntity + { + } +} diff --git a/src/Minuto.Seguros.Domain/Entities/Feed.cs b/src/Minuto.Seguros.Domain/Entities/Feed.cs new file mode 100644 index 0000000..5ae1071 --- /dev/null +++ b/src/Minuto.Seguros.Domain/Entities/Feed.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Minuto.Seguros.Domain.Entities +{ + public class Feed: BaseEntity + { + public string Title { get; set; } + public string Description { get; set; } + public string[] Categories { get; set; } + } +} diff --git a/src/Minuto.Seguros.Domain/Minuto.Seguros.Domain.csproj b/src/Minuto.Seguros.Domain/Minuto.Seguros.Domain.csproj new file mode 100644 index 0000000..86ea3bb --- /dev/null +++ b/src/Minuto.Seguros.Domain/Minuto.Seguros.Domain.csproj @@ -0,0 +1,7 @@ + + + + netcoreapp2.1 + + + diff --git a/src/Minuto.Seguros.Infra.Client.Corporate.Rss/Clients/RssClient.cs b/src/Minuto.Seguros.Infra.Client.Corporate.Rss/Clients/RssClient.cs new file mode 100644 index 0000000..36799fb --- /dev/null +++ b/src/Minuto.Seguros.Infra.Client.Corporate.Rss/Clients/RssClient.cs @@ -0,0 +1,50 @@ +using Microsoft.Extensions.Options; +using Minuto.Seguros.Infra.Client.Corporate.Rss.Contracts; +using Minuto.Seguros.Infra.Client.Corporate.Rss.Dto; +using Minuto.Seguros.Infra.CrossCutting.Configuration; +using Minuto.Seguros.Infra.CrossCutting.Utils; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using System.Xml.Linq; + +namespace Minuto.Seguros.Infra.Client.Corporate.Rss +{ + public class RssClient : IRssClient + { + private readonly AppSettings _appSettings; + + public RssClient( + IOptions appSettings + ) + { + _appSettings = appSettings.Value; + } + + public List GetFeeds() + { + try { + XDocument document = XDocument.Load(_appSettings.UriXmlMinutoSeguros); + rss Rss = SerializationUtil.Deserialize(document); + + var feeds = new List(); + + foreach (var item in Rss.channel.item) + { + feeds.Add(new FeedDto() + { + Title = item.title, + Description = item.description, + Categories = item.category + }); + } + + return feeds; + } + catch (Exception ex) + { + throw ex; + } + } + } +} diff --git a/src/Minuto.Seguros.Infra.Client.Corporate.Rss/Contracts/IRssClient.cs b/src/Minuto.Seguros.Infra.Client.Corporate.Rss/Contracts/IRssClient.cs new file mode 100644 index 0000000..0090003 --- /dev/null +++ b/src/Minuto.Seguros.Infra.Client.Corporate.Rss/Contracts/IRssClient.cs @@ -0,0 +1,13 @@ +using Minuto.Seguros.Infra.Client.Corporate.Rss.Dto; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace Minuto.Seguros.Infra.Client.Corporate.Rss.Contracts +{ + public interface IRssClient + { + List GetFeeds(); + } +} diff --git a/src/Minuto.Seguros.Infra.Client.Corporate.Rss/Dto/FeedDto.cs b/src/Minuto.Seguros.Infra.Client.Corporate.Rss/Dto/FeedDto.cs new file mode 100644 index 0000000..bf64f80 --- /dev/null +++ b/src/Minuto.Seguros.Infra.Client.Corporate.Rss/Dto/FeedDto.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Minuto.Seguros.Infra.Client.Corporate.Rss.Dto +{ + public class FeedDto + { + public string Title { get; set; } + public string Description { get; set; } + public string[] Categories { get; set; } + public string FullMessageClean { get; set; } + } +} diff --git a/src/Minuto.Seguros.Infra.Client.Corporate.Rss/Minuto.Seguros.Infra.Client.Corporate.Rss.csproj b/src/Minuto.Seguros.Infra.Client.Corporate.Rss/Minuto.Seguros.Infra.Client.Corporate.Rss.csproj new file mode 100644 index 0000000..28cbb0b --- /dev/null +++ b/src/Minuto.Seguros.Infra.Client.Corporate.Rss/Minuto.Seguros.Infra.Client.Corporate.Rss.csproj @@ -0,0 +1,11 @@ + + + + netcoreapp2.1 + + + + + + + diff --git a/src/Minuto.Seguros.Infra.CrossCutting/Configuration/AppSettings.cs b/src/Minuto.Seguros.Infra.CrossCutting/Configuration/AppSettings.cs new file mode 100644 index 0000000..2db272a --- /dev/null +++ b/src/Minuto.Seguros.Infra.CrossCutting/Configuration/AppSettings.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Minuto.Seguros.Infra.CrossCutting.Configuration +{ + public class AppSettings + { + public string UriXmlMinutoSeguros { get; set; } + } +} diff --git a/src/Minuto.Seguros.Infra.CrossCutting/Configuration/ConfigEmail.cs b/src/Minuto.Seguros.Infra.CrossCutting/Configuration/ConfigEmail.cs new file mode 100644 index 0000000..7fce722 --- /dev/null +++ b/src/Minuto.Seguros.Infra.CrossCutting/Configuration/ConfigEmail.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Minuto.Seguros.Infra.CrossCutting.Configuration +{ + public class ConfigEmail + { + public string MyProperty { get; set; } + public string Credencial { get; set; } + public string Email { get; set; } + public string Senha { get; set; } + public string Smtp { get; set; } + public int SmtpPort { get; set; } + public bool Ssl { get; set; } + public string EmailsTech { get; set; } + + } +} diff --git a/src/Minuto.Seguros.Infra.CrossCutting/Configuration/ConnectionStrings.cs b/src/Minuto.Seguros.Infra.CrossCutting/Configuration/ConnectionStrings.cs new file mode 100644 index 0000000..6d9fd40 --- /dev/null +++ b/src/Minuto.Seguros.Infra.CrossCutting/Configuration/ConnectionStrings.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Minuto.Seguros.Infra.CrossCutting.Configuration +{ + public class ConnectionStrings + { + public string MinutoSeguroConn { get; set; } + } +} diff --git a/src/Minuto.Seguros.Infra.CrossCutting/Configuration/JwtConfig.cs b/src/Minuto.Seguros.Infra.CrossCutting/Configuration/JwtConfig.cs new file mode 100644 index 0000000..a3dc95e --- /dev/null +++ b/src/Minuto.Seguros.Infra.CrossCutting/Configuration/JwtConfig.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Minuto.Seguros.Infra.CrossCutting.Configuration +{ + public class JwtConfig + { + public string Secret { get; set; } + public string Issuer { get; set; } + public int LifeTimeMinutes { get; set; } + } +} diff --git a/src/Minuto.Seguros.Infra.CrossCutting/Minuto.Seguros.Infra.CrossCutting.csproj b/src/Minuto.Seguros.Infra.CrossCutting/Minuto.Seguros.Infra.CrossCutting.csproj new file mode 100644 index 0000000..923e8f0 --- /dev/null +++ b/src/Minuto.Seguros.Infra.CrossCutting/Minuto.Seguros.Infra.CrossCutting.csproj @@ -0,0 +1,17 @@ + + + + netcoreapp2.1 + + + + + + + + + + + + + diff --git a/src/Minuto.Seguros.Infra.CrossCutting/Utils/ClearSentenceUtil.cs b/src/Minuto.Seguros.Infra.CrossCutting/Utils/ClearSentenceUtil.cs new file mode 100644 index 0000000..7bbe7bd --- /dev/null +++ b/src/Minuto.Seguros.Infra.CrossCutting/Utils/ClearSentenceUtil.cs @@ -0,0 +1,186 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.RegularExpressions; + +namespace Minuto.Seguros.Infra.CrossCutting.Utils +{ + public static class ClearSentenceUtil + { + /// + /// Fontes: https://www.normaculta.com.br/artigo-indefinido/ + /// https://www.normaculta.com.br/artigo-definido/ + /// https://www.normaculta.com.br/preposicao/ + /// + /// + /// + public static string ClearSentence(string sentence) + { + var sentenceClear = string.Concat(" ", sentence, " "); + + sentenceClear = StripHTML(sentenceClear); + + #region | Pontuação + + sentenceClear = sentenceClear + .Replace(",", "") + .Replace(".", "") + .Replace("?", "") + .Replace("!", "") + .Replace(":", "") + .Replace("(", "") + .Replace(")", "") + .Replace(";", "") + .Replace(" - ", " ") + .Replace("\n", "") + .Replace("\"", ""); + + #endregion + + #region | Artigos Definidos + + sentenceClear = sentenceClear + .Replace(" a ", " ") + .Replace(" as ", " ") + .Replace(" A ", " ") + .Replace(" As ", " ") + .Replace(" AS ", " ") + .Replace(" o ", " ") + .Replace(" os ", " ") + .Replace(" O ", " ") + .Replace(" Os ", " ") + .Replace(" OS ", " ") + .Replace(" da ", " ") + .Replace(" Da ", " ") + .Replace(" DA ", " ") + .Replace(" das ", " ") + .Replace(" Das ", " ") + .Replace(" DAS ", " ") + .Replace(" de ", " ") + .Replace(" De ", " ") + .Replace(" DE ", " ") + .Replace(" no ", " ") + .Replace(" No ", " ") + .Replace(" NOS ", " ") + .Replace(" nos ", " ") + .Replace(" Nos ", " ") + .Replace(" NOS ", " "); + #endregion + + #region | Artigos Indefinidos + + sentenceClear = sentenceClear.Replace(" um ", " ") + .Replace(" Um ", " ") + .Replace(" UM ", " ") + .Replace(" uns ", " ") + .Replace(" Uns ", " ") + .Replace(" UNS ", " ") + .Replace(" dum ", " ") + .Replace(" Dum ", " ") + .Replace(" DUM ", " ") + .Replace(" duns ", " ") + .Replace(" Duns ", " ") + .Replace(" DUNS ", " ") + .Replace(" uma ", " ") + .Replace(" Uma ", " ") + .Replace(" UMA ", " ") + .Replace(" umas ", " ") + .Replace(" Umas ", " ") + .Replace(" UMAS ", " ") + .Replace(" em ", " ") + .Replace(" Em ", " ") + .Replace(" EM ", " "); + + #endregion + + #region | Preposições + + sentenceClear = sentenceClear + .Replace(" do ", " ") + .Replace(" Do ", " ") + .Replace(" DO ", " ") + .Replace(" dos ", " ") + .Replace(" Dos ", " ") + .Replace(" DOS ", " ") + .Replace(" DOS ", " ") + + .Replace(" à ", " ") + .Replace(" às ", " ") + .Replace(" À ", " ") + .Replace(" Às ", " ") + .Replace(" ÀS ", " ") + + .Replace(" duma ", " ") + .Replace(" Duma ", " ") + .Replace(" DUMA ", " ") + .Replace(" dumas ", " ") + .Replace(" Dumas ", " ") + .Replace(" DUMAS ", " ") + + .Replace(" disto ", " ") + .Replace(" Disto ", " ") + .Replace(" DISTO ", " ") + + .Replace(" na ", " ") + .Replace(" Na ", " ") + .Replace(" NA ", " ") + .Replace(" nas ", " ") + .Replace(" Nas ", " ") + .Replace(" NAS ", " ") + + .Replace(" ao ", " ") + .Replace(" aos ", " ") + .Replace(" AO ", " ") + .Replace(" Aos ", " ") + .Replace(" AOS ", " ") + + .Replace(" num ", " ") + .Replace(" Num ", " ") + .Replace(" NUM ", " ") + .Replace(" nuns ", " ") + .Replace(" Nuns ", " ") + .Replace(" NUNS ", " ") + + .Replace(" numa ", " ") + .Replace(" Numa ", " ") + .Replace(" NUMA ", " ") + .Replace(" numas ", " ") + .Replace(" Numas ", " ") + .Replace(" NUMAS ", " ") + + .Replace(" pelo ", " ") + .Replace(" Pelo ", " ") + .Replace(" PELO ", " ") + .Replace(" pelos ", " ") + .Replace(" Pelos ", " ") + .Replace(" PELOS ", " ") + + .Replace(" pela ", " ") + .Replace(" Pela ", " ") + .Replace(" PELA ", " ") + .Replace(" pelas ", " ") + .Replace(" Pelas ", " ") + .Replace(" PELAS ", " ") + + .Replace(" nessa ", " ") + .Replace(" Nessa ", " ") + .Replace(" NESSA", " ") + .Replace(" nessas ", " ") + .Replace(" Nessas ", " ") + .Replace(" NESSAS ", " ") + + .Replace(" aonde ", " ") + .Replace(" Aonde ", " ") + .Replace(" AONDE", " "); + + #endregion + + return sentenceClear.TrimEnd().TrimStart().ToLower(); + } + + private static string StripHTML(string input) + { + return Regex.Replace(input, "<.*?>", String.Empty); + } + } +} diff --git a/src/Minuto.Seguros.Infra.CrossCutting/Utils/RssUtil.cs b/src/Minuto.Seguros.Infra.CrossCutting/Utils/RssUtil.cs new file mode 100644 index 0000000..c0afa8d --- /dev/null +++ b/src/Minuto.Seguros.Infra.CrossCutting/Utils/RssUtil.cs @@ -0,0 +1,485 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Minuto.Seguros.Infra.CrossCutting.Utils +{ + // NOTE: Generated code may require at least .NET Framework 4.5 or .NET Core/Standard 2.0. + /// + [System.SerializableAttribute()] + [System.ComponentModel.DesignerCategoryAttribute("code")] + [System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)] + [System.Xml.Serialization.XmlRootAttribute(Namespace = "", IsNullable = false)] + public partial class rss + { + + private rssChannel channelField; + + private decimal versionField; + + /// + public rssChannel channel + { + get + { + return this.channelField; + } + set + { + this.channelField = value; + } + } + + /// + [System.Xml.Serialization.XmlAttributeAttribute()] + public decimal version + { + get + { + return this.versionField; + } + set + { + this.versionField = value; + } + } + } + + /// + [System.SerializableAttribute()] + [System.ComponentModel.DesignerCategoryAttribute("code")] + [System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)] + public partial class rssChannel + { + + private string titleField; + + private link linkField; + + private string link1Field; + + private string descriptionField; + + private string lastBuildDateField; + + private string languageField; + + private string updatePeriodField; + + private byte updateFrequencyField; + + private string generatorField; + + private rssChannelItem[] itemField; + + /// + public string title + { + get + { + return this.titleField; + } + set + { + this.titleField = value; + } + } + + /// + [System.Xml.Serialization.XmlElementAttribute(Namespace = "http://www.w3.org/2005/Atom")] + public link link + { + get + { + return this.linkField; + } + set + { + this.linkField = value; + } + } + + /// + [System.Xml.Serialization.XmlElementAttribute("link")] + public string link1 + { + get + { + return this.link1Field; + } + set + { + this.link1Field = value; + } + } + + /// + public string description + { + get + { + return this.descriptionField; + } + set + { + this.descriptionField = value; + } + } + + /// + public string lastBuildDate + { + get + { + return this.lastBuildDateField; + } + set + { + this.lastBuildDateField = value; + } + } + + /// + public string language + { + get + { + return this.languageField; + } + set + { + this.languageField = value; + } + } + + /// + [System.Xml.Serialization.XmlElementAttribute(Namespace = "http://purl.org/rss/1.0/modules/syndication/")] + public string updatePeriod + { + get + { + return this.updatePeriodField; + } + set + { + this.updatePeriodField = value; + } + } + + /// + [System.Xml.Serialization.XmlElementAttribute(Namespace = "http://purl.org/rss/1.0/modules/syndication/")] + public byte updateFrequency + { + get + { + return this.updateFrequencyField; + } + set + { + this.updateFrequencyField = value; + } + } + + /// + public string generator + { + get + { + return this.generatorField; + } + set + { + this.generatorField = value; + } + } + + /// + [System.Xml.Serialization.XmlElementAttribute("item")] + public rssChannelItem[] item + { + get + { + return this.itemField; + } + set + { + this.itemField = value; + } + } + } + + /// + [System.SerializableAttribute()] + [System.ComponentModel.DesignerCategoryAttribute("code")] + [System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "http://www.w3.org/2005/Atom")] + [System.Xml.Serialization.XmlRootAttribute(Namespace = "http://www.w3.org/2005/Atom", IsNullable = false)] + public partial class link + { + + private string hrefField; + + private string relField; + + private string typeField; + + /// + [System.Xml.Serialization.XmlAttributeAttribute()] + public string href + { + get + { + return this.hrefField; + } + set + { + this.hrefField = value; + } + } + + /// + [System.Xml.Serialization.XmlAttributeAttribute()] + public string rel + { + get + { + return this.relField; + } + set + { + this.relField = value; + } + } + + /// + [System.Xml.Serialization.XmlAttributeAttribute()] + public string type + { + get + { + return this.typeField; + } + set + { + this.typeField = value; + } + } + } + + /// + [System.SerializableAttribute()] + [System.ComponentModel.DesignerCategoryAttribute("code")] + [System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)] + public partial class rssChannelItem + { + + private string titleField; + + private string linkField; + + private string commentsField; + + private string pubDateField; + + private string creatorField; + + private string[] categoryField; + + private rssChannelItemGuid guidField; + + private string descriptionField; + + private string encodedField; + + private string commentRssField; + + private byte comments1Field; + + /// + public string title + { + get + { + return this.titleField; + } + set + { + this.titleField = value; + } + } + + /// + public string link + { + get + { + return this.linkField; + } + set + { + this.linkField = value; + } + } + + /// + public string comments + { + get + { + return this.commentsField; + } + set + { + this.commentsField = value; + } + } + + /// + public string pubDate + { + get + { + return this.pubDateField; + } + set + { + this.pubDateField = value; + } + } + + /// + [System.Xml.Serialization.XmlElementAttribute(Namespace = "http://purl.org/dc/elements/1.1/")] + public string creator + { + get + { + return this.creatorField; + } + set + { + this.creatorField = value; + } + } + + /// + [System.Xml.Serialization.XmlElementAttribute("category")] + public string[] category + { + get + { + return this.categoryField; + } + set + { + this.categoryField = value; + } + } + + /// + public rssChannelItemGuid guid + { + get + { + return this.guidField; + } + set + { + this.guidField = value; + } + } + + /// + public string description + { + get + { + return this.descriptionField; + } + set + { + this.descriptionField = value; + } + } + + /// + [System.Xml.Serialization.XmlElementAttribute(Namespace = "http://purl.org/rss/1.0/modules/content/")] + public string encoded + { + get + { + return this.encodedField; + } + set + { + this.encodedField = value; + } + } + + /// + [System.Xml.Serialization.XmlElementAttribute(Namespace = "http://wellformedweb.org/CommentAPI/")] + public string commentRss + { + get + { + return this.commentRssField; + } + set + { + this.commentRssField = value; + } + } + + /// + [System.Xml.Serialization.XmlElementAttribute("comments", Namespace = "http://purl.org/rss/1.0/modules/slash/")] + public byte comments1 + { + get + { + return this.comments1Field; + } + set + { + this.comments1Field = value; + } + } + } + + /// + [System.SerializableAttribute()] + [System.ComponentModel.DesignerCategoryAttribute("code")] + [System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)] + public partial class rssChannelItemGuid + { + + private bool isPermaLinkField; + + private string valueField; + + /// + [System.Xml.Serialization.XmlAttributeAttribute()] + public bool isPermaLink + { + get + { + return this.isPermaLinkField; + } + set + { + this.isPermaLinkField = value; + } + } + + /// + [System.Xml.Serialization.XmlTextAttribute()] + public string Value + { + get + { + return this.valueField; + } + set + { + this.valueField = value; + } + } + } +} diff --git a/src/Minuto.Seguros.Infra.CrossCutting/Utils/SerializationUtil.cs b/src/Minuto.Seguros.Infra.CrossCutting/Utils/SerializationUtil.cs new file mode 100644 index 0000000..49a4f0b --- /dev/null +++ b/src/Minuto.Seguros.Infra.CrossCutting/Utils/SerializationUtil.cs @@ -0,0 +1,33 @@ +using System; +using System.Xml.Linq; +using System.Xml.Serialization; + +namespace Minuto.Seguros.Infra.CrossCutting.Utils +{ + public static class SerializationUtil + { + + public static T Deserialize(XDocument doc) + { + XmlSerializer xmlSerializer = new XmlSerializer(typeof(T)); + + using (var reader = doc.Root.CreateReader()) + { + return (T)xmlSerializer.Deserialize(reader); + } + } + + public static XDocument Serialize(T value) + { + XmlSerializer xmlSerializer = new XmlSerializer(typeof(T)); + + XDocument doc = new XDocument(); + using (var writer = doc.CreateWriter()) + { + xmlSerializer.Serialize(writer, value); + } + + return doc; + } + } +} diff --git a/src/Minuto.Seguros.Infra.CrossCutting/Utils/WordUtil.cs b/src/Minuto.Seguros.Infra.CrossCutting/Utils/WordUtil.cs new file mode 100644 index 0000000..74cff20 --- /dev/null +++ b/src/Minuto.Seguros.Infra.CrossCutting/Utils/WordUtil.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Minuto.Seguros.Infra.CrossCutting.Utils +{ + public static class WordUtil + { + public static int CountRecurrence(string word, List words) + { + return words.Count(w => w == word); + } + } +} diff --git a/src/Minuto.Seguros.Infra.Data/Connection/Config.cs b/src/Minuto.Seguros.Infra.Data/Connection/Config.cs new file mode 100644 index 0000000..21ff2cd --- /dev/null +++ b/src/Minuto.Seguros.Infra.Data/Connection/Config.cs @@ -0,0 +1,24 @@ +using Microsoft.Extensions.Configuration; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Minuto.Seguros.Infra.Data.Connection +{ + public class Config : IConfig + { + public Config(IConfiguration configuration) + { + IConfigurationSection section = configuration.GetSection("MongoDB"); + MongoConnectionString = section["ConnectionStrings"]; + MongoDatabase = section["Database"]; + } + public Config(string connectionString, string database) + { + MongoConnectionString = connectionString; + MongoDatabase = database; + } + public string MongoConnectionString { get; private set; } + public string MongoDatabase { get; private set; } + } +} diff --git a/src/Minuto.Seguros.Infra.Data/Connection/IConfig.cs b/src/Minuto.Seguros.Infra.Data/Connection/IConfig.cs new file mode 100644 index 0000000..7bf2293 --- /dev/null +++ b/src/Minuto.Seguros.Infra.Data/Connection/IConfig.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Minuto.Seguros.Infra.Data.Connection +{ + public interface IConfig + { + string MongoConnectionString { get; } + string MongoDatabase { get; } + } +} diff --git a/src/Minuto.Seguros.Infra.Data/Connection/IConnect.cs b/src/Minuto.Seguros.Infra.Data/Connection/IConnect.cs new file mode 100644 index 0000000..47bdfd5 --- /dev/null +++ b/src/Minuto.Seguros.Infra.Data/Connection/IConnect.cs @@ -0,0 +1,10 @@ +using MongoDB.Driver; +using System; + +namespace Minuto.Seguros.Infra.Data.Connection +{ + public interface IConnect : IDisposable + { + IMongoCollection Collection(string CollectionName); + } +} diff --git a/src/Minuto.Seguros.Infra.Data/Context/MongoContext.cs b/src/Minuto.Seguros.Infra.Data/Context/MongoContext.cs new file mode 100644 index 0000000..6c8a610 --- /dev/null +++ b/src/Minuto.Seguros.Infra.Data/Context/MongoContext.cs @@ -0,0 +1,72 @@ +using Minuto.Seguros.Domain.Entities; +using Minuto.Seguros.Infra.Data.Connection; +using MongoDB.Bson.Serialization; +using MongoDB.Driver; +using System; + +namespace Minuto.Seguros.Infra.Data.Context +{ + public class MongoContext : IDisposable, IConnect + { + protected MongoClient Client { get; private set; } + protected IMongoDatabase DataBase { get; private set; } + + public IMongoCollection Collection(string CollectionName) + { + return DataBase.GetCollection(CollectionName); + } + public MongoContext(IConfig config) + { + Client = new MongoClient(config.MongoConnectionString); + DataBase = Client.GetDatabase(config.MongoDatabase); + } + + public MongoContext(string connectionString, string database) + { + Client = new MongoClient(connectionString); + DataBase = Client.GetDatabase(database); + Map(); + } + + public IMongoCollection Feeds + { + get + { + return DataBase.GetCollection("Feeds"); + } + } + + private void Map() + { + BsonClassMap.RegisterClassMap(cm => + { + cm.AutoMap(); + }); + } + + #region Dispose + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + protected virtual void Dispose(bool disposing) + { + if (!disposed) + { + if (disposing) + { + DataBase = null; + Client = null; + } + disposed = true; + } + } + ~MongoContext() + { + Dispose(false); + } + private bool disposed = false; + #endregion Dispose + } +} diff --git a/src/Minuto.Seguros.Infra.Data/Contracts/IBaseRepository.cs b/src/Minuto.Seguros.Infra.Data/Contracts/IBaseRepository.cs new file mode 100644 index 0000000..b518e8e --- /dev/null +++ b/src/Minuto.Seguros.Infra.Data/Contracts/IBaseRepository.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Text; +using System.Threading.Tasks; + +namespace Minuto.Seguros.Infra.Data.Contracts +{ + public interface IBaseRepository where T : class + { + T Add(T model); + T Find(Expression> filter); + IEnumerable All(Expression> filter); + } +} diff --git a/src/Minuto.Seguros.Infra.Data/Contracts/IFeedRepository.cs b/src/Minuto.Seguros.Infra.Data/Contracts/IFeedRepository.cs new file mode 100644 index 0000000..13c103e --- /dev/null +++ b/src/Minuto.Seguros.Infra.Data/Contracts/IFeedRepository.cs @@ -0,0 +1,8 @@ +using Minuto.Seguros.Domain.Entities; + +namespace Minuto.Seguros.Infra.Data.Contracts +{ + public interface IFeedRepository: IBaseRepository + { + } +} diff --git a/src/Minuto.Seguros.Infra.Data/DataModule.cs b/src/Minuto.Seguros.Infra.Data/DataModule.cs new file mode 100644 index 0000000..a651b11 --- /dev/null +++ b/src/Minuto.Seguros.Infra.Data/DataModule.cs @@ -0,0 +1,31 @@ +using Autofac; +using Minuto.Seguros.Infra.Data.Context; + +namespace Minuto.Seguros.Infra.Data +{ + public class DataModule: Module + { + public readonly string _connectionString; + public readonly string _database; + + public DataModule(string connectionString, string database) + { + _connectionString = connectionString; + _database = database; + } + + protected override void Load(ContainerBuilder builder) + { + builder.RegisterAssemblyTypes(System.Reflection.Assembly.GetExecutingAssembly()) + .Where(t => t.Name.EndsWith("Repository") && !t.Name.EndsWith("BaseRepository")) + .AsImplementedInterfaces() + .InstancePerLifetimeScope(); + + + builder.Register(c => new MongoContext(_connectionString, _database)) + .As().SingleInstance(); + + base.Load(builder); + } + } +} diff --git a/src/Minuto.Seguros.Infra.Data/Minuto.Seguros.Infra.Data.csproj b/src/Minuto.Seguros.Infra.Data/Minuto.Seguros.Infra.Data.csproj new file mode 100644 index 0000000..d6a7bb6 --- /dev/null +++ b/src/Minuto.Seguros.Infra.Data/Minuto.Seguros.Infra.Data.csproj @@ -0,0 +1,19 @@ + + + + netcoreapp2.1 + + + + + + + + + + + + + + + diff --git a/src/Minuto.Seguros.Infra.Data/Repository/FeedRepository.cs b/src/Minuto.Seguros.Infra.Data/Repository/FeedRepository.cs new file mode 100644 index 0000000..eeb965f --- /dev/null +++ b/src/Minuto.Seguros.Infra.Data/Repository/FeedRepository.cs @@ -0,0 +1,38 @@ +using Minuto.Seguros.Domain.Entities; +using Minuto.Seguros.Infra.Data.Context; +using Minuto.Seguros.Infra.Data.Contracts; +using MongoDB.Driver; +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Text; +using System.Threading.Tasks; + +namespace Minuto.Seguros.Infra.Data.Repository +{ + public class FeedRepository : IFeedRepository + { + private readonly MongoContext _mongoContext; + + public FeedRepository(MongoContext mongoContext) + { + _mongoContext = mongoContext; + } + + public Feed Add(Feed model) + { + _mongoContext.Feeds.InsertOneAsync(model); + return model; + } + + public IEnumerable All(Expression> filter) + { + return _mongoContext.Feeds.Find(filter).ToList(); + } + + public Feed Find(Expression> filter) + { + return _mongoContext.Feeds.Find(filter).FirstOrDefault(); + } + } +} diff --git a/src/Minuto.Seguros.Service/Contracts/IBaseService.cs b/src/Minuto.Seguros.Service/Contracts/IBaseService.cs new file mode 100644 index 0000000..ec79685 --- /dev/null +++ b/src/Minuto.Seguros.Service/Contracts/IBaseService.cs @@ -0,0 +1,16 @@ +using Minuto.Seguros.Domain.Entities; +using Minuto.Seguros.Infra.Data.Contracts; +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Threading.Tasks; + +namespace Minuto.Seguros.Service.Contracts +{ + public interface IBaseService : IDisposable where T : IBaseRepository where TE : BaseEntity + { + TE Add(TE model); + TE Find(Expression> filter); + IEnumerable All(Expression> filter); + } +} diff --git a/src/Minuto.Seguros.Service/Contracts/IFeedService.cs b/src/Minuto.Seguros.Service/Contracts/IFeedService.cs new file mode 100644 index 0000000..991915f --- /dev/null +++ b/src/Minuto.Seguros.Service/Contracts/IFeedService.cs @@ -0,0 +1,15 @@ +using Minuto.Seguros.Domain.Entities; +using Minuto.Seguros.Infra.Data.Contracts; +using Minuto.Seguros.Service.Dto; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Minuto.Seguros.Service.Contracts +{ + public interface IFeedService : IBaseService + { + List GetFeeds(); + List GetAllFeeds(); + } +} diff --git a/src/Minuto.Seguros.Service/Dto/FeedDto.cs b/src/Minuto.Seguros.Service/Dto/FeedDto.cs new file mode 100644 index 0000000..39ea0c2 --- /dev/null +++ b/src/Minuto.Seguros.Service/Dto/FeedDto.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Minuto.Seguros.Service.Dto +{ + public class FeedDto + { + public string Title { get; set; } + public string Description { get; set; } + public string[] Categories { get; set; } + public string FullMessageClean { get; set; } + } +} diff --git a/src/Minuto.Seguros.Service/Dto/TopicDto.cs b/src/Minuto.Seguros.Service/Dto/TopicDto.cs new file mode 100644 index 0000000..5f36f5a --- /dev/null +++ b/src/Minuto.Seguros.Service/Dto/TopicDto.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Minuto.Seguros.Service.Dto +{ + public class TopicDto + { + public string Title { get; set; } + public List TopWords{ get; set; } + public int TotalWords { get; set; } + public string CleanerText { get; set; } + } +} diff --git a/src/Minuto.Seguros.Service/Dto/WordDto.cs b/src/Minuto.Seguros.Service/Dto/WordDto.cs new file mode 100644 index 0000000..5af148e --- /dev/null +++ b/src/Minuto.Seguros.Service/Dto/WordDto.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Minuto.Seguros.Service.Dto +{ + public class WordDto + { + public string Word { get; set; } + public int Amount { get; set; } + } +} diff --git a/src/Minuto.Seguros.Service/Minuto.Seguros.Service.csproj b/src/Minuto.Seguros.Service/Minuto.Seguros.Service.csproj new file mode 100644 index 0000000..c69b29d --- /dev/null +++ b/src/Minuto.Seguros.Service/Minuto.Seguros.Service.csproj @@ -0,0 +1,17 @@ + + + + netcoreapp2.1 + + + + + + + + + + + + + diff --git a/src/Minuto.Seguros.Service/ServiceModule.cs b/src/Minuto.Seguros.Service/ServiceModule.cs new file mode 100644 index 0000000..4b025d7 --- /dev/null +++ b/src/Minuto.Seguros.Service/ServiceModule.cs @@ -0,0 +1,15 @@ +using Autofac; + +namespace Minuto.Seguros.Service +{ + public class ServiceModule : Module + { + protected override void Load(ContainerBuilder builder) + { + builder.RegisterAssemblyTypes(System.Reflection.Assembly.GetExecutingAssembly()) + .Where(t => t.Name.EndsWith("Service") && !t.Name.EndsWith("BaseService")) + .AsImplementedInterfaces(); + base.Load(builder); + } + } +} diff --git a/src/Minuto.Seguros.Service/Services/BaseService.cs b/src/Minuto.Seguros.Service/Services/BaseService.cs new file mode 100644 index 0000000..33881b1 --- /dev/null +++ b/src/Minuto.Seguros.Service/Services/BaseService.cs @@ -0,0 +1,42 @@ +using Minuto.Seguros.Domain.Entities; +using Minuto.Seguros.Infra.Data.Contracts; +using Minuto.Seguros.Service.Contracts; +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Text; +using System.Threading.Tasks; + +namespace Minuto.Seguros.Service.Services +{ + internal abstract class BaseService : IBaseService where T : IBaseRepository where TE : BaseEntity + { + public readonly IBaseRepository _repository; + + public BaseService(IBaseRepository repository) + { + _repository = repository; + } + + public virtual TE Add(TE entity) + { + return _repository.Add(entity); + } + + public IEnumerable All(Expression> filter) + { + return _repository.All(filter); + } + + public void Dispose() + { + GC.SuppressFinalize(this); + } + + public TE Find(Expression> filter) + { + return _repository.Find(filter); + } + + } +} diff --git a/src/Minuto.Seguros.Service/Services/FeedService.cs b/src/Minuto.Seguros.Service/Services/FeedService.cs new file mode 100644 index 0000000..8fe026f --- /dev/null +++ b/src/Minuto.Seguros.Service/Services/FeedService.cs @@ -0,0 +1,108 @@ +using AutoMapper; +using Minuto.Seguros.Domain.Entities; +using Minuto.Seguros.Infra.Client.Corporate.Rss.Contracts; +using Minuto.Seguros.Infra.CrossCutting.Utils; +using Minuto.Seguros.Infra.Data.Contracts; +using Minuto.Seguros.Service.Contracts; +using Minuto.Seguros.Service.Dto; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Minuto.Seguros.Service.Services +{ + internal class FeedService : BaseService, IFeedService + { + private readonly IFeedRepository _feedRepository; + private readonly IRssClient _rssClient; + private readonly IMapper _mapper; + + public FeedService( + IFeedRepository feedRepository, + IRssClient rssClient, + IMapper mapper + ) : base(feedRepository) + { + _feedRepository = feedRepository; + _rssClient = rssClient; + _mapper = mapper; + } + + public List GetFeeds() + { + try + { + var feeds = _rssClient.GetFeeds(); + List feedsMap = new List(); + + foreach (var item in feeds) + { + item.FullMessageClean = ClearSentenceUtil.ClearSentence(string.Concat(item.Title, " ", item.Description, " ", string.Join(", ", item.Categories))); + feedsMap.Add(_mapper.Map(item)); + } + + List words; + List wordsRecurrence; + List topics = new List(); + + foreach (var item in feedsMap) + { + wordsRecurrence = new List(); + words = item.FullMessageClean.Split(' ').ToList(); + + foreach (var word in words) + { + if (!wordsRecurrence.Any(w => w.Word == word)) + { + wordsRecurrence.Add(new WordDto() + { + Word = word, + Amount = WordUtil.CountRecurrence(word, words) + }); + } + } + + topics.Add(new TopicDto() + { + Title = item.Title, + TotalWords = wordsRecurrence.Count(), + TopWords = wordsRecurrence.OrderByDescending(w => w.Amount).Take(10).ToList(), + CleanerText = item.FullMessageClean + }); + } + + return topics; + + } catch(Exception ex) + { + throw ex; + } + + } + + public List GetAllFeeds() + { + try { + var feeds = _rssClient.GetFeeds(); + List feedsMap = new List(); + + foreach (var item in feeds) + { + feedsMap.Add(_mapper.Map(item)); + + /* Sem necessidade, apenas uma amostragem que quis colocar + * pra mostrar que se fosse uma integração continua, teríamos fácil uma base de dados para manipulação. + */ + _feedRepository.Add(_mapper.Map(item)); + } + + return feedsMap; + } + catch (Exception ex) + { + throw ex; + } + } + } +} diff --git a/src/Minuto.Seguros.sln b/src/Minuto.Seguros.sln new file mode 100644 index 0000000..6669990 --- /dev/null +++ b/src/Minuto.Seguros.sln @@ -0,0 +1,83 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27703.2042 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "1 - Application", "1 - Application", "{FFA04C89-7397-4C58-BD03-B2135494A001}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "2 - Domain", "2 - Domain", "{836DD694-FC22-410C-8178-CFC9F962B18F}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "3 - Service", "3 - Service", "{D2C920A4-1557-4097-ABF7-D924252157B9}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "4 - Infra", "4 - Infra", "{58DB3E18-46C1-4263-BC9B-59F45B8AFD1F}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "4.1 - CrossCutting", "4.1 - CrossCutting", "{58E569A2-4C8C-4821-8142-692DDEF5D314}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Minuto.Seguros.Infra.CrossCutting", "Minuto.Seguros.Infra.CrossCutting\Minuto.Seguros.Infra.CrossCutting.csproj", "{67EE7CB7-4804-469F-B05B-1E50F709C790}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "4.2 - Data", "4.2 - Data", "{1B0F4628-3482-46F4-B9D8-80E9021905F0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Minuto.Seguros.Infra.Data", "Minuto.Seguros.Infra.Data\Minuto.Seguros.Infra.Data.csproj", "{D09C4DBC-D564-495B-929A-F8A7BCD51D20}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Minuto.Seguros.Domain", "Minuto.Seguros.Domain\Minuto.Seguros.Domain.csproj", "{89C33D3B-269B-4B8C-9748-25F19660CE02}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Minuto.Seguros.Application", "Minuto.Seguros.Application\Minuto.Seguros.Application.csproj", "{C5B0E5BC-BAB0-40FD-97D5-F8A395E8E00D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Minuto.Seguros.Service", "Minuto.Seguros.Service\Minuto.Seguros.Service.csproj", "{1DC0F90D-0A43-44FF-B97C-EC9090FD23CE}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "4.3 - Client", "4.3 - Client", "{CB64D581-B76A-448B-8324-C83CF71DFDC6}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "4.3.1 - Corporate", "4.3.1 - Corporate", "{D23B1506-6257-46D4-B31F-A4DDDB2B704C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Minuto.Seguros.Infra.Client.Corporate.Rss", "Minuto.Seguros.Infra.Client.Corporate.Rss\Minuto.Seguros.Infra.Client.Corporate.Rss.csproj", "{950035F1-0F08-4C7D-8DBE-508126547D0C}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {67EE7CB7-4804-469F-B05B-1E50F709C790}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {67EE7CB7-4804-469F-B05B-1E50F709C790}.Debug|Any CPU.Build.0 = Debug|Any CPU + {67EE7CB7-4804-469F-B05B-1E50F709C790}.Release|Any CPU.ActiveCfg = Release|Any CPU + {67EE7CB7-4804-469F-B05B-1E50F709C790}.Release|Any CPU.Build.0 = Release|Any CPU + {D09C4DBC-D564-495B-929A-F8A7BCD51D20}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D09C4DBC-D564-495B-929A-F8A7BCD51D20}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D09C4DBC-D564-495B-929A-F8A7BCD51D20}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D09C4DBC-D564-495B-929A-F8A7BCD51D20}.Release|Any CPU.Build.0 = Release|Any CPU + {89C33D3B-269B-4B8C-9748-25F19660CE02}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {89C33D3B-269B-4B8C-9748-25F19660CE02}.Debug|Any CPU.Build.0 = Debug|Any CPU + {89C33D3B-269B-4B8C-9748-25F19660CE02}.Release|Any CPU.ActiveCfg = Release|Any CPU + {89C33D3B-269B-4B8C-9748-25F19660CE02}.Release|Any CPU.Build.0 = Release|Any CPU + {C5B0E5BC-BAB0-40FD-97D5-F8A395E8E00D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C5B0E5BC-BAB0-40FD-97D5-F8A395E8E00D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C5B0E5BC-BAB0-40FD-97D5-F8A395E8E00D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C5B0E5BC-BAB0-40FD-97D5-F8A395E8E00D}.Release|Any CPU.Build.0 = Release|Any CPU + {1DC0F90D-0A43-44FF-B97C-EC9090FD23CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1DC0F90D-0A43-44FF-B97C-EC9090FD23CE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1DC0F90D-0A43-44FF-B97C-EC9090FD23CE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1DC0F90D-0A43-44FF-B97C-EC9090FD23CE}.Release|Any CPU.Build.0 = Release|Any CPU + {950035F1-0F08-4C7D-8DBE-508126547D0C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {950035F1-0F08-4C7D-8DBE-508126547D0C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {950035F1-0F08-4C7D-8DBE-508126547D0C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {950035F1-0F08-4C7D-8DBE-508126547D0C}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {58E569A2-4C8C-4821-8142-692DDEF5D314} = {58DB3E18-46C1-4263-BC9B-59F45B8AFD1F} + {67EE7CB7-4804-469F-B05B-1E50F709C790} = {58E569A2-4C8C-4821-8142-692DDEF5D314} + {1B0F4628-3482-46F4-B9D8-80E9021905F0} = {58DB3E18-46C1-4263-BC9B-59F45B8AFD1F} + {D09C4DBC-D564-495B-929A-F8A7BCD51D20} = {1B0F4628-3482-46F4-B9D8-80E9021905F0} + {89C33D3B-269B-4B8C-9748-25F19660CE02} = {836DD694-FC22-410C-8178-CFC9F962B18F} + {C5B0E5BC-BAB0-40FD-97D5-F8A395E8E00D} = {FFA04C89-7397-4C58-BD03-B2135494A001} + {1DC0F90D-0A43-44FF-B97C-EC9090FD23CE} = {D2C920A4-1557-4097-ABF7-D924252157B9} + {CB64D581-B76A-448B-8324-C83CF71DFDC6} = {58DB3E18-46C1-4263-BC9B-59F45B8AFD1F} + {D23B1506-6257-46D4-B31F-A4DDDB2B704C} = {CB64D581-B76A-448B-8324-C83CF71DFDC6} + {950035F1-0F08-4C7D-8DBE-508126547D0C} = {D23B1506-6257-46D4-B31F-A4DDDB2B704C} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {2DE70224-24E4-489E-89D8-B8CA86B701DB} + EndGlobalSection +EndGlobal