|
- using System.Security.Claims;
- using Campaign_Tracker.Server.Audit;
- using Campaign_Tracker.Server.Authorization;
- using Campaign_Tracker.Server.Municipalities;
- using Microsoft.AspNetCore.Authorization;
- using Microsoft.AspNetCore.Mvc;
-
- namespace Campaign_Tracker.Server.Controllers;
-
- [ApiController]
- [Authorize(Policy = ApplicationPolicy.ClientServicesAccess)]
- [Route("api/municipalities/{profileId}/addresses")]
- public sealed class MunicipalityAddressesController : ControllerBase
- {
- private readonly IMunicipalityAddressRepository _addresses;
- private readonly IAuditService _audit;
- private readonly TimeProvider _timeProvider;
-
- public MunicipalityAddressesController(
- IMunicipalityAddressRepository addresses,
- IAuditService audit,
- TimeProvider timeProvider)
- {
- _addresses = addresses;
- _audit = audit;
- _timeProvider = timeProvider;
- }
-
- [HttpGet]
- public async Task<ActionResult<IReadOnlyList<MunicipalityAddressResponse>>> GetAll(
- string profileId,
- CancellationToken cancellationToken)
- {
- var addresses = await _addresses.GetByProfileIdAsync(profileId, cancellationToken);
- return Ok(addresses.Select(MunicipalityAddressResponse.From).ToArray());
- }
-
- [HttpGet("{addressId}")]
- public async Task<ActionResult<MunicipalityAddressResponse>> GetById(
- string profileId,
- string addressId,
- CancellationToken cancellationToken)
- {
- var address = await _addresses.GetByIdAsync(addressId, cancellationToken);
- return address is null ? NotFound() : Ok(MunicipalityAddressResponse.From(address));
- }
-
- [HttpPost]
- public async Task<ActionResult<MunicipalityAddressResponse>> Add(
- string profileId,
- [FromBody] AddMunicipalityAddressRequest request,
- CancellationToken cancellationToken)
- {
- var actor = GetActor();
- var result = await _addresses.AddAsync(
- profileId,
- request.AddressType,
- request.Street,
- request.City,
- request.State,
- request.ZipCode,
- request.EffectiveDate,
- actor,
- cancellationToken);
-
- if (!result.Saved || result.Address is null)
- return UnprocessableEntity(new MunicipalityAddressProblem(result.Error ?? "Save failed."));
-
- _audit.Record(new AuditEvent(
- EventType: "MUNICIPALITY_ADDRESS_ADDED",
- ActorIdentity: actor,
- Resource: $"municipalities/{profileId}/addresses/{result.Address.AddressId}",
- Outcome: $"added {result.Address.AddressType} address",
- TraceIdentifier: HttpContext.TraceIdentifier,
- RecordedAt: _timeProvider.GetUtcNow()));
-
- return CreatedAtAction(nameof(GetById),
- new { profileId, addressId = result.Address.AddressId },
- MunicipalityAddressResponse.From(result.Address));
- }
-
- [HttpPut("{addressId}")]
- public async Task<ActionResult<MunicipalityAddressResponse>> Update(
- string profileId,
- string addressId,
- [FromBody] UpdateMunicipalityAddressRequest request,
- CancellationToken cancellationToken)
- {
- var actor = GetActor();
- var result = await _addresses.UpdateAsync(
- addressId,
- request.AddressType,
- request.Street,
- request.City,
- request.State,
- request.ZipCode,
- request.EffectiveDate,
- actor,
- cancellationToken);
-
- if (!result.Saved || result.Address is null)
- {
- if (result.IsNotFound)
- return NotFound(new MunicipalityAddressProblem(result.Error ?? "Address not found."));
- return UnprocessableEntity(new MunicipalityAddressProblem(result.Error ?? "Update failed."));
- }
-
- _audit.Record(new AuditEvent(
- EventType: "MUNICIPALITY_ADDRESS_UPDATED",
- ActorIdentity: actor,
- Resource: $"municipalities/{profileId}/addresses/{addressId}",
- Outcome: $"updated {result.Address.AddressType} address — new record {result.Address.AddressId}",
- TraceIdentifier: HttpContext.TraceIdentifier,
- RecordedAt: _timeProvider.GetUtcNow()));
-
- return Ok(MunicipalityAddressResponse.From(result.Address));
- }
-
- [HttpDelete("{addressId}")]
- public async Task<IActionResult> Delete(
- string profileId,
- string addressId,
- CancellationToken cancellationToken)
- {
- var actor = GetActor();
- var result = await _addresses.SoftDeleteAsync(addressId, actor, cancellationToken);
-
- if (!result.Saved)
- return result.IsNotFound ? NotFound() : UnprocessableEntity();
-
- _audit.Record(new AuditEvent(
- EventType: "MUNICIPALITY_ADDRESS_DELETED",
- ActorIdentity: actor,
- Resource: $"municipalities/{profileId}/addresses/{addressId}",
- Outcome: "soft-deleted",
- TraceIdentifier: HttpContext.TraceIdentifier,
- RecordedAt: _timeProvider.GetUtcNow()));
-
- return NoContent();
- }
-
- private string GetActor() =>
- User.Identity?.Name
- ?? User.FindFirstValue(ClaimTypes.NameIdentifier)
- ?? "unknown";
- }
-
- public sealed record AddMunicipalityAddressRequest(
- string AddressType,
- string Street,
- string City,
- string State,
- string ZipCode,
- DateTimeOffset EffectiveDate);
-
- public sealed record UpdateMunicipalityAddressRequest(
- string AddressType,
- string Street,
- string City,
- string State,
- string ZipCode,
- DateTimeOffset EffectiveDate);
-
- public sealed record MunicipalityAddressResponse(
- string AddressId,
- string ProfileId,
- string AddressType,
- string Street,
- string City,
- string State,
- string ZipCode,
- string EffectiveDate,
- bool IsCurrent,
- string CreatedAt,
- string CreatedBy,
- string UpdatedAt,
- string UpdatedBy)
- {
- public static MunicipalityAddressResponse From(MunicipalityAddress a) =>
- new(a.AddressId, a.ProfileId, a.AddressType, a.Street, a.City, a.State, a.ZipCode,
- a.EffectiveDate.ToString("O"), a.IsCurrent,
- a.CreatedAt.ToString("O"), a.CreatedBy,
- a.UpdatedAt.ToString("O"), a.UpdatedBy);
- }
-
- public sealed record MunicipalityAddressProblem(string Error);
|