using Events.WebAPI.Contract.Command; using Events.Auth; using Events.WebAPI.Contract.DTOs; using Events.WebAPI.Contract.Queries.Generic; using MediatR; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.JsonPatch; using Microsoft.AspNetCore.Mvc; using MobilityOne.Common.Commands; namespace Events.WebAPI.Controllers.Generic; public abstract class CrudController : GetController where TDto : class, IHasIdAsPK where TPK : IEquatable { /// /// Creates a new item. /// /// id does not have to be sent (if sent it would be ignored) /// /// A newly created item /// Returns the newly created item (route to the item, and the item in the body) /// If the model is null or not valid [HttpPost] [ProducesResponseType(StatusCodes.Status201Created)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [Authorize(Policy = nameof(Policies.EditData))] public virtual async Task> Create(TDto model, [FromServices] IMediator mediator) { //Note: It never produce ActionResult but we need this because of Swagger description //We cannot user generic type in attributes (i.e. in ProducesResponseType) //if successful it returns ActionResult with Value:null and Result:CreatedAtAction //Thus result.Result.Value is TDto var command = new AddCommand(model); TPK id = await mediator.Send(command); var query = new GetSingleItemQuery(id); var item = await mediator.Send(query); var action = CreatedAtAction(nameof(Get), new { id }, item); return action; } /// /// Update the item /// /// /// /// /// /// if the update was successful /// if there is no item with sent id, or if a user does not have a permission to update the item /// If the model is not valid [HttpPut("{id}")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [Authorize(Policy = nameof(Policies.EditData))] public virtual async Task Update(TPK id, TDto model, [FromServices] IMediator mediator) { if (!model.Id.Equals(id)) //ModelState.IsValid & model != null checked automatically due to [ApiController] { return Problem(statusCode: StatusCodes.Status400BadRequest, detail: $"Different ids: {id} vs {model.Id}"); } else { var query = new GetSingleItemQuery(id); var item = await mediator.Send(query); if (item == null) { return Problem(statusCode: StatusCodes.Status404NotFound, detail: $"Invalid id = {id}"); } await DoUpdate(model, mediator); return NoContent(); } } private static async Task DoUpdate(TDto model, IMediator mediator) { var command = new UpdateCommand(model); await mediator.Send(command); } /// /// Partially update the item /// /// /// RFC 6902 formatted json /// /// /// if the update was successful /// if there is no item with sent id, or if a user does not have a permission to update the item /// If the patched model is not valid [HttpPatch("{id}")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [Authorize(Policy = nameof(Policies.EditData))] public virtual async Task UpdatePartially(TPK id, JsonPatchDocument delta, [FromServices] IMediator mediator) { //Get current DTO based on id (Get will also check for permission to run patch in contrast to direct retrieval)( var getResult = await base.Get(id, mediator); if (getResult.Value != null) { string problem = string.Empty; bool ok = true; TDto dto = getResult.Value; delta.ApplyTo(dto, patchError => { ok = false; problem = $"{patchError.Operation} causing error: {patchError.ErrorMessage}"; }); if (ok) { if (!dto.Id.Equals(id)) //ensures that id has not been changed { problem = $"Id mismatch after patching {id} <> {dto.Id}"; return Problem(detail: problem, statusCode: StatusCodes.Status400BadRequest); } else { await DoUpdate(dto, mediator); return NoContent(); } } else { return Problem(detail: problem, statusCode: StatusCodes.Status400BadRequest); } } else { return getResult.Result; } } /// /// Delete the item base on primary key value (id) /// /// Primary key value /// Query/Command (Request) mediator. (Obtained using Dependency Injection from services) /// /// If the item is deleted /// If the item with id does not exist /// If the valiation exists, and fails [HttpDelete("{id}")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [Authorize(Policy = nameof(Policies.EditData))] public virtual async Task Delete(TPK id, [FromServices] IMediator mediator) { var query = new GetSingleItemQuery(id); var item = await mediator.Send(query); if (item == null) { return Problem(statusCode: StatusCodes.Status404NotFound, detail: $"Invalid id = {id}"); } var command = new DeleteCommand(id); await mediator.Send(command); return NoContent(); } }