Docker aplicado

This commit is contained in:
Eduardo 2026-02-09 18:00:47 -03:00
parent 142bb60967
commit b924397f52
9 changed files with 84 additions and 124 deletions

11
.dockerignore Normal file
View File

@ -0,0 +1,11 @@
.git
.github
.vs
bin
obj
**/bin
**/obj
*.user
*.suo
*.log
README.md

View File

@ -1,60 +0,0 @@
using line_gestao_api.Models;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace line_gestao_api.Controllers;
[ApiController]
[Route("dev")]
public class DevController : ControllerBase
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly IConfiguration _config;
private readonly IWebHostEnvironment _env;
public DevController(
UserManager<ApplicationUser> userManager,
IConfiguration config,
IWebHostEnvironment env)
{
_userManager = userManager;
_config = config;
_env = env;
}
/// <summary>
/// Reseta a senha do admin seeded (somente em Development).
/// </summary>
[HttpPost("reset-admin-password")]
public async Task<IActionResult> ResetAdminPassword()
{
// 🔒 Proteção: só funciona em Development
if (!_env.IsDevelopment())
return NotFound();
var email = (_config["Seed:AdminEmail"] ?? "admin@linegestao.local").Trim().ToLowerInvariant();
var newPassword = _config["Seed:AdminPassword"] ?? "Admin123!";
var normalizedEmail = _userManager.NormalizeEmail(email);
var user = await _userManager.Users
.FirstOrDefaultAsync(u => u.NormalizedEmail == normalizedEmail);
if (user == null)
return NotFound("Admin não encontrado.");
// remove lockout se existir (só pra garantir)
await _userManager.SetLockoutEndDateAsync(user, null);
await _userManager.ResetAccessFailedCountAsync(user);
// reseta senha corretamente (via Identity)
var token = await _userManager.GeneratePasswordResetTokenAsync(user);
var reset = await _userManager.ResetPasswordAsync(user, token, newPassword);
if (!reset.Succeeded)
return BadRequest(reset.Errors.Select(e => e.Description));
return Ok($"Senha do admin resetada com sucesso para: {newPassword}");
}
}

View File

@ -1,26 +0,0 @@
using Microsoft.AspNetCore.Mvc;
namespace line_gestao_api.Controllers
{
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries =
[
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
];
[HttpGet(Name = "GetWeatherForecast")]
public IEnumerable<WeatherForecast> Get()
{
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray();
}
}
}

20
Dockerfile Normal file
View File

@ -0,0 +1,20 @@
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src
COPY ["line-gestao-api.csproj", "./"]
RUN dotnet restore "./line-gestao-api.csproj"
COPY . .
RUN dotnet publish "./line-gestao-api.csproj" -c Release -o /app/publish /p:UseAppHost=false
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS final
WORKDIR /app
ENV ASPNETCORE_URLS=http://+:8080
ENV ASPNETCORE_ENVIRONMENT=Production
COPY --from=build /app/publish .
EXPOSE 8080
ENTRYPOINT ["dotnet", "line-gestao-api.dll"]

View File

@ -12,23 +12,34 @@ var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers(); builder.Services.AddControllers();
// ✅ Upload (Excel / multipart)
builder.Services.Configure<FormOptions>(o => builder.Services.Configure<FormOptions>(o =>
{ {
o.MultipartBodyLengthLimit = 50_000_000; o.MultipartBodyLengthLimit = 50_000_000;
}); });
// ✅ CORS (Angular) var corsOrigins = builder.Configuration
.GetSection("Cors:AllowedOrigins")
.Get<string[]>()?
.Where(o => !string.IsNullOrWhiteSpace(o))
.Select(o => o.Trim())
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToArray()
?? [];
if (corsOrigins.Length == 0)
{
corsOrigins = ["http://localhost:4200"];
}
builder.Services.AddCors(options => builder.Services.AddCors(options =>
{ {
options.AddPolicy("Front", p => options.AddPolicy("Front", p =>
p.WithOrigins("http://localhost:4200") p.WithOrigins(corsOrigins)
.AllowAnyHeader() .AllowAnyHeader()
.AllowAnyMethod() .AllowAnyMethod()
); );
}); });
// ✅ EF Core (PostgreSQL)
builder.Services.AddDbContext<AppDbContext>(options => builder.Services.AddDbContext<AppDbContext>(options =>
options.UseNpgsql(builder.Configuration.GetConnectionString("Default")) options.UseNpgsql(builder.Configuration.GetConnectionString("Default"))
); );
@ -49,12 +60,15 @@ builder.Services.AddIdentityCore<ApplicationUser>(options =>
.AddEntityFrameworkStores<AppDbContext>() .AddEntityFrameworkStores<AppDbContext>()
.AddDefaultTokenProviders(); .AddDefaultTokenProviders();
// ✅ Swagger
builder.Services.AddEndpointsApiExplorer(); builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(); builder.Services.AddSwaggerGen();
// ✅ JWT var jwtKey = builder.Configuration["Jwt:Key"];
var jwtKey = builder.Configuration["Jwt:Key"]!; if (string.IsNullOrWhiteSpace(jwtKey))
{
throw new InvalidOperationException("Configuration 'Jwt:Key' is required.");
}
var issuer = builder.Configuration["Jwt:Issuer"]; var issuer = builder.Configuration["Jwt:Issuer"];
var audience = builder.Configuration["Jwt:Audience"]; var audience = builder.Configuration["Jwt:Audience"];
@ -89,18 +103,21 @@ if (app.Environment.IsDevelopment())
app.UseSwaggerUI(); app.UseSwaggerUI();
} }
app.UseHttpsRedirection(); var useHttpsRedirection = builder.Configuration.GetValue("App:UseHttpsRedirection", !app.Environment.IsDevelopment());
if (useHttpsRedirection)
{
app.UseHttpsRedirection();
}
// ✅ CORS antes de Auth
app.UseCors("Front"); app.UseCors("Front");
app.UseAuthentication(); app.UseAuthentication();
app.UseMiddleware<TenantMiddleware>(); app.UseMiddleware<TenantMiddleware>();
app.UseAuthorization(); app.UseAuthorization();
// ✅ SEED ANTES de subir controllers
await SeedData.EnsureSeedDataAsync(app.Services); await SeedData.EnsureSeedDataAsync(app.Services);
app.MapControllers(); app.MapControllers();
app.MapGet("/health", () => Results.Ok(new { status = "ok" }));
app.Run(); app.Run();

View File

@ -1,13 +0,0 @@
namespace line_gestao_api
{
public class WeatherForecast
{
public DateOnly Date { get; set; }
public int TemperatureC { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
public string? Summary { get; set; }
}
}

View File

@ -1,4 +1,21 @@
{ {
"ConnectionStrings": {
"Default": "Host=localhost;Port=5432;Database=linegestao;Username=linegestao_app;Password=CHANGE_ME"
},
"Jwt": {
"Key": "dev-only-please-replace-with-env-variable-in-production",
"Issuer": "LineGestao",
"Audience": "LineGestao",
"ExpiresMinutes": 360
},
"Cors": {
"AllowedOrigins": [
"http://localhost:4200"
]
},
"App": {
"UseHttpsRedirection": false
},
"Logging": { "Logging": {
"LogLevel": { "LogLevel": {
"Default": "Information", "Default": "Information",

View File

@ -1,13 +1,19 @@
{ {
"ConnectionStrings": { "ConnectionStrings": {
"Default": "Host=localhost;Port=5432;Database=linegestao;Username=linegestao_app;Password=255851Ed@" "Default": "Host=localhost;Port=5432;Database=linegestao;Username=linegestao_app;Password=CHANGE_ME"
}, },
"Jwt": { "Jwt": {
"Key": "vI8/oEYEWN5sBDTisNuZFjZAl+YFvXEJ96POb73/eoq3NaFPkOFXyPRdf/HWGAFnUsF3e3QpYL6Wl4Bc2v+l3g==", "Key": "",
"Issuer": "LineGestao", "Issuer": "LineGestao",
"Audience": "LineGestao", "Audience": "LineGestao",
"ExpiresMinutes": 360 "ExpiresMinutes": 360
}, },
"Cors": {
"AllowedOrigins": [ "http://localhost:4200" ]
},
"App": {
"UseHttpsRedirection": true
},
"Notifications": { "Notifications": {
"CheckIntervalMinutes": 60, "CheckIntervalMinutes": 60,
"ReminderDays": [30, 15, 7] "ReminderDays": [30, 15, 7]
@ -16,6 +22,6 @@
"DefaultTenantName": "Default", "DefaultTenantName": "Default",
"AdminName": "Administrador", "AdminName": "Administrador",
"AdminEmail": "admin@linegestao.local", "AdminEmail": "admin@linegestao.local",
"AdminPassword": "Admin123!" "AdminPassword": "CHANGE_ME"
} }
} }

View File

@ -10,13 +10,6 @@
<UseAppHost>false</UseAppHost> <UseAppHost>false</UseAppHost>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<Compile Remove="NovaPasta\**" />
<Content Remove="NovaPasta\**" />
<EmbeddedResource Remove="NovaPasta\**" />
<None Remove="NovaPasta\**" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Remove="line-gestao-api.Tests\**" /> <Compile Remove="line-gestao-api.Tests\**" />
<Content Remove="line-gestao-api.Tests\**" /> <Content Remove="line-gestao-api.Tests\**" />
@ -24,11 +17,6 @@
<None Remove="line-gestao-api.Tests\**" /> <None Remove="line-gestao-api.Tests\**" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Compile Remove="Controllers\WeatherForecastController.cs" />
<Compile Remove="WeatherForecast.cs" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ClosedXML" Version="0.105.0" /> <PackageReference Include="ClosedXML" Version="0.105.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.1" /> <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.1" />