No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.

209 líneas
7.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. [ApiController]
  9. [Authorize(Policy = ApplicationPolicy.ClientServicesAccess)]
  10. [Route("api/municipalities/{profileId}/contacts")]
  11. public sealed class MunicipalityContactsController : ControllerBase
  12. {
  13. private readonly IMunicipalityProfileRepository _profiles;
  14. private readonly IMunicipalityContactRepository _contacts;
  15. private readonly IAuditService _audit;
  16. private readonly TimeProvider _timeProvider;
  17. public MunicipalityContactsController(
  18. IMunicipalityProfileRepository profiles,
  19. IMunicipalityContactRepository contacts,
  20. IAuditService audit,
  21. TimeProvider timeProvider)
  22. {
  23. _profiles = profiles;
  24. _contacts = contacts;
  25. _audit = audit;
  26. _timeProvider = timeProvider;
  27. }
  28. [HttpGet]
  29. public async Task<ActionResult<IReadOnlyList<MunicipalityContactResponse>>> GetAll(
  30. string profileId,
  31. CancellationToken cancellationToken)
  32. {
  33. if (!await ProfileExists(profileId, cancellationToken))
  34. return NotFound();
  35. var contacts = await _contacts.GetByProfileIdAsync(profileId, cancellationToken);
  36. return Ok(contacts.Select(MunicipalityContactResponse.From).ToArray());
  37. }
  38. [HttpGet("{contactId}")]
  39. public async Task<ActionResult<MunicipalityContactResponse>> GetById(
  40. string profileId,
  41. string contactId,
  42. CancellationToken cancellationToken)
  43. {
  44. var contact = await _contacts.GetByIdAsync(contactId, cancellationToken);
  45. return IsContactInProfile(contact, profileId)
  46. ? Ok(MunicipalityContactResponse.From(contact!))
  47. : NotFound();
  48. }
  49. [HttpPost]
  50. public async Task<ActionResult<MunicipalityContactResponse>> Add(
  51. string profileId,
  52. [FromBody] AddMunicipalityContactRequest request,
  53. CancellationToken cancellationToken)
  54. {
  55. if (!await ProfileExists(profileId, cancellationToken))
  56. return NotFound();
  57. var actor = GetActor();
  58. var result = await _contacts.AddAsync(
  59. profileId,
  60. request.ContactType,
  61. request.Name,
  62. request.RoleTitle,
  63. request.Phone,
  64. request.Email,
  65. actor,
  66. cancellationToken);
  67. if (!result.Saved || result.Contact is null)
  68. return UnprocessableEntity(new MunicipalityContactProblem(result.Error ?? "Save failed."));
  69. _audit.Record(new AuditEvent(
  70. EventType: "MUNICIPALITY_CONTACT_ADDED",
  71. ActorIdentity: actor,
  72. Resource: $"municipalities/{profileId}/contacts/{result.Contact.ContactId}",
  73. Outcome: $"added {result.Contact.ContactType} contact",
  74. TraceIdentifier: HttpContext.TraceIdentifier,
  75. RecordedAt: _timeProvider.GetUtcNow()));
  76. return CreatedAtAction(nameof(GetById),
  77. new { profileId, contactId = result.Contact.ContactId },
  78. MunicipalityContactResponse.From(result.Contact));
  79. }
  80. [HttpPut("{contactId}")]
  81. public async Task<ActionResult<MunicipalityContactResponse>> Update(
  82. string profileId,
  83. string contactId,
  84. [FromBody] UpdateMunicipalityContactRequest request,
  85. CancellationToken cancellationToken)
  86. {
  87. var existing = await _contacts.GetByIdAsync(contactId, cancellationToken);
  88. if (!IsContactInProfile(existing, profileId))
  89. return NotFound(new MunicipalityContactProblem("Contact not found."));
  90. var actor = GetActor();
  91. var result = await _contacts.UpdateAsync(
  92. contactId,
  93. request.ContactType,
  94. request.Name,
  95. request.RoleTitle,
  96. request.Phone,
  97. request.Email,
  98. actor,
  99. cancellationToken);
  100. if (!result.Saved || result.Contact is null)
  101. {
  102. if (result.IsNotFound)
  103. return NotFound(new MunicipalityContactProblem(result.Error ?? "Contact not found."));
  104. return UnprocessableEntity(new MunicipalityContactProblem(result.Error ?? "Update failed."));
  105. }
  106. _audit.Record(new AuditEvent(
  107. EventType: "MUNICIPALITY_CONTACT_UPDATED",
  108. ActorIdentity: actor,
  109. Resource: $"municipalities/{profileId}/contacts/{contactId}",
  110. Outcome: $"updated {result.Contact.ContactType} contact",
  111. TraceIdentifier: HttpContext.TraceIdentifier,
  112. RecordedAt: _timeProvider.GetUtcNow()));
  113. return Ok(MunicipalityContactResponse.From(result.Contact));
  114. }
  115. [HttpDelete("{contactId}")]
  116. public async Task<IActionResult> Delete(
  117. string profileId,
  118. string contactId,
  119. CancellationToken cancellationToken)
  120. {
  121. var existing = await _contacts.GetByIdAsync(contactId, cancellationToken);
  122. if (!IsContactInProfile(existing, profileId))
  123. return NotFound();
  124. var actor = GetActor();
  125. var result = await _contacts.SoftDeleteAsync(contactId, actor, cancellationToken);
  126. if (!result.Saved)
  127. return result.IsNotFound ? NotFound() : UnprocessableEntity();
  128. _audit.Record(new AuditEvent(
  129. EventType: "MUNICIPALITY_CONTACT_DELETED",
  130. ActorIdentity: actor,
  131. Resource: $"municipalities/{profileId}/contacts/{contactId}",
  132. Outcome: "soft-deleted",
  133. TraceIdentifier: HttpContext.TraceIdentifier,
  134. RecordedAt: _timeProvider.GetUtcNow()));
  135. return NoContent();
  136. }
  137. private string GetActor() =>
  138. User.Identity?.Name
  139. ?? User.FindFirstValue(ClaimTypes.NameIdentifier)
  140. ?? "unknown";
  141. private async Task<bool> ProfileExists(
  142. string profileId,
  143. CancellationToken cancellationToken) =>
  144. await _profiles.GetByIdAsync(profileId, cancellationToken) is not null;
  145. private static bool IsContactInProfile(
  146. MunicipalityContact? contact,
  147. string profileId) =>
  148. contact is not null &&
  149. string.Equals(contact.ProfileId, profileId, StringComparison.OrdinalIgnoreCase);
  150. }
  151. public sealed record AddMunicipalityContactRequest(
  152. string ContactType,
  153. string Name,
  154. string? RoleTitle,
  155. string? Phone,
  156. string? Email);
  157. public sealed record UpdateMunicipalityContactRequest(
  158. string ContactType,
  159. string Name,
  160. string? RoleTitle,
  161. string? Phone,
  162. string? Email);
  163. public sealed record MunicipalityContactResponse(
  164. string ContactId,
  165. string ProfileId,
  166. string ContactType,
  167. string Name,
  168. string? RoleTitle,
  169. string? Phone,
  170. string? Email,
  171. string CreatedAt,
  172. string CreatedBy,
  173. string UpdatedAt,
  174. string UpdatedBy)
  175. {
  176. public static MunicipalityContactResponse From(MunicipalityContact c) =>
  177. new(c.ContactId, c.ProfileId, c.ContactType, c.Name, c.RoleTitle, c.Phone, c.Email,
  178. c.CreatedAt.ToString("O"), c.CreatedBy, c.UpdatedAt.ToString("O"), c.UpdatedBy);
  179. }
  180. public sealed record MunicipalityContactProblem(string Error);

Powered by TurnKey Linux.