Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

136 строки
5.1KB

  1. using System.Security.Claims;
  2. using Campaign_Tracker.Server.Audit;
  3. using Campaign_Tracker.Server.Authorization;
  4. using Campaign_Tracker.Server.Municipalities;
  5. using Microsoft.AspNetCore.Authorization;
  6. using Microsoft.AspNetCore.Mvc;
  7. namespace Campaign_Tracker.Server.Controllers;
  8. /// <summary>
  9. /// Municipality account profile management (Story 1.10).
  10. /// Accessible to ClientServices and Admin roles (HasAny check includes Admin bypass).
  11. /// </summary>
  12. [ApiController]
  13. [Authorize(Policy = ApplicationPolicy.ClientServicesAccess)]
  14. [Route("api/municipalities/profiles")]
  15. public sealed class MunicipalityProfileController : ControllerBase
  16. {
  17. private readonly IMunicipalityProfileRepository _profiles;
  18. private readonly IAuditService _audit;
  19. private readonly TimeProvider _timeProvider;
  20. public MunicipalityProfileController(
  21. IMunicipalityProfileRepository profiles,
  22. IAuditService audit,
  23. TimeProvider timeProvider)
  24. {
  25. _profiles = profiles;
  26. _audit = audit;
  27. _timeProvider = timeProvider;
  28. }
  29. // ── AC #1, AC #2: create and immediately return the combined view ─────────
  30. [HttpPost]
  31. public async Task<ActionResult<MunicipalityProfileResponse>> Create(
  32. [FromBody] CreateMunicipalityProfileRequest request,
  33. CancellationToken cancellationToken)
  34. {
  35. var actor = GetActor();
  36. var result = await _profiles.CreateAsync(request.JCode, request.DisplayName, actor, cancellationToken);
  37. if (!result.Saved || result.Profile is null)
  38. return UnprocessableEntity(new MunicipalityProfileProblem(result.Error ?? "Save failed."));
  39. // AC #3: audit the creation
  40. _audit.Record(new AuditEvent(
  41. EventType: "MUNICIPALITY_PROFILE_CREATED",
  42. ActorIdentity: actor,
  43. Resource: $"municipalities/profiles/{result.Profile.ProfileId}",
  44. Outcome: $"created JCode={result.Profile.JCode}",
  45. TraceIdentifier: HttpContext.TraceIdentifier,
  46. RecordedAt: _timeProvider.GetUtcNow()));
  47. var view = await _profiles.GetByIdAsync(result.Profile.ProfileId, cancellationToken);
  48. return Ok(MunicipalityProfileResponse.From(view!));
  49. }
  50. // ── AC #2: list all profiles with resolved legacy data ───────────────────
  51. [HttpGet]
  52. public async Task<ActionResult<IReadOnlyList<MunicipalityProfileResponse>>> GetAll(
  53. CancellationToken cancellationToken)
  54. {
  55. var views = await _profiles.GetAllAsync(cancellationToken);
  56. return Ok(views.Select(MunicipalityProfileResponse.From).ToArray());
  57. }
  58. [HttpGet("{profileId}")]
  59. public async Task<ActionResult<MunicipalityProfileResponse>> GetById(
  60. string profileId,
  61. CancellationToken cancellationToken)
  62. {
  63. var view = await _profiles.GetByIdAsync(profileId, cancellationToken);
  64. return view is null ? NotFound() : Ok(MunicipalityProfileResponse.From(view));
  65. }
  66. // ── AC #3: update with audit log ─────────────────────────────────────────
  67. [HttpPut("{profileId}")]
  68. public async Task<ActionResult<MunicipalityProfileResponse>> Update(
  69. string profileId,
  70. [FromBody] UpdateMunicipalityProfileRequest request,
  71. CancellationToken cancellationToken)
  72. {
  73. var actor = GetActor();
  74. var result = await _profiles.UpdateAsync(profileId, request.DisplayName, actor, cancellationToken);
  75. if (!result.Saved || result.Profile is null)
  76. return UnprocessableEntity(new MunicipalityProfileProblem(result.Error ?? "Update failed."));
  77. _audit.Record(new AuditEvent(
  78. EventType: "MUNICIPALITY_PROFILE_UPDATED",
  79. ActorIdentity: actor,
  80. Resource: $"municipalities/profiles/{profileId}",
  81. Outcome: "updated display name",
  82. TraceIdentifier: HttpContext.TraceIdentifier,
  83. RecordedAt: _timeProvider.GetUtcNow()));
  84. var view = await _profiles.GetByIdAsync(profileId, cancellationToken);
  85. return Ok(MunicipalityProfileResponse.From(view!));
  86. }
  87. private string GetActor() =>
  88. User.Identity?.Name
  89. ?? User.FindFirstValue(ClaimTypes.NameIdentifier)
  90. ?? "unknown";
  91. }
  92. public sealed record CreateMunicipalityProfileRequest(string JCode, string? DisplayName);
  93. public sealed record UpdateMunicipalityProfileRequest(string? DisplayName);
  94. public sealed record MunicipalityProfileResponse(
  95. string ProfileId,
  96. string JCode,
  97. string? DisplayName,
  98. string UpdatedAt,
  99. string UpdatedBy,
  100. string? LegacyName,
  101. string? LegacyMailingAddress,
  102. string? LegacyCityStateZip)
  103. {
  104. public static MunicipalityProfileResponse From(MunicipalityProfileView view) =>
  105. new(view.Profile.ProfileId,
  106. view.Profile.JCode,
  107. view.Profile.DisplayName,
  108. view.Profile.UpdatedAt.ToString("O"),
  109. view.Profile.UpdatedBy,
  110. view.LegacyName,
  111. view.LegacyMailingAddress,
  112. view.LegacyCityStateZip);
  113. }
  114. public sealed record MunicipalityProfileProblem(string Error);

Powered by TurnKey Linux.