using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.JsonPatch; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using Newtonsoft.Json.Linq; using AyaNova.Models; using AyaNova.Api.ControllerHelpers; using AyaNova.Biz; namespace AyaNova.Api.Controllers { //DOCUMENTATING THE API //https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/xmldoc/recommended-tags-for-documentation-comments //https://github.com/domaindrivendev/Swashbuckle.AspNetCore#include-descriptions-from-xml-comments /// /// Sample controller class used during development for testing purposes /// [ApiController] [ApiVersion("8.0")] [Route("api/v{version:apiVersion}/[controller]")] [Produces("application/json")] [Authorize] public class WidgetController : ControllerBase { private readonly AyContext ct; private readonly ILogger log; private readonly ApiServerState serverState; /// /// ctor /// /// /// /// public WidgetController(AyContext dbcontext, ILogger logger, ApiServerState apiServerState) { ct = dbcontext; log = logger; serverState = apiServerState; } /// /// Get full widget object /// /// Required roles: /// BizAdminFull, InventoryFull, BizAdminLimited, InventoryLimited /// /// /// A single widget [HttpGet("{id}")] public async Task GetWidget([FromRoute] long id) { if (serverState.IsClosed) return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason)); //Instantiate the business object handler WidgetBiz biz = WidgetBiz.GetBiz(ct, HttpContext); //NOTE: This is the first check and often the only check but in some cases with some objects this will also need to check biz object rules if (!Authorized.HasReadFullRole(HttpContext.Items, biz.BizType)) return StatusCode(403, new ApiNotAuthorizedResponse()); if (!ModelState.IsValid) return BadRequest(new ApiErrorResponse(ModelState)); var o = await biz.GetAsync(id); if (o == null) return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); // NOTE: HERE would be the second check of biz rules before returning the object // in cases where there is also a business rule to affect retrieval on top of basic rights return Ok(ApiOkResponse.Response(o, !Authorized.HasModifyRole(HttpContext.Items, biz.BizType))); } /// /// Get list for selection / viewing /// /// Required roles: Any (some roles might have restrictions on exact fields that are returned) /// /// /// Paging, filtering and sorting options /// Collection with paging data [HttpGet("List", Name = nameof(List))] public ActionResult List([FromQuery] ListOptions pagingOptions) { if (serverState.IsClosed) return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason)); if (!ModelState.IsValid) return BadRequest(new ApiErrorResponse(ModelState)); //Instantiate the business object handler WidgetBiz biz = WidgetBiz.GetBiz(ct, HttpContext); ApiPagedResponse pr = biz.GetList(Url, nameof(List), pagingOptions).Result; return Ok(new ApiOkWithPagingResponse(pr)); // string ret= biz.GetList(Url, nameof(List), pagingOptions).Result; // return Ok(ret); } // /// // /// Get paged list of widgets // /// // /// Required roles: // /// BizAdminFull, InventoryFull, BizAdminLimited, InventoryLimited // /// // /// Paged collection of widgets with paging data // [HttpGet("ListWidgets", Name = nameof(ListWidgets))]//We MUST have a "Name" defined or we can't get the link for the pagination, non paged urls don't need a name // public async Task ListWidgets([FromQuery] ListOptions pagingOptions) // { // if (serverState.IsClosed) // return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason)); // //Instantiate the business object handler // WidgetBiz biz = WidgetBiz.GetBiz(ct, HttpContext); // if (!Authorized.HasReadFullRole(HttpContext.Items, biz.BizType)) // return StatusCode(403, new ApiNotAuthorizedResponse()); // if (!ModelState.IsValid) // return BadRequest(new ApiErrorResponse(ModelState)); // ApiPagedResponse pr = await biz.GetManyAsync(Url, nameof(ListWidgets), pagingOptions); // return Ok(new ApiOkWithPagingResponse(pr)); // } // /// // /// Get pick list // /// // /// Required roles: Any // /// // /// // /// Paging, filtering and sorting options // /// Paged id/name collection with paging data // [HttpGet("PickList", Name = nameof(WidgetPickList))] // public ActionResult WidgetPickList([FromQuery] ListOptions pagingOptions) // { // if (serverState.IsClosed) // return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason)); // if (!ModelState.IsValid) // return BadRequest(new ApiErrorResponse(ModelState)); // //Instantiate the business object handler // WidgetBiz biz = WidgetBiz.GetBiz(ct, HttpContext); // ApiPagedResponse pr = biz.GetPickList(Url, nameof(WidgetPickList), pagingOptions); // return Ok(new ApiOkWithPagingResponse(pr)); // } /// /// Put (update) widget /// /// Required roles: /// BizAdminFull, InventoryFull /// /// /// /// /// [HttpPut("{id}")] public async Task PutWidget([FromRoute] long id, [FromBody] Widget inObj) { if (!serverState.IsOpen) return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason)); if (!ModelState.IsValid) return BadRequest(new ApiErrorResponse(ModelState)); //Instantiate the business object handler WidgetBiz biz = WidgetBiz.GetBiz(ct, HttpContext); var o = await biz.GetNoLogAsync(id); if (o == null) return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); if (!Authorized.HasModifyRole(HttpContext.Items, biz.BizType)) return StatusCode(403, new ApiNotAuthorizedResponse()); try { if (!biz.Put(o, inObj)) return BadRequest(new ApiErrorResponse(biz.Errors)); } catch (DbUpdateConcurrencyException) { if (!await biz.ExistsAsync(id)) return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); else return StatusCode(409, new ApiErrorResponse(ApiErrorCode.CONCURRENCY_CONFLICT)); } return Ok(ApiOkResponse.Response(new { ConcurrencyToken = o.ConcurrencyToken }, true)); } /// /// Patch (update) widget /// /// Required roles: /// BizAdminFull, InventoryFull /// /// /// /// /// /// [HttpPatch("{id}/{concurrencyToken}")] public async Task PatchWidget([FromRoute] long id, [FromRoute] uint concurrencyToken, [FromBody]JsonPatchDocument objectPatch) { //https://dotnetcoretutorials.com/2017/11/29/json-patch-asp-net-core/ if (!serverState.IsOpen) return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason)); if (!ModelState.IsValid) return BadRequest(new ApiErrorResponse(ModelState)); //Instantiate the business object handler WidgetBiz biz = WidgetBiz.GetBiz(ct, HttpContext); var o = await biz.GetNoLogAsync(id); if (o == null) return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); if (!Authorized.HasModifyRole(HttpContext.Items, biz.BizType)) return StatusCode(403, new ApiNotAuthorizedResponse()); try { //patch and validate if (!biz.Patch(o, objectPatch, concurrencyToken)) return BadRequest(new ApiErrorResponse(biz.Errors)); } catch (DbUpdateConcurrencyException) { if (!await biz.ExistsAsync(id)) return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); else return StatusCode(409, new ApiErrorResponse(ApiErrorCode.CONCURRENCY_CONFLICT)); } return Ok(ApiOkResponse.Response(new { ConcurrencyToken = o.ConcurrencyToken }, true)); } /// /// Post widget /// /// Required roles: /// BizAdminFull, InventoryFull /// /// /// Automatically filled from route path, no need to specify in body /// [HttpPost] public async Task PostWidget([FromBody] Widget inObj, ApiVersion apiVersion) { if (!serverState.IsOpen) return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason)); //Instantiate the business object handler WidgetBiz biz = WidgetBiz.GetBiz(ct, HttpContext); //If a user has change roles if (!Authorized.HasCreateRole(HttpContext.Items, biz.BizType)) return StatusCode(403, new ApiNotAuthorizedResponse()); if (!ModelState.IsValid) return BadRequest(new ApiErrorResponse(ModelState)); //Create and validate Widget o = await biz.CreateAsync(inObj); if (o == null) return BadRequest(new ApiErrorResponse(biz.Errors)); else return CreatedAtAction(nameof(WidgetController.GetWidget), new { id = o.Id, version = apiVersion.ToString() }, new ApiCreatedResponse(o)); } /// /// Duplicate widget /// /// Required roles: /// BizAdminFull, InventoryFull /// /// Create a duplicate of this items id /// Automatically filled from route path, no need to specify in body /// [HttpPost("duplicate/{id}")] public async Task DuplicateWidget([FromRoute] long id, ApiVersion apiVersion) { if (!serverState.IsOpen) return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason)); //Instantiate the business object handler WidgetBiz biz = WidgetBiz.GetBiz(ct, HttpContext); //If a user has change roles if (!Authorized.HasCreateRole(HttpContext.Items, biz.BizType)) return StatusCode(403, new ApiNotAuthorizedResponse()); if (!ModelState.IsValid) return BadRequest(new ApiErrorResponse(ModelState)); var oSrc = await biz.GetNoLogAsync(id); if (oSrc == null) return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); //Create and validate Widget o = await biz.DuplicateAsync(oSrc); if (o == null) return BadRequest(new ApiErrorResponse(biz.Errors)); else return CreatedAtAction(nameof(WidgetController.GetWidget), new { id = o.Id, version = apiVersion.ToString() }, new ApiCreatedResponse(o)); } /// /// Delete widget /// /// Required roles: /// BizAdminFull, InventoryFull /// /// /// /// /// Ok [HttpDelete("{id}")] public async Task DeleteWidget([FromRoute] long id) { if (!serverState.IsOpen) return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason)); if (!ModelState.IsValid) return BadRequest(new ApiErrorResponse(ModelState)); //Instantiate the business object handler WidgetBiz biz = WidgetBiz.GetBiz(ct, HttpContext); var o = await biz.GetNoLogAsync(id); if (o == null) return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); if (!Authorized.HasDeleteRole(HttpContext.Items, biz.BizType)) return StatusCode(403, new ApiNotAuthorizedResponse()); if (!biz.Delete(o)) return BadRequest(new ApiErrorResponse(biz.Errors)); return NoContent(); } /// /// Get route that triggers exception for testing /// /// Nothing, triggers exception [HttpGet("exception")] public ActionResult GetException() { //log.LogInformation("Widget::getexception-> Test exception and log from controller test"); if (!serverState.IsOpen) return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason)); throw new System.NotSupportedException("Test exception from widget controller"); } /// /// Get route that triggers an alternate type of exception for testing /// /// Nothing, triggers exception [HttpGet("altexception")] public ActionResult GetAltException() { if (!serverState.IsOpen) return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason)); throw new System.ArgumentException("Test exception (ALT) from widget controller"); } /// /// Get route that submits a simulated long running operation job for testing /// /// Nothing [HttpGet("TestWidgetJob")] public ActionResult TestWidgetJob() { if (!serverState.IsOpen) return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason)); if (!Authorized.HasModifyRole(HttpContext.Items, AyaType.JobOperations)) return StatusCode(403, new ApiNotAuthorizedResponse()); //Create the job here OpsJob j = new OpsJob(); j.Name = "TestWidgetJob"; j.JobType = JobType.TestWidgetJob; JobsBiz.AddJob(j, ct); return Accepted(new { JobId = j.GId });//202 accepted } //------------ }//eoc }//eons