Compare commits
No commits in common. "965b693a19353f3fd65a4ef560805becaf24fe6f" and "68d91b81513ea5ba37fd5a34a57a1ebdbcbba2a8" have entirely different histories.
965b693a19
...
68d91b8151
@ -8,7 +8,6 @@ namespace OpenCand.API.Config
|
|||||||
public const string DefaultPolicy = "DefaultPolicy";
|
public const string DefaultPolicy = "DefaultPolicy";
|
||||||
public const string CandidatoSearchPolicy = "CandidatoSearchPolicy";
|
public const string CandidatoSearchPolicy = "CandidatoSearchPolicy";
|
||||||
public const string CpfRevealPolicy = "CpfRevealPolicy";
|
public const string CpfRevealPolicy = "CpfRevealPolicy";
|
||||||
public const string EstatisticaPolicy = "EstatisticaPolicy";
|
|
||||||
|
|
||||||
public static void ConfigureRateLimiting(this IServiceCollection services)
|
public static void ConfigureRateLimiting(this IServiceCollection services)
|
||||||
{
|
{
|
||||||
@ -51,15 +50,6 @@ namespace OpenCand.API.Config
|
|||||||
options.QueueLimit = 0; // No burst
|
options.QueueLimit = 0; // No burst
|
||||||
});
|
});
|
||||||
|
|
||||||
// CPF Reveal policy: 25 requests per minute with 10 burst
|
|
||||||
options.AddFixedWindowLimiter(policyName: EstatisticaPolicy, options =>
|
|
||||||
{
|
|
||||||
options.PermitLimit = 25;
|
|
||||||
options.Window = TimeSpan.FromMinutes(1);
|
|
||||||
options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
|
|
||||||
options.QueueLimit = 10; // No burst
|
|
||||||
});
|
|
||||||
|
|
||||||
options.OnRejected = async (context, token) =>
|
options.OnRejected = async (context, token) =>
|
||||||
{
|
{
|
||||||
context.HttpContext.Response.StatusCode = 429;
|
context.HttpContext.Response.StatusCode = 429;
|
||||||
|
@ -5,7 +5,6 @@ using OpenCand.API.Config;
|
|||||||
using OpenCand.API.Model;
|
using OpenCand.API.Model;
|
||||||
using OpenCand.API.Services;
|
using OpenCand.API.Services;
|
||||||
using OpenCand.Core.Models;
|
using OpenCand.Core.Models;
|
||||||
using OpenCand.Core.Utils;
|
|
||||||
|
|
||||||
namespace OpenCand.API.Controllers
|
namespace OpenCand.API.Controllers
|
||||||
{
|
{
|
||||||
@ -28,11 +27,7 @@ namespace OpenCand.API.Controllers
|
|||||||
throw new ArgumentException("Query parameter 'q' cannot be null/empty.", nameof(q));
|
throw new ArgumentException("Query parameter 'q' cannot be null/empty.", nameof(q));
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = await openCandService.SearchCandidatosAsync(q);
|
return await openCandService.SearchCandidatosAsync(q);
|
||||||
|
|
||||||
result.Candidatos.ForEach(c => c.Cpf = CpfMasking.MaskCpf(c.Cpf));
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("random")]
|
[HttpGet("random")]
|
||||||
@ -47,10 +42,7 @@ namespace OpenCand.API.Controllers
|
|||||||
[HttpGet("{id}")]
|
[HttpGet("{id}")]
|
||||||
public async Task<Candidato> GetCandidatoById([FromRoute] Guid id)
|
public async Task<Candidato> GetCandidatoById([FromRoute] Guid id)
|
||||||
{
|
{
|
||||||
var result = await openCandService.GetCandidatoAsync(id);
|
return await openCandService.GetCandidatoAsync(id);
|
||||||
result.Cpf = CpfMasking.MaskCpf(result.Cpf);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("{id}/bens")]
|
[HttpGet("{id}/bens")]
|
||||||
|
@ -6,7 +6,7 @@ using OpenCand.API.Services;
|
|||||||
|
|
||||||
namespace OpenCand.API.Controllers
|
namespace OpenCand.API.Controllers
|
||||||
{
|
{
|
||||||
[EnableRateLimiting(RateLimitingConfig.EstatisticaPolicy)]
|
[EnableRateLimiting(RateLimitingConfig.DefaultPolicy)]
|
||||||
public class EstatisticaController : BaseController
|
public class EstatisticaController : BaseController
|
||||||
{
|
{
|
||||||
private readonly EstatisticaService estatisticaService;
|
private readonly EstatisticaService estatisticaService;
|
||||||
|
@ -27,12 +27,6 @@ namespace OpenCand.API.Controllers
|
|||||||
{
|
{
|
||||||
return await openCandService.GetDataAvailabilityStatsAsync();
|
return await openCandService.GetDataAvailabilityStatsAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("tech")]
|
|
||||||
public async Task<DatabaseTechStats> GetDatabaseTechStats()
|
|
||||||
{
|
|
||||||
return await openCandService.GetDatabaseTechStatsAsync();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,7 +67,7 @@ namespace OpenCand.API.Repository
|
|||||||
{
|
{
|
||||||
result.Partidos = (await connection.QueryAsync<string>(@"SELECT DISTINCT sigla FROM partido ORDER BY sigla ASC;")).AsList();
|
result.Partidos = (await connection.QueryAsync<string>(@"SELECT DISTINCT sigla FROM partido ORDER BY sigla ASC;")).AsList();
|
||||||
result.SiglasUF = (await connection.QueryAsync<string>(@"SELECT DISTINCT siglauf FROM candidato_mapping ORDER BY siglauf ASC;")).AsList();
|
result.SiglasUF = (await connection.QueryAsync<string>(@"SELECT DISTINCT siglauf FROM candidato_mapping ORDER BY siglauf ASC;")).AsList();
|
||||||
result.Anos = (await connection.QueryAsync<int>(@"SELECT DISTINCT ano FROM candidato_mapping ORDER BY ano DESC;")).AsList();
|
result.Anos = (await connection.QueryAsync<int>(@"SELECT DISTINCT ano FROM candidato_mapping ORDER BY ano ASC;")).AsList();
|
||||||
result.Cargos = (await connection.QueryAsync<string>(@"SELECT DISTINCT cargo FROM candidato_mapping ORDER BY cargo ASC;")).AsList();
|
result.Cargos = (await connection.QueryAsync<string>(@"SELECT DISTINCT cargo FROM candidato_mapping ORDER BY cargo ASC;")).AsList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,63 +81,5 @@ namespace OpenCand.API.Repository
|
|||||||
|
|
||||||
return result ?? new DataAvailabilityStats();
|
return result ?? new DataAvailabilityStats();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<DatabaseTechStats> GetDatabaseTechStatsAsync()
|
|
||||||
{
|
|
||||||
string cacheKey = GenerateCacheKey("DatabaseTechStats");
|
|
||||||
|
|
||||||
var result = await GetOrSetCacheAsync(cacheKey, async () =>
|
|
||||||
{
|
|
||||||
using (var connection = new NpgsqlConnection(ConnectionString))
|
|
||||||
{
|
|
||||||
var stats = new DatabaseTechStats();
|
|
||||||
|
|
||||||
|
|
||||||
// Get table stats using reltuples for entries
|
|
||||||
var tableStats = await connection.QueryAsync<TableStats>(@"
|
|
||||||
SELECT
|
|
||||||
pt.schemaname||'.'||pt.tablename as Name,
|
|
||||||
pg_total_relation_size(pt.schemaname||'.'||pt.tablename) as TotalSize,
|
|
||||||
COALESCE(c.reltuples,0)::bigint as Entries
|
|
||||||
FROM pg_tables pt
|
|
||||||
JOIN pg_class c ON c.relname = pt.tablename AND c.relkind = 'r'
|
|
||||||
WHERE pt.schemaname = 'public'
|
|
||||||
ORDER BY pg_total_relation_size(pt.schemaname||'.'||pt.tablename) DESC;");
|
|
||||||
|
|
||||||
|
|
||||||
var tableStatsList = tableStats.ToList();
|
|
||||||
stats.Tables = tableStatsList;
|
|
||||||
stats.TotalSize = tableStatsList.Sum(t => t.TotalSize);
|
|
||||||
stats.TotalEntries = tableStatsList.Sum(t => t.Entries);
|
|
||||||
|
|
||||||
// Get materialized view stats using reltuples for entries
|
|
||||||
var materializedViewStats = await connection.QueryAsync<TableStats>(@"
|
|
||||||
SELECT
|
|
||||||
pmv.schemaname||'.'||pmv.matviewname as Name,
|
|
||||||
pg_total_relation_size(pmv.schemaname||'.'||pmv.matviewname) as TotalSize,
|
|
||||||
COALESCE(c.reltuples,0)::bigint as Entries
|
|
||||||
FROM pg_matviews pmv
|
|
||||||
JOIN pg_class c ON c.relname = pmv.matviewname AND c.relkind = 'm'
|
|
||||||
WHERE pmv.schemaname = 'public'
|
|
||||||
ORDER BY pg_total_relation_size(pmv.schemaname||'.'||pmv.matviewname) DESC;");
|
|
||||||
|
|
||||||
stats.MaterializedViews = materializedViewStats.ToList();
|
|
||||||
|
|
||||||
// Get index stats
|
|
||||||
var indexStats = await connection.QueryFirstOrDefaultAsync<IndexStats>(@"
|
|
||||||
SELECT
|
|
||||||
COUNT(*) as Amount,
|
|
||||||
SUM(pg_relation_size(indexrelid)) as Size
|
|
||||||
FROM pg_stat_user_indexes
|
|
||||||
WHERE schemaname = 'public';");
|
|
||||||
|
|
||||||
stats.Indexes = indexStats ?? new IndexStats();
|
|
||||||
|
|
||||||
return stats;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return result ?? new DatabaseTechStats();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -66,7 +66,6 @@ namespace OpenCand.API.Services
|
|||||||
|
|
||||||
await PerformPreLoad("GetOpenCandStatsAsync", estatisticaService.GetMaioresEnriquecimentos);
|
await PerformPreLoad("GetOpenCandStatsAsync", estatisticaService.GetMaioresEnriquecimentos);
|
||||||
await PerformPreLoad("GetOpenCandStatsAsync", openCandService.GetOpenCandStatsAsync);
|
await PerformPreLoad("GetOpenCandStatsAsync", openCandService.GetOpenCandStatsAsync);
|
||||||
await PerformPreLoad("GetDatabaseTechStatsAsync", openCandService.GetDatabaseTechStatsAsync);
|
|
||||||
await PerformPreLoad("GetDataAvailabilityStatsAsync", openCandService.GetDataAvailabilityStatsAsync);
|
await PerformPreLoad("GetDataAvailabilityStatsAsync", openCandService.GetDataAvailabilityStatsAsync);
|
||||||
|
|
||||||
logger.LogInformation("Single-call endpoints preload completed");
|
logger.LogInformation("Single-call endpoints preload completed");
|
||||||
|
@ -47,16 +47,6 @@ namespace OpenCand.API.Services
|
|||||||
return stats;
|
return stats;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<DatabaseTechStats> GetDatabaseTechStatsAsync()
|
|
||||||
{
|
|
||||||
var stats = await openCandRepository.GetDatabaseTechStatsAsync();
|
|
||||||
|
|
||||||
stats.Tables = stats.Tables.OrderBy(t => t.Name).ToList();
|
|
||||||
stats.MaterializedViews = stats.MaterializedViews.OrderBy(mv => mv.Name).ToList();
|
|
||||||
|
|
||||||
return stats;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<CandidatoSearchResult> SearchCandidatosAsync(string query)
|
public async Task<CandidatoSearchResult> SearchCandidatosAsync(string query)
|
||||||
{
|
{
|
||||||
return new CandidatoSearchResult()
|
return new CandidatoSearchResult()
|
||||||
|
@ -11,34 +11,12 @@
|
|||||||
|
|
||||||
public class DataAvailabilityStats
|
public class DataAvailabilityStats
|
||||||
{
|
{
|
||||||
public List<int> Candidatos { get; set; } = new List<int>();
|
public List<int> Candidatos { get; set; }
|
||||||
public List<int> BemCandidatos { get; set; } = new List<int>();
|
public List<int> BemCandidatos { get; set; }
|
||||||
public List<int> DespesaCandidatos { get; set; } = new List<int>();
|
public List<int> DespesaCandidatos { get; set; }
|
||||||
public List<int> ReceitaCandidatos { get; set; } = new List<int>();
|
public List<int> ReceitaCandidatos { get; set; }
|
||||||
public List<int> RedeSocialCandidatos { get; set; } = new List<int>();
|
public List<int> RedeSocialCandidatos { get; set; }
|
||||||
|
|
||||||
public List<int> FotosCandidatos { get; set; } = new List<int>();
|
public List<int> FotosCandidatos { get; set; }
|
||||||
}
|
|
||||||
|
|
||||||
public class DatabaseTechStats
|
|
||||||
{
|
|
||||||
public List<TableStats> Tables { get; set; } = new List<TableStats>();
|
|
||||||
public List<TableStats> MaterializedViews { get; set; } = new List<TableStats>();
|
|
||||||
public IndexStats Indexes { get; set; } = new IndexStats();
|
|
||||||
public long TotalSize { get; set; }
|
|
||||||
public long TotalEntries { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class TableStats
|
|
||||||
{
|
|
||||||
public string Name { get; set; } = string.Empty;
|
|
||||||
public long TotalSize { get; set; }
|
|
||||||
public long Entries { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class IndexStats
|
|
||||||
{
|
|
||||||
public int Amount { get; set; }
|
|
||||||
public long Size { get; set; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
namespace OpenCand.Core.Utils
|
|
||||||
{
|
|
||||||
public static class CpfMasking
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Masks a CPF number by replacing the middle 3 digits with '*'
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="cpf">The CPF number to mask.</param>
|
|
||||||
/// <returns>The masked CPF number.</returns>
|
|
||||||
public static string MaskCpf(string cpf)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(cpf) || cpf.Length != 11)
|
|
||||||
{
|
|
||||||
return cpf;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mask the middle 3 digits
|
|
||||||
return $"{cpf.Substring(0, 3)}***{cpf.Substring(6)}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user