diff --git a/devdocs/todo.txt b/devdocs/todo.txt index ed2c5150..5e5b6bd9 100644 --- a/devdocs/todo.txt +++ b/devdocs/todo.txt @@ -18,13 +18,12 @@ Changes needed to routes?? CODING WORK +++++++++++ -******************************************************* CURRENT DEVELOPMENT OBJECTIVES: CLIENT SHELL: anything standing in the way of making the initial client shell UI needs to be done first, everything else can wait UI Shell is required for driving development processes of backend, once have client can make up initial test forms and then determine best way to write backend biz objects. Once that is done then can steam ahead on the biz objects but until I have the client I won't know the best way to code them so to avoid re-working shit do it in this order. -******************************************************** + IMMEDIATE ITEMS: ================ diff --git a/server/AyaNova/Controllers/UserController.cs b/server/AyaNova/Controllers/UserController.cs index 0bed7680..f79b5a2e 100644 --- a/server/AyaNova/Controllers/UserController.cs +++ b/server/AyaNova/Controllers/UserController.cs @@ -17,7 +17,7 @@ namespace AyaNova.Api.Controllers { /// - /// Sample controller class used during development for testing purposes + /// User /// [ApiVersion("8.0")] [Route("api/v{version:apiVersion}/[controller]")] diff --git a/server/AyaNova/Controllers/UserOptionsController.cs b/server/AyaNova/Controllers/UserOptionsController.cs new file mode 100644 index 00000000..8b879bfa --- /dev/null +++ b/server/AyaNova/Controllers/UserOptionsController.cs @@ -0,0 +1,495 @@ +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 AyaNova.Models; +using AyaNova.Api.ControllerHelpers; +using AyaNova.Biz; + + +namespace AyaNova.Api.Controllers +{ + + /// + /// UserOptions + /// + [ApiVersion("8.0")] + [Route("api/v{version:apiVersion}/[controller]")] + [Produces("application/json")] + [Authorize] + public class UserOptionsController : Controller + { + private readonly AyContext ct; + private readonly ILogger log; + private readonly ApiServerState serverState; + + + /// + /// ctor + /// + /// + /// + /// + public UserOptionsController(AyContext dbcontext, ILogger logger, ApiServerState apiServerState) + { + ct = dbcontext; + log = logger; + serverState = apiServerState; + } + + + + + /// + /// Get full UserOptions object + /// + /// Required roles: + /// BizAdminFull, InventoryFull, BizAdminLimited, InventoryLimited, TechFull, TechLimited, Accounting + /// + /// + /// A single UserOptions + [HttpGet("{id}")] + public async Task GetUserOptions([FromRoute] long id) + { + if (serverState.IsClosed) + { + return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason)); + } + + if (!Authorized.IsAuthorizedToReadFullRecord(HttpContext.Items, AyaType.UserOptions)) + { + return StatusCode(401, new ApiNotAuthorizedResponse()); + } + + if (!ModelState.IsValid) + { + return BadRequest(new ApiErrorResponse(ModelState)); + } + + //Instantiate the business object handler + UserOptionsBiz biz = new UserOptionsBiz(ct, UserIdFromContext.Id(HttpContext.Items), UserRolesFromContext.Roles(HttpContext.Items)); + + var o = await biz.GetAsync(id); + + if (o == null) + { + return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + } + + //Log + EventLogProcessor.AddEntry(new Event(biz.userId, o.Id, AyaType.UserOptions, AyaEvent.Retrieved), ct); + ct.SaveChanges(); + return Ok(new ApiOkResponse(o)); + } + + + + /// + /// Get paged list of UserOptionss + /// + /// Required roles: Any + /// + /// + /// Paged collection of UserOptionss with paging data + [HttpGet("ListUserOptionss", Name = nameof(ListUserOptionss))]//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 ListUserOptionss([FromQuery] PagingOptions pagingOptions) + { + if (serverState.IsClosed) + { + return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason)); + } + + if (!Authorized.IsAuthorizedToReadFullRecord(HttpContext.Items, AyaType.UserOptions)) + { + return StatusCode(401, new ApiNotAuthorizedResponse()); + } + + if (!ModelState.IsValid) + { + return BadRequest(new ApiErrorResponse(ModelState)); + } + + //Instantiate the business object handler + UserOptionsBiz biz = new UserOptionsBiz(ct, UserIdFromContext.Id(HttpContext.Items), UserRolesFromContext.Roles(HttpContext.Items)); + + ApiPagedResponse pr = await biz.GetManyAsync(Url, nameof(ListUserOptionss), pagingOptions); + return Ok(new ApiOkWithPagingResponse(pr)); + } + + + + /// + /// Get UserOptions pick list + /// + /// Required roles: Any + /// + /// This list supports querying the Name property + /// include a "q" parameter for string to search for + /// use % for wildcards. + /// + /// e.g. q=%Jones% + /// + /// Query is case insensitive + /// + /// Paged id/name collection of UserOptionss with paging data + [HttpGet("PickList", Name = nameof(UserOptionsPickList))] + public async Task UserOptionsPickList([FromQuery] string q, [FromQuery] PagingOptions 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 + UserOptionsBiz biz = new UserOptionsBiz(ct, UserIdFromContext.Id(HttpContext.Items), UserRolesFromContext.Roles(HttpContext.Items)); + + ApiPagedResponse pr = await biz.GetPickListAsync(Url, nameof(UserOptionsPickList), pagingOptions, q); + return Ok(new ApiOkWithPagingResponse(pr)); + } + + + /// + /// Put (update) UserOptions + /// + /// Required roles: + /// BizAdminFull, InventoryFull + /// TechFull (owned only) + /// + /// + /// + /// + /// + [HttpPut("{id}")] + public async Task PutUserOptions([FromRoute] long id, [FromBody] UserOptions inObj) + { + if (!serverState.IsOpen) + { + return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason)); + } + + if (!ModelState.IsValid) + { + return BadRequest(new ApiErrorResponse(ModelState)); + } + + var o = await ct.UserOptions.SingleOrDefaultAsync(m => m.Id == id); + + if (o == null) + { + return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + } + + if (!Authorized.IsAuthorizedToModify(HttpContext.Items, AyaType.UserOptions, o.OwnerId)) + { + return StatusCode(401, new ApiNotAuthorizedResponse()); + } + + //Instantiate the business object handler + UserOptionsBiz biz = new UserOptionsBiz(ct, UserIdFromContext.Id(HttpContext.Items), UserRolesFromContext.Roles(HttpContext.Items)); + + if (!biz.Put(o, inObj)) + { + return BadRequest(new ApiErrorResponse(biz.Errors)); + } + + try + { + //Log + EventLogProcessor.AddEntry(new Event(biz.userId, o.Id, AyaType.UserOptions, AyaEvent.Modified), ct); + await ct.SaveChangesAsync(); + } + catch (DbUpdateConcurrencyException) + { + if (!UserOptionsExists(id)) + { + return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + } + else + { + //exists but was changed by another user + //I considered returning new and old record, but where would it end? + //Better to let the client decide what to do than to send extra data that is not required + return StatusCode(409, new ApiErrorResponse(ApiErrorCode.CONCURRENCY_CONFLICT)); + } + } + + + + return Ok(new ApiOkResponse(new { ConcurrencyToken = o.ConcurrencyToken })); + } + + + + /// + /// Patch (update) UserOptions + /// + /// Required roles: + /// BizAdminFull, InventoryFull + /// TechFull (owned only) + /// + /// + /// + /// + /// + [HttpPatch("{id}/{concurrencyToken}")] + public async Task PatchUserOptions([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 + UserOptionsBiz biz = new UserOptionsBiz(ct, UserIdFromContext.Id(HttpContext.Items), UserRolesFromContext.Roles(HttpContext.Items)); + + + var o = await ct.UserOptions.SingleOrDefaultAsync(m => m.Id == id); + + if (o == null) + { + return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + } + + if (!Authorized.IsAuthorizedToModify(HttpContext.Items, AyaType.UserOptions, o.OwnerId)) + { + return StatusCode(401, new ApiNotAuthorizedResponse()); + } + + //patch and validate + if (!biz.Patch(o, objectPatch, concurrencyToken)) + { + return BadRequest(new ApiErrorResponse(biz.Errors)); + } + + try + { + //Log + EventLogProcessor.AddEntry(new Event(biz.userId, o.Id, AyaType.UserOptions, AyaEvent.Modified), ct); + await ct.SaveChangesAsync(); + } + catch (DbUpdateConcurrencyException) + { + if (!UserOptionsExists(id)) + { + return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + } + else + { + return StatusCode(409, new ApiErrorResponse(ApiErrorCode.CONCURRENCY_CONFLICT)); + } + } + + + + + return Ok(new ApiOkResponse(new { ConcurrencyToken = o.ConcurrencyToken })); + } + + + /// + /// Post UserOptions + /// + /// Required roles: + /// BizAdminFull, InventoryFull, TechFull + /// + /// + /// + [HttpPost] + public async Task PostUserOptions([FromBody] UserOptions inObj) + { + if (!serverState.IsOpen) + { + return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason)); + } + + //If a user has change roles, or editOwnRoles then they can create, true is passed for isOwner since they are creating so by definition the owner + if (!Authorized.IsAuthorizedToCreate(HttpContext.Items, AyaType.UserOptions)) + { + return StatusCode(401, new ApiNotAuthorizedResponse()); + } + + if (!ModelState.IsValid) + { + return BadRequest(new ApiErrorResponse(ModelState)); + } + + //Instantiate the business object handler + UserOptionsBiz biz = new UserOptionsBiz(ct, UserIdFromContext.Id(HttpContext.Items), UserRolesFromContext.Roles(HttpContext.Items)); + + //Create and validate + UserOptions o = await biz.CreateAsync(inObj); + + if (o == null) + { + //error return + return BadRequest(new ApiErrorResponse(biz.Errors)); + } + else + { + + //save to get Id + await ct.SaveChangesAsync(); + + //Log now that we have the Id + EventLogProcessor.AddEntry(new Event(biz.userId, o.Id, AyaType.UserOptions, AyaEvent.Created), ct); + await ct.SaveChangesAsync(); + + //return success and link + return CreatedAtAction("GetUserOptions", new { id = o.Id }, new ApiCreatedResponse(o)); + } + } + + + + /// + /// Delete UserOptions + /// + /// Required roles: + /// BizAdminFull, InventoryFull + /// TechFull (owned only) + /// + /// + /// + /// Ok + [HttpDelete("{id}")] + public async Task DeleteUserOptions([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)); + } + + var dbObj = await ct.UserOptions.SingleOrDefaultAsync(m => m.Id == id); + if (dbObj == null) + { + return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + } + + if (!Authorized.IsAuthorizedToDelete(HttpContext.Items, AyaType.UserOptions, dbObj.OwnerId)) + { + return StatusCode(401, new ApiNotAuthorizedResponse()); + } + + //Instantiate the business object handler + UserOptionsBiz biz = new UserOptionsBiz(ct, UserIdFromContext.Id(HttpContext.Items), UserRolesFromContext.Roles(HttpContext.Items)); + if (!biz.Delete(dbObj)) + { + return BadRequest(new ApiErrorResponse(biz.Errors)); + } + + //Log + EventLogProcessor.DeleteObject(biz.userId, AyaType.UserOptions, dbObj.Id, dbObj.Name, ct); + + await ct.SaveChangesAsync(); + + //Delete children / attached objects + biz.DeleteChildren(dbObj); + + + return NoContent(); + } + + + + + private bool UserOptionsExists(long id) + { + return ct.UserOptions.Any(e => e.Id == id); + } + + + /// + /// Get route that triggers exception for testing + /// + /// Nothing, triggers exception + [HttpGet("exception")] + public ActionResult GetException() + { + if (!serverState.IsOpen) + { + return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason)); + } + + if (!Authorized.IsAuthorizedToReadFullRecord(HttpContext.Items, AyaType.UserOptions)) + { + return StatusCode(401, new ApiNotAuthorizedResponse()); + } + + throw new System.NotSupportedException("Test exception from UserOptions 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)); + } + + if (!Authorized.IsAuthorizedToReadFullRecord(HttpContext.Items, AyaType.UserOptions)) + { + return StatusCode(401, new ApiNotAuthorizedResponse()); + } + + throw new System.ArgumentException("Test exception (ALT) from UserOptions controller"); + } + + + /// + /// Get route that submits a simulated long running operation job for testing + /// + /// Nothing + [HttpGet("TestUserOptionsJob")] + public ActionResult TestUserOptionsJob() + { + if (!serverState.IsOpen) + { + return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason)); + } + + if (!Authorized.IsAuthorizedToModify(HttpContext.Items, AyaType.JobOperations)) + { + return StatusCode(401, new ApiNotAuthorizedResponse()); + } + + //Create the job here + OpsJob j = new OpsJob(); + j.Name = "TestUserOptionsJob"; + j.JobType = JobType.TestUserOptionsJob; + JobsBiz.AddJob(j, ct); + return Accepted(new { JobId = j.GId });//202 accepted + } + + //------------ + + + }//eoc +}//eons \ No newline at end of file diff --git a/server/AyaNova/biz/ValidateJsonPatch.cs b/server/AyaNova/biz/ValidateJsonPatch.cs index ea60f04f..74b8ed56 100644 --- a/server/AyaNova/biz/ValidateJsonPatch.cs +++ b/server/AyaNova/biz/ValidateJsonPatch.cs @@ -1,16 +1,6 @@ using System.Linq; -using System.Threading.Tasks; -using Microsoft.EntityFrameworkCore; -using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.JsonPatch; -using EnumsNET; -using AyaNova.Util; -using AyaNova.Api.ControllerHelpers; -using AyaNova.Biz; -using AyaNova.Models; -using Newtonsoft.Json.Linq; using System; -using System.Collections.Generic; namespace AyaNova.Biz {