You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

187 line
6.3KB

  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. [ApiController]
  9. [Authorize(Policy = ApplicationPolicy.ClientServicesAccess)]
  10. [Route("api/municipalities/{profileId}/addresses")]
  11. public sealed class MunicipalityAddressesController : ControllerBase
  12. {
  13. private readonly IMunicipalityAddressRepository _addresses;
  14. private readonly IAuditService _audit;
  15. private readonly TimeProvider _timeProvider;
  16. public MunicipalityAddressesController(
  17. IMunicipalityAddressRepository addresses,
  18. IAuditService audit,
  19. TimeProvider timeProvider)
  20. {
  21. _addresses = addresses;
  22. _audit = audit;
  23. _timeProvider = timeProvider;
  24. }
  25. [HttpGet]
  26. public async Task<ActionResult<IReadOnlyList<MunicipalityAddressResponse>>> GetAll(
  27. string profileId,
  28. CancellationToken cancellationToken)
  29. {
  30. var addresses = await _addresses.GetByProfileIdAsync(profileId, cancellationToken);
  31. return Ok(addresses.Select(MunicipalityAddressResponse.From).ToArray());
  32. }
  33. [HttpGet("{addressId}")]
  34. public async Task<ActionResult<MunicipalityAddressResponse>> GetById(
  35. string profileId,
  36. string addressId,
  37. CancellationToken cancellationToken)
  38. {
  39. var address = await _addresses.GetByIdAsync(addressId, cancellationToken);
  40. return address is null ? NotFound() : Ok(MunicipalityAddressResponse.From(address));
  41. }
  42. [HttpPost]
  43. public async Task<ActionResult<MunicipalityAddressResponse>> Add(
  44. string profileId,
  45. [FromBody] AddMunicipalityAddressRequest request,
  46. CancellationToken cancellationToken)
  47. {
  48. var actor = GetActor();
  49. var result = await _addresses.AddAsync(
  50. profileId,
  51. request.AddressType,
  52. request.Street,
  53. request.City,
  54. request.State,
  55. request.ZipCode,
  56. request.EffectiveDate,
  57. actor,
  58. cancellationToken);
  59. if (!result.Saved || result.Address is null)
  60. return UnprocessableEntity(new MunicipalityAddressProblem(result.Error ?? "Save failed."));
  61. _audit.Record(new AuditEvent(
  62. EventType: "MUNICIPALITY_ADDRESS_ADDED",
  63. ActorIdentity: actor,
  64. Resource: $"municipalities/{profileId}/addresses/{result.Address.AddressId}",
  65. Outcome: $"added {result.Address.AddressType} address",
  66. TraceIdentifier: HttpContext.TraceIdentifier,
  67. RecordedAt: _timeProvider.GetUtcNow()));
  68. return CreatedAtAction(nameof(GetById),
  69. new { profileId, addressId = result.Address.AddressId },
  70. MunicipalityAddressResponse.From(result.Address));
  71. }
  72. [HttpPut("{addressId}")]
  73. public async Task<ActionResult<MunicipalityAddressResponse>> Update(
  74. string profileId,
  75. string addressId,
  76. [FromBody] UpdateMunicipalityAddressRequest request,
  77. CancellationToken cancellationToken)
  78. {
  79. var actor = GetActor();
  80. var result = await _addresses.UpdateAsync(
  81. addressId,
  82. request.AddressType,
  83. request.Street,
  84. request.City,
  85. request.State,
  86. request.ZipCode,
  87. request.EffectiveDate,
  88. actor,
  89. cancellationToken);
  90. if (!result.Saved || result.Address is null)
  91. {
  92. if (result.IsNotFound)
  93. return NotFound(new MunicipalityAddressProblem(result.Error ?? "Address not found."));
  94. return UnprocessableEntity(new MunicipalityAddressProblem(result.Error ?? "Update failed."));
  95. }
  96. _audit.Record(new AuditEvent(
  97. EventType: "MUNICIPALITY_ADDRESS_UPDATED",
  98. ActorIdentity: actor,
  99. Resource: $"municipalities/{profileId}/addresses/{addressId}",
  100. Outcome: $"updated {result.Address.AddressType} address — new record {result.Address.AddressId}",
  101. TraceIdentifier: HttpContext.TraceIdentifier,
  102. RecordedAt: _timeProvider.GetUtcNow()));
  103. return Ok(MunicipalityAddressResponse.From(result.Address));
  104. }
  105. [HttpDelete("{addressId}")]
  106. public async Task<IActionResult> Delete(
  107. string profileId,
  108. string addressId,
  109. CancellationToken cancellationToken)
  110. {
  111. var actor = GetActor();
  112. var result = await _addresses.SoftDeleteAsync(addressId, actor, cancellationToken);
  113. if (!result.Saved)
  114. return result.IsNotFound ? NotFound() : UnprocessableEntity();
  115. _audit.Record(new AuditEvent(
  116. EventType: "MUNICIPALITY_ADDRESS_DELETED",
  117. ActorIdentity: actor,
  118. Resource: $"municipalities/{profileId}/addresses/{addressId}",
  119. Outcome: "soft-deleted",
  120. TraceIdentifier: HttpContext.TraceIdentifier,
  121. RecordedAt: _timeProvider.GetUtcNow()));
  122. return NoContent();
  123. }
  124. private string GetActor() =>
  125. User.Identity?.Name
  126. ?? User.FindFirstValue(ClaimTypes.NameIdentifier)
  127. ?? "unknown";
  128. }
  129. public sealed record AddMunicipalityAddressRequest(
  130. string AddressType,
  131. string Street,
  132. string City,
  133. string State,
  134. string ZipCode,
  135. DateTimeOffset EffectiveDate);
  136. public sealed record UpdateMunicipalityAddressRequest(
  137. string AddressType,
  138. string Street,
  139. string City,
  140. string State,
  141. string ZipCode,
  142. DateTimeOffset EffectiveDate);
  143. public sealed record MunicipalityAddressResponse(
  144. string AddressId,
  145. string ProfileId,
  146. string AddressType,
  147. string Street,
  148. string City,
  149. string State,
  150. string ZipCode,
  151. string EffectiveDate,
  152. bool IsCurrent,
  153. string CreatedAt,
  154. string CreatedBy,
  155. string UpdatedAt,
  156. string UpdatedBy)
  157. {
  158. public static MunicipalityAddressResponse From(MunicipalityAddress a) =>
  159. new(a.AddressId, a.ProfileId, a.AddressType, a.Street, a.City, a.State, a.ZipCode,
  160. a.EffectiveDate.ToString("O"), a.IsCurrent,
  161. a.CreatedAt.ToString("O"), a.CreatedBy,
  162. a.UpdatedAt.ToString("O"), a.UpdatedBy);
  163. }
  164. public sealed record MunicipalityAddressProblem(string Error);

Powered by TurnKey Linux.