管理端后端
This commit is contained in:
1 parent
eebc5518c5
commit
4ef6f6c90d
24 files changed
+1745
-10
No files matched your search
@@ -8,12 +8,18 @@ public sealed class GlobalExceptionHandler(ILogger<GlobalExceptionHandler> logge
|
||||
public async ValueTask<bool> TryHandleAsync(HttpContext httpContext, Exception exception, CancellationToken cancellationToken)
|
||||
{
|
||||
var traceId = httpContext.TraceIdentifier;
|
||||
logger.LogError(exception, "Unhandled request failure. TraceId: {TraceId}", traceId);
|
||||
var status = exception switch
|
||||
{
|
||||
UnauthorizedAccessException => StatusCodes.Status403Forbidden,
|
||||
InvalidOperationException => StatusCodes.Status409Conflict,
|
||||
_ => StatusCodes.Status500InternalServerError
|
||||
};
|
||||
if (status >= 500) logger.LogError(exception, "Unhandled request failure. TraceId: {TraceId}", traceId);
|
||||
var problem = new ProblemDetails
|
||||
{
|
||||
Status = StatusCodes.Status500InternalServerError,
|
||||
Title = "An unexpected error occurred.",
|
||||
Type = "https://tools.ietf.org/html/rfc9110#section-15.6.1",
|
||||
Status = status,
|
||||
Title = status == StatusCodes.Status403Forbidden ? "The action is outside your authorized scope." : status == StatusCodes.Status409Conflict ? "The request conflicts with current data or workflow state." : "An unexpected error occurred.",
|
||||
Type = status == StatusCodes.Status403Forbidden ? "https://tools.ietf.org/html/rfc9110#section-15.5.4" : "https://tools.ietf.org/html/rfc9110#section-15.6.1",
|
||||
Instance = httpContext.Request.Path
|
||||
};
|
||||
problem.Extensions["traceId"] = traceId;
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
using ElectionSystem.Api.Modules.Organization;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace ElectionSystem.Api.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Authorize]
|
||||
public abstract class AdministrationControllerBase(RegionScopeService scopeService) : ControllerBase
|
||||
{
|
||||
protected Guid ActorId => RegionScopeService.GetRequiredUserId(User);
|
||||
protected RegionScopeService ScopeService { get; } = scopeService;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using ElectionSystem.Api.Infrastructure.Persistence;
|
||||
using ElectionSystem.Api.Modules.Organization.Domain;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace ElectionSystem.Api.Controllers;
|
||||
|
||||
[Route("api/admin/administrative-regions")]
|
||||
public sealed class AdministrativeRegionsController(ElectionDbContext dbContext, Modules.Organization.RegionScopeService scopeService, Modules.Organization.AreaCityCatalogImportService catalogImportService) : AdministrationControllerBase(scopeService)
|
||||
{
|
||||
[HttpGet]
|
||||
public async Task<ActionResult> List([FromQuery] Guid? parentId, CancellationToken cancellationToken)
|
||||
{
|
||||
var allowed = await ScopeService.GetManagedRegionIdsAsync(ActorId, cancellationToken);
|
||||
var items = await dbContext.AdministrativeRegions.Where(x => x.ParentId == parentId && allowed.Contains(x.Id)).OrderBy(x => x.Code)
|
||||
.Select(x => new { x.Id, x.ParentId, x.Code, x.Name, x.Level, x.Source, x.SourceVersion, x.IsSynthetic, x.IsActive }).ToListAsync(cancellationToken);
|
||||
return Ok(items);
|
||||
}
|
||||
|
||||
[HttpPost("area-city-import")]
|
||||
[RequestSizeLimit(15_000_000)]
|
||||
public async Task<ActionResult> ImportAreaCity(IFormFile file, [FromForm] string sourceVersion, CancellationToken cancellationToken)
|
||||
{
|
||||
await ScopeService.RequireSystemAdministratorAsync(ActorId, cancellationToken);
|
||||
if (file.Length == 0 || !Path.GetExtension(file.FileName).Equals(".csv", StringComparison.OrdinalIgnoreCase)) return ValidationProblem("请上传 AreaCity 的 ok_data_level4.csv 文件。");
|
||||
await using var stream = file.OpenReadStream();
|
||||
return Ok(await catalogImportService.ImportAsync(stream, sourceVersion, cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<ActionResult> Create(CreateAdministrativeRegionRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
await ScopeService.RequireSystemAdministratorAsync(ActorId, cancellationToken);
|
||||
if (request.Level != AdministrativeRegionLevel.Village) return ValidationProblem("基础省市区镇目录必须由受控目录导入,管理端仅可新增经确认的村/社区。");
|
||||
var parent = await dbContext.AdministrativeRegions.SingleOrDefaultAsync(x => x.Id == request.ParentId && x.IsActive, cancellationToken);
|
||||
if (parent is null || parent.Level != AdministrativeRegionLevel.Township) return ValidationProblem("村/社区必须属于有效的镇/街道。");
|
||||
if (await dbContext.AdministrativeRegions.AnyAsync(x => x.Code == request.Code && x.SourceVersion == request.SourceVersion, cancellationToken)) return Conflict(new { code = "region_code_exists" });
|
||||
var region = new AdministrativeRegion { ParentId = parent.Id, Code = request.Code.Trim(), Name = request.Name.Trim(), Level = request.Level, Source = "LocalApprovedVillageImport", SourceVersion = request.SourceVersion.Trim() };
|
||||
dbContext.AdministrativeRegions.Add(region);
|
||||
await dbContext.SaveChangesAsync(cancellationToken);
|
||||
return CreatedAtAction(nameof(List), new { parentId = parent.Id }, new { region.Id });
|
||||
}
|
||||
}
|
||||
|
||||
public sealed record CreateAdministrativeRegionRequest(Guid ParentId, string Code, string Name, AdministrativeRegionLevel Level, string SourceVersion);
|
||||
Loaded 3 of 24 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user