Compare commits
5 Commits
1d1211d372
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 337898c88f | |||
| 8553dfe90c | |||
| 5326d87a6d | |||
| 83fbcbe80f | |||
| 8c00e7f7cc |
@@ -15,36 +15,102 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GestionaDenunciasAN", "Gest
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ApiDenuncias", "ApiDenuncias\ApiDenuncias.csproj", "{98BF7472-0E7B-4329-A3DC-BB74A942BDAB}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ApiDenuncias", "ApiDenuncias\ApiDenuncias.csproj", "{98BF7472-0E7B-4329-A3DC-BB74A942BDAB}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GestionaDenuncias.Shared", "GestionaDenuncias.Shared\GestionaDenuncias.Shared.csproj", "{94F25A9A-6084-4F8C-9FF1-F74C6BF83B0E}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Debug|x64 = Debug|x64
|
||||||
|
Debug|x86 = Debug|x86
|
||||||
Release|Any CPU = Release|Any CPU
|
Release|Any CPU = Release|Any CPU
|
||||||
|
Release|x64 = Release|x64
|
||||||
|
Release|x86 = Release|x86
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
{B8C0C071-81ED-4265-86F0-169CB5A0C82E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{B8C0C071-81ED-4265-86F0-169CB5A0C82E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{B8C0C071-81ED-4265-86F0-169CB5A0C82E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{B8C0C071-81ED-4265-86F0-169CB5A0C82E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{B8C0C071-81ED-4265-86F0-169CB5A0C82E}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{B8C0C071-81ED-4265-86F0-169CB5A0C82E}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{B8C0C071-81ED-4265-86F0-169CB5A0C82E}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{B8C0C071-81ED-4265-86F0-169CB5A0C82E}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
{B8C0C071-81ED-4265-86F0-169CB5A0C82E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{B8C0C071-81ED-4265-86F0-169CB5A0C82E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{B8C0C071-81ED-4265-86F0-169CB5A0C82E}.Release|Any CPU.Build.0 = Release|Any CPU
|
{B8C0C071-81ED-4265-86F0-169CB5A0C82E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{B8C0C071-81ED-4265-86F0-169CB5A0C82E}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{B8C0C071-81ED-4265-86F0-169CB5A0C82E}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{B8C0C071-81ED-4265-86F0-169CB5A0C82E}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{B8C0C071-81ED-4265-86F0-169CB5A0C82E}.Release|x86.Build.0 = Release|Any CPU
|
||||||
{ED8F423B-D059-4A55-AA8F-0122201E4E1A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{ED8F423B-D059-4A55-AA8F-0122201E4E1A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{ED8F423B-D059-4A55-AA8F-0122201E4E1A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{ED8F423B-D059-4A55-AA8F-0122201E4E1A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{ED8F423B-D059-4A55-AA8F-0122201E4E1A}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{ED8F423B-D059-4A55-AA8F-0122201E4E1A}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{ED8F423B-D059-4A55-AA8F-0122201E4E1A}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{ED8F423B-D059-4A55-AA8F-0122201E4E1A}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
{ED8F423B-D059-4A55-AA8F-0122201E4E1A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{ED8F423B-D059-4A55-AA8F-0122201E4E1A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{ED8F423B-D059-4A55-AA8F-0122201E4E1A}.Release|Any CPU.Build.0 = Release|Any CPU
|
{ED8F423B-D059-4A55-AA8F-0122201E4E1A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{ED8F423B-D059-4A55-AA8F-0122201E4E1A}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{ED8F423B-D059-4A55-AA8F-0122201E4E1A}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{ED8F423B-D059-4A55-AA8F-0122201E4E1A}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{ED8F423B-D059-4A55-AA8F-0122201E4E1A}.Release|x86.Build.0 = Release|Any CPU
|
||||||
{690BFF6A-F3FC-4D94-9E32-C689FBB69455}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{690BFF6A-F3FC-4D94-9E32-C689FBB69455}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{690BFF6A-F3FC-4D94-9E32-C689FBB69455}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{690BFF6A-F3FC-4D94-9E32-C689FBB69455}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{690BFF6A-F3FC-4D94-9E32-C689FBB69455}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{690BFF6A-F3FC-4D94-9E32-C689FBB69455}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{690BFF6A-F3FC-4D94-9E32-C689FBB69455}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{690BFF6A-F3FC-4D94-9E32-C689FBB69455}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
{690BFF6A-F3FC-4D94-9E32-C689FBB69455}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{690BFF6A-F3FC-4D94-9E32-C689FBB69455}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{690BFF6A-F3FC-4D94-9E32-C689FBB69455}.Release|Any CPU.Build.0 = Release|Any CPU
|
{690BFF6A-F3FC-4D94-9E32-C689FBB69455}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{690BFF6A-F3FC-4D94-9E32-C689FBB69455}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{690BFF6A-F3FC-4D94-9E32-C689FBB69455}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{690BFF6A-F3FC-4D94-9E32-C689FBB69455}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{690BFF6A-F3FC-4D94-9E32-C689FBB69455}.Release|x86.Build.0 = Release|Any CPU
|
||||||
{063515F3-D202-45DD-91DA-A494FBD005AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{063515F3-D202-45DD-91DA-A494FBD005AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{063515F3-D202-45DD-91DA-A494FBD005AD}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{063515F3-D202-45DD-91DA-A494FBD005AD}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{063515F3-D202-45DD-91DA-A494FBD005AD}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{063515F3-D202-45DD-91DA-A494FBD005AD}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{063515F3-D202-45DD-91DA-A494FBD005AD}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{063515F3-D202-45DD-91DA-A494FBD005AD}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
{063515F3-D202-45DD-91DA-A494FBD005AD}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{063515F3-D202-45DD-91DA-A494FBD005AD}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{063515F3-D202-45DD-91DA-A494FBD005AD}.Release|Any CPU.Build.0 = Release|Any CPU
|
{063515F3-D202-45DD-91DA-A494FBD005AD}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{063515F3-D202-45DD-91DA-A494FBD005AD}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{063515F3-D202-45DD-91DA-A494FBD005AD}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{063515F3-D202-45DD-91DA-A494FBD005AD}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{063515F3-D202-45DD-91DA-A494FBD005AD}.Release|x86.Build.0 = Release|Any CPU
|
||||||
{77BE75E1-E1FD-AAE7-D897-398BED72CEB1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{77BE75E1-E1FD-AAE7-D897-398BED72CEB1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{77BE75E1-E1FD-AAE7-D897-398BED72CEB1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{77BE75E1-E1FD-AAE7-D897-398BED72CEB1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{77BE75E1-E1FD-AAE7-D897-398BED72CEB1}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{77BE75E1-E1FD-AAE7-D897-398BED72CEB1}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{77BE75E1-E1FD-AAE7-D897-398BED72CEB1}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{77BE75E1-E1FD-AAE7-D897-398BED72CEB1}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
{77BE75E1-E1FD-AAE7-D897-398BED72CEB1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{77BE75E1-E1FD-AAE7-D897-398BED72CEB1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{77BE75E1-E1FD-AAE7-D897-398BED72CEB1}.Release|Any CPU.Build.0 = Release|Any CPU
|
{77BE75E1-E1FD-AAE7-D897-398BED72CEB1}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{77BE75E1-E1FD-AAE7-D897-398BED72CEB1}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{77BE75E1-E1FD-AAE7-D897-398BED72CEB1}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{77BE75E1-E1FD-AAE7-D897-398BED72CEB1}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{77BE75E1-E1FD-AAE7-D897-398BED72CEB1}.Release|x86.Build.0 = Release|Any CPU
|
||||||
{98BF7472-0E7B-4329-A3DC-BB74A942BDAB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{98BF7472-0E7B-4329-A3DC-BB74A942BDAB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{98BF7472-0E7B-4329-A3DC-BB74A942BDAB}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{98BF7472-0E7B-4329-A3DC-BB74A942BDAB}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{98BF7472-0E7B-4329-A3DC-BB74A942BDAB}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{98BF7472-0E7B-4329-A3DC-BB74A942BDAB}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{98BF7472-0E7B-4329-A3DC-BB74A942BDAB}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{98BF7472-0E7B-4329-A3DC-BB74A942BDAB}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
{98BF7472-0E7B-4329-A3DC-BB74A942BDAB}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{98BF7472-0E7B-4329-A3DC-BB74A942BDAB}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{98BF7472-0E7B-4329-A3DC-BB74A942BDAB}.Release|Any CPU.Build.0 = Release|Any CPU
|
{98BF7472-0E7B-4329-A3DC-BB74A942BDAB}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{98BF7472-0E7B-4329-A3DC-BB74A942BDAB}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{98BF7472-0E7B-4329-A3DC-BB74A942BDAB}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{98BF7472-0E7B-4329-A3DC-BB74A942BDAB}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{98BF7472-0E7B-4329-A3DC-BB74A942BDAB}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{94F25A9A-6084-4F8C-9FF1-F74C6BF83B0E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{94F25A9A-6084-4F8C-9FF1-F74C6BF83B0E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{94F25A9A-6084-4F8C-9FF1-F74C6BF83B0E}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{94F25A9A-6084-4F8C-9FF1-F74C6BF83B0E}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{94F25A9A-6084-4F8C-9FF1-F74C6BF83B0E}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{94F25A9A-6084-4F8C-9FF1-F74C6BF83B0E}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{94F25A9A-6084-4F8C-9FF1-F74C6BF83B0E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{94F25A9A-6084-4F8C-9FF1-F74C6BF83B0E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{94F25A9A-6084-4F8C-9FF1-F74C6BF83B0E}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{94F25A9A-6084-4F8C-9FF1-F74C6BF83B0E}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{94F25A9A-6084-4F8C-9FF1-F74C6BF83B0E}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{94F25A9A-6084-4F8C-9FF1-F74C6BF83B0E}.Release|x86.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|||||||
@@ -7,12 +7,20 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Azure.Identity" Version="1.13.2" />
|
||||||
|
<PackageReference Include="Azure.Security.KeyVault.Secrets" Version="4.7.0" />
|
||||||
|
<PackageReference Include="Konscious.Security.Cryptography.Argon2" Version="1.3.1" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.25" />
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.25" />
|
||||||
|
<PackageReference Include="MySqlConnector" Version="2.4.0" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\GestionaDenunciasAN\GestionaDenunciasAN.csproj" />
|
<ProjectReference Include="..\GestionaDenuncias.Shared\GestionaDenuncias.Shared.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Include="Scripts\gestiondenuncias_schema.sql" CopyToOutputDirectory="PreserveNewest" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
namespace ApiDenuncias.Configuration;
|
||||||
|
|
||||||
|
public sealed class ComplaintStorageOptions
|
||||||
|
{
|
||||||
|
public const string SectionName = "ComplaintStorage";
|
||||||
|
|
||||||
|
public string ConnectionString { get; set; } = string.Empty;
|
||||||
|
public bool AutoCreateSchema { get; set; }
|
||||||
|
public bool UseKeyVault { get; set; } = true;
|
||||||
|
public string HostSecretName { get; set; } = "bbdd-host";
|
||||||
|
public string UserSecretName { get; set; } = "bbdd-user";
|
||||||
|
public string PasswordSecretName { get; set; } = "bbdd-password";
|
||||||
|
public string DatabaseSecretName { get; set; } = "bbdd-name";
|
||||||
|
public string PortSecretName { get; set; } = "bbdd-port";
|
||||||
|
public string SslModeSecretName { get; set; } = "bbdd-ssl-mode";
|
||||||
|
public uint DefaultPort { get; set; } = 3306;
|
||||||
|
public string DefaultSslMode { get; set; } = "Required";
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace GestionaDenunciasAN.Models
|
namespace ApiDenuncias.Configuration
|
||||||
{
|
{
|
||||||
public class GestionaOptions
|
public class GestionaOptions
|
||||||
{
|
{
|
||||||
@@ -8,5 +8,10 @@
|
|||||||
public string GroupLink { get; set; } = null!;
|
public string GroupLink { get; set; } = null!;
|
||||||
public string Location { get; set; } = null!;
|
public string Location { get; set; } = null!;
|
||||||
public string? ExternalProcedureId { get; set; }
|
public string? ExternalProcedureId { get; set; }
|
||||||
|
public string? CircuitTemplateId { get; set; }
|
||||||
|
public string? CircuitSignerStampHref { get; set; }
|
||||||
|
public string? CircuitSignerStampTitle { get; set; }
|
||||||
|
public string? CircuitRecipientGroupHref { get; set; }
|
||||||
|
public string? CircuitVersion { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
namespace GestionaDenunciasAN.Configuration;
|
namespace ApiDenuncias.Configuration;
|
||||||
|
|
||||||
public sealed class GlobalLeaksOptions
|
public sealed class GlobalLeaksOptions
|
||||||
{
|
{
|
||||||
public const string SectionName = "GlobalLeaks";
|
public const string SectionName = "GlobalLeaks";
|
||||||
|
|
||||||
public string BaseUrl { get; set; } = "https://prebuzon.antifraudeandalucia.es";
|
public string BaseUrl { get; set; } = "https://prebuzon.antifraudeandalucia.es";
|
||||||
|
public string? HostHeader { get; set; }
|
||||||
|
public bool AllowInvalidCertificate { get; set; }
|
||||||
public int TimeoutSeconds { get; set; } = 120;
|
public int TimeoutSeconds { get; set; } = 120;
|
||||||
public int MaxDownloadBytes { get; set; } = 500 * 1024 * 1024;
|
public int MaxDownloadBytes { get; set; } = 500 * 1024 * 1024;
|
||||||
}
|
}
|
||||||
@@ -8,4 +8,5 @@ public sealed class JwtOptions
|
|||||||
public string Audience { get; set; } = "GestionaDenunciasAN";
|
public string Audience { get; set; } = "GestionaDenunciasAN";
|
||||||
public string SigningKey { get; set; } = string.Empty;
|
public string SigningKey { get; set; } = string.Empty;
|
||||||
public int ExpirationMinutes { get; set; } = 480;
|
public int ExpirationMinutes { get; set; } = 480;
|
||||||
|
public bool RequireHttpsMetadata { get; set; } = true;
|
||||||
}
|
}
|
||||||
|
|||||||
14
Antifraude.Net/ApiDenuncias/Configuration/KeyVaultOptions.cs
Normal file
14
Antifraude.Net/ApiDenuncias/Configuration/KeyVaultOptions.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
namespace ApiDenuncias.Configuration;
|
||||||
|
|
||||||
|
public sealed class KeyVaultOptions
|
||||||
|
{
|
||||||
|
public const string SectionName = "KeyVault";
|
||||||
|
|
||||||
|
public bool Enabled { get; set; } = true;
|
||||||
|
|
||||||
|
public string VaultUrl { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string EncryptionKeySecretName { get; set; } = "denuncias-encryption-key";
|
||||||
|
|
||||||
|
public bool AllowLocalEncryptionKeyFallback { get; set; }
|
||||||
|
}
|
||||||
@@ -3,8 +3,8 @@ using System.Security.Claims;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using ApiDenuncias.Configuration;
|
using ApiDenuncias.Configuration;
|
||||||
using GestionaDenunciasAN.Models;
|
using GestionaDenuncias.Shared.Models;
|
||||||
using GestionaDenunciasAN.Services;
|
using ApiDenuncias.Services;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
using ApiDenuncias.Services;
|
using ApiDenuncias.Services;
|
||||||
using GestionaDenunciasAN.Models;
|
using GestionaDenuncias.Shared.Models;
|
||||||
using GestionaDenunciasAN.Services;
|
using ApiDenuncias.Services;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
using GestionaDenunciasAN.Models;
|
using GestionaDenuncias.Shared.Models;
|
||||||
using GestionaDenunciasAN.Services;
|
using ApiDenuncias.Services;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
using GestionaDenunciasAN.Models;
|
using GestionaDenuncias.Shared.Models;
|
||||||
using GestionaDenunciasAN.Services;
|
using ApiDenuncias.Services;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
@@ -14,17 +14,20 @@ public sealed class InboxController : ControllerBase
|
|||||||
private readonly GlobalLeaksClient _globalLeaksClient;
|
private readonly GlobalLeaksClient _globalLeaksClient;
|
||||||
private readonly DenunciaInboxService _inboxService;
|
private readonly DenunciaInboxService _inboxService;
|
||||||
private readonly IInboxTrackingService _trackingService;
|
private readonly IInboxTrackingService _trackingService;
|
||||||
|
private readonly ILogger<InboxController> _logger;
|
||||||
|
|
||||||
public InboxController(
|
public InboxController(
|
||||||
GlobalLeaksSessionStore sessionStore,
|
GlobalLeaksSessionStore sessionStore,
|
||||||
GlobalLeaksClient globalLeaksClient,
|
GlobalLeaksClient globalLeaksClient,
|
||||||
DenunciaInboxService inboxService,
|
DenunciaInboxService inboxService,
|
||||||
IInboxTrackingService trackingService)
|
IInboxTrackingService trackingService,
|
||||||
|
ILogger<InboxController> logger)
|
||||||
{
|
{
|
||||||
_sessionStore = sessionStore;
|
_sessionStore = sessionStore;
|
||||||
_globalLeaksClient = globalLeaksClient;
|
_globalLeaksClient = globalLeaksClient;
|
||||||
_inboxService = inboxService;
|
_inboxService = inboxService;
|
||||||
_trackingService = trackingService;
|
_trackingService = trackingService;
|
||||||
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("session")]
|
[HttpGet("session")]
|
||||||
@@ -67,6 +70,13 @@ public sealed class InboxController : ControllerBase
|
|||||||
{
|
{
|
||||||
return StatusCode(ex.StatusCode, new ApiError(ex.Message));
|
return StatusCode(ex.StatusCode, new ApiError(ex.Message));
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "No se ha podido cargar la bandeja GlobalLeaks para {Username}.", username);
|
||||||
|
return StatusCode(
|
||||||
|
StatusCodes.Status500InternalServerError,
|
||||||
|
new ApiError($"No se ha podido cargar la bandeja: {ex.GetType().Name}: {ex.Message}"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("session/clear")]
|
[HttpPost("session/clear")]
|
||||||
@@ -104,6 +114,13 @@ public sealed class InboxController : ControllerBase
|
|||||||
{
|
{
|
||||||
return StatusCode(ex.StatusCode, new ApiError(ex.Message));
|
return StatusCode(ex.StatusCode, new ApiError(ex.Message));
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "No se ha podido cargar la bandeja GlobalLeaks para {Username}.", username);
|
||||||
|
return StatusCode(
|
||||||
|
StatusCodes.Status500InternalServerError,
|
||||||
|
new ApiError($"No se ha podido cargar la bandeja: {ex.GetType().Name}: {ex.Message}"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("reports/{reportId}/import")]
|
[HttpPost("reports/{reportId}/import")]
|
||||||
@@ -125,6 +142,8 @@ public sealed class InboxController : ControllerBase
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
await _trackingService.EnsureReportCanBeImportedByUserAsync(username, report, cancellationToken);
|
||||||
|
|
||||||
var zip = await _globalLeaksClient.DownloadReportZipAsync(session.SessionId!, report.Id, cancellationToken);
|
var zip = await _globalLeaksClient.DownloadReportZipAsync(session.SessionId!, report.Id, cancellationToken);
|
||||||
|
|
||||||
FileDownloadResult? json = null;
|
FileDownloadResult? json = null;
|
||||||
@@ -158,6 +177,13 @@ public sealed class InboxController : ControllerBase
|
|||||||
{
|
{
|
||||||
return StatusCode(ex.StatusCode, new ApiError(ex.Message));
|
return StatusCode(ex.StatusCode, new ApiError(ex.Message));
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "No se ha podido importar la denuncia {ReportId} para {Username}.", reportId, username);
|
||||||
|
return StatusCode(
|
||||||
|
StatusCodes.Status500InternalServerError,
|
||||||
|
new ApiError($"No se ha podido importar la denuncia: {ex.GetType().Name}: {ex.Message}"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("local/ensure-storage")]
|
[HttpPost("local/ensure-storage")]
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
using GestionaDenunciasAN.Models;
|
using GestionaDenuncias.Shared.Models;
|
||||||
using GestionaDenunciasAN.Services;
|
using ApiDenuncias.Services;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
@@ -34,6 +34,15 @@ public sealed class TrackingController : ControllerBase
|
|||||||
return Ok(new { ok = true });
|
return Ok(new { ok = true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpPost("import-permission")]
|
||||||
|
public async Task<IActionResult> EnsureImportPermission(
|
||||||
|
TrackingImportPermissionRequest request,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
await _trackingService.EnsureReportCanBeImportedByUserAsync(GetUsername(), request.Report, cancellationToken);
|
||||||
|
return Ok(new { ok = true });
|
||||||
|
}
|
||||||
|
|
||||||
private string GetUsername()
|
private string GetUsername()
|
||||||
=> User.Identity?.Name ?? throw new InvalidOperationException("No hay usuario autenticado.");
|
=> User.Identity?.Name ?? throw new InvalidOperationException("No hay usuario autenticado.");
|
||||||
}
|
}
|
||||||
|
|||||||
4
Antifraude.Net/ApiDenuncias/GlobalUsings.cs
Normal file
4
Antifraude.Net/ApiDenuncias/GlobalUsings.cs
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
global using ApiDenuncias.Configuration;
|
||||||
|
global using ApiDenuncias.Services;
|
||||||
|
global using GestionaDenuncias.Shared.Models;
|
||||||
|
global using GestionaDenuncias.Shared.Services;
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using GestionaDenunciasAN.Models;
|
using GestionaDenuncias.Shared.Models;
|
||||||
|
|
||||||
namespace GestionaDenunciasAN.Helpers;
|
namespace ApiDenuncias.Helpers;
|
||||||
|
|
||||||
public static class GlobalLeaksJsonEnricher
|
public static class GlobalLeaksJsonEnricher
|
||||||
{
|
{
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using GestionaDenunciasAN.Models;
|
using GestionaDenuncias.Shared.Models;
|
||||||
|
|
||||||
namespace GestionaDenunciasAN.Helpers;
|
namespace ApiDenuncias.Helpers;
|
||||||
|
|
||||||
public static class ReportParser
|
public static class ReportParser
|
||||||
{
|
{
|
||||||
@@ -2,18 +2,20 @@ using System.Net.Http.Headers;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using ApiDenuncias.Configuration;
|
using ApiDenuncias.Configuration;
|
||||||
using ApiDenuncias.Services;
|
using ApiDenuncias.Services;
|
||||||
using GestionaDenunciasAN.Configuration;
|
using ApiDenuncias.Configuration;
|
||||||
using GestionaDenunciasAN.Models;
|
using GestionaDenuncias.Shared.Models;
|
||||||
using GestionaDenunciasAN.Services;
|
using ApiDenuncias.Services;
|
||||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||||
using Microsoft.AspNetCore.DataProtection;
|
using Microsoft.AspNetCore.DataProtection;
|
||||||
using Microsoft.AspNetCore.Diagnostics;
|
using Microsoft.AspNetCore.Diagnostics;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using Microsoft.IdentityModel.Tokens;
|
using Microsoft.IdentityModel.Tokens;
|
||||||
|
using GestionaDenuncias.Shared.Models;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
builder.Services.Configure<JwtOptions>(builder.Configuration.GetSection(JwtOptions.SectionName));
|
builder.Services.Configure<JwtOptions>(builder.Configuration.GetSection(JwtOptions.SectionName));
|
||||||
|
builder.Services.Configure<KeyVaultOptions>(builder.Configuration.GetSection(KeyVaultOptions.SectionName));
|
||||||
builder.Services.Configure<GestionaOptions>(builder.Configuration.GetSection("Gestiona"));
|
builder.Services.Configure<GestionaOptions>(builder.Configuration.GetSection("Gestiona"));
|
||||||
builder.Services.Configure<GlobalLeaksOptions>(builder.Configuration.GetSection(GlobalLeaksOptions.SectionName));
|
builder.Services.Configure<GlobalLeaksOptions>(builder.Configuration.GetSection(GlobalLeaksOptions.SectionName));
|
||||||
builder.Services.Configure<ComplaintStorageOptions>(builder.Configuration.GetSection(ComplaintStorageOptions.SectionName));
|
builder.Services.Configure<ComplaintStorageOptions>(builder.Configuration.GetSection(ComplaintStorageOptions.SectionName));
|
||||||
@@ -28,7 +30,9 @@ builder.Services.AddDataProtection()
|
|||||||
builder.Services.AddSingleton<LoginRateLimiter>();
|
builder.Services.AddSingleton<LoginRateLimiter>();
|
||||||
builder.Services.AddSingleton<GlobalLeaksSessionStore>();
|
builder.Services.AddSingleton<GlobalLeaksSessionStore>();
|
||||||
builder.Services.AddScoped<GlobalLeaksClient>();
|
builder.Services.AddScoped<GlobalLeaksClient>();
|
||||||
|
builder.Services.AddSingleton<MySqlConnectionStringProvider>();
|
||||||
builder.Services.AddScoped<MySqlDenunciaStore>();
|
builder.Services.AddScoped<MySqlDenunciaStore>();
|
||||||
|
builder.Services.AddSingleton<IEncryptionKeyProvider, KeyVaultEncryptionKeyProvider>();
|
||||||
builder.Services.AddScoped<IDenunciaStore, EncryptedDenunciaStore>();
|
builder.Services.AddScoped<IDenunciaStore, EncryptedDenunciaStore>();
|
||||||
builder.Services.AddScoped<IInboxTrackingService, InboxTrackingService>();
|
builder.Services.AddScoped<IInboxTrackingService, InboxTrackingService>();
|
||||||
builder.Services.AddScoped<DenunciaInboxService>();
|
builder.Services.AddScoped<DenunciaInboxService>();
|
||||||
@@ -50,7 +54,7 @@ builder.Services
|
|||||||
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
|
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
|
||||||
.AddJwtBearer(options =>
|
.AddJwtBearer(options =>
|
||||||
{
|
{
|
||||||
options.RequireHttpsMetadata = !builder.Environment.IsDevelopment();
|
options.RequireHttpsMetadata = jwt.RequireHttpsMetadata;
|
||||||
options.TokenValidationParameters = new TokenValidationParameters
|
options.TokenValidationParameters = new TokenValidationParameters
|
||||||
{
|
{
|
||||||
ValidateIssuer = true,
|
ValidateIssuer = true,
|
||||||
@@ -62,6 +66,30 @@ builder.Services
|
|||||||
IssuerSigningKey = new SymmetricSecurityKey(signingKey),
|
IssuerSigningKey = new SymmetricSecurityKey(signingKey),
|
||||||
ClockSkew = TimeSpan.FromMinutes(1)
|
ClockSkew = TimeSpan.FromMinutes(1)
|
||||||
};
|
};
|
||||||
|
options.Events = new JwtBearerEvents
|
||||||
|
{
|
||||||
|
OnAuthenticationFailed = context =>
|
||||||
|
{
|
||||||
|
var logger = context.HttpContext.RequestServices
|
||||||
|
.GetRequiredService<ILoggerFactory>()
|
||||||
|
.CreateLogger("ApiDenuncias.Jwt");
|
||||||
|
logger.LogWarning(context.Exception, "JWT no valido en {Path}", context.HttpContext.Request.Path);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
},
|
||||||
|
OnChallenge = context =>
|
||||||
|
{
|
||||||
|
var logger = context.HttpContext.RequestServices
|
||||||
|
.GetRequiredService<ILoggerFactory>()
|
||||||
|
.CreateLogger("ApiDenuncias.Jwt");
|
||||||
|
logger.LogWarning(
|
||||||
|
"JWT rechazado en {Path}. Error={Error}. Description={Description}. AuthorizationHeader={HasAuthorizationHeader}",
|
||||||
|
context.HttpContext.Request.Path,
|
||||||
|
context.Error,
|
||||||
|
context.ErrorDescription,
|
||||||
|
context.Request.Headers.ContainsKey("Authorization"));
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
builder.Services.AddAuthorization();
|
builder.Services.AddAuthorization();
|
||||||
@@ -82,8 +110,15 @@ app.UseExceptionHandler(errorApp =>
|
|||||||
logger.LogError(feature.Error, "Error no controlado en {Path}", context.Request.Path);
|
logger.LogError(feature.Error, "Error no controlado en {Path}", context.Request.Path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var detailedErrors = context.RequestServices
|
||||||
|
.GetRequiredService<IConfiguration>()
|
||||||
|
.GetValue("DetailedApiErrors", false);
|
||||||
|
var message = detailedErrors && feature?.Error is not null
|
||||||
|
? $"La API de denuncias no ha podido completar la operacion: {feature.Error.GetType().Name}: {feature.Error.Message}"
|
||||||
|
: "La API de denuncias no ha podido completar la operacion.";
|
||||||
|
|
||||||
context.Response.StatusCode = StatusCodes.Status500InternalServerError;
|
context.Response.StatusCode = StatusCodes.Status500InternalServerError;
|
||||||
await context.Response.WriteAsJsonAsync(new ApiError("La API de denuncias no ha podido completar la operacion."));
|
await context.Response.WriteAsJsonAsync(new ApiError(message));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -93,7 +128,10 @@ if (app.Environment.IsDevelopment())
|
|||||||
app.UseSwaggerUI();
|
app.UseSwaggerUI();
|
||||||
}
|
}
|
||||||
|
|
||||||
app.UseHttpsRedirection();
|
if (builder.Configuration.GetValue("ForceHttpsRedirection", false))
|
||||||
|
{
|
||||||
|
app.UseHttpsRedirection();
|
||||||
|
}
|
||||||
app.UseAuthentication();
|
app.UseAuthentication();
|
||||||
app.UseAuthorization();
|
app.UseAuthorization();
|
||||||
app.MapGet("/health", () => Results.Ok(new { status = "ok" })).AllowAnonymous();
|
app.MapGet("/health", () => Results.Ok(new { status = "ok" })).AllowAnonymous();
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
using System.IO.Compression;
|
using System.IO.Compression;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using GestionaDenunciasAN.Helpers;
|
using ApiDenuncias.Helpers;
|
||||||
using GestionaDenunciasAN.Models;
|
using GestionaDenuncias.Shared.Models;
|
||||||
|
|
||||||
namespace GestionaDenunciasAN.Services;
|
namespace ApiDenuncias.Services;
|
||||||
|
|
||||||
public sealed class DenunciaInboxService
|
public sealed class DenunciaInboxService
|
||||||
{
|
{
|
||||||
@@ -119,21 +119,39 @@ public sealed class DenunciaInboxService
|
|||||||
using var zipStream = new MemoryStream(zipBytes, writable: false);
|
using var zipStream = new MemoryStream(zipBytes, writable: false);
|
||||||
using var archive = new ZipArchive(zipStream, ZipArchiveMode.Read, leaveOpen: false);
|
using var archive = new ZipArchive(zipStream, ZipArchiveMode.Read, leaveOpen: false);
|
||||||
|
|
||||||
var reportEntry = archive.Entries.FirstOrDefault(entry =>
|
var reportEntry = FindReportEntry(archive);
|
||||||
string.Equals(NormalizeEntryPath(entry.FullName), "report.txt", StringComparison.OrdinalIgnoreCase));
|
|
||||||
|
|
||||||
if (reportEntry is null)
|
if (reportEntry is null)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("El ZIP no contiene el fichero report.txt.");
|
var entries = archive.Entries
|
||||||
|
.Where(entry => !string.IsNullOrWhiteSpace(entry.Name))
|
||||||
|
.Select(entry => NormalizeEntryPath(entry.FullName))
|
||||||
|
.Take(30)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
entries.Length == 0
|
||||||
|
? "El ZIP no contiene ficheros."
|
||||||
|
: $"El ZIP no contiene un report reconocible. Ficheros encontrados: {string.Join(", ", entries)}");
|
||||||
}
|
}
|
||||||
|
|
||||||
var reportText = await ReadEntryTextAsync(reportEntry, cancellationToken);
|
var reportIsPdf = IsPdfEntry(reportEntry);
|
||||||
var denuncia = ReportParser.ParseReport(reportText);
|
var reportText = reportIsPdf
|
||||||
|
? string.Empty
|
||||||
|
: await ReadEntryTextAsync(reportEntry, cancellationToken);
|
||||||
|
var denuncia = reportIsPdf
|
||||||
|
? new DenunciasGestiona()
|
||||||
|
: ReportParser.ParseReport(reportText);
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(globalLeaksJson))
|
if (!string.IsNullOrWhiteSpace(globalLeaksJson))
|
||||||
{
|
{
|
||||||
GlobalLeaksJsonEnricher.Enrich(denuncia, globalLeaksJson);
|
GlobalLeaksJsonEnricher.Enrich(denuncia, globalLeaksJson);
|
||||||
}
|
}
|
||||||
|
else if (reportIsPdf)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
"El report viene en PDF y no se ha recibido el JSON de GlobalLeaks necesario para extraer los datos de la denuncia.");
|
||||||
|
}
|
||||||
|
|
||||||
if (denuncia.Id_Denuncia == 0)
|
if (denuncia.Id_Denuncia == 0)
|
||||||
{
|
{
|
||||||
@@ -146,6 +164,12 @@ public sealed class DenunciaInboxService
|
|||||||
$"No se ha podido determinar el identificador de la denuncia en {sourceName}.");
|
$"No se ha podido determinar el identificador de la denuncia en {sourceName}.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (reportIsPdf)
|
||||||
|
{
|
||||||
|
reportText = BuildSyntheticReportText(denuncia);
|
||||||
|
denuncia.TextoOriginalReport = reportText;
|
||||||
|
}
|
||||||
|
|
||||||
denuncia.ProcedureId = Guid.Empty;
|
denuncia.ProcedureId = Guid.Empty;
|
||||||
denuncia.GroupId = Guid.Empty;
|
denuncia.GroupId = Guid.Empty;
|
||||||
if (string.IsNullOrWhiteSpace(denuncia.Expediente_Gestiona))
|
if (string.IsNullOrWhiteSpace(denuncia.Expediente_Gestiona))
|
||||||
@@ -170,17 +194,17 @@ public sealed class DenunciaInboxService
|
|||||||
new(
|
new(
|
||||||
id_Fichero: 0,
|
id_Fichero: 0,
|
||||||
id_Tipo: 1,
|
id_Tipo: 1,
|
||||||
descripcion: "report.txt original",
|
descripcion: IsPdfEntry(reportEntry) ? "report.pdf original" : "report.txt original",
|
||||||
fecha: reportEntry.LastWriteTime.UtcDateTime == DateTime.MinValue
|
fecha: reportEntry.LastWriteTime.UtcDateTime == DateTime.MinValue
|
||||||
? DateTime.UtcNow
|
? DateTime.UtcNow
|
||||||
: reportEntry.LastWriteTime.UtcDateTime,
|
: reportEntry.LastWriteTime.UtcDateTime,
|
||||||
observaciones: "",
|
observaciones: "",
|
||||||
id_Denuncia: denunciaId,
|
id_Denuncia: denunciaId,
|
||||||
nombreFichero: "report.txt",
|
nombreFichero: IsPdfEntry(reportEntry) ? "report.pdf" : "report.txt",
|
||||||
fichero: await ReadEntryBytesAsync(reportEntry, cancellationToken))
|
fichero: await ReadEntryBytesAsync(reportEntry, cancellationToken))
|
||||||
};
|
};
|
||||||
|
|
||||||
foreach (var entry in archive.Entries.Where(IsSupportedAttachmentEntry))
|
foreach (var entry in archive.Entries.Where(entry => IsSupportedAttachmentEntry(entry) && !IsSameEntry(entry, reportEntry)))
|
||||||
{
|
{
|
||||||
files.Add(new FicherosDenuncias(
|
files.Add(new FicherosDenuncias(
|
||||||
id_Fichero: 0,
|
id_Fichero: 0,
|
||||||
@@ -385,6 +409,44 @@ public sealed class DenunciaInboxService
|
|||||||
return IsDirectChildOf(normalized, "files") || IsDirectChildOf(normalized, "files_attached_from_recipients");
|
return IsDirectChildOf(normalized, "files") || IsDirectChildOf(normalized, "files_attached_from_recipients");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static ZipArchiveEntry? FindReportEntry(ZipArchive archive)
|
||||||
|
{
|
||||||
|
return archive.Entries.FirstOrDefault(IsReportEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsReportEntry(ZipArchiveEntry entry)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(entry.Name))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var fileName = Path.GetFileName(NormalizeEntryPath(entry.FullName));
|
||||||
|
if (string.Equals(fileName, "report.txt", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var nameWithoutExtension = Path.GetFileNameWithoutExtension(fileName);
|
||||||
|
var extension = Path.GetExtension(fileName);
|
||||||
|
return (extension.Equals(".txt", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
extension.Equals(".pdf", StringComparison.OrdinalIgnoreCase)) &&
|
||||||
|
nameWithoutExtension.StartsWith("report", StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsPdfEntry(ZipArchiveEntry entry)
|
||||||
|
{
|
||||||
|
return Path.GetExtension(entry.Name).Equals(".pdf", StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsSameEntry(ZipArchiveEntry left, ZipArchiveEntry right)
|
||||||
|
{
|
||||||
|
return string.Equals(
|
||||||
|
NormalizeEntryPath(left.FullName),
|
||||||
|
NormalizeEntryPath(right.FullName),
|
||||||
|
StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
private static bool IsDirectChildOf(string normalizedEntryPath, string rootFolder)
|
private static bool IsDirectChildOf(string normalizedEntryPath, string rootFolder)
|
||||||
{
|
{
|
||||||
if (!normalizedEntryPath.StartsWith(rootFolder + "/", StringComparison.OrdinalIgnoreCase))
|
if (!normalizedEntryPath.StartsWith(rootFolder + "/", StringComparison.OrdinalIgnoreCase))
|
||||||
@@ -436,5 +498,94 @@ public sealed class DenunciaInboxService
|
|||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string BuildSyntheticReportText(DenunciasGestiona denuncia)
|
||||||
|
{
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
builder.AppendLine($"ID: {denuncia.Id_Denuncia}");
|
||||||
|
if (denuncia.Fecha != DateTime.MinValue)
|
||||||
|
{
|
||||||
|
builder.AppendLine($"Fecha: {denuncia.Fecha:O}");
|
||||||
|
}
|
||||||
|
|
||||||
|
AppendMetadata(builder, "Etiqueta", denuncia.Etiqueta);
|
||||||
|
AppendMetadata(builder, "Estado", denuncia.Estado);
|
||||||
|
builder.AppendLine();
|
||||||
|
|
||||||
|
AppendSection(builder, "Datos del denunciante",
|
||||||
|
("Indique si actúa como persona física o en representación de una persona jurídica.", denuncia.TipoDenunciante),
|
||||||
|
("Nombre", denuncia.Nombre),
|
||||||
|
("1º Apellido", denuncia.PrimerApellido),
|
||||||
|
("2º Apellido", denuncia.SegundoApellido),
|
||||||
|
("Razón social", denuncia.RazonSocial),
|
||||||
|
("SEXO", denuncia.Sexo),
|
||||||
|
("CONTACTO TELEFÓNICO", denuncia.Telefono),
|
||||||
|
("País de Origen", denuncia.PaisOrigen),
|
||||||
|
("NIF (DNI, NIE)", denuncia.Dni));
|
||||||
|
|
||||||
|
AppendSection(builder, "Descripción",
|
||||||
|
("Asunto", denuncia.Asunto),
|
||||||
|
("¿A quién denuncia?", denuncia.A_Quien_Denuncia),
|
||||||
|
("Describa su denuncia", denuncia.Descripcion_Denuncia),
|
||||||
|
("¿Ha denunciado estos hechos ante otras instituciones u órganos?", denuncia.Denunciado_Ante_Inst),
|
||||||
|
("POR FAVOR. INDIQUE EL ORGANISMO O LA INSTITUCION DONDE HA DENUNCIADO LOS HECHOS", denuncia.OrganismoDenunciado),
|
||||||
|
("¿Solicita medidas concretas de protección?", denuncia.SolicitaProteccion),
|
||||||
|
("DESCRIBA LAS MEDIDAS DE PROTECCIÓN SOLICITADAS", denuncia.MedidasProteccionSolicitadas),
|
||||||
|
("Lugar en el que ocurrieron los hechos que denuncia", denuncia.Lugar_Hechos),
|
||||||
|
("Fecha de los hechos que denuncia", denuncia.Fecha_Hechos == DateTime.MinValue ? string.Empty : denuncia.Fecha_Hechos.ToString("dd/MM/yyyy")),
|
||||||
|
("Autorización para remitir su denuncia", denuncia.AutorizaRemision),
|
||||||
|
("En tal caso, ¿desea que su denuncia se remita anonimizada (sin datos personales)?", denuncia.PreferenciaRemision));
|
||||||
|
|
||||||
|
AppendSection(builder, "Preferencias de notificación",
|
||||||
|
("Preferencia de notificación", denuncia.Notificacion_Preferencia),
|
||||||
|
("Notificaciones Electrónicas", denuncia.Notificacion_Electronica),
|
||||||
|
("Correo electrónico", denuncia.Correo_Electronico),
|
||||||
|
("Seguimiento Online", denuncia.SeguimientoOnline),
|
||||||
|
("Autorizo recibir notificaciones vía Correo Postal", denuncia.NotificacionPostal),
|
||||||
|
("Provincia", denuncia.Provincia),
|
||||||
|
("Tipo de vía", denuncia.DireccionTipoVia),
|
||||||
|
("Nombre de la vía", denuncia.Direccion),
|
||||||
|
("Código Postal", denuncia.CodigoPostal),
|
||||||
|
("Localidad", denuncia.Municipio),
|
||||||
|
("Número/Km", denuncia.DireccionNumero),
|
||||||
|
("Bloque", denuncia.DireccionBloque),
|
||||||
|
("Escalera", denuncia.DireccionEscalera),
|
||||||
|
("Planta", denuncia.DireccionPiso),
|
||||||
|
("Puerta", denuncia.DireccionPuerta),
|
||||||
|
("Extra", denuncia.DireccionExtra));
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(denuncia.Comments))
|
||||||
|
{
|
||||||
|
builder.AppendLine("{Messages}");
|
||||||
|
builder.AppendLine(denuncia.Comments);
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AppendMetadata(StringBuilder builder, string label, string? value)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(value))
|
||||||
|
{
|
||||||
|
builder.AppendLine($"{label}: {value}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AppendSection(StringBuilder builder, string section, params (string Label, string? Value)[] fields)
|
||||||
|
{
|
||||||
|
builder.AppendLine(section);
|
||||||
|
foreach (var (label, value) in fields)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(value))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.AppendLine($" {label}");
|
||||||
|
builder.AppendLine($" {value}");
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.AppendLine();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,27 +1,37 @@
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using GestionaDenunciasAN.Models;
|
using ApiDenuncias.Helpers;
|
||||||
using GestionaDenunciasAN.Services;
|
using GestionaDenuncias.Shared.Models;
|
||||||
|
using ApiDenuncias.Services;
|
||||||
using Microsoft.AspNetCore.DataProtection;
|
using Microsoft.AspNetCore.DataProtection;
|
||||||
|
|
||||||
namespace ApiDenuncias.Services;
|
namespace ApiDenuncias.Services;
|
||||||
|
|
||||||
public sealed class EncryptedDenunciaStore : IDenunciaStore
|
public sealed class EncryptedDenunciaStore : IDenunciaStore
|
||||||
{
|
{
|
||||||
private const string ProtectedStringPrefix = "enc:v1:";
|
private const string DataProtectionStringPrefix = "enc:v1:";
|
||||||
private static readonly byte[] ProtectedBytesPrefix = Encoding.ASCII.GetBytes("enc:v1:");
|
private const string KeyVaultStringPrefix = "enc:v2:";
|
||||||
|
private static readonly byte[] DataProtectionBytesPrefix = Encoding.ASCII.GetBytes("enc:v1:");
|
||||||
|
private static readonly byte[] KeyVaultBytesPrefix = Encoding.ASCII.GetBytes("enc:v2:");
|
||||||
|
private const int AesGcmNonceSize = 12;
|
||||||
|
private const int AesGcmTagSize = 16;
|
||||||
private static readonly PropertyInfo[] ComplaintProperties = typeof(DenunciasGestiona)
|
private static readonly PropertyInfo[] ComplaintProperties = typeof(DenunciasGestiona)
|
||||||
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
|
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
|
||||||
.Where(property => property.CanRead && property.CanWrite)
|
.Where(property => property.CanRead && property.CanWrite)
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
private readonly MySqlDenunciaStore _inner;
|
private readonly MySqlDenunciaStore _inner;
|
||||||
|
private readonly IEncryptionKeyProvider _encryptionKeyProvider;
|
||||||
private readonly IDataProtector _protector;
|
private readonly IDataProtector _protector;
|
||||||
|
|
||||||
public EncryptedDenunciaStore(MySqlDenunciaStore inner, IDataProtectionProvider dataProtectionProvider)
|
public EncryptedDenunciaStore(
|
||||||
|
MySqlDenunciaStore inner,
|
||||||
|
IEncryptionKeyProvider encryptionKeyProvider,
|
||||||
|
IDataProtectionProvider dataProtectionProvider)
|
||||||
{
|
{
|
||||||
_inner = inner;
|
_inner = inner;
|
||||||
|
_encryptionKeyProvider = encryptionKeyProvider;
|
||||||
_protector = dataProtectionProvider.CreateProtector("ApiDenuncias.DatabaseSensitiveData.v1");
|
_protector = dataProtectionProvider.CreateProtector("ApiDenuncias.DatabaseSensitiveData.v1");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,31 +39,47 @@ public sealed class EncryptedDenunciaStore : IDenunciaStore
|
|||||||
=> _inner.EnsureSchemaAsync(cancellationToken);
|
=> _inner.EnsureSchemaAsync(cancellationToken);
|
||||||
|
|
||||||
public async Task<List<DenunciasGestiona>> GetAllDenunciasAsync(CancellationToken cancellationToken = default)
|
public async Task<List<DenunciasGestiona>> GetAllDenunciasAsync(CancellationToken cancellationToken = default)
|
||||||
=> (await _inner.GetAllDenunciasAsync(cancellationToken))
|
{
|
||||||
.Select(UnprotectComplaint)
|
var key = await _encryptionKeyProvider.GetKeyAsync(cancellationToken);
|
||||||
|
return (await _inner.GetAllDenunciasAsync(cancellationToken))
|
||||||
|
.Select(denuncia => UnprotectComplaint(denuncia, key))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<List<FicherosDenuncias>> GetAllFicherosAsync(CancellationToken cancellationToken = default)
|
public async Task<List<FicherosDenuncias>> GetAllFicherosAsync(CancellationToken cancellationToken = default)
|
||||||
=> (await _inner.GetAllFicherosAsync(cancellationToken))
|
{
|
||||||
.Select(UnprotectAttachment)
|
var key = await _encryptionKeyProvider.GetKeyAsync(cancellationToken);
|
||||||
|
return (await _inner.GetAllFicherosAsync(cancellationToken))
|
||||||
|
.Select(fichero => UnprotectAttachment(fichero, key))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<List<FicherosDenuncias>> GetFicherosByDenunciaAsync(int denunciaId, CancellationToken cancellationToken = default)
|
public async Task<List<FicherosDenuncias>> GetFicherosByDenunciaAsync(int denunciaId, CancellationToken cancellationToken = default)
|
||||||
=> (await _inner.GetFicherosByDenunciaAsync(denunciaId, cancellationToken))
|
{
|
||||||
.Select(UnprotectAttachment)
|
var key = await _encryptionKeyProvider.GetKeyAsync(cancellationToken);
|
||||||
|
return (await _inner.GetFicherosByDenunciaAsync(denunciaId, cancellationToken))
|
||||||
|
.Select(fichero => UnprotectAttachment(fichero, key))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<DenunciasGestiona?> GetDenunciaByIdAsync(int denunciaId, CancellationToken cancellationToken = default)
|
public async Task<DenunciasGestiona?> GetDenunciaByIdAsync(int denunciaId, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
|
var key = await _encryptionKeyProvider.GetKeyAsync(cancellationToken);
|
||||||
var denuncia = await _inner.GetDenunciaByIdAsync(denunciaId, cancellationToken);
|
var denuncia = await _inner.GetDenunciaByIdAsync(denunciaId, cancellationToken);
|
||||||
return denuncia is null ? null : UnprotectComplaint(denuncia);
|
return denuncia is null ? null : UnprotectComplaint(denuncia, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task UpsertDenunciaAsync(DenunciasGestiona denuncia, CancellationToken cancellationToken = default)
|
public async Task UpsertDenunciaAsync(DenunciasGestiona denuncia, CancellationToken cancellationToken = default)
|
||||||
=> _inner.UpsertDenunciaAsync(ProtectComplaint(denuncia), cancellationToken);
|
{
|
||||||
|
var key = await _encryptionKeyProvider.GetKeyAsync(cancellationToken);
|
||||||
|
await _inner.UpsertDenunciaAsync(ProtectComplaint(denuncia, key), cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
public Task UpsertFicherosAsync(IEnumerable<FicherosDenuncias> ficheros, CancellationToken cancellationToken = default)
|
public async Task UpsertFicherosAsync(IEnumerable<FicherosDenuncias> ficheros, CancellationToken cancellationToken = default)
|
||||||
=> _inner.UpsertFicherosAsync(ficheros.Select(ProtectAttachment).ToArray(), cancellationToken);
|
{
|
||||||
|
var key = await _encryptionKeyProvider.GetKeyAsync(cancellationToken);
|
||||||
|
await _inner.UpsertFicherosAsync(ficheros.Select(fichero => ProtectAttachment(fichero, key)).ToArray(), cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
public Task MarkFicherosAsUploadedAsync(
|
public Task MarkFicherosAsUploadedAsync(
|
||||||
int denunciaId,
|
int denunciaId,
|
||||||
@@ -62,11 +88,106 @@ public sealed class EncryptedDenunciaStore : IDenunciaStore
|
|||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
=> _inner.MarkFicherosAsUploadedAsync(denunciaId, fileNames, uploadedAtUtc, cancellationToken);
|
=> _inner.MarkFicherosAsUploadedAsync(denunciaId, fileNames, uploadedAtUtc, cancellationToken);
|
||||||
|
|
||||||
private DenunciasGestiona ProtectComplaint(DenunciasGestiona source)
|
private DenunciasGestiona ProtectComplaint(DenunciasGestiona source, byte[] key)
|
||||||
=> TransformComplaint(source, ProtectString);
|
=> TransformComplaint(ToPersistentComplaint(source), value => ProtectString(value, key));
|
||||||
|
|
||||||
private DenunciasGestiona UnprotectComplaint(DenunciasGestiona source)
|
private DenunciasGestiona UnprotectComplaint(DenunciasGestiona source, byte[] key)
|
||||||
=> TransformComplaint(source, UnprotectString);
|
{
|
||||||
|
var decrypted = TransformComplaint(source, value => UnprotectString(value, key));
|
||||||
|
return RebuildComplaintFromPayload(decrypted);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DenunciasGestiona ToPersistentComplaint(DenunciasGestiona source)
|
||||||
|
{
|
||||||
|
return new DenunciasGestiona
|
||||||
|
{
|
||||||
|
// Permanentes tecnicos y de trazabilidad.
|
||||||
|
Id_RegistroDenuncia = source.Id_RegistroDenuncia,
|
||||||
|
Id_Denuncia = source.Id_Denuncia,
|
||||||
|
Fecha = source.Fecha,
|
||||||
|
Expediente_Gestiona = source.Expediente_Gestiona,
|
||||||
|
CodigoExpedienteGestiona = source.CodigoExpedienteGestiona,
|
||||||
|
Id_Persona_Gestiona = source.Id_Persona_Gestiona,
|
||||||
|
Etiqueta = source.Etiqueta,
|
||||||
|
Estado = source.Estado,
|
||||||
|
Confidencial = source.Confidencial,
|
||||||
|
EsActualizacion = source.EsActualizacion,
|
||||||
|
ProcedureId = source.ProcedureId,
|
||||||
|
GroupId = source.GroupId,
|
||||||
|
NombreDenuncia = source.NombreDenuncia,
|
||||||
|
EstadoDenuncia = source.EstadoDenuncia,
|
||||||
|
ArchivoElegido = source.ArchivoElegido,
|
||||||
|
FechaSubidaAGestiona = source.FechaSubidaAGestiona,
|
||||||
|
EnGestiona = source.EnGestiona,
|
||||||
|
EnRechazada = source.EnRechazada,
|
||||||
|
|
||||||
|
// Payload temporal cifrado. Los campos funcionales se derivan de aqui al leer.
|
||||||
|
CamposFormularioJson = source.CamposFormularioJson,
|
||||||
|
TextoOriginalReport = source.TextoOriginalReport
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DenunciasGestiona RebuildComplaintFromPayload(DenunciasGestiona stored)
|
||||||
|
{
|
||||||
|
var rebuilt = TryParseStoredReport(stored) ?? new DenunciasGestiona();
|
||||||
|
|
||||||
|
if (rebuilt.Id_Denuncia == 0)
|
||||||
|
{
|
||||||
|
rebuilt.Id_Denuncia = stored.Id_Denuncia;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rebuilt.Fecha == DateTime.MinValue)
|
||||||
|
{
|
||||||
|
rebuilt.Fecha = stored.Fecha;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(rebuilt.CamposFormularioJson))
|
||||||
|
{
|
||||||
|
rebuilt.CamposFormularioJson = stored.CamposFormularioJson;
|
||||||
|
}
|
||||||
|
|
||||||
|
rebuilt.TextoOriginalReport = stored.TextoOriginalReport;
|
||||||
|
ApplyPersistentTechnicalFields(rebuilt, stored);
|
||||||
|
return rebuilt;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DenunciasGestiona? TryParseStoredReport(DenunciasGestiona stored)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(stored.TextoOriginalReport))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return ReportParser.ParseReport(stored.TextoOriginalReport);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ApplyPersistentTechnicalFields(DenunciasGestiona target, DenunciasGestiona stored)
|
||||||
|
{
|
||||||
|
target.Id_RegistroDenuncia = stored.Id_RegistroDenuncia;
|
||||||
|
target.Id_Denuncia = stored.Id_Denuncia == 0 ? target.Id_Denuncia : stored.Id_Denuncia;
|
||||||
|
target.Expediente_Gestiona = stored.Expediente_Gestiona;
|
||||||
|
target.CodigoExpedienteGestiona = stored.CodigoExpedienteGestiona;
|
||||||
|
target.Id_Persona_Gestiona = stored.Id_Persona_Gestiona;
|
||||||
|
target.Etiqueta = stored.Etiqueta;
|
||||||
|
target.Estado = stored.Estado;
|
||||||
|
target.Confidencial = stored.Confidencial || target.Confidencial;
|
||||||
|
target.EsActualizacion = stored.EsActualizacion;
|
||||||
|
target.ProcedureId = stored.ProcedureId;
|
||||||
|
target.GroupId = stored.GroupId;
|
||||||
|
target.NombreDenuncia = stored.NombreDenuncia;
|
||||||
|
target.EstadoDenuncia = stored.EstadoDenuncia;
|
||||||
|
target.ArchivoElegido = stored.ArchivoElegido;
|
||||||
|
target.FechaSubidaAGestiona = stored.FechaSubidaAGestiona;
|
||||||
|
target.EnGestiona = stored.EnGestiona;
|
||||||
|
target.EnRechazada = stored.EnRechazada;
|
||||||
|
}
|
||||||
|
|
||||||
private static DenunciasGestiona TransformComplaint(DenunciasGestiona source, Func<string, string> transformString)
|
private static DenunciasGestiona TransformComplaint(DenunciasGestiona source, Func<string, string> transformString)
|
||||||
{
|
{
|
||||||
@@ -88,7 +209,7 @@ public sealed class EncryptedDenunciaStore : IDenunciaStore
|
|||||||
return target;
|
return target;
|
||||||
}
|
}
|
||||||
|
|
||||||
private FicherosDenuncias ProtectAttachment(FicherosDenuncias source)
|
private FicherosDenuncias ProtectAttachment(FicherosDenuncias source, byte[] key)
|
||||||
{
|
{
|
||||||
var content = source.Fichero ?? [];
|
var content = source.Fichero ?? [];
|
||||||
var hash = string.IsNullOrWhiteSpace(source.ContentSha256)
|
var hash = string.IsNullOrWhiteSpace(source.ContentSha256)
|
||||||
@@ -99,56 +220,62 @@ public sealed class EncryptedDenunciaStore : IDenunciaStore
|
|||||||
{
|
{
|
||||||
Id_Fichero = source.Id_Fichero,
|
Id_Fichero = source.Id_Fichero,
|
||||||
Id_Tipo = source.Id_Tipo,
|
Id_Tipo = source.Id_Tipo,
|
||||||
Descripcion = ProtectString(source.Descripcion ?? string.Empty),
|
Descripcion = ProtectString(source.Descripcion ?? string.Empty, key),
|
||||||
Fecha = source.Fecha,
|
Fecha = source.Fecha,
|
||||||
Observaciones = ProtectString(source.Observaciones ?? string.Empty),
|
Observaciones = ProtectString(source.Observaciones ?? string.Empty, key),
|
||||||
Id_Denuncia = source.Id_Denuncia,
|
Id_Denuncia = source.Id_Denuncia,
|
||||||
NombreFichero = source.NombreFichero,
|
NombreFichero = source.NombreFichero,
|
||||||
Fichero = ProtectBytes(content),
|
Fichero = ProtectBytes(content, key),
|
||||||
Subido = source.Subido,
|
Subido = source.Subido,
|
||||||
FechaSubida = source.FechaSubida,
|
FechaSubida = source.FechaSubida,
|
||||||
ContentSha256 = hash
|
ContentSha256 = hash
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private FicherosDenuncias UnprotectAttachment(FicherosDenuncias source)
|
private FicherosDenuncias UnprotectAttachment(FicherosDenuncias source, byte[] key)
|
||||||
{
|
{
|
||||||
return new FicherosDenuncias
|
return new FicherosDenuncias
|
||||||
{
|
{
|
||||||
Id_Fichero = source.Id_Fichero,
|
Id_Fichero = source.Id_Fichero,
|
||||||
Id_Tipo = source.Id_Tipo,
|
Id_Tipo = source.Id_Tipo,
|
||||||
Descripcion = UnprotectString(source.Descripcion ?? string.Empty),
|
Descripcion = UnprotectString(source.Descripcion ?? string.Empty, key),
|
||||||
Fecha = source.Fecha,
|
Fecha = source.Fecha,
|
||||||
Observaciones = UnprotectString(source.Observaciones ?? string.Empty),
|
Observaciones = UnprotectString(source.Observaciones ?? string.Empty, key),
|
||||||
Id_Denuncia = source.Id_Denuncia,
|
Id_Denuncia = source.Id_Denuncia,
|
||||||
NombreFichero = source.NombreFichero,
|
NombreFichero = source.NombreFichero,
|
||||||
Fichero = UnprotectBytes(source.Fichero ?? []),
|
Fichero = UnprotectBytes(source.Fichero ?? [], key),
|
||||||
Subido = source.Subido,
|
Subido = source.Subido,
|
||||||
FechaSubida = source.FechaSubida,
|
FechaSubida = source.FechaSubida,
|
||||||
ContentSha256 = source.ContentSha256
|
ContentSha256 = source.ContentSha256
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private string ProtectString(string value)
|
private string ProtectString(string value, byte[] key)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(value) || value.StartsWith(ProtectedStringPrefix, StringComparison.Ordinal))
|
if (string.IsNullOrWhiteSpace(value) ||
|
||||||
|
value.StartsWith(KeyVaultStringPrefix, StringComparison.Ordinal) ||
|
||||||
|
value.StartsWith(DataProtectionStringPrefix, StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ProtectedStringPrefix + _protector.Protect(value);
|
var encrypted = EncryptBytes(Encoding.UTF8.GetBytes(value), key);
|
||||||
|
return KeyVaultStringPrefix + Convert.ToBase64String(encrypted);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string UnprotectString(string value)
|
private string UnprotectString(string value, byte[] key)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(value) || !value.StartsWith(ProtectedStringPrefix, StringComparison.Ordinal))
|
if (string.IsNullOrWhiteSpace(value))
|
||||||
{
|
{
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (value.StartsWith(KeyVaultStringPrefix, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return _protector.Unprotect(value[ProtectedStringPrefix.Length..]);
|
var encrypted = Convert.FromBase64String(value[KeyVaultStringPrefix.Length..]);
|
||||||
|
return Encoding.UTF8.GetString(DecryptBytes(encrypted, key));
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@@ -156,28 +283,63 @@ public sealed class EncryptedDenunciaStore : IDenunciaStore
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] ProtectBytes(byte[] value)
|
if (!value.StartsWith(DataProtectionStringPrefix, StringComparison.Ordinal))
|
||||||
{
|
|
||||||
if (value.Length == 0 || StartsWith(value, ProtectedBytesPrefix))
|
|
||||||
{
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
var protectedBytes = _protector.Protect(value);
|
|
||||||
var base64Bytes = Encoding.ASCII.GetBytes(Convert.ToBase64String(protectedBytes));
|
|
||||||
return [.. ProtectedBytesPrefix, .. base64Bytes];
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] UnprotectBytes(byte[] value)
|
|
||||||
{
|
|
||||||
if (value.Length == 0 || !StartsWith(value, ProtectedBytesPrefix))
|
|
||||||
{
|
{
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var base64 = Encoding.ASCII.GetString(value, ProtectedBytesPrefix.Length, value.Length - ProtectedBytesPrefix.Length);
|
return _protector.Unprotect(value[DataProtectionStringPrefix.Length..]);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] ProtectBytes(byte[] value, byte[] key)
|
||||||
|
{
|
||||||
|
if (value.Length == 0 ||
|
||||||
|
StartsWith(value, KeyVaultBytesPrefix) ||
|
||||||
|
StartsWith(value, DataProtectionBytesPrefix))
|
||||||
|
{
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
var encrypted = EncryptBytes(value, key);
|
||||||
|
var base64Bytes = Encoding.ASCII.GetBytes(Convert.ToBase64String(encrypted));
|
||||||
|
return [.. KeyVaultBytesPrefix, .. base64Bytes];
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] UnprotectBytes(byte[] value, byte[] key)
|
||||||
|
{
|
||||||
|
if (value.Length == 0)
|
||||||
|
{
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StartsWith(value, KeyVaultBytesPrefix))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var base64 = Encoding.ASCII.GetString(value, KeyVaultBytesPrefix.Length, value.Length - KeyVaultBytesPrefix.Length);
|
||||||
|
return DecryptBytes(Convert.FromBase64String(base64), key);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!StartsWith(value, DataProtectionBytesPrefix))
|
||||||
|
{
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var base64 = Encoding.ASCII.GetString(value, DataProtectionBytesPrefix.Length, value.Length - DataProtectionBytesPrefix.Length);
|
||||||
return _protector.Unprotect(Convert.FromBase64String(base64));
|
return _protector.Unprotect(Convert.FromBase64String(base64));
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
@@ -186,6 +348,36 @@ public sealed class EncryptedDenunciaStore : IDenunciaStore
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static byte[] EncryptBytes(byte[] plainBytes, byte[] key)
|
||||||
|
{
|
||||||
|
var nonce = RandomNumberGenerator.GetBytes(AesGcmNonceSize);
|
||||||
|
var cipherBytes = new byte[plainBytes.Length];
|
||||||
|
var tag = new byte[AesGcmTagSize];
|
||||||
|
|
||||||
|
using var aes = new AesGcm(key, AesGcmTagSize);
|
||||||
|
aes.Encrypt(nonce, plainBytes, cipherBytes, tag);
|
||||||
|
|
||||||
|
return [.. nonce, .. tag, .. cipherBytes];
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] DecryptBytes(byte[] encryptedBytes, byte[] key)
|
||||||
|
{
|
||||||
|
if (encryptedBytes.Length < AesGcmNonceSize + AesGcmTagSize)
|
||||||
|
{
|
||||||
|
throw new CryptographicException("Payload cifrado invalido.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var nonce = encryptedBytes.AsSpan(0, AesGcmNonceSize);
|
||||||
|
var tag = encryptedBytes.AsSpan(AesGcmNonceSize, AesGcmTagSize);
|
||||||
|
var cipherBytes = encryptedBytes.AsSpan(AesGcmNonceSize + AesGcmTagSize);
|
||||||
|
var plainBytes = new byte[cipherBytes.Length];
|
||||||
|
|
||||||
|
using var aes = new AesGcm(key, AesGcmTagSize);
|
||||||
|
aes.Decrypt(nonce, cipherBytes, tag, plainBytes);
|
||||||
|
|
||||||
|
return plainBytes;
|
||||||
|
}
|
||||||
|
|
||||||
private static bool StartsWith(byte[] value, byte[] prefix)
|
private static bool StartsWith(byte[] value, byte[] prefix)
|
||||||
{
|
{
|
||||||
if (value.Length < prefix.Length)
|
if (value.Length < prefix.Length)
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ using System.Text.Json;
|
|||||||
using System.Text.Json.Nodes;
|
using System.Text.Json.Nodes;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace GestionaDenunciasAN.Services;
|
namespace ApiDenuncias.Services;
|
||||||
|
|
||||||
public sealed class GestionaDocumentWorkflowService
|
public sealed class GestionaDocumentWorkflowService
|
||||||
{
|
{
|
||||||
@@ -53,11 +53,9 @@ public sealed class GestionaDocumentWorkflowService
|
|||||||
|
|
||||||
using var metaReq = new HttpRequestMessage(HttpMethod.Post, documentsTargetUrl);
|
using var metaReq = new HttpRequestMessage(HttpMethod.Post, documentsTargetUrl);
|
||||||
metaReq.Headers.TryAddWithoutValidation("X-Gestiona-Access-Token", GestionaAccessToken);
|
metaReq.Headers.TryAddWithoutValidation("X-Gestiona-Access-Token", GestionaAccessToken);
|
||||||
|
metaReq.Headers.TryAddWithoutValidation("Prefer", "return=minimal");
|
||||||
metaReq.Headers.Accept.Clear();
|
metaReq.Headers.Accept.Clear();
|
||||||
metaReq.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/vnd.gestiona.file-document+json")
|
metaReq.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
||||||
{
|
|
||||||
Parameters = { new NameValueHeaderValue("version", "4") }
|
|
||||||
});
|
|
||||||
|
|
||||||
metaReq.Content = new StringContent(metaJson, Encoding.UTF8);
|
metaReq.Content = new StringContent(metaJson, Encoding.UTF8);
|
||||||
metaReq.Content.Headers.ContentType =
|
metaReq.Content.Headers.ContentType =
|
||||||
@@ -94,7 +92,7 @@ public sealed class GestionaDocumentWorkflowService
|
|||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
// Fallback a busqueda por nombre.
|
// Si Gestiona no devuelve un JSON valido, intentamos con la cabecera Location.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,20 +102,22 @@ public sealed class GestionaDocumentWorkflowService
|
|||||||
return location!;
|
return location!;
|
||||||
}
|
}
|
||||||
|
|
||||||
var found = await BuscarDocumentoEnExpedientePorNombreAsync(documentsTargetUrl, fileName);
|
|
||||||
if (!string.IsNullOrWhiteSpace(found))
|
|
||||||
{
|
|
||||||
return found!;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new InvalidOperationException("No se pudo obtener la URL del documento creado en Gestiona.");
|
throw new InvalidOperationException("No se pudo obtener la URL del documento creado en Gestiona.");
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task TramitarDocumentoAsync(string documentUrl, string assignedGroupHref, int? complaintId = null)
|
public async Task TramitarDocumentoAsync(string documentUrl, string assignedGroupHref, int? complaintId = null)
|
||||||
{
|
{
|
||||||
var docUrlAbs = EnsureAbsoluteGestionaUrl(documentUrl, GestionaApiBase);
|
var docUrlAbs = EnsureAbsoluteGestionaUrl(documentUrl, GestionaApiBase);
|
||||||
var template = await ObtenerTemplateCircuitoFirmaAsync(docUrlAbs);
|
var payload = BuildConfiguredCircuitPayload(docUrlAbs, assignedGroupHref, complaintId);
|
||||||
var payload = BuildCircuitPayloadFromTemplate(template, assignedGroupHref, complaintId);
|
string? templateNameForLog = "configurada";
|
||||||
|
string? templateHrefForLog = GetConfiguredTemplateHref(docUrlAbs);
|
||||||
|
|
||||||
|
if (payload is null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
"Faltan Gestiona:CircuitTemplateId o Gestiona:CircuitSignerStampHref. No se listan plantillas para evitar campos deprecated.");
|
||||||
|
}
|
||||||
|
|
||||||
var json = payload.ToJsonString(new JsonSerializerOptions(JsonSerializerDefaults.Web));
|
var json = payload.ToJsonString(new JsonSerializerOptions(JsonSerializerDefaults.Web));
|
||||||
|
|
||||||
using var req = new HttpRequestMessage(HttpMethod.Post, $"{docUrlAbs.TrimEnd('/')}/circuit");
|
using var req = new HttpRequestMessage(HttpMethod.Post, $"{docUrlAbs.TrimEnd('/')}/circuit");
|
||||||
@@ -134,8 +134,8 @@ public sealed class GestionaDocumentWorkflowService
|
|||||||
_logger.LogError(
|
_logger.LogError(
|
||||||
"Fallo al tramitar documento {DocumentUrl} con plantilla {TemplateName} ({TemplateHref}). Status: {StatusCode}. Body: {Body}",
|
"Fallo al tramitar documento {DocumentUrl} con plantilla {TemplateName} ({TemplateHref}). Status: {StatusCode}. Body: {Body}",
|
||||||
docUrlAbs,
|
docUrlAbs,
|
||||||
template.Name ?? "(sin nombre)",
|
templateNameForLog,
|
||||||
template.Href,
|
templateHrefForLog,
|
||||||
(int)resp.StatusCode,
|
(int)resp.StatusCode,
|
||||||
body);
|
body);
|
||||||
|
|
||||||
@@ -146,11 +146,67 @@ public sealed class GestionaDocumentWorkflowService
|
|||||||
_logger.LogInformation(
|
_logger.LogInformation(
|
||||||
"Documento {DocumentUrl} enviado a circuito {TemplateName} ({TemplateHref}) para denuncia {ComplaintId}.",
|
"Documento {DocumentUrl} enviado a circuito {TemplateName} ({TemplateHref}) para denuncia {ComplaintId}.",
|
||||||
docUrlAbs,
|
docUrlAbs,
|
||||||
template.Name ?? "(sin nombre)",
|
templateNameForLog,
|
||||||
template.Href,
|
templateHrefForLog,
|
||||||
complaintId);
|
complaintId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private JsonObject? BuildConfiguredCircuitPayload(string documentUrl, string assignedGroupHref, int? complaintId)
|
||||||
|
{
|
||||||
|
_ = assignedGroupHref;
|
||||||
|
_ = complaintId;
|
||||||
|
|
||||||
|
var templateHref = GetConfiguredTemplateHref(documentUrl);
|
||||||
|
var signerHref = _configuration["Gestiona:CircuitSignerStampHref"];
|
||||||
|
if (string.IsNullOrWhiteSpace(templateHref) || string.IsNullOrWhiteSpace(signerHref))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var payload = new JsonObject
|
||||||
|
{
|
||||||
|
["block_edit"] = true,
|
||||||
|
["send_alerts"] = true,
|
||||||
|
["version"] = _configuration["Gestiona:CircuitVersion"] ?? "2",
|
||||||
|
["signers"] = new JsonArray
|
||||||
|
{
|
||||||
|
JsonSerializer.SerializeToNode(new
|
||||||
|
{
|
||||||
|
rel = "signer-stamp",
|
||||||
|
href = signerHref,
|
||||||
|
title = _configuration["Gestiona:CircuitSignerStampTitle"] ?? "oaaf-complaints-tramit"
|
||||||
|
})
|
||||||
|
},
|
||||||
|
["links"] = new JsonArray
|
||||||
|
{
|
||||||
|
JsonSerializer.SerializeToNode(new { rel = "self", href = templateHref })
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var recipientGroupHref = _configuration["Gestiona:CircuitRecipientGroupHref"];
|
||||||
|
if (!string.IsNullOrWhiteSpace(recipientGroupHref))
|
||||||
|
{
|
||||||
|
payload["recipients"] = new JsonArray
|
||||||
|
{
|
||||||
|
JsonSerializer.SerializeToNode(new
|
||||||
|
{
|
||||||
|
rel = "group",
|
||||||
|
href = recipientGroupHref
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string? GetConfiguredTemplateHref(string documentUrl)
|
||||||
|
{
|
||||||
|
var templateId = _configuration["Gestiona:CircuitTemplateId"];
|
||||||
|
return string.IsNullOrWhiteSpace(templateId)
|
||||||
|
? null
|
||||||
|
: $"{documentUrl.TrimEnd('/')}/circuit/templates/{templateId.Trim()}";
|
||||||
|
}
|
||||||
|
|
||||||
private HttpClient CreateRawHttp() => _httpClientFactory.CreateClient();
|
private HttpClient CreateRawHttp() => _httpClientFactory.CreateClient();
|
||||||
|
|
||||||
private async Task<string> CreateUploadAsync(byte[] contentBytes, string fileName)
|
private async Task<string> CreateUploadAsync(byte[] contentBytes, string fileName)
|
||||||
@@ -196,55 +252,6 @@ public sealed class GestionaDocumentWorkflowService
|
|||||||
return uploadUri;
|
return uploadUri;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<string?> BuscarDocumentoEnExpedientePorNombreAsync(string documentsTargetUrl, string fileName)
|
|
||||||
{
|
|
||||||
using var req = new HttpRequestMessage(HttpMethod.Get, documentsTargetUrl);
|
|
||||||
req.Headers.TryAddWithoutValidation("X-Gestiona-Access-Token", GestionaAccessToken);
|
|
||||||
req.Headers.TryAddWithoutValidation("Accept", "*/*");
|
|
||||||
|
|
||||||
using var resp = await CreateRawHttp().SendAsync(req);
|
|
||||||
var body = await resp.Content.ReadAsStringAsync();
|
|
||||||
if (!resp.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException(
|
|
||||||
$"BuscarDocumentoEnExpedientePorNombreAsync: {(int)resp.StatusCode} {resp.StatusCode}\n{body}");
|
|
||||||
}
|
|
||||||
|
|
||||||
using var doc = JsonDocument.Parse(body);
|
|
||||||
if (!doc.RootElement.TryGetProperty("content", out var content) || content.ValueKind != JsonValueKind.Array)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var items = content.EnumerateArray().ToList();
|
|
||||||
for (var idx = items.Count - 1; idx >= 0; idx--)
|
|
||||||
{
|
|
||||||
var item = items[idx];
|
|
||||||
var name = item.TryGetProperty("name", out var pName) ? pName.GetString() : null;
|
|
||||||
if (!string.Equals(name, fileName, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.TryGetProperty("links", out var links) && links.ValueKind == JsonValueKind.Array)
|
|
||||||
{
|
|
||||||
foreach (var link in links.EnumerateArray())
|
|
||||||
{
|
|
||||||
var rel = link.TryGetProperty("rel", out var pRel) ? pRel.GetString() : null;
|
|
||||||
var href = link.TryGetProperty("href", out var pHref) ? pHref.GetString() : null;
|
|
||||||
if (string.Equals(rel, "self", StringComparison.OrdinalIgnoreCase) &&
|
|
||||||
!string.IsNullOrWhiteSpace(href) &&
|
|
||||||
href!.Contains("/documents/", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return href;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string ResolveDocumentsContainerUrl(string url)
|
private static string ResolveDocumentsContainerUrl(string url)
|
||||||
{
|
{
|
||||||
var normalized = url.TrimEnd('/');
|
var normalized = url.TrimEnd('/');
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
using GestionaDenunciasAN.Models;
|
using GestionaDenuncias.Shared.Models;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@@ -13,7 +13,7 @@ using System.Text.Json.Serialization;
|
|||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace GestionaDenunciasAN.Services
|
namespace ApiDenuncias.Services
|
||||||
{
|
{
|
||||||
public class GestionaService : IGestionaService
|
public class GestionaService : IGestionaService
|
||||||
{
|
{
|
||||||
@@ -58,7 +58,7 @@ namespace GestionaDenunciasAN.Services
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reemplaza este helper si quieres controlar la versión en Accept:
|
// Reemplaza este helper si quieres controlar la versión en Accept:
|
||||||
private void AddTokenAndAccept(HttpRequestMessage req, string mediaType, string? version = null)
|
private void AddTokenAndAccept(HttpRequestMessage req, string mediaType, string? version = null)
|
||||||
{
|
{
|
||||||
req.Headers.TryAddWithoutValidation("X-Gestiona-Access-Token", _opts.AccessToken);
|
req.Headers.TryAddWithoutValidation("X-Gestiona-Access-Token", _opts.AccessToken);
|
||||||
@@ -80,7 +80,7 @@ namespace GestionaDenunciasAN.Services
|
|||||||
{
|
{
|
||||||
req.Headers.TryAddWithoutValidation("X-Gestiona-Access-Token", _opts.AccessToken);
|
req.Headers.TryAddWithoutValidation("X-Gestiona-Access-Token", _opts.AccessToken);
|
||||||
req.Headers.Accept.Clear();
|
req.Headers.Accept.Clear();
|
||||||
req.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/vnd.gestiona.files-page+json"));
|
req.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -102,7 +102,7 @@ namespace GestionaDenunciasAN.Services
|
|||||||
var url = await ResolveExternalProcedureCreateFileUrlAsync(effectiveProcedureId);
|
var url = await ResolveExternalProcedureCreateFileUrlAsync(effectiveProcedureId);
|
||||||
using var req = new HttpRequestMessage(HttpMethod.Post, url);
|
using var req = new HttpRequestMessage(HttpMethod.Post, url);
|
||||||
req.Headers.Accept.Clear();
|
req.Headers.Accept.Clear();
|
||||||
req.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/vnd.gestiona.file-opening+json"));
|
req.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
||||||
req.Headers.TryAddWithoutValidation("X-Gestiona-Access-Token", _opts.AccessToken);
|
req.Headers.TryAddWithoutValidation("X-Gestiona-Access-Token", _opts.AccessToken);
|
||||||
|
|
||||||
using var resp = await _http.SendAsync(req);
|
using var resp = await _http.SendAsync(req);
|
||||||
@@ -113,7 +113,7 @@ namespace GestionaDenunciasAN.Services
|
|||||||
using var doc = JsonDocument.Parse(body);
|
using var doc = JsonDocument.Parse(body);
|
||||||
var fileUrl = GetLinkHref(doc.RootElement, "file")
|
var fileUrl = GetLinkHref(doc.RootElement, "file")
|
||||||
?? throw new InvalidOperationException("CreateFileAsync: Gestiona no ha devuelto link 'file'.");
|
?? throw new InvalidOperationException("CreateFileAsync: Gestiona no ha devuelto link 'file'.");
|
||||||
var fileOpenUrl = GetLinkHref(doc.RootElement, "file-open") ?? resp.Headers.Location?.ToString();
|
var fileOpenUrl = GetLinkHref(doc.RootElement, "file-open");
|
||||||
|
|
||||||
return new GestionaCreateFileResponse(fileUrl, fileOpenUrl);
|
return new GestionaCreateFileResponse(fileUrl, fileOpenUrl);
|
||||||
}
|
}
|
||||||
@@ -208,7 +208,7 @@ namespace GestionaDenunciasAN.Services
|
|||||||
content.Headers.ContentType!.Parameters.Add(new NameValueHeaderValue("version", "1"));
|
content.Headers.ContentType!.Parameters.Add(new NameValueHeaderValue("version", "1"));
|
||||||
|
|
||||||
using var req = new HttpRequestMessage(HttpMethod.Post, url) { Content = content };
|
using var req = new HttpRequestMessage(HttpMethod.Post, url) { Content = content };
|
||||||
AddTokenAndAccept(req, "application/vnd.gestiona.file+json", "2");
|
AddTokenAndAccept(req, "application/json");
|
||||||
|
|
||||||
using var resp = await _http.SendAsync(req);
|
using var resp = await _http.SendAsync(req);
|
||||||
var body = await resp.Content.ReadAsStringAsync();
|
var body = await resp.Content.ReadAsStringAsync();
|
||||||
@@ -228,7 +228,7 @@ namespace GestionaDenunciasAN.Services
|
|||||||
{
|
{
|
||||||
Content = content
|
Content = content
|
||||||
};
|
};
|
||||||
AddTokenAndAccept(req, "application/vnd.gestiona.file-folder+json");
|
AddTokenAndAccept(req, "application/json");
|
||||||
|
|
||||||
using var resp = await _http.SendAsync(req);
|
using var resp = await _http.SendAsync(req);
|
||||||
var body = await resp.Content.ReadAsStringAsync();
|
var body = await resp.Content.ReadAsStringAsync();
|
||||||
@@ -256,7 +256,7 @@ namespace GestionaDenunciasAN.Services
|
|||||||
throw new InvalidOperationException($"CreateUpload (POST): {(int)createResp.StatusCode} {createResp.StatusCode}\n{createBody}");
|
throw new InvalidOperationException($"CreateUpload (POST): {(int)createResp.StatusCode} {createResp.StatusCode}\n{createBody}");
|
||||||
|
|
||||||
var uploadUri = createResp.Headers.Location?.ToString()
|
var uploadUri = createResp.Headers.Location?.ToString()
|
||||||
?? throw new InvalidOperationException("No se devolvió Location en /rest/uploads");
|
?? throw new InvalidOperationException("No se devolvió Location en /rest/uploads");
|
||||||
|
|
||||||
string md5Hex;
|
string md5Hex;
|
||||||
using (var md5 = MD5.Create())
|
using (var md5 = MD5.Create())
|
||||||
@@ -310,8 +310,7 @@ namespace GestionaDenunciasAN.Services
|
|||||||
{ Content = metaContent };
|
{ Content = metaContent };
|
||||||
metaReq.Headers.TryAddWithoutValidation("X-Gestiona-Access-Token", _opts.AccessToken);
|
metaReq.Headers.TryAddWithoutValidation("X-Gestiona-Access-Token", _opts.AccessToken);
|
||||||
metaReq.Headers.Accept.Clear();
|
metaReq.Headers.Accept.Clear();
|
||||||
metaReq.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/vnd.gestiona.file-document+json")
|
metaReq.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
||||||
{ Parameters = { new NameValueHeaderValue("version", "4") } });
|
|
||||||
|
|
||||||
using var metaResp = await _http.SendAsync(metaReq);
|
using var metaResp = await _http.SendAsync(metaReq);
|
||||||
var body = await metaResp.Content.ReadAsStringAsync();
|
var body = await metaResp.Content.ReadAsStringAsync();
|
||||||
@@ -387,7 +386,7 @@ namespace GestionaDenunciasAN.Services
|
|||||||
if (thirdParty.IsLegalEntity)
|
if (thirdParty.IsLegalEntity)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(thirdParty.BusinessName))
|
if (string.IsNullOrWhiteSpace(thirdParty.BusinessName))
|
||||||
throw new ArgumentException("La razón social es obligatoria para terceros jurídicos.", nameof(thirdParty));
|
throw new ArgumentException("La razón social es obligatoria para terceros jurídicos.", nameof(thirdParty));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -562,7 +561,7 @@ namespace GestionaDenunciasAN.Services
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- CONSULTAS DE EXPEDIENTES (sin recorrer histórico paginado) ---
|
// --- CONSULTAS DE EXPEDIENTES (sin recorrer histórico paginado) ---
|
||||||
|
|
||||||
private async Task<string> GetFilesAsync(object? filter = null)
|
private async Task<string> GetFilesAsync(object? filter = null)
|
||||||
{
|
{
|
||||||
@@ -613,7 +612,7 @@ namespace GestionaDenunciasAN.Services
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Devuelve el JSON crudo de /rest/files acumulando hasta maxPages páginas.
|
/// Devuelve el JSON crudo de /rest/files acumulando hasta maxPages páginas.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public async Task<string> ListarExpedientesJsonAsyncBasico(int maxPages = 1)
|
public async Task<string> ListarExpedientesJsonAsyncBasico(int maxPages = 1)
|
||||||
{
|
{
|
||||||
@@ -911,7 +910,7 @@ namespace GestionaDenunciasAN.Services
|
|||||||
using var resp = await _http.SendAsync(req);
|
using var resp = await _http.SendAsync(req);
|
||||||
var body = await resp.Content.ReadAsStringAsync();
|
var body = await resp.Content.ReadAsStringAsync();
|
||||||
if (!resp.IsSuccessStatusCode)
|
if (!resp.IsSuccessStatusCode)
|
||||||
throw new InvalidOperationException($"Error actualizando dirección del tercero: {(int)resp.StatusCode} {resp.ReasonPhrase}\n{body}");
|
throw new InvalidOperationException($"Error actualizando dirección del tercero: {(int)resp.StatusCode} {resp.ReasonPhrase}\n{body}");
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<bool> ThirdHasAddressesAsync(string thirdSelfHref)
|
private async Task<bool> ThirdHasAddressesAsync(string thirdSelfHref)
|
||||||
@@ -1161,7 +1160,7 @@ namespace GestionaDenunciasAN.Services
|
|||||||
return value switch
|
return value switch
|
||||||
{
|
{
|
||||||
"" => "ESP",
|
"" => "ESP",
|
||||||
"es" or "esp" or "espana" or "españa" or "spain" => "ESP",
|
"es" or "esp" or "espana" or "españa" or "spain" => "ESP",
|
||||||
"prt" or "pt" or "portugal" => "PRT",
|
"prt" or "pt" or "portugal" => "PRT",
|
||||||
_ when country is { Length: >= 3 } => country.Trim().ToUpperInvariant()[..3],
|
_ when country is { Length: >= 3 } => country.Trim().ToUpperInvariant()[..3],
|
||||||
_ => "ESP",
|
_ => "ESP",
|
||||||
@@ -5,12 +5,12 @@ using System.Net.Http.Json;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using GestionaDenunciasAN.Configuration;
|
using ApiDenuncias.Configuration;
|
||||||
using GestionaDenunciasAN.Models;
|
using GestionaDenuncias.Shared.Models;
|
||||||
using Konscious.Security.Cryptography;
|
using Konscious.Security.Cryptography;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
namespace GestionaDenunciasAN.Services;
|
namespace ApiDenuncias.Services;
|
||||||
|
|
||||||
public sealed class GlobalLeaksClient
|
public sealed class GlobalLeaksClient
|
||||||
{
|
{
|
||||||
@@ -23,11 +23,25 @@ public sealed class GlobalLeaksClient
|
|||||||
{
|
{
|
||||||
_options = options.Value;
|
_options = options.Value;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_httpClient = new HttpClient
|
|
||||||
|
var handler = new HttpClientHandler();
|
||||||
|
if (_options.AllowInvalidCertificate)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("GlobalLeaks permite certificados TLS no validos. Usar solo temporalmente en PRE.");
|
||||||
|
handler.ServerCertificateCustomValidationCallback =
|
||||||
|
HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
|
||||||
|
}
|
||||||
|
|
||||||
|
_httpClient = new HttpClient(handler)
|
||||||
{
|
{
|
||||||
BaseAddress = new Uri(_options.BaseUrl.TrimEnd('/')),
|
BaseAddress = new Uri(_options.BaseUrl.TrimEnd('/')),
|
||||||
Timeout = TimeSpan.FromSeconds(_options.TimeoutSeconds),
|
Timeout = TimeSpan.FromSeconds(_options.TimeoutSeconds),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(_options.HostHeader))
|
||||||
|
{
|
||||||
|
_httpClient.DefaultRequestHeaders.Host = _options.HostHeader.Trim();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<GlSession> LoginAsync(
|
public async Task<GlSession> LoginAsync(
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace GestionaDenunciasAN.Services;
|
namespace ApiDenuncias.Services;
|
||||||
|
|
||||||
public sealed class GlobalLeaksValidationException(string message, int statusCode = 400) : Exception(message)
|
public sealed class GlobalLeaksValidationException(string message, int statusCode = 400) : Exception(message)
|
||||||
{
|
{
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using GestionaDenunciasAN.Models;
|
using GestionaDenuncias.Shared.Models;
|
||||||
using Microsoft.AspNetCore.DataProtection;
|
using Microsoft.AspNetCore.DataProtection;
|
||||||
|
|
||||||
namespace GestionaDenunciasAN.Services;
|
namespace ApiDenuncias.Services;
|
||||||
|
|
||||||
public sealed class GlobalLeaksSessionStore
|
public sealed class GlobalLeaksSessionStore
|
||||||
{
|
{
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace ApiDenuncias.Services;
|
||||||
|
|
||||||
|
public interface IEncryptionKeyProvider
|
||||||
|
{
|
||||||
|
ValueTask<byte[]> GetKeyAsync(CancellationToken cancellationToken = default);
|
||||||
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
using GestionaDenunciasAN.Models;
|
using GestionaDenuncias.Shared.Models;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace GestionaDenunciasAN.Services
|
namespace ApiDenuncias.Services
|
||||||
{
|
{
|
||||||
public interface IGestionaService
|
public interface IGestionaService
|
||||||
{
|
{
|
||||||
@@ -22,7 +22,7 @@ namespace GestionaDenunciasAN.Services
|
|||||||
);
|
);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Abre el expediente (lo pone en OPEN_EDITABLE), asigna título, clasificación y lo vincula al grupo indicado.
|
/// Abre el expediente (lo pone en OPEN_EDITABLE), asigna título, clasificación y lo vincula al grupo indicado.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Task OpenFileAsync(
|
Task OpenFileAsync(
|
||||||
string fileUrl,
|
string fileUrl,
|
||||||
@@ -47,7 +47,7 @@ namespace GestionaDenunciasAN.Services
|
|||||||
);
|
);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Crea el documento (metadata) y sube el contenido PDF a la raíz o a una carpeta.
|
/// Crea el documento (metadata) y sube el contenido PDF a la raíz o a una carpeta.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Task UploadDocumentAsync(
|
Task UploadDocumentAsync(
|
||||||
string fileUrl,
|
string fileUrl,
|
||||||
@@ -56,7 +56,7 @@ namespace GestionaDenunciasAN.Services
|
|||||||
);
|
);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Crea una carpeta de nombre 'folderName' en la raíz del expediente y devuelve su GUID.
|
/// Crea una carpeta de nombre 'folderName' en la raíz del expediente y devuelve su GUID.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Task<Guid> CreateFolderAsync(
|
Task<Guid> CreateFolderAsync(
|
||||||
string fileUrl,
|
string fileUrl,
|
||||||
@@ -89,9 +89,9 @@ namespace GestionaDenunciasAN.Services
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Usa el NIF tal cual viene.
|
/// Usa el NIF tal cual viene.
|
||||||
/// Si es anónimo o vacío → no crea ni enlaza.
|
/// Si es anónimo o vacío ? no crea ni enlaza.
|
||||||
/// Si no existe, lo crea.
|
/// Si no existe, lo crea.
|
||||||
/// Si no está enlazado al expediente, lo enlaza.
|
/// Si no está enlazado al expediente, lo enlaza.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Task AsegurarTerceroYEnlazarAsync(string fileUrl, ThirdPartyIdentityData thirdParty);
|
Task AsegurarTerceroYEnlazarAsync(string fileUrl, ThirdPartyIdentityData thirdParty);
|
||||||
|
|
||||||
@@ -102,13 +102,13 @@ namespace GestionaDenunciasAN.Services
|
|||||||
// =========================
|
// =========================
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Devuelve el JSON crudo del listado operativo de expedientes, sin recorrer histórico paginado.
|
/// Devuelve el JSON crudo del listado operativo de expedientes, sin recorrer histórico paginado.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Task<string> ListarExpedientesJsonAsyncBasico(int maxPages = 1);
|
Task<string> ListarExpedientesJsonAsyncBasico(int maxPages = 1);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Busca directamente un expediente cuyo asunto sea "Denuncia {idDenuncia}-CD".
|
/// Busca directamente un expediente cuyo asunto sea "Denuncia {idDenuncia}-CD".
|
||||||
/// Devuelve URL, número de expediente y título si lo encuentra; null si no.
|
/// Devuelve URL, número de expediente y título si lo encuentra; null si no.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Task<GestionaExpedienteInfo?> BuscarExpedientePorIdEnAsuntoAsync(int idDenuncia);
|
Task<GestionaExpedienteInfo?> BuscarExpedientePorIdEnAsuntoAsync(int idDenuncia);
|
||||||
|
|
||||||
@@ -1,22 +1,20 @@
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using GestionaDenunciasAN.Configuration;
|
using GestionaDenuncias.Shared.Models;
|
||||||
using GestionaDenunciasAN.Models;
|
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
using MySqlConnector;
|
using MySqlConnector;
|
||||||
|
|
||||||
namespace GestionaDenunciasAN.Services;
|
namespace ApiDenuncias.Services;
|
||||||
|
|
||||||
public sealed class InboxTrackingService : IInboxTrackingService
|
public sealed class InboxTrackingService : IInboxTrackingService
|
||||||
{
|
{
|
||||||
private readonly ComplaintStorageOptions _options;
|
|
||||||
private readonly IDenunciaStore _denunciaStore;
|
private readonly IDenunciaStore _denunciaStore;
|
||||||
|
private readonly MySqlConnectionStringProvider _connectionStringProvider;
|
||||||
|
|
||||||
public InboxTrackingService(
|
public InboxTrackingService(
|
||||||
IOptions<ComplaintStorageOptions> options,
|
IDenunciaStore denunciaStore,
|
||||||
IDenunciaStore denunciaStore)
|
MySqlConnectionStringProvider connectionStringProvider)
|
||||||
{
|
{
|
||||||
_options = options.Value;
|
|
||||||
_denunciaStore = denunciaStore;
|
_denunciaStore = denunciaStore;
|
||||||
|
_connectionStringProvider = connectionStringProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<InboxUserState> GetUserStateAsync(string username, CancellationToken cancellationToken = default)
|
public async Task<InboxUserState> GetUserStateAsync(string username, CancellationToken cancellationToken = default)
|
||||||
@@ -107,9 +105,49 @@ public sealed class InboxTrackingService : IInboxTrackingService
|
|||||||
TrackingNote = BuildTrackingNote(meta)
|
TrackingNote = BuildTrackingNote(meta)
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
|
.Where(report => !IsLockedByAnotherUser(report))
|
||||||
.ToArray();
|
.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task EnsureReportCanBeImportedByUserAsync(
|
||||||
|
string username,
|
||||||
|
ReportDto report,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
await _denunciaStore.EnsureSchemaAsync(cancellationToken);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(report.Id))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("No se ha podido validar la propiedad de la denuncia.");
|
||||||
|
}
|
||||||
|
|
||||||
|
await using var connection = await OpenConnectionAsync(cancellationToken);
|
||||||
|
var userId = await EnsureUserAsync(connection, username, cancellationToken);
|
||||||
|
await using var transaction = await connection.BeginTransactionAsync(cancellationToken);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await UpsertInboxReportAsync(connection, (MySqlTransaction)transaction, report, cancellationToken);
|
||||||
|
await transaction.CommitAsync(cancellationToken);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
await transaction.RollbackAsync(cancellationToken);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
var metadata = await LoadMetadataAsync(connection, userId, [report.Id], cancellationToken);
|
||||||
|
if (metadata.TryGetValue(report.Id, out var meta) && meta.LockedByAnotherUser)
|
||||||
|
{
|
||||||
|
var owner = string.IsNullOrWhiteSpace(meta.LastDownloadedByUsername)
|
||||||
|
? "otro usuario"
|
||||||
|
: meta.LastDownloadedByUsername;
|
||||||
|
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"La denuncia ya fue importada por {owner}. Solo ese usuario puede ver e importar sus actualizaciones.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async Task MarkReportImportedAsync(
|
public async Task MarkReportImportedAsync(
|
||||||
string username,
|
string username,
|
||||||
ReportDto report,
|
ReportDto report,
|
||||||
@@ -390,6 +428,11 @@ public sealed class InboxTrackingService : IInboxTrackingService
|
|||||||
? null
|
? null
|
||||||
: reader.GetString(reader.GetOrdinal("last_downloaded_by_username"));
|
: reader.GetString(reader.GetOrdinal("last_downloaded_by_username"));
|
||||||
var downloadedByCurrentUser = reader.GetInt32(reader.GetOrdinal("downloaded_by_current_user")) == 1;
|
var downloadedByCurrentUser = reader.GetInt32(reader.GetOrdinal("downloaded_by_current_user")) == 1;
|
||||||
|
var lockedByAnotherUser =
|
||||||
|
!downloadedByCurrentUser &&
|
||||||
|
!reader.IsDBNull(reader.GetOrdinal("imported_to_store_at_utc")) &&
|
||||||
|
!string.IsNullOrWhiteSpace(lastDownloadedByUsername);
|
||||||
|
|
||||||
var downloadedByAnotherUser =
|
var downloadedByAnotherUser =
|
||||||
!downloadedByCurrentUser &&
|
!downloadedByCurrentUser &&
|
||||||
!string.IsNullOrWhiteSpace(lastDownloadedByUsername);
|
!string.IsNullOrWhiteSpace(lastDownloadedByUsername);
|
||||||
@@ -402,6 +445,7 @@ public sealed class InboxTrackingService : IInboxTrackingService
|
|||||||
LastDownloadedAtUtc = GetDateTimeOffset(reader, "last_downloaded_at_utc"),
|
LastDownloadedAtUtc = GetDateTimeOffset(reader, "last_downloaded_at_utc"),
|
||||||
AlreadyImported = !reader.IsDBNull(reader.GetOrdinal("imported_to_store_at_utc")),
|
AlreadyImported = !reader.IsDBNull(reader.GetOrdinal("imported_to_store_at_utc")),
|
||||||
AlreadyInGestiona = reader.GetInt32(reader.GetOrdinal("already_in_gestiona")) == 1,
|
AlreadyInGestiona = reader.GetInt32(reader.GetOrdinal("already_in_gestiona")) == 1,
|
||||||
|
LockedByAnotherUser = lockedByAnotherUser,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -410,13 +454,8 @@ public sealed class InboxTrackingService : IInboxTrackingService
|
|||||||
|
|
||||||
private async Task<MySqlConnection> OpenConnectionAsync(CancellationToken cancellationToken)
|
private async Task<MySqlConnection> OpenConnectionAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(_options.ConnectionString))
|
var connectionString = await _connectionStringProvider.GetConnectionStringAsync(cancellationToken);
|
||||||
{
|
var connection = new MySqlConnection(connectionString);
|
||||||
throw new InvalidOperationException(
|
|
||||||
"Falta configurar ComplaintStorage:ConnectionString en appsettings.json.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var connection = new MySqlConnection(_options.ConnectionString);
|
|
||||||
await connection.OpenAsync(cancellationToken);
|
await connection.OpenAsync(cancellationToken);
|
||||||
await using var timeZoneCommand = new MySqlCommand("SET time_zone = '+00:00';", connection);
|
await using var timeZoneCommand = new MySqlCommand("SET time_zone = '+00:00';", connection);
|
||||||
await timeZoneCommand.ExecuteNonQueryAsync(cancellationToken);
|
await timeZoneCommand.ExecuteNonQueryAsync(cancellationToken);
|
||||||
@@ -482,6 +521,13 @@ public sealed class InboxTrackingService : IInboxTrackingService
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (metadata.LockedByAnotherUser)
|
||||||
|
{
|
||||||
|
return string.IsNullOrWhiteSpace(metadata.LastDownloadedByUsername)
|
||||||
|
? "Importada por otro usuario"
|
||||||
|
: $"Importada por {metadata.LastDownloadedByUsername}";
|
||||||
|
}
|
||||||
|
|
||||||
if (metadata.AlreadyInGestiona)
|
if (metadata.AlreadyInGestiona)
|
||||||
{
|
{
|
||||||
return "Ya existe expediente en Gestiona";
|
return "Ya existe expediente en Gestiona";
|
||||||
@@ -514,9 +560,15 @@ public sealed class InboxTrackingService : IInboxTrackingService
|
|||||||
{
|
{
|
||||||
public bool DownloadedByCurrentUser { get; init; }
|
public bool DownloadedByCurrentUser { get; init; }
|
||||||
public bool DownloadedByAnotherUser { get; init; }
|
public bool DownloadedByAnotherUser { get; init; }
|
||||||
|
public bool LockedByAnotherUser { get; init; }
|
||||||
public string? LastDownloadedByUsername { get; init; }
|
public string? LastDownloadedByUsername { get; init; }
|
||||||
public DateTimeOffset? LastDownloadedAtUtc { get; init; }
|
public DateTimeOffset? LastDownloadedAtUtc { get; init; }
|
||||||
public bool AlreadyImported { get; init; }
|
public bool AlreadyImported { get; init; }
|
||||||
public bool AlreadyInGestiona { get; init; }
|
public bool AlreadyInGestiona { get; init; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool IsLockedByAnotherUser(ReportDto report)
|
||||||
|
=> report.AlreadyImported &&
|
||||||
|
report.DownloadedByAnotherUser &&
|
||||||
|
!report.DownloadedByCurrentUser;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
using ApiDenuncias.Configuration;
|
||||||
|
using Azure;
|
||||||
|
using Azure.Core;
|
||||||
|
using Azure.Identity;
|
||||||
|
using Azure.Security.KeyVault.Secrets;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
|
namespace ApiDenuncias.Services;
|
||||||
|
|
||||||
|
public sealed class KeyVaultEncryptionKeyProvider : IEncryptionKeyProvider
|
||||||
|
{
|
||||||
|
private readonly KeyVaultOptions _options;
|
||||||
|
private readonly IConfiguration _configuration;
|
||||||
|
private readonly ILogger<KeyVaultEncryptionKeyProvider> _logger;
|
||||||
|
private readonly Lazy<Task<byte[]>> _keyLoader;
|
||||||
|
|
||||||
|
public KeyVaultEncryptionKeyProvider(
|
||||||
|
IOptions<KeyVaultOptions> options,
|
||||||
|
IConfiguration configuration,
|
||||||
|
ILogger<KeyVaultEncryptionKeyProvider> logger)
|
||||||
|
{
|
||||||
|
_options = options.Value;
|
||||||
|
_configuration = configuration;
|
||||||
|
_logger = logger;
|
||||||
|
_keyLoader = new Lazy<Task<byte[]>>(LoadKeyAsync);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<byte[]> GetKeyAsync(CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var key = await _keyLoader.Value.WaitAsync(cancellationToken);
|
||||||
|
return key.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<byte[]> LoadKeyAsync()
|
||||||
|
{
|
||||||
|
var configuredLocalKey = _configuration["Encryption:LocalDevelopmentKey"];
|
||||||
|
if (!_options.Enabled)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(configuredLocalKey))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
"Key Vault esta deshabilitado y no se ha configurado Encryption:LocalDevelopmentKey.");
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogWarning("Key Vault deshabilitado. Usando clave local solo para pruebas de desarrollo.");
|
||||||
|
return NormalizeKey(configuredLocalKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(_options.VaultUrl))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("KeyVault:VaultUrl no esta configurado.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(_options.EncryptionKeySecretName))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("KeyVault:EncryptionKeySecretName no esta configurado.");
|
||||||
|
}
|
||||||
|
var credential = new DefaultAzureCredential();
|
||||||
|
var client = new SecretClient(new Uri(_options.VaultUrl), credential);
|
||||||
|
KeyVaultSecret secret;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var response = await client.GetSecretAsync(_options.EncryptionKeySecretName);
|
||||||
|
secret = response.Value;
|
||||||
|
}
|
||||||
|
catch (RequestFailedException ex) when (ex.Status == StatusCodes.Status404NotFound && _options.AllowLocalEncryptionKeyFallback)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(configuredLocalKey))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"El secreto '{_options.EncryptionKeySecretName}' no existe en Key Vault y no se ha configurado Encryption:LocalDevelopmentKey.");
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogWarning(
|
||||||
|
"El secreto {SecretName} no existe en Key Vault. Usando clave local temporal por AllowLocalEncryptionKeyFallback=true. No usar en produccion real.",
|
||||||
|
_options.EncryptionKeySecretName);
|
||||||
|
return NormalizeKey(configuredLocalKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(secret.Value))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"El secreto '{_options.EncryptionKeySecretName}' de Key Vault esta vacio.");
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogInformation(
|
||||||
|
"Clave de cifrado cargada desde Key Vault {VaultUrl} usando el secreto {SecretName}.",
|
||||||
|
_options.VaultUrl,
|
||||||
|
_options.EncryptionKeySecretName);
|
||||||
|
|
||||||
|
return NormalizeKey(secret.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] NormalizeKey(string secretValue)
|
||||||
|
{
|
||||||
|
var trimmed = secretValue.Trim();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var base64Key = Convert.FromBase64String(trimmed);
|
||||||
|
if (base64Key.Length is 16 or 24 or 32)
|
||||||
|
{
|
||||||
|
return base64Key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (FormatException)
|
||||||
|
{
|
||||||
|
// Si no es base64, derivamos una clave estable desde el valor textual.
|
||||||
|
}
|
||||||
|
|
||||||
|
return SHA256.HashData(Encoding.UTF8.GetBytes(trimmed));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
using ApiDenuncias.Configuration;
|
||||||
|
using Azure;
|
||||||
|
using Azure.Identity;
|
||||||
|
using Azure.Security.KeyVault.Secrets;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using MySqlConnector;
|
||||||
|
|
||||||
|
namespace ApiDenuncias.Services;
|
||||||
|
|
||||||
|
public sealed class MySqlConnectionStringProvider
|
||||||
|
{
|
||||||
|
private readonly ComplaintStorageOptions _storageOptions;
|
||||||
|
private readonly KeyVaultOptions _keyVaultOptions;
|
||||||
|
private readonly ILogger<MySqlConnectionStringProvider> _logger;
|
||||||
|
|
||||||
|
public MySqlConnectionStringProvider(
|
||||||
|
IOptions<ComplaintStorageOptions> storageOptions,
|
||||||
|
IOptions<KeyVaultOptions> keyVaultOptions,
|
||||||
|
ILogger<MySqlConnectionStringProvider> logger)
|
||||||
|
{
|
||||||
|
_storageOptions = storageOptions.Value;
|
||||||
|
_keyVaultOptions = keyVaultOptions.Value;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<string> GetConnectionStringAsync(CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
return await LoadConnectionStringAsync().WaitAsync(cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<string> LoadConnectionStringAsync()
|
||||||
|
{
|
||||||
|
if (!_storageOptions.UseKeyVault || !_keyVaultOptions.Enabled)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(_storageOptions.ConnectionString))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
"Falta configurar ComplaintStorage:ConnectionString o activar Key Vault para obtener la conexion MySQL.");
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogWarning("Conexion MySQL cargada desde appsettings. Usar solo para desarrollo/local.");
|
||||||
|
return _storageOptions.ConnectionString;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(_keyVaultOptions.VaultUrl))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("KeyVault:VaultUrl no esta configurado.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var client = new SecretClient(new Uri(_keyVaultOptions.VaultUrl), new DefaultAzureCredential());
|
||||||
|
var host = await GetRequiredSecretAsync(client, _storageOptions.HostSecretName);
|
||||||
|
var user = await GetRequiredSecretAsync(client, _storageOptions.UserSecretName);
|
||||||
|
var password = await GetRequiredSecretAsync(client, _storageOptions.PasswordSecretName);
|
||||||
|
var database = await GetRequiredSecretAsync(client, _storageOptions.DatabaseSecretName);
|
||||||
|
var port = await GetOptionalUIntSecretAsync(client, _storageOptions.PortSecretName, _storageOptions.DefaultPort);
|
||||||
|
var sslMode = await GetOptionalSecretAsync(client, _storageOptions.SslModeSecretName, _storageOptions.DefaultSslMode);
|
||||||
|
|
||||||
|
var builder = new MySqlConnectionStringBuilder
|
||||||
|
{
|
||||||
|
Server = host,
|
||||||
|
Port = port,
|
||||||
|
UserID = user,
|
||||||
|
Password = password,
|
||||||
|
Database = database,
|
||||||
|
SslMode = ParseSslMode(sslMode),
|
||||||
|
};
|
||||||
|
|
||||||
|
_logger.LogInformation(
|
||||||
|
"Conexion MySQL cargada desde Key Vault {VaultUrl}. Host={Host}; Database={Database}; User={User}; Port={Port}.",
|
||||||
|
_keyVaultOptions.VaultUrl,
|
||||||
|
host,
|
||||||
|
database,
|
||||||
|
user,
|
||||||
|
port);
|
||||||
|
|
||||||
|
return builder.ConnectionString;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<string> GetRequiredSecretAsync(SecretClient client, string secretName)
|
||||||
|
{
|
||||||
|
var value = await GetOptionalSecretAsync(client, secretName, null);
|
||||||
|
if (string.IsNullOrWhiteSpace(value))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"El secreto obligatorio '{secretName}' de Key Vault no existe o esta vacio.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return value.Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<string> GetOptionalSecretAsync(SecretClient client, string secretName, string? fallback)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(secretName))
|
||||||
|
{
|
||||||
|
return fallback ?? string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var secret = await client.GetSecretAsync(secretName.Trim());
|
||||||
|
return string.IsNullOrWhiteSpace(secret.Value.Value)
|
||||||
|
? fallback ?? string.Empty
|
||||||
|
: secret.Value.Value.Trim();
|
||||||
|
}
|
||||||
|
catch (RequestFailedException ex) when (ex.Status == StatusCodes.Status404NotFound)
|
||||||
|
{
|
||||||
|
return fallback ?? string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<uint> GetOptionalUIntSecretAsync(SecretClient client, string secretName, uint fallback)
|
||||||
|
{
|
||||||
|
var value = await GetOptionalSecretAsync(client, secretName, null);
|
||||||
|
return uint.TryParse(value, out var parsed) && parsed > 0
|
||||||
|
? parsed
|
||||||
|
: fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MySqlSslMode ParseSslMode(string? value)
|
||||||
|
{
|
||||||
|
return Enum.TryParse<MySqlSslMode>(value, ignoreCase: true, out var parsed)
|
||||||
|
? parsed
|
||||||
|
: MySqlSslMode.Required;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
using System.Data;
|
using System.Data;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using GestionaDenunciasAN.Configuration;
|
using ApiDenuncias.Configuration;
|
||||||
using GestionaDenunciasAN.Models;
|
using GestionaDenuncias.Shared.Models;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using MySqlConnector;
|
using MySqlConnector;
|
||||||
|
|
||||||
namespace GestionaDenunciasAN.Services;
|
namespace ApiDenuncias.Services;
|
||||||
|
|
||||||
public sealed class MySqlDenunciaStore : IDenunciaStore
|
public sealed class MySqlDenunciaStore : IDenunciaStore
|
||||||
{
|
{
|
||||||
@@ -103,15 +103,18 @@ public sealed class MySqlDenunciaStore : IDenunciaStore
|
|||||||
private readonly ILogger<MySqlDenunciaStore> _logger;
|
private readonly ILogger<MySqlDenunciaStore> _logger;
|
||||||
private static readonly SemaphoreSlim SchemaGate = new(1, 1);
|
private static readonly SemaphoreSlim SchemaGate = new(1, 1);
|
||||||
private static volatile bool SchemaEnsured;
|
private static volatile bool SchemaEnsured;
|
||||||
|
private readonly MySqlConnectionStringProvider _connectionStringProvider;
|
||||||
|
|
||||||
public MySqlDenunciaStore(
|
public MySqlDenunciaStore(
|
||||||
IOptions<ComplaintStorageOptions> options,
|
IOptions<ComplaintStorageOptions> options,
|
||||||
IHostEnvironment environment,
|
IHostEnvironment environment,
|
||||||
ILogger<MySqlDenunciaStore> logger)
|
ILogger<MySqlDenunciaStore> logger,
|
||||||
|
MySqlConnectionStringProvider connectionStringProvider)
|
||||||
{
|
{
|
||||||
_options = options.Value;
|
_options = options.Value;
|
||||||
_environment = environment;
|
_environment = environment;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
_connectionStringProvider = connectionStringProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task EnsureSchemaAsync(CancellationToken cancellationToken = default)
|
public Task EnsureSchemaAsync(CancellationToken cancellationToken = default)
|
||||||
@@ -705,12 +708,12 @@ public sealed class MySqlDenunciaStore : IDenunciaStore
|
|||||||
content_mime_type = @contentMimeType,
|
content_mime_type = @contentMimeType,
|
||||||
content_sha256 = @contentSha256,
|
content_sha256 = @contentSha256,
|
||||||
uploaded_to_gestiona = CASE
|
uploaded_to_gestiona = CASE
|
||||||
WHEN LOWER(@originalFileName) = 'report.txt' THEN @uploadedToGestiona
|
WHEN LOWER(@originalFileName) = 'report.txt' OR LOWER(@originalFileName) = 'report.pdf' THEN @uploadedToGestiona
|
||||||
WHEN complaint_attachments.content_sha256 = @contentSha256 THEN complaint_attachments.uploaded_to_gestiona
|
WHEN complaint_attachments.content_sha256 = @contentSha256 THEN complaint_attachments.uploaded_to_gestiona
|
||||||
ELSE @uploadedToGestiona
|
ELSE @uploadedToGestiona
|
||||||
END,
|
END,
|
||||||
uploaded_at_utc = CASE
|
uploaded_at_utc = CASE
|
||||||
WHEN LOWER(@originalFileName) = 'report.txt' THEN @uploadedAtUtc
|
WHEN LOWER(@originalFileName) = 'report.txt' OR LOWER(@originalFileName) = 'report.pdf' THEN @uploadedAtUtc
|
||||||
WHEN complaint_attachments.content_sha256 = @contentSha256 THEN complaint_attachments.uploaded_at_utc
|
WHEN complaint_attachments.content_sha256 = @contentSha256 THEN complaint_attachments.uploaded_at_utc
|
||||||
ELSE @uploadedAtUtc
|
ELSE @uploadedAtUtc
|
||||||
END,
|
END,
|
||||||
@@ -850,13 +853,8 @@ public sealed class MySqlDenunciaStore : IDenunciaStore
|
|||||||
|
|
||||||
private async Task<MySqlConnection> OpenConnectionAsync(CancellationToken cancellationToken)
|
private async Task<MySqlConnection> OpenConnectionAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(_options.ConnectionString))
|
var connectionString = await _connectionStringProvider.GetConnectionStringAsync(cancellationToken);
|
||||||
{
|
var connection = new MySqlConnection(connectionString);
|
||||||
throw new InvalidOperationException(
|
|
||||||
"Falta configurar ComplaintStorage:ConnectionString en appsettings.json.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var connection = new MySqlConnection(_options.ConnectionString);
|
|
||||||
await connection.OpenAsync(cancellationToken);
|
await connection.OpenAsync(cancellationToken);
|
||||||
|
|
||||||
await using var timeZoneCommand = new MySqlCommand("SET time_zone = '+00:00';", connection);
|
await using var timeZoneCommand = new MySqlCommand("SET time_zone = '+00:00';", connection);
|
||||||
@@ -1,22 +1,20 @@
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using GestionaDenunciasAN.Configuration;
|
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
using MySqlConnector;
|
using MySqlConnector;
|
||||||
|
|
||||||
namespace ApiDenuncias.Services;
|
namespace ApiDenuncias.Services;
|
||||||
|
|
||||||
public sealed class UserComplaintAccessService
|
public sealed class UserComplaintAccessService
|
||||||
{
|
{
|
||||||
private readonly ComplaintStorageOptions _options;
|
private readonly MySqlConnectionStringProvider _connectionStringProvider;
|
||||||
|
|
||||||
public UserComplaintAccessService(IOptions<ComplaintStorageOptions> options)
|
public UserComplaintAccessService(MySqlConnectionStringProvider connectionStringProvider)
|
||||||
{
|
{
|
||||||
_options = options.Value;
|
_connectionStringProvider = connectionStringProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<HashSet<int>> GetAllowedComplaintIdsAsync(string username, CancellationToken cancellationToken = default)
|
public async Task<HashSet<int>> GetAllowedComplaintIdsAsync(string username, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(_options.ConnectionString))
|
if (string.IsNullOrWhiteSpace(username))
|
||||||
{
|
{
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@@ -31,7 +29,8 @@ public sealed class UserComplaintAccessService
|
|||||||
AND uir.download_count > 0;
|
AND uir.download_count > 0;
|
||||||
""";
|
""";
|
||||||
|
|
||||||
await using var connection = new MySqlConnection(_options.ConnectionString);
|
var connectionString = await _connectionStringProvider.GetConnectionStringAsync(cancellationToken);
|
||||||
|
await using var connection = new MySqlConnection(connectionString);
|
||||||
await connection.OpenAsync(cancellationToken);
|
await connection.OpenAsync(cancellationToken);
|
||||||
|
|
||||||
await using var command = new MySqlCommand(sql, connection);
|
await using var command = new MySqlCommand(sql, connection);
|
||||||
|
|||||||
@@ -4,5 +4,16 @@
|
|||||||
"Default": "Information",
|
"Default": "Information",
|
||||||
"Microsoft.AspNetCore": "Warning"
|
"Microsoft.AspNetCore": "Warning"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"KeyVault": {
|
||||||
|
"Enabled": false
|
||||||
|
},
|
||||||
|
"Encryption": {
|
||||||
|
"LocalDevelopmentKey": "local-development-only-denuncias-encryption-key"
|
||||||
|
},
|
||||||
|
"ComplaintStorage": {
|
||||||
|
"ConnectionString": "Server=192.168.41.25;Port=13306;Database=gestiondenuncias;Uid=tecnosis;Pwd=tsl4net.Ts87;",
|
||||||
|
"UseKeyVault": false,
|
||||||
|
"AutoCreateSchema": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,15 +6,32 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"AllowedHosts": "*",
|
"AllowedHosts": "*",
|
||||||
|
"DetailedApiErrors": true,
|
||||||
"Jwt": {
|
"Jwt": {
|
||||||
"Issuer": "ApiDenuncias",
|
"Issuer": "ApiDenuncias",
|
||||||
"Audience": "GestionaDenunciasAN",
|
"Audience": "GestionaDenunciasAN",
|
||||||
"SigningKey": "dev-local-api-denuncias-jwt-signing-key-please-change",
|
"SigningKey": "dev-local-api-denuncias-jwt-signing-key-please-change",
|
||||||
"ExpirationMinutes": 480
|
"ExpirationMinutes": 480,
|
||||||
|
"RequireHttpsMetadata": false
|
||||||
|
},
|
||||||
|
"ForceHttpsRedirection": false,
|
||||||
|
"KeyVault": {
|
||||||
|
"Enabled": true,
|
||||||
|
"VaultUrl": "https://oaaf-kv-pre.vault.azure.net",
|
||||||
|
"EncryptionKeySecretName": "denuncias-encryption-key",
|
||||||
|
"AllowLocalEncryptionKeyFallback": true
|
||||||
|
},
|
||||||
|
"Encryption": {
|
||||||
|
"LocalDevelopmentKey": "presentacion-pre-denuncias-encryption-key-cambiar-antes-de-produccion"
|
||||||
},
|
},
|
||||||
"Gestiona": {
|
"Gestiona": {
|
||||||
"ApiBase": "https://02.g3stiona.com",
|
"ApiBase": "https://02.g3stiona.com",
|
||||||
"AccessToken": "_yr.xVvPOllsyd1TYZRxUxg__c",
|
"AccessToken": "_yr.xVvPOllsyd1TYZRxUxg__c",
|
||||||
|
"CircuitTemplateId": "bb997758-7436-46ab-9dc3-50dce2e02cfa",
|
||||||
|
"CircuitSignerStampHref": "https://02.g3stiona.com/rest/organ-stamps/3c6eaab4-7fcd-4b21-8676-bf8719be5d36",
|
||||||
|
"CircuitSignerStampTitle": "oaaf-complaints-tramit",
|
||||||
|
"CircuitRecipientGroupHref": "https://02.g3stiona.com/rest/groups/454fa4ec-8b82-4240-9419-113f45d4b004",
|
||||||
|
"CircuitVersion": "2",
|
||||||
"PreferredCircuitTemplateName": "CT-Actualización de denuncia",
|
"PreferredCircuitTemplateName": "CT-Actualización de denuncia",
|
||||||
"UserLink": "https://02.g3stiona.com/rest/users/0c168833-8e27-4695-a301-b79924031f63",
|
"UserLink": "https://02.g3stiona.com/rest/users/0c168833-8e27-4695-a301-b79924031f63",
|
||||||
"GroupLink": "https://02.g3stiona.com/rest/groups/6dbfc433-1eb6-4b9a-a533-bfebc652c101",
|
"GroupLink": "https://02.g3stiona.com/rest/groups/6dbfc433-1eb6-4b9a-a533-bfebc652c101",
|
||||||
@@ -22,11 +39,22 @@
|
|||||||
},
|
},
|
||||||
"GlobalLeaks": {
|
"GlobalLeaks": {
|
||||||
"BaseUrl": "https://prebuzon.antifraudeandalucia.es",
|
"BaseUrl": "https://prebuzon.antifraudeandalucia.es",
|
||||||
|
"HostHeader": "",
|
||||||
|
"AllowInvalidCertificate": true,
|
||||||
"TimeoutSeconds": 120,
|
"TimeoutSeconds": 120,
|
||||||
"MaxDownloadBytes": 524288000
|
"MaxDownloadBytes": 524288000
|
||||||
},
|
},
|
||||||
"ComplaintStorage": {
|
"ComplaintStorage": {
|
||||||
"ConnectionString": "Server=192.168.41.25;Port=13306;Database=gestiondenuncias;Uid=tecnosis;Pwd=tsl4net.Ts87;",
|
"ConnectionString": "",
|
||||||
|
"UseKeyVault": true,
|
||||||
|
"HostSecretName": "bbdd-host",
|
||||||
|
"UserSecretName": "bbdd-user",
|
||||||
|
"PasswordSecretName": "bbdd-password",
|
||||||
|
"DatabaseSecretName": "bbdd-name",
|
||||||
|
"PortSecretName": "bbdd-port",
|
||||||
|
"SslModeSecretName": "bbdd-ssl-mode",
|
||||||
|
"DefaultPort": 3306,
|
||||||
|
"DefaultSslMode": "Required",
|
||||||
"AutoCreateSchema": true
|
"AutoCreateSchema": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -121,6 +121,16 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item dropdown">
|
||||||
|
<a class="nav-link dropdown-toggle" style="font-family:'Satoshi'; color:white" href="#" id="tabFichMaestros" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">AVANZADO</a>
|
||||||
|
<div class="dropdown-menu" id="dropFicherosMaestros" style="font-family: 'Satoshi';" aria-labelledby="dropFicherosMaestros">
|
||||||
|
<a class="dropdown-item" href="/GruposEnumeraciones">Enumeraciones</a>
|
||||||
|
<a class="dropdown-item" href="/Grupos de Usuarios">Grupos de Usuarios</a>
|
||||||
|
<a class="dropdown-item" href="/Plantillas">Plantillas</a>
|
||||||
|
<a class="dropdown-item" href="/Usuarios">Usuarios</a>
|
||||||
|
<a class="dropdown-item" href="/Permisos">Permisos</a>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,306 @@
|
|||||||
|
@page "/Enumeraciones/"
|
||||||
|
@page "/Enumeraciones/{cl}"
|
||||||
|
@using System.Net.Http.Headers
|
||||||
|
@using System.Linq.Expressions
|
||||||
|
@using Microsoft.AspNetCore.WebUtilities
|
||||||
|
@using Newtonsoft.Json
|
||||||
|
@using System.Text
|
||||||
|
@using Serialize.Linq.Serializers
|
||||||
|
@using GestionPersonalWeb.Models
|
||||||
|
@using BlazorBootstrap
|
||||||
|
@using bdAntifraude.db
|
||||||
|
@using Microsoft.AspNetCore.Components
|
||||||
|
@rendermode InteractiveServer
|
||||||
|
@inject IJSRuntime JS
|
||||||
|
@inject NavigationManager NavigationManager
|
||||||
|
@inject IHttpClientFactory HttpClientFactory
|
||||||
|
@inject IHttpContextAccessor HttpContextAccessor
|
||||||
|
@inject UserState UserState
|
||||||
|
|
||||||
|
<Toasts class="p-3 font-weight-bold" Style="color:white;" AutoHide="true" Delay="4000" Messages="mensajes" Placement="ToastsPlacement.BottomCenter" />
|
||||||
|
<div class="pagina">
|
||||||
|
<div class="d-flex">
|
||||||
|
<div class="cabecera">
|
||||||
|
<h6 style="padding-top: 13px;padding-right: 15px;">
|
||||||
|
<b>Enumeración</b>
|
||||||
|
</h6>
|
||||||
|
</div>
|
||||||
|
<button @onclick="@(() => abrirPopupModificacion(new ENUMERACIONES(), true))" class="btnOAAFAzul">Nuevo </button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (lEnumeraciones == null)
|
||||||
|
{
|
||||||
|
<div id="cargando" class="loadingFrame">
|
||||||
|
<div class="loadingImg"></div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else if (!lEnumeraciones.Any())
|
||||||
|
{
|
||||||
|
<p>No se encontraron datos para mostrar.</p>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="botonera col-12 gap-1" style="display:flex;" role="group">
|
||||||
|
</div>
|
||||||
|
<div style="display:flex; justify-content:start; gap:15px;width:100%"></div>
|
||||||
|
|
||||||
|
<div style="overflow-x:auto;" class="">
|
||||||
|
<Grid TItem="ENUMERACIONES"
|
||||||
|
Class="table tablaRegPers"
|
||||||
|
Data="@lEnumeraciones"
|
||||||
|
AllowFiltering="false"
|
||||||
|
AllowPaging="false"
|
||||||
|
AllowSorting="true"
|
||||||
|
EmptyText="No se han encontrado datos"
|
||||||
|
Height="80"
|
||||||
|
PageSizeSelectorVisible="false"
|
||||||
|
Responsive="true"
|
||||||
|
PaginationItemsTextFormat="{0} - {1} de {2} elementos">
|
||||||
|
<GridColumns>
|
||||||
|
<GridColumn TItem="ENUMERACIONES" HeaderText="">
|
||||||
|
<button @onclick="@(() => abrirPopupModificacion(@context, false))" class="btnOAAFAzul">Editar</button>
|
||||||
|
</GridColumn>
|
||||||
|
<GridColumn TItem="ENUMERACIONES" HeaderText="Código" PropertyName="CODIGO" FilterButtonCSSClass="hidden" SortKeySelector="item => item.CODIGO">
|
||||||
|
@context.CODIGO
|
||||||
|
</GridColumn>
|
||||||
|
<GridColumn TItem="ENUMERACIONES" HeaderText="Descripción" PropertyName="DESCRIPCION" FilterButtonCSSClass="hidden" SortKeySelector="item => item.DESCRIPCION">
|
||||||
|
@context.DESCRIPCION
|
||||||
|
</GridColumn>
|
||||||
|
<GridColumn TItem="ENUMERACIONES" HeaderText="Descripción" PropertyName="VALORNUMERICO1" FilterButtonCSSClass="hidden" SortKeySelector="item => item.VALORNUMERICO1">
|
||||||
|
@context.VALORNUMERICO1
|
||||||
|
</GridColumn>
|
||||||
|
<GridColumn TItem="ENUMERACIONES" HeaderText="Descripción" PropertyName="VALORNUMERICO2" FilterButtonCSSClass="hidden" SortKeySelector="item => item.VALORNUMERICO2">
|
||||||
|
@context.VALORNUMERICO2
|
||||||
|
</GridColumn>
|
||||||
|
<GridColumn TItem="ENUMERACIONES" HeaderText="Descripción" PropertyName="VALORNUMERICO3" FilterButtonCSSClass="hidden" SortKeySelector="item => item.VALORNUMERICO3">
|
||||||
|
@context.VALORNUMERICO3
|
||||||
|
</GridColumn>
|
||||||
|
<GridColumn TItem="ENUMERACIONES" HeaderText="Descripción" PropertyName="VALORNUMERICO4" FilterButtonCSSClass="hidden" SortKeySelector="item => item.VALORNUMERICO4">
|
||||||
|
@context.VALORNUMERICO4
|
||||||
|
</GridColumn>
|
||||||
|
<GridColumn TItem="ENUMERACIONES" HeaderText="Descripción" PropertyName="VALORALFABETICO1" FilterButtonCSSClass="hidden" SortKeySelector="item => item.VALORALFABETICO1">
|
||||||
|
@context.VALORALFABETICO1
|
||||||
|
</GridColumn>
|
||||||
|
<GridColumn TItem="ENUMERACIONES" HeaderText="Descripción" PropertyName="VALORALFABETICO2" FilterButtonCSSClass="hidden" SortKeySelector="item => item.VALORALFABETICO2">
|
||||||
|
@context.VALORALFABETICO2
|
||||||
|
</GridColumn>
|
||||||
|
<GridColumn TItem="ENUMERACIONES" HeaderText="Descripción" PropertyName="VALORALFABETICO3" FilterButtonCSSClass="hidden" SortKeySelector="item => item.VALORALFABETICO3">
|
||||||
|
@context.VALORALFABETICO3
|
||||||
|
</GridColumn>
|
||||||
|
<GridColumn TItem="ENUMERACIONES" HeaderText="Descripción" PropertyName="VALORALFABETICO4" FilterButtonCSSClass="hidden" SortKeySelector="item => item.VALORALFABETICO4">
|
||||||
|
@context.VALORALFABETICO4
|
||||||
|
</GridColumn>
|
||||||
|
<GridColumn TItem="ENUMERACIONES" HeaderText="Descripción" PropertyName="VALORALFABETICOLARGO" FilterButtonCSSClass="hidden" SortKeySelector="item => item.VALORALFABETICOLARGO">
|
||||||
|
@context.VALORALFABETICOLARGO
|
||||||
|
</GridColumn>
|
||||||
|
</GridColumns>
|
||||||
|
</Grid>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Vista móvil -->
|
||||||
|
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<!--Popup de edicion-->
|
||||||
|
<EditForm EditContext="@editContext" OnValidSubmit="GuardarCambiosPopup" OnInvalidSubmit="@MostrarErroresPopup" FormName="fiestasForm">
|
||||||
|
<DataAnnotationsValidator></DataAnnotationsValidator>
|
||||||
|
<Modal @ref="popupGestionDatos" title="@tituloPopup" IsVerticallyCentered="true" UseStaticBackdrop="true" CloseOnEscape="false">
|
||||||
|
<BodyTemplate>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<label for="txtEDescripcion" class="fw-bold">Código: </label>
|
||||||
|
<input class="form-control" id="txtEDescripcion" @bind-value="@ItemEnEdicion.CODIGO" />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-12">
|
||||||
|
<label for="txtEDescripcion" class="fw-bold">Descripcion: </label>
|
||||||
|
<input class="form-control" id="txtEDescripcion" @bind-value="@ItemEnEdicion.DESCRIPCION" />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label for="txtEDescripcion" class="fw-bold">Valor Numérico 1: </label>
|
||||||
|
<input class="form-control" id="txtEDescripcion" @bind-value="@ItemEnEdicion.VALORNUMERICO1" />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label for="txtEDescripcion" class="fw-bold">Valor Numérico 2: </label>
|
||||||
|
<input class="form-control" id="txtEDescripcion" @bind-value="@ItemEnEdicion.VALORNUMERICO2" />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label for="txtEDescripcion" class="fw-bold">Valor Numérico 3: </label>
|
||||||
|
<input class="form-control" id="txtEDescripcion" @bind-value="@ItemEnEdicion.VALORNUMERICO3" />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label for="txtEDescripcion" class="fw-bold">Valor Numérico 4: </label>
|
||||||
|
<input class="form-control" id="txtEDescripcion" @bind-value="@ItemEnEdicion.VALORNUMERICO4" />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label for="txtEDescripcion" class="fw-bold">Valor Alfabético 1: </label>
|
||||||
|
<input class="form-control" id="txtEDescripcion" @bind-value="@ItemEnEdicion.VALORALFABETICO1" />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label for="txtEDescripcion" class="fw-bold">Valor Alfabético 2: </label>
|
||||||
|
<input class="form-control" id="txtEDescripcion" @bind-value="@ItemEnEdicion.VALORALFABETICO2" />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label for="txtEDescripcion" class="fw-bold">Valor Alfabético 3: </label>
|
||||||
|
<input class="form-control" id="txtEDescripcion" @bind-value="@ItemEnEdicion.VALORALFABETICO3" />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label for="txtEDescripcion" class="fw-bold">Valor Alfabético 4: </label>
|
||||||
|
<input class="form-control" id="txtEDescripcion" @bind-value="@ItemEnEdicion.VALORALFABETICO4" />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-12">
|
||||||
|
<label for="txtEDescripcion" class="fw-bold">Valor Alfabético Largo: </label>
|
||||||
|
<input class="form-control" id="txtEDescripcion" @bind-value="@ItemEnEdicion.VALORALFABETICOLARGO" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</BodyTemplate>
|
||||||
|
<FooterTemplate>
|
||||||
|
<Button Color="ButtonColor.Secondary" @onclick="cerrarPopupModificacion">Cerrar</Button>
|
||||||
|
<Button Type="ButtonType.Submit" Color="ButtonColor.Primary">@(EsItemNuevo ? "Añadir" : "Modificar")</Button>
|
||||||
|
</FooterTemplate>
|
||||||
|
</Modal>
|
||||||
|
</EditForm>
|
||||||
|
@code {
|
||||||
|
[Parameter]
|
||||||
|
public string? cl { get; set; } = "";
|
||||||
|
GRUPOSENUMERACIONES grupo = new GRUPOSENUMERACIONES();
|
||||||
|
List<ENUMERACIONES> lEnumeraciones = new List<ENUMERACIONES>();
|
||||||
|
private string _filter = "";
|
||||||
|
|
||||||
|
private string tituloPopup = "";
|
||||||
|
private Modal popupGestionDatos = default;
|
||||||
|
private bool EsItemNuevo = false;
|
||||||
|
private ENUMERACIONES ItemEnEdicion { get; set; } = new ENUMERACIONES();
|
||||||
|
|
||||||
|
private EditContext? editContext;
|
||||||
|
List<ToastMessage> mensajes = new List<ToastMessage>();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
var url = NavigationManager.ToAbsoluteUri(NavigationManager.Uri);
|
||||||
|
var token = UserState.Token;
|
||||||
|
var cliente = Utilidades.ObtenerCliente(UserState.Token, HttpClientFactory);
|
||||||
|
editContext = new EditContext(lEnumeraciones);
|
||||||
|
|
||||||
|
if (QueryHelpers.ParseQuery(url.Query).TryGetValue("cl", out var clValue))
|
||||||
|
{
|
||||||
|
cl = clValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(cl))
|
||||||
|
{
|
||||||
|
//iContrato = new CONTRATOS();
|
||||||
|
//mostrarBtn = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
string idDesencriptado = Utilidades.Desencriptar(cl);
|
||||||
|
int id = int.Parse(idDesencriptado);
|
||||||
|
|
||||||
|
var response = await cliente.GetAsync($"/api/GRUPOSENUMERACIONES/{id}");
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
throw new Exception($"Error al obtener los datos. Código: {response.StatusCode}");
|
||||||
|
}
|
||||||
|
var resultContent = await response.Content.ReadAsStringAsync();
|
||||||
|
grupo = JsonConvert.DeserializeObject<GRUPOSENUMERACIONES>(resultContent) ?? throw new Exception("Error al deserializar los datos.");
|
||||||
|
|
||||||
|
lEnumeraciones = await Utilidades.ObtenerObjeto<List<ENUMERACIONES>>(cliente, "/api/ENUMERACIONES/EnumeracionesGrupo/"+grupo.GRUPO);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task abrirPopupModificacion(ENUMERACIONES objeto, bool esNuevo)
|
||||||
|
{
|
||||||
|
ItemEnEdicion = Utilidades.ClonarObjeto(objeto);
|
||||||
|
EsItemNuevo = esNuevo;
|
||||||
|
if (!EsItemNuevo)
|
||||||
|
{
|
||||||
|
tituloPopup = "Modificando Enumeracion";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
tituloPopup = "Nueva Enumeracion";
|
||||||
|
}
|
||||||
|
|
||||||
|
await popupGestionDatos.ShowAsync();
|
||||||
|
}
|
||||||
|
private async Task cerrarPopupModificacion()
|
||||||
|
{
|
||||||
|
await popupGestionDatos.HideAsync();
|
||||||
|
}
|
||||||
|
private async Task GuardarCambiosPopup()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ValidarDatos();
|
||||||
|
|
||||||
|
if (!editContext!.GetValidationMessages().Any())
|
||||||
|
{
|
||||||
|
string accion = EsItemNuevo ? "create" : "update";
|
||||||
|
await GestionarDatos(accion);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mensajes.Add(new ToastMessage
|
||||||
|
{
|
||||||
|
Type = ToastType.Warning,
|
||||||
|
Message = $"Debe rellenar los campos obligatorios.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
mensajes.Add(new ToastMessage
|
||||||
|
{
|
||||||
|
Type = ToastType.Danger,
|
||||||
|
Message = $"Error al guardar.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
private async Task GestionarDatos(string accion)
|
||||||
|
{
|
||||||
|
var cliente = Utilidades.ObtenerCliente(UserState.Token, HttpClientFactory);
|
||||||
|
var copia = new List<ENUMERACIONES>(lEnumeraciones);
|
||||||
|
cliente = Utilidades.ObtenerCliente(UserState.Token, HttpClientFactory);
|
||||||
|
|
||||||
|
switch (accion)
|
||||||
|
{
|
||||||
|
case "update":
|
||||||
|
int indice = copia.FindIndex(x => x.IDENUMERACION == ItemEnEdicion.IDENUMERACION);
|
||||||
|
if (indice > -1)
|
||||||
|
{
|
||||||
|
copia[indice] = ItemEnEdicion;
|
||||||
|
}
|
||||||
|
var response = await Utilidades.ActualizarObjeto(cliente, "/api/ENUMERACIONES/" + ItemEnEdicion.IDENUMERACION, ItemEnEdicion, mensajes);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case "create":
|
||||||
|
copia.Add(ItemEnEdicion);
|
||||||
|
var responsec = await Utilidades.NuevoObjeto(cliente, "/api/ENUMERACIONES/", ItemEnEdicion, mensajes);
|
||||||
|
break;
|
||||||
|
case "delete":
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
cerrarPopupModificacion();
|
||||||
|
lEnumeraciones = copia.ToList();
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
}
|
||||||
|
private void ValidarDatos()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
private void MostrarErroresPopup()
|
||||||
|
{
|
||||||
|
// messageStore?.Clear();
|
||||||
|
// foreach (var field in new[] { nameof(descripcionItem) })
|
||||||
|
// {
|
||||||
|
// ValidarYActualizar(new ChangeEventArgs { Value = typeof(enumeraciones).GetProperty(field)?.GetValue(itemSeleccionado) }, field);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
@page "/GruposEnumeraciones"
|
||||||
|
@using System.Net.Http.Headers
|
||||||
|
@using System.Linq.Expressions
|
||||||
|
@using Newtonsoft.Json
|
||||||
|
@using System.Text
|
||||||
|
@using Serialize.Linq.Serializers
|
||||||
|
@using GestionPersonalWeb.Models
|
||||||
|
@using BlazorBootstrap
|
||||||
|
@using bdAntifraude.db
|
||||||
|
@using Microsoft.AspNetCore.Components
|
||||||
|
@rendermode InteractiveServer
|
||||||
|
@inject IJSRuntime JS
|
||||||
|
@inject NavigationManager NavigationManager
|
||||||
|
@inject IHttpClientFactory HttpClientFactory
|
||||||
|
@inject IHttpContextAccessor HttpContextAccessor
|
||||||
|
@inject UserState UserState
|
||||||
|
|
||||||
|
<Toasts class="p-3 font-weight-bold" Style="color:white;" AutoHide="true" Delay="4000" Messages="mensajes" Placement="ToastsPlacement.BottomCenter" />
|
||||||
|
<div class="pagina">
|
||||||
|
<div class="d-flex">
|
||||||
|
<div class="cabecera">
|
||||||
|
<h6 style="padding-top: 13px;padding-right: 15px;">
|
||||||
|
<b>Grupos Enumeraciones</b>
|
||||||
|
</h6>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (lGrupoEnumeraciones == null)
|
||||||
|
{
|
||||||
|
<div id="cargando" class="loadingFrame">
|
||||||
|
<div class="loadingImg"></div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else if (!lGrupoEnumeraciones.Any())
|
||||||
|
{
|
||||||
|
<p>No se encontraron datos para mostrar.</p>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="botonera col-12 gap-1" style="display:flex;" role="group">
|
||||||
|
</div>
|
||||||
|
<div style="display:flex; justify-content:start; gap:15px;width:100%"></div>
|
||||||
|
|
||||||
|
<div style="overflow-x:auto;" class="">
|
||||||
|
<Grid TItem="GRUPOSENUMERACIONES"
|
||||||
|
Class="table tablaRegPers"
|
||||||
|
Data="@lGrupoEnumeraciones"
|
||||||
|
AllowFiltering="false"
|
||||||
|
AllowPaging="false"
|
||||||
|
AllowSorting="true"
|
||||||
|
EmptyText="No se han encontrado datos"
|
||||||
|
Height="80"
|
||||||
|
PageSizeSelectorVisible="false"
|
||||||
|
Responsive="true"
|
||||||
|
PaginationItemsTextFormat="{0} - {1} de {2} elementos">
|
||||||
|
<GridColumns>
|
||||||
|
<GridColumn TItem="GRUPOSENUMERACIONES" HeaderText="Grupo" PropertyName="GRUPO" FilterButtonCSSClass="hidden" SortKeySelector="item => item.GRUPO">
|
||||||
|
<NavLink class="btn btn-link" href="@HashRed(context.IDGRUPOENUMERACION.ToString())">@context.GRUPO</NavLink>
|
||||||
|
</GridColumn>
|
||||||
|
<GridColumn TItem="GRUPOSENUMERACIONES" HeaderText="Descripcion" PropertyName="DESCRIPCION" FilterButtonCSSClass="hidden" SortKeySelector="item => item.DESCRIPCION">
|
||||||
|
@context.DESCRIPCION
|
||||||
|
</GridColumn>
|
||||||
|
</GridColumns>
|
||||||
|
</Grid>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Vista móvil -->
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
@code {
|
||||||
|
List<GRUPOSENUMERACIONES> lGrupoEnumeraciones = new List<GRUPOSENUMERACIONES>();
|
||||||
|
// Bandera que indica si se está en modo "Ver Todos"
|
||||||
|
private bool verTodosActive = false;
|
||||||
|
List<ToastMessage> mensajes = new List<ToastMessage>();
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
verTodosActive = false;
|
||||||
|
var token = UserState.Token;
|
||||||
|
var cliente = Utilidades.ObtenerCliente(UserState.Token, HttpClientFactory);
|
||||||
|
|
||||||
|
var resultPersonas = await cliente.GetAsync("/api/GRUPOSENUMERACIONES");
|
||||||
|
var resultContent = await resultPersonas.Content.ReadAsStringAsync();
|
||||||
|
lGrupoEnumeraciones = JsonConvert.DeserializeObject<List<GRUPOSENUMERACIONES>>(resultContent) ?? new List<GRUPOSENUMERACIONES>(); ;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string HashRed(string id)
|
||||||
|
{
|
||||||
|
string link = "/Enumeraciones?cl=" + tsUtilidades.crypt.FEncS(
|
||||||
|
id,
|
||||||
|
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890.:/-*",
|
||||||
|
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890.:/-*",
|
||||||
|
875421649);
|
||||||
|
return link;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="PdfSharpCore" Version="1.3.67" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
@@ -6,14 +6,14 @@ using PdfSharpCore.Drawing;
|
|||||||
using PdfSharpCore.Pdf;
|
using PdfSharpCore.Pdf;
|
||||||
using PdfSharpCore.Pdf.IO;
|
using PdfSharpCore.Pdf.IO;
|
||||||
|
|
||||||
namespace GestionaDenunciasAN.Helpers
|
namespace GestionaDenuncias.Shared.Helpers
|
||||||
{
|
{
|
||||||
public static class PdfHelper
|
public static class PdfHelper
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fusiona varios ficheros (PDF, imágenes, TXT) en un único PDF.
|
/// Fusiona varios ficheros (PDF, imágenes, TXT) en un único PDF.
|
||||||
/// Los .txt se renderizan con márgenes iguales, alineación a la izquierda y ajuste de líneas,
|
/// Los .txt se renderizan con márgenes iguales, alineación a la izquierda y ajuste de líneas,
|
||||||
/// preservando líneas en blanco.
|
/// preservando líneas en blanco.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="files">Secuencia de tuplas (FileName, ContentBytes)</param>
|
/// <param name="files">Secuencia de tuplas (FileName, ContentBytes)</param>
|
||||||
/// <returns>Bytes del PDF combinado</returns>
|
/// <returns>Bytes del PDF combinado</returns>
|
||||||
@@ -52,7 +52,7 @@ namespace GestionaDenunciasAN.Helpers
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case ".txt":
|
case ".txt":
|
||||||
// Renderizado de TXT con margen y ajuste de líneas, preservando líneas en blanco
|
// Renderizado de TXT con margen y ajuste de líneas, preservando líneas en blanco
|
||||||
var text = Encoding.UTF8.GetString(content);
|
var text = Encoding.UTF8.GetString(content);
|
||||||
PdfPage pageTxt = outputDoc.AddPage();
|
PdfPage pageTxt = outputDoc.AddPage();
|
||||||
XGraphics gfxTxt = XGraphics.FromPdfPage(pageTxt);
|
XGraphics gfxTxt = XGraphics.FromPdfPage(pageTxt);
|
||||||
@@ -72,7 +72,7 @@ namespace GestionaDenunciasAN.Helpers
|
|||||||
|
|
||||||
foreach (var origLine in text.Replace("\r\n", "\n").Replace('\r', '\n').Split('\n'))
|
foreach (var origLine in text.Replace("\r\n", "\n").Replace('\r', '\n').Split('\n'))
|
||||||
{
|
{
|
||||||
// Línea en blanco: preservarla
|
// Línea en blanco: preservarla
|
||||||
if (string.IsNullOrWhiteSpace(origLine))
|
if (string.IsNullOrWhiteSpace(origLine))
|
||||||
{
|
{
|
||||||
y += lineHeight;
|
y += lineHeight;
|
||||||
@@ -99,7 +99,7 @@ namespace GestionaDenunciasAN.Helpers
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Dibujar la línea acumulada
|
// Dibujar la línea acumulada
|
||||||
gfxTxt.DrawString(
|
gfxTxt.DrawString(
|
||||||
currentLine,
|
currentLine,
|
||||||
font,
|
font,
|
||||||
@@ -109,7 +109,7 @@ namespace GestionaDenunciasAN.Helpers
|
|||||||
y += lineHeight;
|
y += lineHeight;
|
||||||
currentLine = word;
|
currentLine = word;
|
||||||
|
|
||||||
// Paginación si se sale por abajo
|
// Paginación si se sale por abajo
|
||||||
if (y + lineHeight > pageHeight - marginBottom)
|
if (y + lineHeight > pageHeight - marginBottom)
|
||||||
{
|
{
|
||||||
gfxTxt.Dispose();
|
gfxTxt.Dispose();
|
||||||
@@ -120,7 +120,7 @@ namespace GestionaDenunciasAN.Helpers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dibujar la última línea del párrafo
|
// Dibujar la última línea del párrafo
|
||||||
if (!string.IsNullOrEmpty(currentLine))
|
if (!string.IsNullOrEmpty(currentLine))
|
||||||
{
|
{
|
||||||
gfxTxt.DrawString(
|
gfxTxt.DrawString(
|
||||||
@@ -145,7 +145,7 @@ namespace GestionaDenunciasAN.Helpers
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new NotSupportedException($"Extensión no soportada: {ext}");
|
throw new NotSupportedException($"Extensión no soportada: {ext}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace GestionaDenunciasAN.Models;
|
namespace GestionaDenuncias.Shared.Models;
|
||||||
|
|
||||||
public sealed record ApiLoginResponse(
|
public sealed record ApiLoginResponse(
|
||||||
string Username,
|
string Username,
|
||||||
@@ -36,6 +36,10 @@ public sealed record MarkReportImportedRequest(
|
|||||||
ReportDto Report,
|
ReportDto Report,
|
||||||
int? ComplaintId);
|
int? ComplaintId);
|
||||||
|
|
||||||
|
public sealed record TrackingImportPermissionRequest(
|
||||||
|
string Username,
|
||||||
|
ReportDto Report);
|
||||||
|
|
||||||
public sealed record GestionaCreateFileRequest(
|
public sealed record GestionaCreateFileRequest(
|
||||||
Guid ProcedureId,
|
Guid ProcedureId,
|
||||||
string Subject,
|
string Subject,
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
namespace GestionaDenunciasAN.Models;
|
namespace GestionaDenuncias.Shared.Models;
|
||||||
|
|
||||||
public sealed record ApiError(string Error, bool SessionExpired = false);
|
public sealed record ApiError(string Error, bool SessionExpired = false);
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
namespace GestionaDenunciasAN.Models;
|
namespace GestionaDenuncias.Shared.Models;
|
||||||
|
|
||||||
public sealed record ContextDto(string Id, string Name);
|
public sealed record ContextDto(string Id, string Name);
|
||||||
@@ -3,7 +3,7 @@ using System.Text;
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace GestionaDenunciasAN.Models;
|
namespace GestionaDenuncias.Shared.Models;
|
||||||
|
|
||||||
public class DenunciasGestiona
|
public class DenunciasGestiona
|
||||||
{
|
{
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace GestionaDenunciasAN.Models
|
namespace GestionaDenuncias.Shared.Models
|
||||||
{
|
{
|
||||||
public class ExpedienteTerceroDto
|
public class ExpedienteTerceroDto
|
||||||
{
|
{
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
// Models/FicherosDenuncias.cs
|
// Models/FicherosDenuncias.cs
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace GestionaDenunciasAN.Models
|
namespace GestionaDenuncias.Shared.Models
|
||||||
{
|
{
|
||||||
public class FicherosDenuncias
|
public class FicherosDenuncias
|
||||||
{
|
{
|
||||||
@@ -29,17 +29,26 @@ namespace GestionaDenunciasAN.Models
|
|||||||
// Fichero completo en formato byte array (BLOB)
|
// Fichero completo en formato byte array (BLOB)
|
||||||
public byte[] Fichero { get; set; } = [];
|
public byte[] Fichero { get; set; } = [];
|
||||||
|
|
||||||
// → Nuevo: marca si ya se subió a Gestión
|
// ? Nuevo: marca si ya se subió a Gestión
|
||||||
public bool Subido { get; set; }
|
public bool Subido { get; set; }
|
||||||
|
|
||||||
// → Nuevo: fecha en que se subió por última vez
|
// ? Nuevo: fecha en que se subió por última vez
|
||||||
public DateTime? FechaSubida { get; set; }
|
public DateTime? FechaSubida { get; set; }
|
||||||
|
|
||||||
// Hash SHA-256 del contenido, para evitar re-subir adjuntos repetidos.
|
// Hash SHA-256 del contenido, para evitar re-subir adjuntos repetidos.
|
||||||
public string ContentSha256 { get; set; } = string.Empty;
|
public string ContentSha256 { get; set; } = string.Empty;
|
||||||
|
|
||||||
public bool EsReport =>
|
public bool EsReport
|
||||||
string.Equals(NombreFichero, "report.txt", StringComparison.OrdinalIgnoreCase);
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var fileName = System.IO.Path.GetFileNameWithoutExtension(NombreFichero);
|
||||||
|
var extension = System.IO.Path.GetExtension(NombreFichero);
|
||||||
|
return fileName.StartsWith("report", StringComparison.OrdinalIgnoreCase) &&
|
||||||
|
(extension.Equals(".txt", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
extension.Equals(".pdf", StringComparison.OrdinalIgnoreCase));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public FicherosDenuncias() { }
|
public FicherosDenuncias() { }
|
||||||
|
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
namespace GestionaDenunciasAN.Models;
|
namespace GestionaDenuncias.Shared.Models;
|
||||||
|
|
||||||
public sealed record FileDownloadResult(byte[] Content, string FileName);
|
public sealed record FileDownloadResult(byte[] Content, string FileName);
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace GestionaDenunciasAN.Models;
|
namespace GestionaDenuncias.Shared.Models;
|
||||||
|
|
||||||
public sealed class GestionaExpedienteInfo
|
public sealed class GestionaExpedienteInfo
|
||||||
{
|
{
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
namespace GestionaDenunciasAN.Models;
|
namespace GestionaDenuncias.Shared.Models;
|
||||||
|
|
||||||
public sealed record GlSession(string Id, string Username, string? Role = null);
|
public sealed record GlSession(string Id, string Username, string? Role = null);
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace GestionaDenunciasAN.Models;
|
namespace GestionaDenuncias.Shared.Models;
|
||||||
|
|
||||||
public sealed class GlobalLeaksStoredSession
|
public sealed class GlobalLeaksStoredSession
|
||||||
{
|
{
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace GestionaDenunciasAN.Models;
|
namespace GestionaDenuncias.Shared.Models;
|
||||||
|
|
||||||
public sealed record ImportSummary(
|
public sealed record ImportSummary(
|
||||||
int TotalCandidates,
|
int TotalCandidates,
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace GestionaDenunciasAN.Models;
|
namespace GestionaDenuncias.Shared.Models;
|
||||||
|
|
||||||
public sealed record InboxUserState
|
public sealed record InboxUserState
|
||||||
{
|
{
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
namespace GestionaDenunciasAN.Models;
|
namespace GestionaDenuncias.Shared.Models;
|
||||||
|
|
||||||
public sealed record LoginRequest(string Username, string Password, string Authcode);
|
public sealed record LoginRequest(string Username, string Password, string Authcode);
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
namespace GestionaDenunciasAN.Models;
|
namespace GestionaDenuncias.Shared.Models;
|
||||||
|
|
||||||
public sealed record LoginResponse(string Username);
|
public sealed record LoginResponse(string Username);
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace GestionaDenunciasAN.Models;
|
namespace GestionaDenuncias.Shared.Models;
|
||||||
|
|
||||||
public sealed record ReportDto
|
public sealed record ReportDto
|
||||||
{
|
{
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace GestionaDenunciasAN.Models;
|
namespace GestionaDenuncias.Shared.Models;
|
||||||
|
|
||||||
public sealed class ReportFieldEntry
|
public sealed class ReportFieldEntry
|
||||||
{
|
{
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace GestionaDenunciasAN.Models;
|
namespace GestionaDenuncias.Shared.Models;
|
||||||
|
|
||||||
public sealed class ThirdPartyAddressData
|
public sealed class ThirdPartyAddressData
|
||||||
{
|
{
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace GestionaDenunciasAN.Models;
|
namespace GestionaDenuncias.Shared.Models;
|
||||||
|
|
||||||
public sealed class ThirdPartyIdentityData
|
public sealed class ThirdPartyIdentityData
|
||||||
{
|
{
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using GestionaDenunciasAN.Models;
|
using GestionaDenuncias.Shared.Models;
|
||||||
|
|
||||||
namespace GestionaDenunciasAN.Services;
|
namespace GestionaDenuncias.Shared.Services;
|
||||||
|
|
||||||
public interface IDenunciaStore
|
public interface IDenunciaStore
|
||||||
{
|
{
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using GestionaDenunciasAN.Models;
|
using GestionaDenuncias.Shared.Models;
|
||||||
|
|
||||||
namespace GestionaDenunciasAN.Services;
|
namespace GestionaDenuncias.Shared.Services;
|
||||||
|
|
||||||
public interface IInboxTrackingService
|
public interface IInboxTrackingService
|
||||||
{
|
{
|
||||||
@@ -14,4 +14,9 @@ public interface IInboxTrackingService
|
|||||||
ReportDto report,
|
ReportDto report,
|
||||||
int? complaintId,
|
int? complaintId,
|
||||||
CancellationToken cancellationToken = default);
|
CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
Task EnsureReportCanBeImportedByUserAsync(
|
||||||
|
string username,
|
||||||
|
ReportDto report,
|
||||||
|
CancellationToken cancellationToken = default);
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
|
||||||
namespace GestionaDenunciasAN.Services;
|
namespace GestionaDenuncias.Shared.Services;
|
||||||
|
|
||||||
public sealed class LoginRateLimiter
|
public sealed class LoginRateLimiter
|
||||||
{
|
{
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@inherits LayoutComponentBase
|
@inherits LayoutComponentBase
|
||||||
|
|
||||||
<div class="main">
|
<div class="main">
|
||||||
<div class="">
|
<div class="">
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
@page "/Actualizaciones"
|
@page "/Actualizaciones"
|
||||||
@rendermode InteractiveServer
|
@rendermode InteractiveServer
|
||||||
@attribute [Authorize]
|
@attribute [Authorize]
|
||||||
@using GestionaDenunciasAN.Models
|
@using GestionaDenuncias.Shared.Models
|
||||||
@using System.Globalization
|
@using System.Globalization
|
||||||
@using System.IO
|
@using System.IO
|
||||||
@using System.Linq
|
@using System.Linq
|
||||||
@using System.Text
|
@using System.Text
|
||||||
|
|
||||||
@using GestionaDenunciasAN.Helpers
|
@using GestionaDenuncias.Shared.Helpers
|
||||||
@using GestionaDenunciasAN.Services
|
@using GestionaDenunciasAN.Services
|
||||||
@attribute [StreamRendering]
|
@attribute [StreamRendering]
|
||||||
@inject GestionaDenunciasAN.Models.UserState userState
|
@inject GestionaDenunciasAN.Models.UserState userState
|
||||||
@@ -731,6 +731,19 @@ else
|
|||||||
useAutoFoundExpediente = true;
|
useAutoFoundExpediente = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool IsReportFileName(string? fileName)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(fileName))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var name = Path.GetFileNameWithoutExtension(fileName);
|
||||||
|
var extension = Path.GetExtension(fileName);
|
||||||
|
return name.StartsWith("report", StringComparison.OrdinalIgnoreCase) &&
|
||||||
|
(extension.Equals(".txt", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
extension.Equals(".pdf", StringComparison.OrdinalIgnoreCase));
|
||||||
|
}
|
||||||
private string FixFileName(string input)
|
private string FixFileName(string input)
|
||||||
{
|
{
|
||||||
var n = input.Normalize(NormalizationForm.FormD);
|
var n = input.Normalize(NormalizationForm.FormD);
|
||||||
@@ -871,7 +884,7 @@ else
|
|||||||
string? documentoParaTramitar = null;
|
string? documentoParaTramitar = null;
|
||||||
|
|
||||||
var report = todos.FirstOrDefault(t =>
|
var report = todos.FirstOrDefault(t =>
|
||||||
string.Equals(t.FileName, reportTxt, StringComparison.OrdinalIgnoreCase));
|
IsReportFileName(t.FileName));
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(report.FileName))
|
if (!string.IsNullOrWhiteSpace(report.FileName))
|
||||||
{
|
{
|
||||||
@@ -887,7 +900,7 @@ else
|
|||||||
}
|
}
|
||||||
|
|
||||||
var adjuntos = todos
|
var adjuntos = todos
|
||||||
.Where(t => !string.Equals(t.FileName, reportTxt, StringComparison.OrdinalIgnoreCase))
|
.Where(t => !IsReportFileName(t.FileName))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
if (adjuntos.Count > 0 && uploadMode == "merge")
|
if (adjuntos.Count > 0 && uploadMode == "merge")
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@page "/Buscador"
|
@page "/Buscador"
|
||||||
@rendermode InteractiveServer
|
@rendermode InteractiveServer
|
||||||
@attribute [Authorize]
|
@attribute [Authorize]
|
||||||
|
|
||||||
@@ -62,7 +62,7 @@
|
|||||||
checked="@IsModo(ModoUltimos)"
|
checked="@IsModo(ModoUltimos)"
|
||||||
@onchange="@(() => SetModo(ModoUltimos))" />
|
@onchange="@(() => SetModo(ModoUltimos))" />
|
||||||
<label class="form-check-label" for="modoUltimos">
|
<label class="form-check-label" for="modoUltimos">
|
||||||
Últimos X meses
|
<EFBFBD>ltimos X meses
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -90,7 +90,7 @@
|
|||||||
{
|
{
|
||||||
<div class="row g-2 mt-2">
|
<div class="row g-2 mt-2">
|
||||||
<div class="col-md-3">
|
<div class="col-md-3">
|
||||||
<label class="form-label">Últimos meses</label>
|
<label class="form-label"><EFBFBD>ltimos meses</label>
|
||||||
<select class="form-select" @bind="mesesUltimos">
|
<select class="form-select" @bind="mesesUltimos">
|
||||||
<option value="3">3 meses</option>
|
<option value="3">3 meses</option>
|
||||||
<option value="6">6 meses</option>
|
<option value="6">6 meses</option>
|
||||||
@@ -100,7 +100,7 @@
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
<!-- Botón buscar -->
|
<!-- Bot<EFBFBD>n buscar -->
|
||||||
<div class="row g-2 mt-3">
|
<div class="row g-2 mt-3">
|
||||||
<div class="col-md-3">
|
<div class="col-md-3">
|
||||||
<button class="btn btn-primary"
|
<button class="btn btn-primary"
|
||||||
@@ -109,7 +109,7 @@
|
|||||||
@if (isSearching)
|
@if (isSearching)
|
||||||
{
|
{
|
||||||
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
|
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
|
||||||
<span class="ms-2">Buscando…</span>
|
<span class="ms-2">Buscando<EFBFBD></span>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -145,7 +145,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th>Expediente</th>
|
<th>Expediente</th>
|
||||||
<th>Asunto</th>
|
<th>Asunto</th>
|
||||||
<th>Fecha creación</th>
|
<th>Fecha creaci<EFBFBD>n</th>
|
||||||
<th>Estado</th>
|
<th>Estado</th>
|
||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -177,7 +177,7 @@
|
|||||||
<dt class="col-sm-2">Asunto</dt>
|
<dt class="col-sm-2">Asunto</dt>
|
||||||
<dd class="col-sm-10">@exp.Asunto</dd>
|
<dd class="col-sm-10">@exp.Asunto</dd>
|
||||||
|
|
||||||
<dt class="col-sm-2">Fecha creación</dt>
|
<dt class="col-sm-2">Fecha creaci<EFBFBD>n</dt>
|
||||||
<dd class="col-sm-10">
|
<dd class="col-sm-10">
|
||||||
@exp.FechaCreacion?.ToLocalTime().ToString("dd/MM/yyyy HH:mm")
|
@exp.FechaCreacion?.ToLocalTime().ToString("dd/MM/yyyy HH:mm")
|
||||||
</dd>
|
</dd>
|
||||||
@@ -228,7 +228,7 @@
|
|||||||
|
|
||||||
private List<ExpedienteTerceroDto> expedientes = new();
|
private List<ExpedienteTerceroDto> expedientes = new();
|
||||||
|
|
||||||
// expediente cuyo detalle está abierto
|
// expediente cuyo detalle est<EFBFBD> abierto
|
||||||
private ExpedienteTerceroDto? expedienteSeleccionado;
|
private ExpedienteTerceroDto? expedienteSeleccionado;
|
||||||
|
|
||||||
private bool IsModo(string valor) => string.Equals(modoFecha, valor, StringComparison.Ordinal);
|
private bool IsModo(string valor) => string.Equals(modoFecha, valor, StringComparison.Ordinal);
|
||||||
@@ -243,7 +243,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// BÚSQUEDA PRINCIPAL
|
// B<EFBFBD>SQUEDA PRINCIPAL
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
private async Task BuscarAsync()
|
private async Task BuscarAsync()
|
||||||
@@ -263,7 +263,7 @@
|
|||||||
DateTimeOffset? desde = null;
|
DateTimeOffset? desde = null;
|
||||||
DateTimeOffset? hasta = null;
|
DateTimeOffset? hasta = null;
|
||||||
|
|
||||||
// Calcular rango según el modo
|
// Calcular rango seg<EFBFBD>n el modo
|
||||||
if (modoFecha == ModoRango)
|
if (modoFecha == ModoRango)
|
||||||
{
|
{
|
||||||
if (!fechaDesde.HasValue || !fechaHasta.HasValue)
|
if (!fechaDesde.HasValue || !fechaHasta.HasValue)
|
||||||
@@ -285,7 +285,7 @@
|
|||||||
{
|
{
|
||||||
if (mesesUltimos <= 0)
|
if (mesesUltimos <= 0)
|
||||||
{
|
{
|
||||||
errorMessage = "El número de meses debe ser mayor que 0.";
|
errorMessage = "El n<EFBFBD>mero de meses debe ser mayor que 0.";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@page "/Error"
|
@page "/Error"
|
||||||
@using System.Diagnostics
|
@using System.Diagnostics
|
||||||
|
|
||||||
<PageTitle>Error</PageTitle>
|
<PageTitle>Error</PageTitle>
|
||||||
|
|||||||
@@ -17,9 +17,6 @@
|
|||||||
para seguir trayendo denuncias.
|
para seguir trayendo denuncias.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn btn-outline-secondary" @onclick="ProcessLocalZipsAsync" disabled="@LocalBusy">
|
|
||||||
@(LocalBusy ? "Procesando..." : "Procesar carpeta local")
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if (!string.IsNullOrWhiteSpace(StatusMessage))
|
@if (!string.IsNullOrWhiteSpace(StatusMessage))
|
||||||
@@ -230,53 +227,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card shadow-sm mt-4">
|
|
||||||
<div class="card-body">
|
|
||||||
<div class="d-flex flex-column flex-md-row justify-content-between align-items-md-center gap-3 mb-3">
|
|
||||||
<div>
|
|
||||||
<h5 class="card-title mb-1">Carpeta local</h5>
|
|
||||||
<p class="text-muted mb-0">
|
|
||||||
ZIPs pendientes detectados en <code>C:\ZipsDenuncias</code>.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<button type="button" class="btn btn-outline-secondary" @onclick="ProcessLocalZipsAsync" disabled="@LocalBusy">
|
|
||||||
@(LocalBusy ? "Procesando..." : "Procesar ZIPs")
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if (!ExistingZips.Any())
|
|
||||||
{
|
|
||||||
<p class="mb-0 text-muted">No hay ZIPs pendientes en la carpeta.</p>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<div class="table-responsive">
|
|
||||||
<table class="table table-striped align-middle">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>ZIP</th>
|
|
||||||
<th class="text-end">Acciones</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@foreach (var zip in ExistingZips)
|
|
||||||
{
|
|
||||||
<tr>
|
|
||||||
<td>@zip</td>
|
|
||||||
<td class="text-end">
|
|
||||||
<button type="button" class="btn btn-outline-danger btn-sm" @onclick="() => DeleteZipAsync(zip)">
|
|
||||||
Eliminar
|
|
||||||
</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
@@ -285,7 +235,6 @@
|
|||||||
private List<ReportDto> VisibleReports = [];
|
private List<ReportDto> VisibleReports = [];
|
||||||
private HashSet<string> SelectedIds = [];
|
private HashSet<string> SelectedIds = [];
|
||||||
|
|
||||||
private IReadOnlyList<string> ExistingZips = Array.Empty<string>();
|
|
||||||
private ApiGlobalLeaksSessionDto? SessionInfo;
|
private ApiGlobalLeaksSessionDto? SessionInfo;
|
||||||
private InboxUserState UserInboxState = new();
|
private InboxUserState UserInboxState = new();
|
||||||
private string CurrentUsername { get; set; } = string.Empty;
|
private string CurrentUsername { get; set; } = string.Empty;
|
||||||
@@ -303,7 +252,6 @@
|
|||||||
private bool ReportsBusy { get; set; }
|
private bool ReportsBusy { get; set; }
|
||||||
private bool RenewBusy { get; set; }
|
private bool RenewBusy { get; set; }
|
||||||
private bool ImportBusy { get; set; }
|
private bool ImportBusy { get; set; }
|
||||||
private bool LocalBusy { get; set; }
|
|
||||||
|
|
||||||
private bool CanUseGlobalLeaks => SessionInfo?.HasActiveSession == true;
|
private bool CanUseGlobalLeaks => SessionInfo?.HasActiveSession == true;
|
||||||
private int SelectedReportsCount => SelectedIds.Count;
|
private int SelectedReportsCount => SelectedIds.Count;
|
||||||
@@ -337,8 +285,6 @@
|
|||||||
"La aplicacion sigue iniciada, pero la sesion de GlobalLeaks no esta activa. Introduce un nuevo 2FA para renovarla.",
|
"La aplicacion sigue iniciada, pero la sesion de GlobalLeaks no esta activa. Introduce un nuevo 2FA para renovarla.",
|
||||||
"alert-warning");
|
"alert-warning");
|
||||||
}
|
}
|
||||||
|
|
||||||
await TryRefreshLocalZipListAsync();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task LoadSessionStateAsync()
|
private async Task LoadSessionStateAsync()
|
||||||
@@ -457,7 +403,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await RefreshLocalZipListAsync();
|
|
||||||
SelectedIds.Clear();
|
SelectedIds.Clear();
|
||||||
await LoadReportsAsync();
|
await LoadReportsAsync();
|
||||||
|
|
||||||
@@ -497,55 +442,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ProcessLocalZipsAsync()
|
|
||||||
{
|
|
||||||
LocalBusy = true;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var result = await ApiDenuncias.ProcessLocalZipsAsync();
|
|
||||||
await RefreshLocalZipListAsync();
|
|
||||||
|
|
||||||
if (result.TotalCandidates == 0)
|
|
||||||
{
|
|
||||||
SetStatus("No hay ZIPs pendientes en la carpeta local.", "alert-info");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
SetStatus(
|
|
||||||
result.Errors.Count == 0
|
|
||||||
? $"Se han procesado {result.ImportedCount} ZIP(s) de la carpeta local."
|
|
||||||
: $"Se han procesado {result.ImportedCount} ZIP(s) con incidencias: {string.Join(" | ", result.Errors)}",
|
|
||||||
result.Errors.Count == 0 ? "alert-success" : "alert-warning");
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
LocalBusy = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task DeleteZipAsync(string zipName)
|
|
||||||
{
|
|
||||||
await ApiDenuncias.DeleteZipAsync(zipName);
|
|
||||||
await RefreshLocalZipListAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task TryRefreshLocalZipListAsync()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await RefreshLocalZipListAsync();
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
ExistingZips = Array.Empty<string>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task RefreshLocalZipListAsync()
|
|
||||||
{
|
|
||||||
ExistingZips = await ApiDenuncias.GetExistingZipNamesAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ApplyFilters()
|
private void ApplyFilters()
|
||||||
{
|
{
|
||||||
IEnumerable<ReportDto> filtered = Reports;
|
IEnumerable<ReportDto> filtered = Reports;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@page "/Gestiona"
|
@page "/Gestiona"
|
||||||
@rendermode InteractiveServer
|
@rendermode InteractiveServer
|
||||||
@attribute [Authorize]
|
@attribute [Authorize]
|
||||||
@using GestionaDenunciasAN.Models
|
@using GestionaDenunciasAN.Models
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
@inject IDenunciaStore DenunciaStore
|
@inject IDenunciaStore DenunciaStore
|
||||||
@inject ApiDenunciasClient ApiDenuncias
|
@inject ApiDenunciasClient ApiDenuncias
|
||||||
|
|
||||||
<PageTitle>Denuncias Gestión</PageTitle>
|
<PageTitle>Denuncias Gesti<EFBFBD>n</PageTitle>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
/* Contenedor para la lista de denuncias */
|
/* Contenedor para la lista de denuncias */
|
||||||
@@ -65,7 +65,7 @@
|
|||||||
.card-body {
|
.card-body {
|
||||||
padding: 1.25rem;
|
padding: 1.25rem;
|
||||||
}
|
}
|
||||||
/* Estilos para los títulos de sección dentro de la card */
|
/* Estilos para los t<EFBFBD>tulos de secci<EFBFBD>n dentro de la card */
|
||||||
.section-heading {
|
.section-heading {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
@@ -76,9 +76,9 @@
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<h1>Denuncias en Gestión</h1>
|
<h1>Denuncias en Gesti<EFBFBD>n</h1>
|
||||||
|
|
||||||
<!-- Campo de búsqueda -->
|
<!-- Campo de b<EFBFBD>squeda -->
|
||||||
<input type="text"
|
<input type="text"
|
||||||
class="form-control"
|
class="form-control"
|
||||||
placeholder="Buscar denuncias..."
|
placeholder="Buscar denuncias..."
|
||||||
@@ -92,7 +92,7 @@
|
|||||||
}
|
}
|
||||||
else if (denunciasGestiona == null || !denunciasGestiona.Any())
|
else if (denunciasGestiona == null || !denunciasGestiona.Any())
|
||||||
{
|
{
|
||||||
<p>No hay denuncias en gestión.</p>
|
<p>No hay denuncias en gesti<EFBFBD>n.</p>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -138,12 +138,12 @@ else
|
|||||||
}
|
}
|
||||||
@if (!string.IsNullOrWhiteSpace(denuncia.ExpedienteGestionaMostrable))
|
@if (!string.IsNullOrWhiteSpace(denuncia.ExpedienteGestionaMostrable))
|
||||||
{
|
{
|
||||||
<dt class="col-sm-3">Nº expediente Gestiona</dt>
|
<dt class="col-sm-3">N<EFBFBD> expediente Gestiona</dt>
|
||||||
<dd class="col-sm-9">@denuncia.ExpedienteGestionaMostrable</dd>
|
<dd class="col-sm-9">@denuncia.ExpedienteGestionaMostrable</dd>
|
||||||
}
|
}
|
||||||
@if (denuncia.Id_Persona_Gestiona != 0)
|
@if (denuncia.Id_Persona_Gestiona != 0)
|
||||||
{
|
{
|
||||||
<dt class="col-sm-3">ID Persona Gestión</dt>
|
<dt class="col-sm-3">ID Persona Gesti<EFBFBD>n</dt>
|
||||||
<dd class="col-sm-9">@denuncia.Id_Persona_Gestiona</dd>
|
<dd class="col-sm-9">@denuncia.Id_Persona_Gestiona</dd>
|
||||||
}
|
}
|
||||||
@if (!string.IsNullOrWhiteSpace(denuncia.Etiqueta))
|
@if (!string.IsNullOrWhiteSpace(denuncia.Etiqueta))
|
||||||
@@ -192,13 +192,13 @@ else
|
|||||||
<dd class="col-sm-9">@denuncia.Asunto</dd>
|
<dd class="col-sm-9">@denuncia.Asunto</dd>
|
||||||
<dt class="col-sm-3">A Quien Denuncia</dt>
|
<dt class="col-sm-3">A Quien Denuncia</dt>
|
||||||
<dd class="col-sm-9">@denuncia.A_Quien_Denuncia</dd>
|
<dd class="col-sm-9">@denuncia.A_Quien_Denuncia</dd>
|
||||||
<dt class="col-sm-3">Descripción Denuncia</dt>
|
<dt class="col-sm-3">Descripci<EFBFBD>n Denuncia</dt>
|
||||||
<dd class="col-sm-9">@denuncia.Descripcion_Denuncia</dd>
|
<dd class="col-sm-9">@denuncia.Descripcion_Denuncia</dd>
|
||||||
<dt class="col-sm-3">Denunciado Ante Inst</dt>
|
<dt class="col-sm-3">Denunciado Ante Inst</dt>
|
||||||
<dd class="col-sm-9">@denuncia.Denunciado_Ante_Inst</dd>
|
<dd class="col-sm-9">@denuncia.Denunciado_Ante_Inst</dd>
|
||||||
@if (!string.IsNullOrWhiteSpace(denuncia.Modalidad_Informacion))
|
@if (!string.IsNullOrWhiteSpace(denuncia.Modalidad_Informacion))
|
||||||
{
|
{
|
||||||
<dt class="col-sm-3">Modalidad Información</dt>
|
<dt class="col-sm-3">Modalidad Informaci<EFBFBD>n</dt>
|
||||||
<dd class="col-sm-9">@denuncia.Modalidad_Informacion</dd>
|
<dd class="col-sm-9">@denuncia.Modalidad_Informacion</dd>
|
||||||
}
|
}
|
||||||
<dt class="col-sm-3">Lugar Hechos</dt>
|
<dt class="col-sm-3">Lugar Hechos</dt>
|
||||||
@@ -210,27 +210,27 @@ else
|
|||||||
}
|
}
|
||||||
</dl>
|
</dl>
|
||||||
|
|
||||||
<!-- Datos de Notificación -->
|
<!-- Datos de Notificaci<EFBFBD>n -->
|
||||||
<h5 class="section-heading">Datos de Notificación</h5>
|
<h5 class="section-heading">Datos de Notificaci<EFBFBD>n</h5>
|
||||||
<dl class="row">
|
<dl class="row">
|
||||||
@if (!string.IsNullOrWhiteSpace(denuncia.Notificacion_Preferencia))
|
@if (!string.IsNullOrWhiteSpace(denuncia.Notificacion_Preferencia))
|
||||||
{
|
{
|
||||||
<dt class="col-sm-3">Notificación Preferencia</dt>
|
<dt class="col-sm-3">Notificaci<EFBFBD>n Preferencia</dt>
|
||||||
<dd class="col-sm-9">@denuncia.Notificacion_Preferencia</dd>
|
<dd class="col-sm-9">@denuncia.Notificacion_Preferencia</dd>
|
||||||
}
|
}
|
||||||
@if (!string.IsNullOrWhiteSpace(denuncia.Notificacion_Electronica))
|
@if (!string.IsNullOrWhiteSpace(denuncia.Notificacion_Electronica))
|
||||||
{
|
{
|
||||||
<dt class="col-sm-3">Notificación Electrónica</dt>
|
<dt class="col-sm-3">Notificaci<EFBFBD>n Electr<EFBFBD>nica</dt>
|
||||||
<dd class="col-sm-9">@denuncia.Notificacion_Electronica</dd>
|
<dd class="col-sm-9">@denuncia.Notificacion_Electronica</dd>
|
||||||
}
|
}
|
||||||
@if (!string.IsNullOrWhiteSpace(denuncia.Correo_Electronico))
|
@if (!string.IsNullOrWhiteSpace(denuncia.Correo_Electronico))
|
||||||
{
|
{
|
||||||
<dt class="col-sm-3">Correo Electrónico</dt>
|
<dt class="col-sm-3">Correo Electr<EFBFBD>nico</dt>
|
||||||
<dd class="col-sm-9">@denuncia.Correo_Electronico</dd>
|
<dd class="col-sm-9">@denuncia.Correo_Electronico</dd>
|
||||||
}
|
}
|
||||||
@if (!string.IsNullOrWhiteSpace(denuncia.Notificacion_Sms))
|
@if (!string.IsNullOrWhiteSpace(denuncia.Notificacion_Sms))
|
||||||
{
|
{
|
||||||
<dt class="col-sm-3">Notificación SMS</dt>
|
<dt class="col-sm-3">Notificaci<EFBFBD>n SMS</dt>
|
||||||
<dd class="col-sm-9">@denuncia.Notificacion_Sms</dd>
|
<dd class="col-sm-9">@denuncia.Notificacion_Sms</dd>
|
||||||
}
|
}
|
||||||
</dl>
|
</dl>
|
||||||
@@ -241,7 +241,7 @@ else
|
|||||||
@if (denuncia.Condiciones)
|
@if (denuncia.Condiciones)
|
||||||
{
|
{
|
||||||
<dt class="col-sm-3">Condiciones</dt>
|
<dt class="col-sm-3">Condiciones</dt>
|
||||||
<dd class="col-sm-9">Sí</dd>
|
<dd class="col-sm-9">S<EFBFBD></dd>
|
||||||
}
|
}
|
||||||
@if (!string.IsNullOrWhiteSpace(denuncia.Comments))
|
@if (!string.IsNullOrWhiteSpace(denuncia.Comments))
|
||||||
{
|
{
|
||||||
@@ -258,7 +258,7 @@ else
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Nombre</th>
|
<th>Nombre</th>
|
||||||
<th>Tamaño (bytes)</th>
|
<th>Tama<EFBFBD>o (bytes)</th>
|
||||||
<th>Ver</th>
|
<th>Ver</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -309,7 +309,7 @@ else
|
|||||||
private List<DenunciasGestiona> denunciasGestiona = new();
|
private List<DenunciasGestiona> denunciasGestiona = new();
|
||||||
private Dictionary<int, List<FicherosDenuncias>> ficherosAdjuntos = new();
|
private Dictionary<int, List<FicherosDenuncias>> ficherosAdjuntos = new();
|
||||||
|
|
||||||
// Variable para la búsqueda
|
// Variable para la b<EFBFBD>squeda
|
||||||
private string busqueda = "";
|
private string busqueda = "";
|
||||||
|
|
||||||
private bool hasLoaded = false;
|
private bool hasLoaded = false;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@page "/Instrucciones"
|
@page "/Instrucciones"
|
||||||
@attribute [Authorize]
|
@attribute [Authorize]
|
||||||
@attribute [StreamRendering]
|
@attribute [StreamRendering]
|
||||||
@inject GestionaDenunciasAN.Models.UserState userState
|
@inject GestionaDenunciasAN.Models.UserState userState
|
||||||
@@ -7,22 +7,22 @@
|
|||||||
<PageTitle>Instrucciones</PageTitle>
|
<PageTitle>Instrucciones</PageTitle>
|
||||||
|
|
||||||
<div class="container mt-4">
|
<div class="container mt-4">
|
||||||
<h1 class="mb-4">Guía de Uso — Gestión de Denuncias</h1>
|
<h1 class="mb-4">Gu<EFBFBD>a de Uso <EFBFBD> Gesti<EFBFBD>n de Denuncias</h1>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Esta aplicación permite procesar denuncias desde archivos ZIP y gestionarlas en tres etapas:
|
Esta aplicaci<EFBFBD>n permite procesar denuncias desde archivos ZIP y gestionarlas en tres etapas:
|
||||||
<strong>Pendientes</strong>, <strong>Gestión</strong> (aceptadas) y <strong>Rechazadas</strong>.
|
<strong>Pendientes</strong>, <strong>Gesti<EFBFBD>n</strong> (aceptadas) y <strong>Rechazadas</strong>.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h2>1. Carga de ZIPs</h2>
|
<h2>1. Carga de ZIPs</h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
Sitúate en la pestaña <strong>Gestión de ZIP</strong>. Haz clic en <em>Subir nuevo ZIP</em>,
|
Sit<EFBFBD>ate en la pesta<EFBFBD>a <strong>Gesti<EFBFBD>n de ZIP</strong>. Haz clic en <em>Subir nuevo ZIP</em>,
|
||||||
selecciona uno o varios archivos <code>.zip</code> y espera a que se extraigan.
|
selecciona uno o varios archivos <code>.zip</code> y espera a que se extraigan.
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
Cada ZIP debe incluir un <code>report.txt</code> con los campos de la denuncia, y opcionalmente
|
Cada ZIP debe incluir un <code>report.txt</code> con los campos de la denuncia, y opcionalmente
|
||||||
subcarpetas <code>files</code> o <code>files_attached_from_recipients</code> con PDF e imágenes.
|
subcarpetas <code>files</code> o <code>files_attached_from_recipients</code> con PDF e im<EFBFBD>genes.
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
Tras el procesado, la app lee los <code>report.txt</code> y actualiza la base de datos:
|
Tras el procesado, la app lee los <code>report.txt</code> y actualiza la base de datos:
|
||||||
@@ -32,10 +32,10 @@
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h2>2. Pestaña <strong>Pendientes</strong></h2>
|
<h2>2. Pesta<EFBFBD>a <strong>Pendientes</strong></h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
Verás cada denuncia en una tarjeta colapsable con sus datos y el listado de ficheros adjuntos.
|
Ver<EFBFBD>s cada denuncia en una tarjeta colapsable con sus datos y el listado de ficheros adjuntos.
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
Hay dos acciones:
|
Hay dos acciones:
|
||||||
@@ -47,30 +47,30 @@
|
|||||||
<li>
|
<li>
|
||||||
Elegir el modo de subida:
|
Elegir el modo de subida:
|
||||||
<ul>
|
<ul>
|
||||||
<li><em>Unir</em> todos los ficheros en un único PDF.</li>
|
<li><em>Unir</em> todos los ficheros en un <EFBFBD>nico PDF.</li>
|
||||||
<li><em>Subir</em> cada fichero de forma independiente.</li>
|
<li><em>Subir</em> cada fichero de forma independiente.</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li>Seleccionar el grupo de destino (600, 510 o 700).</li>
|
<li>Seleccionar el grupo de destino (600, 510 o 700).</li>
|
||||||
<li>
|
<li>
|
||||||
Confirmar. La denuncia se crea y abre en Gestióna, sube los documentos
|
Confirmar. La denuncia se crea y abre en Gesti<EFBFBD>na, sube los documentos
|
||||||
y pasa a la pestaña <strong>Gestión</strong>.
|
y pasa a la pesta<EFBFBD>a <strong>Gesti<EFBFBD>n</strong>.
|
||||||
</li>
|
</li>
|
||||||
</ol>
|
</ol>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<strong>Rechazar denuncia</strong> (rojo): abre un modal para poner el motivo.
|
<strong>Rechazar denuncia</strong> (rojo): abre un modal para poner el motivo.
|
||||||
Al confirmar, la denuncia se marca como rechazada y va a la pestaña
|
Al confirmar, la denuncia se marca como rechazada y va a la pesta<EFBFBD>a
|
||||||
<strong>Rechazados</strong>.
|
<strong>Rechazados</strong>.
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h2>3. Pestaña <strong>Gestión</strong></h2>
|
<h2>3. Pesta<EFBFBD>a <strong>Gesti<EFBFBD>n</strong></h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
Aquí se listan las denuncias que ya han sido <em>enviadas a Gestión</em>.
|
Aqu<EFBFBD> se listan las denuncias que ya han sido <em>enviadas a Gesti<EFBFBD>n</em>.
|
||||||
Aparecen con fondo verde.
|
Aparecen con fondo verde.
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
@@ -78,34 +78,34 @@
|
|||||||
<ul>
|
<ul>
|
||||||
<li>ID, nombre, archivo subido</li>
|
<li>ID, nombre, archivo subido</li>
|
||||||
<li>Fecha y hora de subida</li>
|
<li>Fecha y hora de subida</li>
|
||||||
<li>Detalles completos y enlaces “Ver” a los PDFs/imágenes</li>
|
<li>Detalles completos y enlaces <EFBFBD>Ver<EFBFBD> a los PDFs/im<EFBFBD>genes</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h2>4. Pestaña <strong>Rechazadas</strong></h2>
|
<h2>4. Pesta<EFBFBD>a <strong>Rechazadas</strong></h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
Aquí verás todas las denuncias que han sido rechazadas. Fondo rojo.
|
Aqu<EFBFBD> ver<EFBFBD>s todas las denuncias que han sido rechazadas. Fondo rojo.
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
Cada tarjeta muestra el motivo de rechazo y la fecha/hora en que se marcó.
|
Cada tarjeta muestra el motivo de rechazo y la fecha/hora en que se marc<EFBFBD>.
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h2>5. Flujo completo</h2>
|
<h2>5. Flujo completo</h2>
|
||||||
<ol>
|
<ol>
|
||||||
<li>Subes uno o varios ZIP en la pestaña <strong>Gestión de ZIP</strong>.</li>
|
<li>Subes uno o varios ZIP en la pesta<EFBFBD>a <strong>Gesti<EFBFBD>n de ZIP</strong>.</li>
|
||||||
<li>La aplicación extrae y parsea informes, los añade a <strong>Pendientes</strong>.</li>
|
<li>La aplicaci<EFBFBD>n extrae y parsea informes, los a<EFBFBD>ade a <strong>Pendientes</strong>.</li>
|
||||||
<li>
|
<li>
|
||||||
En <strong>Pendientes</strong> eliges qué hacer con cada denuncia:
|
En <strong>Pendientes</strong> eliges qu<EFBFBD> hacer con cada denuncia:
|
||||||
<ul>
|
<ul>
|
||||||
<li><strong>Configurar subida</strong> → pasa a <strong>Gestión</strong>.</li>
|
<li><strong>Configurar subida</strong> ? pasa a <strong>Gesti<EFBFBD>n</strong>.</li>
|
||||||
<li><strong>Rechazar denuncia</strong> → pasa a <strong>Rechazadas</strong>.</li>
|
<li><strong>Rechazar denuncia</strong> ? pasa a <strong>Rechazadas</strong>.</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
En <strong>Gestión</strong> puedes revisar lo ya subido; en
|
En <strong>Gesti<EFBFBD>n</strong> puedes revisar lo ya subido; en
|
||||||
<strong>Rechazadas</strong> ves los motivos.
|
<strong>Rechazadas</strong> ves los motivos.
|
||||||
</li>
|
</li>
|
||||||
</ol>
|
</ol>
|
||||||
|
|||||||
@@ -2,13 +2,13 @@
|
|||||||
@rendermode InteractiveServer
|
@rendermode InteractiveServer
|
||||||
@attribute [Authorize]
|
@attribute [Authorize]
|
||||||
|
|
||||||
@using GestionaDenunciasAN.Models
|
@using GestionaDenuncias.Shared.Models
|
||||||
@using System.Globalization
|
@using System.Globalization
|
||||||
@using System.IO
|
@using System.IO
|
||||||
@using System.Linq
|
@using System.Linq
|
||||||
@using System.Text
|
@using System.Text
|
||||||
|
|
||||||
@using GestionaDenunciasAN.Helpers
|
@using GestionaDenuncias.Shared.Helpers
|
||||||
@using GestionaDenunciasAN.Services
|
@using GestionaDenunciasAN.Services
|
||||||
|
|
||||||
@attribute [StreamRendering]
|
@attribute [StreamRendering]
|
||||||
@@ -175,6 +175,11 @@
|
|||||||
|
|
||||||
<h1>Denuncias Pendientes</h1>
|
<h1>Denuncias Pendientes</h1>
|
||||||
|
|
||||||
|
@if (!string.IsNullOrWhiteSpace(loadError))
|
||||||
|
{
|
||||||
|
<div class="alert alert-danger">@loadError</div>
|
||||||
|
}
|
||||||
|
|
||||||
<input type="text"
|
<input type="text"
|
||||||
class="form-control"
|
class="form-control"
|
||||||
placeholder="Buscar denuncias..."
|
placeholder="Buscar denuncias..."
|
||||||
@@ -849,6 +854,7 @@ else
|
|||||||
private Dictionary<int, string> preselectedFicheros = new();
|
private Dictionary<int, string> preselectedFicheros = new();
|
||||||
|
|
||||||
private bool hasLoaded = false;
|
private bool hasLoaded = false;
|
||||||
|
private string loadError = string.Empty;
|
||||||
private bool showModal = false;
|
private bool showModal = false;
|
||||||
private bool showModalRechazo = false;
|
private bool showModalRechazo = false;
|
||||||
|
|
||||||
@@ -876,6 +882,9 @@ else
|
|||||||
|
|
||||||
private async Task CargarDatosAsync()
|
private async Task CargarDatosAsync()
|
||||||
{
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
loadError = string.Empty;
|
||||||
var todas = await CargarDenunciasJsonAsync();
|
var todas = await CargarDenunciasJsonAsync();
|
||||||
|
|
||||||
// Asegura ProcedureId/GroupId por si faltan
|
// Asegura ProcedureId/GroupId por si faltan
|
||||||
@@ -897,6 +906,13 @@ else
|
|||||||
.Where(f => idsPend.Contains(f.Id_Denuncia))
|
.Where(f => idsPend.Contains(f.Id_Denuncia))
|
||||||
.GroupBy(f => f.Id_Denuncia)
|
.GroupBy(f => f.Id_Denuncia)
|
||||||
.ToDictionary(g => g.Key, g => g.ToList());
|
.ToDictionary(g => g.Key, g => g.ToList());
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
pendientes.Clear();
|
||||||
|
ficherosAdjuntos.Clear();
|
||||||
|
loadError = $"No se han podido cargar las denuncias pendientes: {ex.Message}";
|
||||||
|
}
|
||||||
|
|
||||||
hasLoaded = true;
|
hasLoaded = true;
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
@@ -912,6 +928,19 @@ else
|
|||||||
return await DenunciaStore.GetAllFicherosAsync();
|
return await DenunciaStore.GetAllFicherosAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool IsReportFileName(string? fileName)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(fileName))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var name = Path.GetFileNameWithoutExtension(fileName);
|
||||||
|
var extension = Path.GetExtension(fileName);
|
||||||
|
return name.StartsWith("report", StringComparison.OrdinalIgnoreCase) &&
|
||||||
|
(extension.Equals(".txt", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
extension.Equals(".pdf", StringComparison.OrdinalIgnoreCase));
|
||||||
|
}
|
||||||
private string FixFileName(string input)
|
private string FixFileName(string input)
|
||||||
{
|
{
|
||||||
var normalized = input.Normalize(NormalizationForm.FormD);
|
var normalized = input.Normalize(NormalizationForm.FormD);
|
||||||
@@ -995,7 +1024,7 @@ else
|
|||||||
string? documentoParaTramitar = null;
|
string? documentoParaTramitar = null;
|
||||||
|
|
||||||
var report = todos.FirstOrDefault(t =>
|
var report = todos.FirstOrDefault(t =>
|
||||||
string.Equals(t.FileName, reportTxt, StringComparison.OrdinalIgnoreCase));
|
IsReportFileName(t.FileName));
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(report.FileName))
|
if (!string.IsNullOrWhiteSpace(report.FileName))
|
||||||
{
|
{
|
||||||
@@ -1013,7 +1042,7 @@ else
|
|||||||
}
|
}
|
||||||
|
|
||||||
var adjuntos = todos
|
var adjuntos = todos
|
||||||
.Where(t => !string.Equals(t.FileName, reportTxt, StringComparison.OrdinalIgnoreCase))
|
.Where(t => !IsReportFileName(t.FileName))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
if (adjuntos.Count > 0 && uploadMode == "merge")
|
if (adjuntos.Count > 0 && uploadMode == "merge")
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@page "/Rechazados"
|
@page "/Rechazados"
|
||||||
@rendermode InteractiveServer
|
@rendermode InteractiveServer
|
||||||
@attribute [Authorize]
|
@attribute [Authorize]
|
||||||
@using GestionaDenunciasAN.Models
|
@using GestionaDenunciasAN.Models
|
||||||
@@ -62,7 +62,7 @@
|
|||||||
.card-body {
|
.card-body {
|
||||||
padding: 1.25rem;
|
padding: 1.25rem;
|
||||||
}
|
}
|
||||||
/* Estilos para los títulos de sección dentro de la card */
|
/* Estilos para los t<EFBFBD>tulos de secci<EFBFBD>n dentro de la card */
|
||||||
.section-heading {
|
.section-heading {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
@@ -75,7 +75,7 @@
|
|||||||
|
|
||||||
<h1>Denuncias Rechazadas</h1>
|
<h1>Denuncias Rechazadas</h1>
|
||||||
|
|
||||||
<!-- Campo de búsqueda -->
|
<!-- Campo de b<EFBFBD>squeda -->
|
||||||
<input type="text"
|
<input type="text"
|
||||||
class="form-control"
|
class="form-control"
|
||||||
placeholder="Buscar denuncias..."
|
placeholder="Buscar denuncias..."
|
||||||
@@ -134,12 +134,12 @@ else
|
|||||||
}
|
}
|
||||||
@if (!string.IsNullOrWhiteSpace(denuncia.ExpedienteGestionaMostrable))
|
@if (!string.IsNullOrWhiteSpace(denuncia.ExpedienteGestionaMostrable))
|
||||||
{
|
{
|
||||||
<dt class="col-sm-3">Nº expediente Gestiona</dt>
|
<dt class="col-sm-3">N<EFBFBD> expediente Gestiona</dt>
|
||||||
<dd class="col-sm-9">@denuncia.ExpedienteGestionaMostrable</dd>
|
<dd class="col-sm-9">@denuncia.ExpedienteGestionaMostrable</dd>
|
||||||
}
|
}
|
||||||
@if (denuncia.Id_Persona_Gestiona != 0)
|
@if (denuncia.Id_Persona_Gestiona != 0)
|
||||||
{
|
{
|
||||||
<dt class="col-sm-3">ID Persona Gestión</dt>
|
<dt class="col-sm-3">ID Persona Gesti<EFBFBD>n</dt>
|
||||||
<dd class="col-sm-9">@denuncia.Id_Persona_Gestiona</dd>
|
<dd class="col-sm-9">@denuncia.Id_Persona_Gestiona</dd>
|
||||||
}
|
}
|
||||||
@if (!string.IsNullOrWhiteSpace(denuncia.Etiqueta))
|
@if (!string.IsNullOrWhiteSpace(denuncia.Etiqueta))
|
||||||
@@ -187,13 +187,13 @@ else
|
|||||||
<dd class="col-sm-9">@denuncia.Asunto</dd>
|
<dd class="col-sm-9">@denuncia.Asunto</dd>
|
||||||
<dt class="col-sm-3">A Quien Denuncia</dt>
|
<dt class="col-sm-3">A Quien Denuncia</dt>
|
||||||
<dd class="col-sm-9">@denuncia.A_Quien_Denuncia</dd>
|
<dd class="col-sm-9">@denuncia.A_Quien_Denuncia</dd>
|
||||||
<dt class="col-sm-3">Descripción Denuncia</dt>
|
<dt class="col-sm-3">Descripci<EFBFBD>n Denuncia</dt>
|
||||||
<dd class="col-sm-9">@denuncia.Descripcion_Denuncia</dd>
|
<dd class="col-sm-9">@denuncia.Descripcion_Denuncia</dd>
|
||||||
<dt class="col-sm-3">Denunciado Ante Inst</dt>
|
<dt class="col-sm-3">Denunciado Ante Inst</dt>
|
||||||
<dd class="col-sm-9">@denuncia.Denunciado_Ante_Inst</dd>
|
<dd class="col-sm-9">@denuncia.Denunciado_Ante_Inst</dd>
|
||||||
@if (!string.IsNullOrWhiteSpace(denuncia.Modalidad_Informacion))
|
@if (!string.IsNullOrWhiteSpace(denuncia.Modalidad_Informacion))
|
||||||
{
|
{
|
||||||
<dt class="col-sm-3">Modalidad Información</dt>
|
<dt class="col-sm-3">Modalidad Informaci<EFBFBD>n</dt>
|
||||||
<dd class="col-sm-9">@denuncia.Modalidad_Informacion</dd>
|
<dd class="col-sm-9">@denuncia.Modalidad_Informacion</dd>
|
||||||
}
|
}
|
||||||
<dt class="col-sm-3">Lugar Hechos</dt>
|
<dt class="col-sm-3">Lugar Hechos</dt>
|
||||||
@@ -205,27 +205,27 @@ else
|
|||||||
}
|
}
|
||||||
</dl>
|
</dl>
|
||||||
|
|
||||||
<!-- Datos de Notificación -->
|
<!-- Datos de Notificaci<EFBFBD>n -->
|
||||||
<h5 class="section-heading">Datos de Notificación</h5>
|
<h5 class="section-heading">Datos de Notificaci<EFBFBD>n</h5>
|
||||||
<dl class="row">
|
<dl class="row">
|
||||||
@if (!string.IsNullOrWhiteSpace(denuncia.Notificacion_Preferencia))
|
@if (!string.IsNullOrWhiteSpace(denuncia.Notificacion_Preferencia))
|
||||||
{
|
{
|
||||||
<dt class="col-sm-3">Notificación Preferencia</dt>
|
<dt class="col-sm-3">Notificaci<EFBFBD>n Preferencia</dt>
|
||||||
<dd class="col-sm-9">@denuncia.Notificacion_Preferencia</dd>
|
<dd class="col-sm-9">@denuncia.Notificacion_Preferencia</dd>
|
||||||
}
|
}
|
||||||
@if (!string.IsNullOrWhiteSpace(denuncia.Notificacion_Electronica))
|
@if (!string.IsNullOrWhiteSpace(denuncia.Notificacion_Electronica))
|
||||||
{
|
{
|
||||||
<dt class="col-sm-3">Notificación Electrónica</dt>
|
<dt class="col-sm-3">Notificaci<EFBFBD>n Electr<EFBFBD>nica</dt>
|
||||||
<dd class="col-sm-9">@denuncia.Notificacion_Electronica</dd>
|
<dd class="col-sm-9">@denuncia.Notificacion_Electronica</dd>
|
||||||
}
|
}
|
||||||
@if (!string.IsNullOrWhiteSpace(denuncia.Correo_Electronico))
|
@if (!string.IsNullOrWhiteSpace(denuncia.Correo_Electronico))
|
||||||
{
|
{
|
||||||
<dt class="col-sm-3">Correo Electrónico</dt>
|
<dt class="col-sm-3">Correo Electr<EFBFBD>nico</dt>
|
||||||
<dd class="col-sm-9">@denuncia.Correo_Electronico</dd>
|
<dd class="col-sm-9">@denuncia.Correo_Electronico</dd>
|
||||||
}
|
}
|
||||||
@if (!string.IsNullOrWhiteSpace(denuncia.Notificacion_Sms))
|
@if (!string.IsNullOrWhiteSpace(denuncia.Notificacion_Sms))
|
||||||
{
|
{
|
||||||
<dt class="col-sm-3">Notificación SMS</dt>
|
<dt class="col-sm-3">Notificaci<EFBFBD>n SMS</dt>
|
||||||
<dd class="col-sm-9">@denuncia.Notificacion_Sms</dd>
|
<dd class="col-sm-9">@denuncia.Notificacion_Sms</dd>
|
||||||
}
|
}
|
||||||
</dl>
|
</dl>
|
||||||
@@ -236,7 +236,7 @@ else
|
|||||||
@if (denuncia.Condiciones)
|
@if (denuncia.Condiciones)
|
||||||
{
|
{
|
||||||
<dt class="col-sm-3">Condiciones</dt>
|
<dt class="col-sm-3">Condiciones</dt>
|
||||||
<dd class="col-sm-9">Sí</dd>
|
<dd class="col-sm-9">S<EFBFBD></dd>
|
||||||
}
|
}
|
||||||
@if (!string.IsNullOrWhiteSpace(denuncia.Comments))
|
@if (!string.IsNullOrWhiteSpace(denuncia.Comments))
|
||||||
{
|
{
|
||||||
@@ -253,7 +253,7 @@ else
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Nombre</th>
|
<th>Nombre</th>
|
||||||
<th>Tamaño (bytes)</th>
|
<th>Tama<EFBFBD>o (bytes)</th>
|
||||||
<th>Ver</th>
|
<th>Ver</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -304,7 +304,7 @@ else
|
|||||||
private List<DenunciasGestiona> denunciasRechazadas = new();
|
private List<DenunciasGestiona> denunciasRechazadas = new();
|
||||||
private Dictionary<int, List<FicherosDenuncias>> ficherosAdjuntos = new();
|
private Dictionary<int, List<FicherosDenuncias>> ficherosAdjuntos = new();
|
||||||
|
|
||||||
// Variable para la búsqueda
|
// Variable para la b<EFBFBD>squeda
|
||||||
private string busqueda = "";
|
private string busqueda = "";
|
||||||
|
|
||||||
private bool hasLoaded = false;
|
private bool hasLoaded = false;
|
||||||
|
|||||||
@@ -11,5 +11,7 @@
|
|||||||
@using GestionaDenunciasAN
|
@using GestionaDenunciasAN
|
||||||
@using GestionaDenunciasAN.Components
|
@using GestionaDenunciasAN.Components
|
||||||
@using GestionaDenunciasAN.Components.Layout
|
@using GestionaDenunciasAN.Components.Layout
|
||||||
|
@using GestionaDenuncias.Shared.Models
|
||||||
|
@using GestionaDenuncias.Shared.Services
|
||||||
@using GestionaDenunciasAN.Models
|
@using GestionaDenunciasAN.Models
|
||||||
@using GestionaDenunciasAN.Services
|
@using GestionaDenunciasAN.Services
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
namespace GestionaDenunciasAN.Configuration;
|
|
||||||
|
|
||||||
public sealed class ComplaintStorageOptions
|
|
||||||
{
|
|
||||||
public const string SectionName = "ComplaintStorage";
|
|
||||||
|
|
||||||
public string ConnectionString { get; set; } = string.Empty;
|
|
||||||
public bool AutoCreateSchema { get; set; }
|
|
||||||
}
|
|
||||||
@@ -7,13 +7,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Konscious.Security.Cryptography.Argon2" Version="1.3.1" />
|
<ProjectReference Include="..\GestionaDenuncias.Shared\GestionaDenuncias.Shared.csproj" />
|
||||||
<PackageReference Include="MySqlConnector" Version="2.4.0" />
|
|
||||||
<PackageReference Include="PdfSharpCore" Version="1.3.67" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Content Include="Scripts\gestiondenuncias_schema.sql" CopyToOutputDirectory="PreserveNewest" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
2
Antifraude.Net/GestionaDenunciasAN/GlobalUsings.cs
Normal file
2
Antifraude.Net/GestionaDenunciasAN/GlobalUsings.cs
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
global using GestionaDenuncias.Shared.Models;
|
||||||
|
global using GestionaDenuncias.Shared.Services;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace GestionaDenunciasAN.Models
|
namespace GestionaDenunciasAN.Models
|
||||||
{
|
{
|
||||||
public class UserState
|
public class UserState
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using System.Text.RegularExpressions;
|
|||||||
using GestionaDenunciasAN.Components;
|
using GestionaDenunciasAN.Components;
|
||||||
using GestionaDenunciasAN.Configuration;
|
using GestionaDenunciasAN.Configuration;
|
||||||
using GestionaDenunciasAN.Models;
|
using GestionaDenunciasAN.Models;
|
||||||
|
using GestionaDenuncias.Shared.Models;
|
||||||
using GestionaDenunciasAN.Services;
|
using GestionaDenunciasAN.Services;
|
||||||
using Microsoft.AspNetCore.Authentication;
|
using Microsoft.AspNetCore.Authentication;
|
||||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||||
@@ -71,7 +72,10 @@ app.Use(async (context, next) =>
|
|||||||
await next();
|
await next();
|
||||||
});
|
});
|
||||||
|
|
||||||
app.UseHttpsRedirection();
|
if (builder.Configuration.GetValue("ForceHttpsRedirection", false))
|
||||||
|
{
|
||||||
|
app.UseHttpsRedirection();
|
||||||
|
}
|
||||||
app.UseStaticFiles();
|
app.UseStaticFiles();
|
||||||
app.UseRouting();
|
app.UseRouting();
|
||||||
app.UseAuthentication();
|
app.UseAuthentication();
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using GestionaDenunciasAN.Models;
|
using GestionaDenuncias.Shared.Models;
|
||||||
|
|
||||||
namespace GestionaDenunciasAN.Services;
|
namespace GestionaDenunciasAN.Services;
|
||||||
|
|
||||||
@@ -16,7 +16,6 @@ public sealed class ApiDenunciaStore : IDenunciaStore
|
|||||||
|
|
||||||
public async Task<List<DenunciasGestiona>> GetAllDenunciasAsync(CancellationToken cancellationToken = default)
|
public async Task<List<DenunciasGestiona>> GetAllDenunciasAsync(CancellationToken cancellationToken = default)
|
||||||
=> (await _api.GetAsync<List<DenunciasGestiona>>("api/denuncias", cancellationToken)) ?? [];
|
=> (await _api.GetAsync<List<DenunciasGestiona>>("api/denuncias", cancellationToken)) ?? [];
|
||||||
|
|
||||||
public async Task<List<FicherosDenuncias>> GetAllFicherosAsync(CancellationToken cancellationToken = default)
|
public async Task<List<FicherosDenuncias>> GetAllFicherosAsync(CancellationToken cancellationToken = default)
|
||||||
=> (await _api.GetAsync<List<FicherosDenuncias>>("api/denuncias/ficheros", cancellationToken)) ?? [];
|
=> (await _api.GetAsync<List<FicherosDenuncias>>("api/denuncias/ficheros", cancellationToken)) ?? [];
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ using System.Net;
|
|||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
using System.Net.Http.Json;
|
using System.Net.Http.Json;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using GestionaDenunciasAN.Models;
|
using GestionaDenuncias.Shared.Models;
|
||||||
using Microsoft.AspNetCore.Components.Authorization;
|
using Microsoft.AspNetCore.Components.Authorization;
|
||||||
|
|
||||||
namespace GestionaDenunciasAN.Services;
|
namespace GestionaDenunciasAN.Services;
|
||||||
@@ -18,15 +18,18 @@ public sealed class ApiDenunciasClient
|
|||||||
private readonly IHttpClientFactory _httpClientFactory;
|
private readonly IHttpClientFactory _httpClientFactory;
|
||||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||||
private readonly AuthenticationStateProvider _authenticationStateProvider;
|
private readonly AuthenticationStateProvider _authenticationStateProvider;
|
||||||
|
private readonly ILogger<ApiDenunciasClient> _logger;
|
||||||
|
|
||||||
public ApiDenunciasClient(
|
public ApiDenunciasClient(
|
||||||
IHttpClientFactory httpClientFactory,
|
IHttpClientFactory httpClientFactory,
|
||||||
IHttpContextAccessor httpContextAccessor,
|
IHttpContextAccessor httpContextAccessor,
|
||||||
AuthenticationStateProvider authenticationStateProvider)
|
AuthenticationStateProvider authenticationStateProvider,
|
||||||
|
ILogger<ApiDenunciasClient> logger)
|
||||||
{
|
{
|
||||||
_httpClientFactory = httpClientFactory;
|
_httpClientFactory = httpClientFactory;
|
||||||
_httpContextAccessor = httpContextAccessor;
|
_httpContextAccessor = httpContextAccessor;
|
||||||
_authenticationStateProvider = authenticationStateProvider;
|
_authenticationStateProvider = authenticationStateProvider;
|
||||||
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<ApiLoginResponse> LoginAsync(LoginRequest request, CancellationToken cancellationToken = default)
|
public Task<ApiLoginResponse> LoginAsync(LoginRequest request, CancellationToken cancellationToken = default)
|
||||||
@@ -212,16 +215,26 @@ public sealed class ApiDenunciasClient
|
|||||||
var token = await GetAccessTokenAsync();
|
var token = await GetAccessTokenAsync();
|
||||||
if (string.IsNullOrWhiteSpace(token))
|
if (string.IsNullOrWhiteSpace(token))
|
||||||
{
|
{
|
||||||
|
_logger.LogWarning("No hay token de API disponible para llamar a {Path}. Usuario autenticado={IsAuthenticated}",
|
||||||
|
path,
|
||||||
|
_httpContextAccessor.HttpContext?.User.Identity?.IsAuthenticated);
|
||||||
throw new UnauthorizedAccessException("No hay token de API activo. Vuelve a iniciar sesion.");
|
throw new UnauthorizedAccessException("No hay token de API activo. Vuelve a iniciar sesion.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_logger.LogInformation("Llamando a API protegida {Path}. Token presente={TokenPresent}", path, true);
|
||||||
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
|
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
|
||||||
}
|
}
|
||||||
|
|
||||||
using var response = await client.SendAsync(request, cancellationToken);
|
using var response = await client.SendAsync(request, cancellationToken);
|
||||||
if (response.StatusCode == HttpStatusCode.Unauthorized)
|
if (response.StatusCode == HttpStatusCode.Unauthorized)
|
||||||
{
|
{
|
||||||
throw new UnauthorizedAccessException("La sesion de API ha caducado. Vuelve a iniciar sesion.");
|
if (authorize)
|
||||||
|
{
|
||||||
|
throw new UnauthorizedAccessException($"La sesion de API ha caducado al llamar a {path}. Vuelve a iniciar sesion.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var message = await ReadErrorMessageAsync(response, cancellationToken);
|
||||||
|
throw new UnauthorizedAccessException(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!response.IsSuccessStatusCode)
|
if (!response.IsSuccessStatusCode)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using GestionaDenunciasAN.Models;
|
using GestionaDenuncias.Shared.Models;
|
||||||
|
|
||||||
namespace GestionaDenunciasAN.Services;
|
namespace GestionaDenunciasAN.Services;
|
||||||
|
|
||||||
@@ -32,4 +32,13 @@ public sealed class ApiInboxTrackingService : IInboxTrackingService
|
|||||||
"api/tracking/imported",
|
"api/tracking/imported",
|
||||||
new MarkReportImportedRequest(username, report, complaintId),
|
new MarkReportImportedRequest(username, report, complaintId),
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
|
|
||||||
|
public Task EnsureReportCanBeImportedByUserAsync(
|
||||||
|
string username,
|
||||||
|
ReportDto report,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
=> _api.PostAsync(
|
||||||
|
"api/tracking/import-permission",
|
||||||
|
new TrackingImportPermissionRequest(username, report),
|
||||||
|
cancellationToken);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"AllowedHosts": "*",
|
"AllowedHosts": "*",
|
||||||
|
"ForceHttpsRedirection": false,
|
||||||
"ApiDenuncias": {
|
"ApiDenuncias": {
|
||||||
"BaseUrl": "https://localhost:7093"
|
"BaseUrl": "http://localhost:7093"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"AllowedHosts": "rp-antifraude.tecnosis.online;crp-antifraude.tecnosis.online;localhost;127.0.0.1",
|
"AllowedHosts": "registrodepersonal.antifraudeandalucia.es;rp-antifraude.tecnosis.online;crp-antifraude.tecnosis.online;localhost;127.0.0.1",
|
||||||
"SwaggerCC": "http://localhost:103/api/",
|
"SwaggerCC": "http://localhost:103/api/",
|
||||||
"SwaggerVB": "http://localhost:888/",
|
"SwaggerVB": "http://localhost:888/",
|
||||||
"CertificateLogin": {
|
"CertificateLogin": {
|
||||||
@@ -12,7 +12,9 @@
|
|||||||
"Ssl-Client-Cert"
|
"Ssl-Client-Cert"
|
||||||
],
|
],
|
||||||
"AllowedParentOrigins": [
|
"AllowedParentOrigins": [
|
||||||
"https://rp-antifraude.tecnosis.online"
|
"https://rp-antifraude.tecnosis.online",
|
||||||
|
"https://registrodepersonal.antifraudeandalucia.es",
|
||||||
|
"http://192.168.41.122:5000"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"ReverseProxy": {
|
"ReverseProxy": {
|
||||||
|
|||||||
Reference in New Issue
Block a user