Branch Produção
This commit is contained in:
parent
dc3351a5f8
commit
2872de9f4b
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<key id="df4b6848-d6f8-4258-b66f-efc9c9908378" version="1">
|
||||||
|
<creationDate>2026-02-11T18:29:16.4250416Z</creationDate>
|
||||||
|
<activationDate>2026-02-11T18:29:16.4250416Z</activationDate>
|
||||||
|
<expirationDate>2026-05-12T18:29:16.4250416Z</expirationDate>
|
||||||
|
<descriptor deserializerType="Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AuthenticatedEncryptorDescriptorDeserializer, Microsoft.AspNetCore.DataProtection, Version=10.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60">
|
||||||
|
<descriptor>
|
||||||
|
<encryption algorithm="AES_256_CBC" />
|
||||||
|
<validation algorithm="HMACSHA256" />
|
||||||
|
<masterKey p4:requiresEncryption="true" xmlns:p4="http://schemas.asp.net/2015/03/dataProtection">
|
||||||
|
<!-- Warning: the key below is in an unencrypted form. -->
|
||||||
|
<value>ZPHp/1oEBziSImYed0VIGR6ghUDI9+xXPXqb8t5Hs91BPBBvLM2NCReZtsmfKfmoqP0uFJ0jK+t/7tr6XxBAaA==</value>
|
||||||
|
</masterKey>
|
||||||
|
</descriptor>
|
||||||
|
</descriptor>
|
||||||
|
</key>
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
# dotenv files
|
# dotenv files
|
||||||
.env
|
.env
|
||||||
|
appsettings.Local.json
|
||||||
|
|
||||||
# User-specific files
|
# User-specific files
|
||||||
*.rsuser
|
*.rsuser
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ using line_gestao_api.Services;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.RateLimiting;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.IdentityModel.Tokens;
|
using Microsoft.IdentityModel.Tokens;
|
||||||
|
|
||||||
|
|
@ -60,7 +61,8 @@ public class AuthController : ControllerBase
|
||||||
UserName = email,
|
UserName = email,
|
||||||
TenantId = tenantId.Value,
|
TenantId = tenantId.Value,
|
||||||
IsActive = true,
|
IsActive = true,
|
||||||
EmailConfirmed = true
|
EmailConfirmed = true,
|
||||||
|
LockoutEnabled = true
|
||||||
};
|
};
|
||||||
|
|
||||||
var createResult = await _userManager.CreateAsync(user, req.Password);
|
var createResult = await _userManager.CreateAsync(user, req.Password);
|
||||||
|
|
@ -78,6 +80,7 @@ public class AuthController : ControllerBase
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("login")]
|
[HttpPost("login")]
|
||||||
|
[EnableRateLimiting("auth-login")]
|
||||||
public async Task<IActionResult> Login(LoginRequest req)
|
public async Task<IActionResult> Login(LoginRequest req)
|
||||||
{
|
{
|
||||||
var email = (req.Email ?? "").Trim().ToLowerInvariant();
|
var email = (req.Email ?? "").Trim().ToLowerInvariant();
|
||||||
|
|
@ -115,9 +118,24 @@ public class AuthController : ControllerBase
|
||||||
if (!user.IsActive)
|
if (!user.IsActive)
|
||||||
return Unauthorized("Usuário desativado.");
|
return Unauthorized("Usuário desativado.");
|
||||||
|
|
||||||
|
if (await _userManager.IsLockedOutAsync(user))
|
||||||
|
return Unauthorized("Usuário temporariamente bloqueado por tentativas inválidas. Tente novamente em alguns minutos.");
|
||||||
|
|
||||||
var valid = await _userManager.CheckPasswordAsync(user, password);
|
var valid = await _userManager.CheckPasswordAsync(user, password);
|
||||||
if (!valid)
|
if (!valid)
|
||||||
|
{
|
||||||
|
if (user.LockoutEnabled)
|
||||||
|
{
|
||||||
|
await _userManager.AccessFailedAsync(user);
|
||||||
|
}
|
||||||
|
|
||||||
return Unauthorized("Credenciais inválidas.");
|
return Unauthorized("Credenciais inválidas.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user.LockoutEnabled)
|
||||||
|
{
|
||||||
|
await _userManager.ResetAccessFailedCountAsync(user);
|
||||||
|
}
|
||||||
|
|
||||||
var effectiveTenantId = await EnsureValidTenantIdAsync(user);
|
var effectiveTenantId = await EnsureValidTenantIdAsync(user);
|
||||||
if (!effectiveTenantId.HasValue)
|
if (!effectiveTenantId.HasValue)
|
||||||
|
|
|
||||||
|
|
@ -93,6 +93,7 @@ namespace line_gestao_api.Controllers
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
|
[Authorize(Roles = "admin,gestor")]
|
||||||
public async Task<ActionResult<ChipVirgemDetailDto>> Create([FromBody] CreateChipVirgemDto req)
|
public async Task<ActionResult<ChipVirgemDetailDto>> Create([FromBody] CreateChipVirgemDto req)
|
||||||
{
|
{
|
||||||
var now = DateTime.UtcNow;
|
var now = DateTime.UtcNow;
|
||||||
|
|
|
||||||
|
|
@ -138,6 +138,7 @@ namespace line_gestao_api.Controllers
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
|
[Authorize(Roles = "admin,gestor")]
|
||||||
public async Task<ActionResult<ControleRecebidoDetailDto>> Create([FromBody] CreateControleRecebidoDto req)
|
public async Task<ActionResult<ControleRecebidoDetailDto>> Create([FromBody] CreateControleRecebidoDto req)
|
||||||
{
|
{
|
||||||
var now = DateTime.UtcNow;
|
var now = DateTime.UtcNow;
|
||||||
|
|
|
||||||
|
|
@ -686,6 +686,7 @@ namespace line_gestao_api.Controllers
|
||||||
// ✅ 5. CREATE
|
// ✅ 5. CREATE
|
||||||
// ==========================================================
|
// ==========================================================
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
|
[Authorize(Roles = "admin,gestor")]
|
||||||
public async Task<ActionResult<MobileLineDetailDto>> Create([FromBody] CreateMobileLineDto req)
|
public async Task<ActionResult<MobileLineDetailDto>> Create([FromBody] CreateMobileLineDto req)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(req.Cliente))
|
if (string.IsNullOrWhiteSpace(req.Cliente))
|
||||||
|
|
@ -788,6 +789,7 @@ namespace line_gestao_api.Controllers
|
||||||
// ✅ 6. UPDATE
|
// ✅ 6. UPDATE
|
||||||
// ==========================================================
|
// ==========================================================
|
||||||
[HttpPut("{id:guid}")]
|
[HttpPut("{id:guid}")]
|
||||||
|
[Authorize(Roles = "admin,gestor")]
|
||||||
public async Task<IActionResult> Update(Guid id, [FromBody] UpdateMobileLineRequest req)
|
public async Task<IActionResult> Update(Guid id, [FromBody] UpdateMobileLineRequest req)
|
||||||
{
|
{
|
||||||
var x = await _db.MobileLines.FirstOrDefaultAsync(a => a.Id == id);
|
var x = await _db.MobileLines.FirstOrDefaultAsync(a => a.Id == id);
|
||||||
|
|
@ -3649,19 +3651,124 @@ namespace line_gestao_api.Controllers
|
||||||
return new DateTime(dt.Year, dt.Month, dt.Day, 12, 0, 0, DateTimeKind.Utc);
|
return new DateTime(dt.Year, dt.Month, dt.Day, 12, 0, 0, DateTimeKind.Utc);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static decimal? TryDecimal(string? s)
|
private static decimal? TryDecimal(string? input)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(s)) return null;
|
if (string.IsNullOrWhiteSpace(input)) return null;
|
||||||
|
|
||||||
s = s.Replace("R$", "", StringComparison.OrdinalIgnoreCase).Trim();
|
var normalized = NormalizeDecimalInput(input);
|
||||||
|
if (string.IsNullOrWhiteSpace(normalized)) return null;
|
||||||
|
|
||||||
if (decimal.TryParse(s, NumberStyles.Any, new CultureInfo("pt-BR"), out var d)) return d;
|
return decimal.TryParse(normalized, NumberStyles.Float, CultureInfo.InvariantCulture, out var value)
|
||||||
if (decimal.TryParse(s, NumberStyles.Any, CultureInfo.InvariantCulture, out d)) return d;
|
? value
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
var s2 = s.Replace(".", "").Replace(",", ".");
|
private static string NormalizeDecimalInput(string raw)
|
||||||
if (decimal.TryParse(s2, NumberStyles.Any, CultureInfo.InvariantCulture, out d)) return d;
|
{
|
||||||
|
var s = raw
|
||||||
|
.Replace("R$", "", StringComparison.OrdinalIgnoreCase)
|
||||||
|
.Replace("%", "", StringComparison.OrdinalIgnoreCase)
|
||||||
|
.Replace(" ", "")
|
||||||
|
.Replace("\u00A0", "")
|
||||||
|
.Trim();
|
||||||
|
|
||||||
return null;
|
if (string.IsNullOrWhiteSpace(s)) return string.Empty;
|
||||||
|
|
||||||
|
var negativeByParentheses = s.StartsWith("(") && s.EndsWith(")");
|
||||||
|
if (negativeByParentheses && s.Length >= 2)
|
||||||
|
{
|
||||||
|
s = s[1..^1];
|
||||||
|
}
|
||||||
|
|
||||||
|
var allowed = new StringBuilder(s.Length);
|
||||||
|
foreach (var ch in s)
|
||||||
|
{
|
||||||
|
if (char.IsDigit(ch) || ch == '.' || ch == ',' || ch == '-' || ch == '+' || ch == 'e' || ch == 'E')
|
||||||
|
{
|
||||||
|
allowed.Append(ch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s = allowed.ToString();
|
||||||
|
if (string.IsNullOrWhiteSpace(s)) return string.Empty;
|
||||||
|
|
||||||
|
string normalized;
|
||||||
|
var commaCount = s.Count(c => c == ',');
|
||||||
|
var dotCount = s.Count(c => c == '.');
|
||||||
|
|
||||||
|
if (s.IndexOf('e') >= 0 || s.IndexOf('E') >= 0)
|
||||||
|
{
|
||||||
|
normalized = s.Replace(",", ".");
|
||||||
|
}
|
||||||
|
else if (commaCount > 0 && dotCount > 0)
|
||||||
|
{
|
||||||
|
var lastComma = s.LastIndexOf(',');
|
||||||
|
var lastDot = s.LastIndexOf('.');
|
||||||
|
|
||||||
|
if (lastComma > lastDot)
|
||||||
|
{
|
||||||
|
// Ex.: 1.234,56 -> 1234.56
|
||||||
|
normalized = s.Replace(".", "").Replace(",", ".");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Ex.: 1,234.56 -> 1234.56
|
||||||
|
normalized = s.Replace(",", "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (commaCount > 0)
|
||||||
|
{
|
||||||
|
normalized = NormalizeSingleSeparatorNumber(s, ',');
|
||||||
|
}
|
||||||
|
else if (dotCount > 0)
|
||||||
|
{
|
||||||
|
normalized = NormalizeSingleSeparatorNumber(s, '.');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
normalized = s;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (negativeByParentheses && !normalized.StartsWith("-"))
|
||||||
|
{
|
||||||
|
normalized = "-" + normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string NormalizeSingleSeparatorNumber(string value, char separator)
|
||||||
|
{
|
||||||
|
var separatorCount = value.Count(c => c == separator);
|
||||||
|
if (separatorCount <= 0) return value;
|
||||||
|
|
||||||
|
if (separatorCount == 1)
|
||||||
|
{
|
||||||
|
var idx = value.IndexOf(separator);
|
||||||
|
var leftDigits = value[..idx].Count(char.IsDigit);
|
||||||
|
var rightDigits = value[(idx + 1)..].Count(char.IsDigit);
|
||||||
|
|
||||||
|
// "1.234"/"1,234" normalmente representa milhar.
|
||||||
|
var looksLikeThousandsSeparator = rightDigits == 3 && leftDigits > 0 && leftDigits <= 3;
|
||||||
|
if (looksLikeThousandsSeparator)
|
||||||
|
{
|
||||||
|
return value.Replace(separator.ToString(), "");
|
||||||
|
}
|
||||||
|
|
||||||
|
return separator == ',' ? value.Replace(",", ".") : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
var parts = value.Split(separator);
|
||||||
|
var allGroupsAreThousands = parts.Skip(1).All(p => p.Length == 3 && p.All(char.IsDigit));
|
||||||
|
if (allGroupsAreThousands)
|
||||||
|
{
|
||||||
|
return value.Replace(separator.ToString(), "");
|
||||||
|
}
|
||||||
|
|
||||||
|
var last = value.LastIndexOf(separator);
|
||||||
|
var whole = value[..last].Replace(separator.ToString(), "");
|
||||||
|
var fraction = value[(last + 1)..];
|
||||||
|
return $"{whole}.{fraction}";
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int TryInt(string s)
|
private static int TryInt(string s)
|
||||||
|
|
|
||||||
|
|
@ -187,6 +187,7 @@ namespace line_gestao_api.Controllers
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
|
[Authorize(Roles = "admin,gestor")]
|
||||||
public async Task<ActionResult<MuregDetailDto>> Create([FromBody] CreateMuregDto req)
|
public async Task<ActionResult<MuregDetailDto>> Create([FromBody] CreateMuregDto req)
|
||||||
{
|
{
|
||||||
if (req.MobileLineId == Guid.Empty)
|
if (req.MobileLineId == Guid.Empty)
|
||||||
|
|
@ -288,6 +289,7 @@ namespace line_gestao_api.Controllers
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPut("{id:guid}")]
|
[HttpPut("{id:guid}")]
|
||||||
|
[Authorize(Roles = "admin,gestor")]
|
||||||
public async Task<IActionResult> Update(Guid id, [FromBody] UpdateMuregDto req)
|
public async Task<IActionResult> Update(Guid id, [FromBody] UpdateMuregDto req)
|
||||||
{
|
{
|
||||||
var entity = await _db.MuregLines.FirstOrDefaultAsync(x => x.Id == id);
|
var entity = await _db.MuregLines.FirstOrDefaultAsync(x => x.Id == id);
|
||||||
|
|
@ -375,6 +377,7 @@ namespace line_gestao_api.Controllers
|
||||||
// ✅ POST: /api/mureg/import-excel (mantido)
|
// ✅ POST: /api/mureg/import-excel (mantido)
|
||||||
// ==========================================================
|
// ==========================================================
|
||||||
[HttpPost("import-excel")]
|
[HttpPost("import-excel")]
|
||||||
|
[Authorize(Roles = "admin,gestor")]
|
||||||
[Consumes("multipart/form-data")]
|
[Consumes("multipart/form-data")]
|
||||||
[RequestSizeLimit(50_000_000)]
|
[RequestSizeLimit(50_000_000)]
|
||||||
public async Task<IActionResult> ImportExcel([FromForm] ImportExcelForm form)
|
public async Task<IActionResult> ImportExcel([FromForm] ImportExcelForm form)
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,12 @@ public class NotificationsController : ControllerBase
|
||||||
})
|
})
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
|
var todayUtc = DateTime.SpecifyKind(DateTime.UtcNow.Date, DateTimeKind.Utc);
|
||||||
|
foreach (var item in items)
|
||||||
|
{
|
||||||
|
ApplyEffectiveType(item, todayUtc);
|
||||||
|
}
|
||||||
|
|
||||||
return Ok(items);
|
return Ok(items);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -160,6 +166,11 @@ public class NotificationsController : ControllerBase
|
||||||
notification.Tipo))
|
notification.Tipo))
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
|
var todayUtc = DateTime.SpecifyKind(DateTime.UtcNow.Date, DateTimeKind.Utc);
|
||||||
|
rows = rows
|
||||||
|
.Select(r => r with { Tipo = ResolveEffectiveType(r.Tipo, r.DataReferencia, todayUtc) })
|
||||||
|
.ToList();
|
||||||
|
|
||||||
using var workbook = new XLWorkbook();
|
using var workbook = new XLWorkbook();
|
||||||
var worksheet = workbook.Worksheets.Add("Notificacoes");
|
var worksheet = workbook.Worksheets.Add("Notificacoes");
|
||||||
|
|
||||||
|
|
@ -291,6 +302,38 @@ public class NotificationsController : ControllerBase
|
||||||
return filter?.Trim().ToLowerInvariant();
|
return filter?.Trim().ToLowerInvariant();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void ApplyEffectiveType(NotificationDto item, DateTime todayUtc)
|
||||||
|
{
|
||||||
|
item.Tipo = ResolveEffectiveType(item.Tipo, item.DtTerminoFidelizacao ?? item.ReferenciaData, todayUtc);
|
||||||
|
var effectiveDate = item.DtTerminoFidelizacao ?? item.ReferenciaData;
|
||||||
|
if (!effectiveDate.HasValue)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var endDateUtc = DateTime.SpecifyKind(effectiveDate.Value.Date, DateTimeKind.Utc);
|
||||||
|
var daysUntil = (endDateUtc - todayUtc).Days;
|
||||||
|
item.DiasParaVencer = daysUntil < 0 ? 0 : daysUntil;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ResolveEffectiveType(string currentType, DateTime? referenceDate, DateTime todayUtc)
|
||||||
|
{
|
||||||
|
if (!referenceDate.HasValue)
|
||||||
|
{
|
||||||
|
return currentType;
|
||||||
|
}
|
||||||
|
|
||||||
|
var isKnownType = currentType.Equals("AVencer", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| currentType.Equals("Vencido", StringComparison.OrdinalIgnoreCase);
|
||||||
|
if (!isKnownType)
|
||||||
|
{
|
||||||
|
return currentType;
|
||||||
|
}
|
||||||
|
|
||||||
|
var endDateUtc = DateTime.SpecifyKind(referenceDate.Value.Date, DateTimeKind.Utc);
|
||||||
|
return endDateUtc < todayUtc ? "Vencido" : "AVencer";
|
||||||
|
}
|
||||||
|
|
||||||
private sealed record NotificationExportRow(
|
private sealed record NotificationExportRow(
|
||||||
string? Conta,
|
string? Conta,
|
||||||
string? Linha,
|
string? Linha,
|
||||||
|
|
|
||||||
|
|
@ -165,6 +165,7 @@ public class ParcelamentosController : ControllerBase
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
|
[Authorize(Roles = "admin,gestor")]
|
||||||
public async Task<ActionResult<ParcelamentoDetailDto>> Create([FromBody] ParcelamentoUpsertDto req)
|
public async Task<ActionResult<ParcelamentoDetailDto>> Create([FromBody] ParcelamentoUpsertDto req)
|
||||||
{
|
{
|
||||||
var now = DateTime.UtcNow;
|
var now = DateTime.UtcNow;
|
||||||
|
|
@ -201,6 +202,7 @@ public class ParcelamentosController : ControllerBase
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPut("{id:guid}")]
|
[HttpPut("{id:guid}")]
|
||||||
|
[Authorize(Roles = "admin,gestor")]
|
||||||
public async Task<IActionResult> Update(Guid id, [FromBody] ParcelamentoUpsertDto req)
|
public async Task<IActionResult> Update(Guid id, [FromBody] ParcelamentoUpsertDto req)
|
||||||
{
|
{
|
||||||
var entity = await _db.ParcelamentoLines
|
var entity = await _db.ParcelamentoLines
|
||||||
|
|
|
||||||
|
|
@ -112,6 +112,7 @@ namespace line_gestao_api.Controllers
|
||||||
// ✅ CREATE
|
// ✅ CREATE
|
||||||
// ==========================================================
|
// ==========================================================
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
|
[Authorize(Roles = "admin,gestor")]
|
||||||
public async Task<ActionResult<TrocaNumeroDetailDto>> Create([FromBody] CreateTrocaNumeroDto req)
|
public async Task<ActionResult<TrocaNumeroDetailDto>> Create([FromBody] CreateTrocaNumeroDto req)
|
||||||
{
|
{
|
||||||
var now = DateTime.UtcNow;
|
var now = DateTime.UtcNow;
|
||||||
|
|
@ -140,6 +141,7 @@ namespace line_gestao_api.Controllers
|
||||||
// ✅ UPDATE
|
// ✅ UPDATE
|
||||||
// ==========================================================
|
// ==========================================================
|
||||||
[HttpPut("{id:guid}")]
|
[HttpPut("{id:guid}")]
|
||||||
|
[Authorize(Roles = "admin,gestor")]
|
||||||
public async Task<IActionResult> Update(Guid id, [FromBody] UpdateTrocaNumeroRequest req)
|
public async Task<IActionResult> Update(Guid id, [FromBody] UpdateTrocaNumeroRequest req)
|
||||||
{
|
{
|
||||||
var x = await _db.TrocaNumeroLines.FirstOrDefaultAsync(a => a.Id == id);
|
var x = await _db.TrocaNumeroLines.FirstOrDefaultAsync(a => a.Id == id);
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,12 @@ namespace line_gestao_api.Data;
|
||||||
|
|
||||||
public class SeedOptions
|
public class SeedOptions
|
||||||
{
|
{
|
||||||
|
public bool Enabled { get; set; } = true;
|
||||||
public string DefaultTenantName { get; set; } = "Default";
|
public string DefaultTenantName { get; set; } = "Default";
|
||||||
public string AdminName { get; set; } = "Administrador";
|
public string AdminName { get; set; } = "Administrador";
|
||||||
public string AdminEmail { get; set; } = "admin@linegestao.local";
|
public string AdminEmail { get; set; } = "admin@linegestao.local";
|
||||||
public string AdminPassword { get; set; } = "Admin123!";
|
public string AdminPassword { get; set; } = "DevAdmin123!";
|
||||||
|
public bool ReapplyAdminCredentialsOnStartup { get; set; } = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class SeedData
|
public static class SeedData
|
||||||
|
|
@ -29,6 +31,11 @@ public static class SeedData
|
||||||
|
|
||||||
await db.Database.MigrateAsync();
|
await db.Database.MigrateAsync();
|
||||||
|
|
||||||
|
if (!options.Enabled)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var roles = new[] { "admin", "gestor", "operador", "leitura" };
|
var roles = new[] { "admin", "gestor", "operador", "leitura" };
|
||||||
foreach (var role in roles)
|
foreach (var role in roles)
|
||||||
{
|
{
|
||||||
|
|
@ -69,7 +76,8 @@ public static class SeedData
|
||||||
Name = options.AdminName,
|
Name = options.AdminName,
|
||||||
TenantId = tenant.Id,
|
TenantId = tenant.Id,
|
||||||
EmailConfirmed = true,
|
EmailConfirmed = true,
|
||||||
IsActive = true
|
IsActive = true,
|
||||||
|
LockoutEnabled = true
|
||||||
};
|
};
|
||||||
|
|
||||||
var createResult = await userManager.CreateAsync(adminUser, options.AdminPassword);
|
var createResult = await userManager.CreateAsync(adminUser, options.AdminPassword);
|
||||||
|
|
@ -78,21 +86,28 @@ public static class SeedData
|
||||||
await userManager.AddToRoleAsync(adminUser, "admin");
|
await userManager.AddToRoleAsync(adminUser, "admin");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else if (options.ReapplyAdminCredentialsOnStartup)
|
||||||
{
|
{
|
||||||
existingAdmin.UserName = options.AdminEmail;
|
|
||||||
existingAdmin.Email = options.AdminEmail;
|
|
||||||
existingAdmin.Name = options.AdminName;
|
existingAdmin.Name = options.AdminName;
|
||||||
existingAdmin.TenantId = tenant.Id;
|
existingAdmin.Email = options.AdminEmail;
|
||||||
|
existingAdmin.UserName = options.AdminEmail;
|
||||||
existingAdmin.EmailConfirmed = true;
|
existingAdmin.EmailConfirmed = true;
|
||||||
existingAdmin.IsActive = true;
|
existingAdmin.IsActive = true;
|
||||||
|
existingAdmin.LockoutEnabled = true;
|
||||||
|
|
||||||
|
await userManager.SetLockoutEndDateAsync(existingAdmin, null);
|
||||||
|
await userManager.ResetAccessFailedCountAsync(existingAdmin);
|
||||||
await userManager.UpdateAsync(existingAdmin);
|
await userManager.UpdateAsync(existingAdmin);
|
||||||
|
|
||||||
if (!await userManager.CheckPasswordAsync(existingAdmin, options.AdminPassword))
|
|
||||||
{
|
|
||||||
var resetToken = await userManager.GeneratePasswordResetTokenAsync(existingAdmin);
|
var resetToken = await userManager.GeneratePasswordResetTokenAsync(existingAdmin);
|
||||||
await userManager.ResetPasswordAsync(existingAdmin, resetToken, options.AdminPassword);
|
var resetPasswordResult = await userManager.ResetPasswordAsync(existingAdmin, resetToken, options.AdminPassword);
|
||||||
|
if (!resetPasswordResult.Succeeded)
|
||||||
|
{
|
||||||
|
var removePasswordResult = await userManager.RemovePasswordAsync(existingAdmin);
|
||||||
|
if (removePasswordResult.Succeeded)
|
||||||
|
{
|
||||||
|
await userManager.AddPasswordAsync(existingAdmin, options.AdminPassword);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!await userManager.IsInRoleAsync(existingAdmin, "admin"))
|
if (!await userManager.IsInRoleAsync(existingAdmin, "admin"))
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,15 @@ namespace line_gestao_api.Dtos
|
||||||
public GeralDashboardVivoKpiDto Vivo { get; set; } = new();
|
public GeralDashboardVivoKpiDto Vivo { get; set; } = new();
|
||||||
public GeralDashboardTravelKpiDto TravelMundo { get; set; } = new();
|
public GeralDashboardTravelKpiDto TravelMundo { get; set; } = new();
|
||||||
public GeralDashboardAdditionalKpiDto Adicionais { get; set; } = new();
|
public GeralDashboardAdditionalKpiDto Adicionais { get; set; } = new();
|
||||||
|
public List<GeralDashboardLineTotalDto> TotaisLine { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GeralDashboardLineTotalDto
|
||||||
|
{
|
||||||
|
public string Tipo { get; set; } = string.Empty;
|
||||||
|
public int QtdLinhas { get; set; }
|
||||||
|
public decimal ValorTotalLine { get; set; }
|
||||||
|
public decimal LucroTotalLine { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class GeralDashboardVivoKpiDto
|
public class GeralDashboardVivoKpiDto
|
||||||
|
|
|
||||||
72
Program.cs
72
Program.cs
|
|
@ -1,17 +1,29 @@
|
||||||
using System.Text;
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.RateLimiting;
|
||||||
using line_gestao_api.Data;
|
using line_gestao_api.Data;
|
||||||
using line_gestao_api.Models;
|
using line_gestao_api.Models;
|
||||||
using line_gestao_api.Services;
|
using line_gestao_api.Services;
|
||||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||||
|
using Microsoft.AspNetCore.DataProtection;
|
||||||
using Microsoft.AspNetCore.Http.Features;
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
using Microsoft.AspNetCore.HttpOverrides;
|
using Microsoft.AspNetCore.HttpOverrides;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
using Microsoft.AspNetCore.RateLimiting;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.IdentityModel.Tokens;
|
using Microsoft.IdentityModel.Tokens;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
builder.Configuration.AddJsonFile("appsettings.Local.json", optional: true, reloadOnChange: true);
|
||||||
|
|
||||||
|
var dataProtectionKeyPath = builder.Environment.IsProduction()
|
||||||
|
? "/var/www/html/line-gestao-api/publish/.aspnet-keys"
|
||||||
|
: Path.Combine(builder.Environment.ContentRootPath, ".aspnet-keys");
|
||||||
|
|
||||||
builder.Services.AddControllers();
|
builder.Services.AddControllers();
|
||||||
|
builder.Services.AddDataProtection()
|
||||||
|
.PersistKeysToFileSystem(new DirectoryInfo(dataProtectionKeyPath))
|
||||||
|
.SetApplicationName("line-gestao-api");
|
||||||
builder.Services.Configure<ForwardedHeadersOptions>(options =>
|
builder.Services.Configure<ForwardedHeadersOptions>(options =>
|
||||||
{
|
{
|
||||||
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
|
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
|
||||||
|
|
@ -24,7 +36,7 @@ builder.Services.Configure<FormOptions>(o =>
|
||||||
o.MultipartBodyLengthLimit = 50_000_000;
|
o.MultipartBodyLengthLimit = 50_000_000;
|
||||||
});
|
});
|
||||||
|
|
||||||
var corsOrigins = builder.Configuration
|
var configuredCorsOrigins = builder.Configuration
|
||||||
.GetSection("Cors:AllowedOrigins")
|
.GetSection("Cors:AllowedOrigins")
|
||||||
.Get<string[]>()?
|
.Get<string[]>()?
|
||||||
.Where(o => !string.IsNullOrWhiteSpace(o))
|
.Where(o => !string.IsNullOrWhiteSpace(o))
|
||||||
|
|
@ -33,18 +45,43 @@ var corsOrigins = builder.Configuration
|
||||||
.ToArray()
|
.ToArray()
|
||||||
?? [];
|
?? [];
|
||||||
|
|
||||||
if (corsOrigins.Length == 0)
|
var allowAnyCorsOrigin = configuredCorsOrigins.Any(o => o == "*");
|
||||||
|
var corsOrigins = configuredCorsOrigins
|
||||||
|
.Where(o => o != "*")
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
var isProduction = builder.Environment.IsProduction();
|
||||||
|
if (isProduction && allowAnyCorsOrigin)
|
||||||
{
|
{
|
||||||
|
throw new InvalidOperationException("CORS with wildcard '*' is not allowed in production. Configure explicit origins in 'Cors:AllowedOrigins'.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!allowAnyCorsOrigin && corsOrigins.Length == 0)
|
||||||
|
{
|
||||||
|
if (isProduction)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("No CORS origins configured for production. Set 'Cors:AllowedOrigins' with explicit trusted origins.");
|
||||||
|
}
|
||||||
|
|
||||||
corsOrigins = ["http://localhost:4200"];
|
corsOrigins = ["http://localhost:4200"];
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.Services.AddCors(options =>
|
builder.Services.AddCors(options =>
|
||||||
{
|
{
|
||||||
options.AddPolicy("Front", p =>
|
options.AddPolicy("Front", p =>
|
||||||
|
{
|
||||||
|
if (allowAnyCorsOrigin)
|
||||||
|
{
|
||||||
|
p.AllowAnyOrigin()
|
||||||
|
.AllowAnyHeader()
|
||||||
|
.AllowAnyMethod();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
p.WithOrigins(corsOrigins)
|
p.WithOrigins(corsOrigins)
|
||||||
.AllowAnyHeader()
|
.AllowAnyHeader()
|
||||||
.AllowAnyMethod()
|
.AllowAnyMethod();
|
||||||
);
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
builder.Services.AddDbContext<AppDbContext>(options =>
|
builder.Services.AddDbContext<AppDbContext>(options =>
|
||||||
|
|
@ -63,6 +100,9 @@ builder.Services.AddIdentityCore<ApplicationUser>(options =>
|
||||||
{
|
{
|
||||||
options.Password.RequiredLength = 6;
|
options.Password.RequiredLength = 6;
|
||||||
options.User.RequireUniqueEmail = false;
|
options.User.RequireUniqueEmail = false;
|
||||||
|
options.Lockout.AllowedForNewUsers = true;
|
||||||
|
options.Lockout.MaxFailedAccessAttempts = 5;
|
||||||
|
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(15);
|
||||||
})
|
})
|
||||||
.AddRoles<IdentityRole<Guid>>()
|
.AddRoles<IdentityRole<Guid>>()
|
||||||
.AddEntityFrameworkStores<AppDbContext>()
|
.AddEntityFrameworkStores<AppDbContext>()
|
||||||
|
|
@ -77,6 +117,12 @@ if (string.IsNullOrWhiteSpace(jwtKey))
|
||||||
throw new InvalidOperationException("Configuration 'Jwt:Key' is required.");
|
throw new InvalidOperationException("Configuration 'Jwt:Key' is required.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var jwtKeyBytes = Encoding.UTF8.GetByteCount(jwtKey);
|
||||||
|
if (jwtKeyBytes < 16)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Configuration 'Jwt:Key' must be at least 16 bytes (128 bits). Current length: {jwtKeyBytes} bytes.");
|
||||||
|
}
|
||||||
|
|
||||||
var issuer = builder.Configuration["Jwt:Issuer"];
|
var issuer = builder.Configuration["Jwt:Issuer"];
|
||||||
var audience = builder.Configuration["Jwt:Audience"];
|
var audience = builder.Configuration["Jwt:Audience"];
|
||||||
|
|
||||||
|
|
@ -97,6 +143,21 @@ builder.Services
|
||||||
});
|
});
|
||||||
|
|
||||||
builder.Services.AddAuthorization();
|
builder.Services.AddAuthorization();
|
||||||
|
builder.Services.AddRateLimiter(options =>
|
||||||
|
{
|
||||||
|
options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
|
||||||
|
options.AddPolicy("auth-login", httpContext =>
|
||||||
|
RateLimitPartition.GetFixedWindowLimiter(
|
||||||
|
partitionKey: httpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown",
|
||||||
|
factory: _ => new FixedWindowRateLimiterOptions
|
||||||
|
{
|
||||||
|
PermitLimit = 8,
|
||||||
|
Window = TimeSpan.FromMinutes(1),
|
||||||
|
QueueLimit = 0,
|
||||||
|
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
|
||||||
|
AutoReplenishment = true
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
builder.Services.Configure<NotificationOptions>(builder.Configuration.GetSection("Notifications"));
|
builder.Services.Configure<NotificationOptions>(builder.Configuration.GetSection("Notifications"));
|
||||||
builder.Services.AddHostedService<VigenciaNotificationBackgroundService>();
|
builder.Services.AddHostedService<VigenciaNotificationBackgroundService>();
|
||||||
|
|
@ -119,6 +180,7 @@ if (useHttpsRedirection)
|
||||||
}
|
}
|
||||||
|
|
||||||
app.UseCors("Front");
|
app.UseCors("Front");
|
||||||
|
app.UseRateLimiter();
|
||||||
|
|
||||||
app.UseAuthentication();
|
app.UseAuthentication();
|
||||||
app.UseMiddleware<TenantMiddleware>();
|
app.UseMiddleware<TenantMiddleware>();
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,32 @@ namespace line_gestao_api.Services
|
||||||
{
|
{
|
||||||
TotalLinhas = g.Count(),
|
TotalLinhas = g.Count(),
|
||||||
TotalAtivas = g.Count(x => (x.Status ?? "").ToLower().Contains("ativo")),
|
TotalAtivas = g.Count(x => (x.Status ?? "").ToLower().Contains("ativo")),
|
||||||
|
PfAtivas = g.Count(x =>
|
||||||
|
(x.Status ?? "").ToLower().Contains("ativo") &&
|
||||||
|
((x.Skil ?? "").ToLower().Contains("fís") || (x.Skil ?? "").ToLower().Contains("fis") || (x.Skil ?? "").ToLower().Contains("pf"))),
|
||||||
|
PjAtivas = g.Count(x =>
|
||||||
|
(x.Status ?? "").ToLower().Contains("ativo") &&
|
||||||
|
((x.Skil ?? "").ToLower().Contains("jur") || (x.Skil ?? "").ToLower().Contains("pj"))),
|
||||||
|
PfValorTotalLineAtivas = g.Sum(x =>
|
||||||
|
(x.Status ?? "").ToLower().Contains("ativo") &&
|
||||||
|
((x.Skil ?? "").ToLower().Contains("fís") || (x.Skil ?? "").ToLower().Contains("fis") || (x.Skil ?? "").ToLower().Contains("pf"))
|
||||||
|
? (x.ValorContratoLine ?? 0m)
|
||||||
|
: 0m),
|
||||||
|
PjValorTotalLineAtivas = g.Sum(x =>
|
||||||
|
(x.Status ?? "").ToLower().Contains("ativo") &&
|
||||||
|
((x.Skil ?? "").ToLower().Contains("jur") || (x.Skil ?? "").ToLower().Contains("pj"))
|
||||||
|
? (x.ValorContratoLine ?? 0m)
|
||||||
|
: 0m),
|
||||||
|
PfLucroTotalLineAtivas = g.Sum(x =>
|
||||||
|
(x.Status ?? "").ToLower().Contains("ativo") &&
|
||||||
|
((x.Skil ?? "").ToLower().Contains("fís") || (x.Skil ?? "").ToLower().Contains("fis") || (x.Skil ?? "").ToLower().Contains("pf"))
|
||||||
|
? (x.Lucro ?? 0m)
|
||||||
|
: 0m),
|
||||||
|
PjLucroTotalLineAtivas = g.Sum(x =>
|
||||||
|
(x.Status ?? "").ToLower().Contains("ativo") &&
|
||||||
|
((x.Skil ?? "").ToLower().Contains("jur") || (x.Skil ?? "").ToLower().Contains("pj"))
|
||||||
|
? (x.Lucro ?? 0m)
|
||||||
|
: 0m),
|
||||||
TotalBloqueados = g.Count(x =>
|
TotalBloqueados = g.Count(x =>
|
||||||
(x.Status ?? "").ToLower().Contains("bloque") ||
|
(x.Status ?? "").ToLower().Contains("bloque") ||
|
||||||
(x.Status ?? "").ToLower().Contains("perda") ||
|
(x.Status ?? "").ToLower().Contains("perda") ||
|
||||||
|
|
@ -662,7 +688,8 @@ namespace line_gestao_api.Services
|
||||||
new() { ServiceName = ServiceVivoSync, CountLines = totals.NotPaidSync, TotalValue = 0m },
|
new() { ServiceName = ServiceVivoSync, CountLines = totals.NotPaidSync, TotalValue = 0m },
|
||||||
new() { ServiceName = ServiceVivoGestaoDispositivo, CountLines = totals.NotPaidGestaoDispositivo, TotalValue = 0m }
|
new() { ServiceName = ServiceVivoGestaoDispositivo, CountLines = totals.NotPaidGestaoDispositivo, TotalValue = 0m }
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
TotaisLine = BuildTotaisLineRows(totals)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -730,6 +757,43 @@ namespace line_gestao_api.Services
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static List<GeralDashboardLineTotalDto> BuildTotaisLineRows(TotalsProjection? totals)
|
||||||
|
{
|
||||||
|
if (totals == null)
|
||||||
|
{
|
||||||
|
return new List<GeralDashboardLineTotalDto>();
|
||||||
|
}
|
||||||
|
|
||||||
|
var diffQtd = totals.PjAtivas - totals.PfAtivas;
|
||||||
|
var diffValor = totals.PjValorTotalLineAtivas - totals.PfValorTotalLineAtivas;
|
||||||
|
var diffLucro = totals.PjLucroTotalLineAtivas - totals.PfLucroTotalLineAtivas;
|
||||||
|
|
||||||
|
return new List<GeralDashboardLineTotalDto>
|
||||||
|
{
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Tipo = "PESSOA FISICA",
|
||||||
|
QtdLinhas = totals.PfAtivas,
|
||||||
|
ValorTotalLine = totals.PfValorTotalLineAtivas,
|
||||||
|
LucroTotalLine = totals.PfLucroTotalLineAtivas
|
||||||
|
},
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Tipo = "PESSOA JURIDICA",
|
||||||
|
QtdLinhas = totals.PjAtivas,
|
||||||
|
ValorTotalLine = totals.PjValorTotalLineAtivas,
|
||||||
|
LucroTotalLine = totals.PjLucroTotalLineAtivas
|
||||||
|
},
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Tipo = "DIFERENCA PJ X PF",
|
||||||
|
QtdLinhas = diffQtd,
|
||||||
|
ValorTotalLine = diffValor,
|
||||||
|
LucroTotalLine = diffLucro
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private static List<GeralDashboardClientGroupDto> BuildClientGroups(IEnumerable<ClientGroupProjection> rows)
|
private static List<GeralDashboardClientGroupDto> BuildClientGroups(IEnumerable<ClientGroupProjection> rows)
|
||||||
{
|
{
|
||||||
var list = new List<GeralDashboardClientGroupDto>();
|
var list = new List<GeralDashboardClientGroupDto>();
|
||||||
|
|
@ -1037,6 +1101,12 @@ namespace line_gestao_api.Services
|
||||||
{
|
{
|
||||||
public int TotalLinhas { get; set; }
|
public int TotalLinhas { get; set; }
|
||||||
public int TotalAtivas { get; set; }
|
public int TotalAtivas { get; set; }
|
||||||
|
public int PfAtivas { get; set; }
|
||||||
|
public int PjAtivas { get; set; }
|
||||||
|
public decimal PfValorTotalLineAtivas { get; set; }
|
||||||
|
public decimal PjValorTotalLineAtivas { get; set; }
|
||||||
|
public decimal PfLucroTotalLineAtivas { get; set; }
|
||||||
|
public decimal PjLucroTotalLineAtivas { get; set; }
|
||||||
public int TotalBloqueados { get; set; }
|
public int TotalBloqueados { get; set; }
|
||||||
public int VivoLinhas { get; set; }
|
public int VivoLinhas { get; set; }
|
||||||
public decimal VivoFranquiaTotalGb { get; set; }
|
public decimal VivoFranquiaTotalGb { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
{
|
{
|
||||||
"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=1231234512Ed@"
|
||||||
},
|
},
|
||||||
"Jwt": {
|
"Jwt": {
|
||||||
"Key": "dev-only-please-replace-with-env-variable-in-production",
|
"Key": "linegestao-local-jwt-key-2026-dev-strong-123456789",
|
||||||
"Issuer": "LineGestao",
|
"Issuer": "LineGestao",
|
||||||
"Audience": "LineGestao",
|
"Audience": "LineGestao",
|
||||||
"ExpiresMinutes": 360
|
"ExpiresMinutes": 360
|
||||||
|
|
@ -28,9 +28,11 @@
|
||||||
"ReminderDays": [30, 15, 7]
|
"ReminderDays": [30, 15, 7]
|
||||||
},
|
},
|
||||||
"Seed": {
|
"Seed": {
|
||||||
|
"Enabled": true,
|
||||||
|
"ReapplyAdminCredentialsOnStartup": true,
|
||||||
"DefaultTenantName": "Default",
|
"DefaultTenantName": "Default",
|
||||||
"AdminName": "Administrador",
|
"AdminName": "Administrador",
|
||||||
"AdminEmail": "admin@linegestao.local",
|
"AdminEmail": "admin@linegestao.local",
|
||||||
"AdminPassword": "Admin123!"
|
"AdminPassword": "DevAdmin123!"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"ConnectionStrings": {
|
||||||
|
"Default": "Host=localhost;Port=5432;Database=linegestao;Username=linegestao_app;Password=1231234512Ed@"
|
||||||
|
},
|
||||||
|
"Jwt": {
|
||||||
|
"Key": "YOUR_LOCAL_STRONG_JWT_KEY_MIN_32_CHARS",
|
||||||
|
"Issuer": "LineGestao",
|
||||||
|
"Audience": "LineGestao",
|
||||||
|
"ExpiresMinutes": 360
|
||||||
|
},
|
||||||
|
"Seed": {
|
||||||
|
"Enabled": true,
|
||||||
|
"ReapplyAdminCredentialsOnStartup": false,
|
||||||
|
"DefaultTenantName": "Default",
|
||||||
|
"AdminName": "Administrador",
|
||||||
|
"AdminEmail": "admin@linegestao.local",
|
||||||
|
"AdminPassword": "YOUR_LOCAL_ADMIN_SEED_PASSWORD"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,18 +1,26 @@
|
||||||
{
|
{
|
||||||
"ConnectionStrings": {
|
"ConnectionStrings": {
|
||||||
"Default": "Host=localhost;Port=5432;Database=linegestao;Username=linegestao_app;Password=CHANGE_ME"
|
"Default": "Host=localhost;Port=5432;Database=linegestao;Username=linegestao_app;Password=1231234512Ed@"
|
||||||
},
|
},
|
||||||
"Jwt": {
|
"Jwt": {
|
||||||
"Key": "",
|
"Key": "linegestao-local-jwt-key-2026-dev-strong-123456789",
|
||||||
"Issuer": "LineGestao",
|
"Issuer": "LineGestao",
|
||||||
"Audience": "LineGestao",
|
"Audience": "LineGestao",
|
||||||
"ExpiresMinutes": 360
|
"ExpiresMinutes": 360
|
||||||
},
|
},
|
||||||
"Cors": {
|
"Cors": {
|
||||||
"AllowedOrigins": [ "http://localhost:4200" ]
|
"AllowedOrigins": [
|
||||||
|
"http://localhost:4200"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"App": {
|
"App": {
|
||||||
"UseHttpsRedirection": true
|
"UseHttpsRedirection": false
|
||||||
|
},
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"Notifications": {
|
"Notifications": {
|
||||||
"CheckIntervalMinutes": 60,
|
"CheckIntervalMinutes": 60,
|
||||||
|
|
@ -20,9 +28,11 @@
|
||||||
"ReminderDays": [ 30, 15, 7 ]
|
"ReminderDays": [ 30, 15, 7 ]
|
||||||
},
|
},
|
||||||
"Seed": {
|
"Seed": {
|
||||||
|
"Enabled": true,
|
||||||
|
"ReapplyAdminCredentialsOnStartup": true,
|
||||||
"DefaultTenantName": "Default",
|
"DefaultTenantName": "Default",
|
||||||
"AdminName": "Administrador",
|
"AdminName": "Administrador",
|
||||||
"AdminEmail": "admin@linegestao.local",
|
"AdminEmail": "admin@linegestao.local",
|
||||||
"AdminPassword": "CHANGE_ME"
|
"AdminPassword": "DevAdmin123!"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,13 @@
|
||||||
<None Remove="line-gestao-api.Tests\**" />
|
<None Remove="line-gestao-api.Tests\**" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Remove="publish\**" />
|
||||||
|
<Content Remove="publish\**" />
|
||||||
|
<EmbeddedResource Remove="publish\**" />
|
||||||
|
<None Remove="publish\**" />
|
||||||
|
</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" />
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue