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; namespace AyaNova.Biz { internal class UserBiz : BizObject, IJobObject { private readonly AyContext ct; public readonly long userId; private readonly AuthorizationRoles userRoles; internal UserBiz(AyContext dbcontext, long currentUserId, AuthorizationRoles UserRoles) { ct = dbcontext; userId = currentUserId; userRoles = UserRoles; } //////////////////////////////////////////////////////////////////////////////////////////////// //CREATE internal async Task CreateAsync(User inObj) { Validate(inObj, true); if (HasErrors) return null; else { //do stuff with User User outObj = inObj; outObj.OwnerId = userId; //SearchHelper(break down text fields, save to db) //TagHelper(collection of tags??) await ct.User.AddAsync(outObj); return outObj; } } //////////////////////////////////////////////////////////////////////////////////////////////// /// GET //Get one internal async Task GetAsync(long fetchId) { //This is simple so nothing more here, but often will be copying to a different output object or some other ops return await ct.User.SingleOrDefaultAsync(m => m.Id == fetchId); } //get many (paged) internal async Task> GetManyAsync(IUrlHelper Url, string routeName, PagingOptions pagingOptions) { pagingOptions.Offset = pagingOptions.Offset ?? PagingOptions.DefaultOffset; pagingOptions.Limit = pagingOptions.Limit ?? PagingOptions.DefaultLimit; var items = await ct.User .OrderBy(m => m.Id) .Skip(pagingOptions.Offset.Value) .Take(pagingOptions.Limit.Value) .ToArrayAsync(); var totalRecordCount = await ct.User.CountAsync(); var pageLinks = new PaginationLinkBuilder(Url, routeName, null, pagingOptions, totalRecordCount).PagingLinksObject(); ApiPagedResponse pr = new ApiPagedResponse(items, pageLinks); return pr; } //get picklist (paged) internal async Task> GetPickListAsync(IUrlHelper Url, string routeName, PagingOptions pagingOptions, string q) { pagingOptions.Offset = pagingOptions.Offset ?? PagingOptions.DefaultOffset; pagingOptions.Limit = pagingOptions.Limit ?? PagingOptions.DefaultLimit; NameIdItem[] items; int totalRecordCount = 0; if (!string.IsNullOrWhiteSpace(q)) { items = await ct.User .Where(m => EF.Functions.ILike(m.Name, q)) .OrderBy(m => m.Name) .Skip(pagingOptions.Offset.Value) .Take(pagingOptions.Limit.Value) .Select(m => new NameIdItem() { Id = m.Id, Name = m.Name }).ToArrayAsync(); totalRecordCount = await ct.User.Where(m => EF.Functions.ILike(m.Name, q)).CountAsync(); } else { items = await ct.User .OrderBy(m => m.Name) .Skip(pagingOptions.Offset.Value) .Take(pagingOptions.Limit.Value) .Select(m => new NameIdItem() { Id = m.Id, Name = m.Name }).ToArrayAsync(); totalRecordCount = await ct.User.CountAsync(); } var pageLinks = new PaginationLinkBuilder(Url, routeName, null, pagingOptions, totalRecordCount).PagingLinksObject(); ApiPagedResponse pr = new ApiPagedResponse(items, pageLinks); return pr; } //////////////////////////////////////////////////////////////////////////////////////////////// //UPDATE // //put internal bool Put(User dbObj, User inObj) { //Replace the db object with the PUT object CopyObject.Copy(inObj, dbObj, "Id"); //Set "original" value of concurrency token to input token //this will allow EF to check it out ct.Entry(dbObj).OriginalValues["ConcurrencyToken"] = inObj.ConcurrencyToken; Validate(dbObj, false); if (HasErrors) return false; return true; } //patch internal bool Patch(User dbObj, JsonPatchDocument objectPatch, uint concurrencyToken) { //Do the patching objectPatch.ApplyTo(dbObj); ct.Entry(dbObj).OriginalValues["ConcurrencyToken"] = concurrencyToken; Validate(dbObj, false); if (HasErrors) return false; return true; } //////////////////////////////////////////////////////////////////////////////////////////////// //DELETE // internal bool Delete(User dbObj) { //Determine if the object can be deleted, do the deletion tentatively //Probably also in here deal with tags and associated search text etc ValidateCanDelete(dbObj); if (HasErrors) return false; ct.User.Remove(dbObj); return true; } /// /// Delete child objects like tags and attachments and etc /// /// internal void DeleteChildren(User dbObj) { //TAGS TagMapBiz.DeleteAllForObject(new AyaTypeId(AyaType.User, dbObj.Id), ct); } //////////////////////////////////////////////////////////////////////////////////////////////// //VALIDATION // //Can save or update? private void Validate(User inObj, bool isNew) { //run validation and biz rules if (isNew) { //NEW Users must be active if (inObj.Active == null || ((bool)inObj.Active) == false) { AddError(ValidationErrorType.InvalidValue, "Active", "New User must be active"); } } //OwnerId required if (!isNew) { if (inObj.OwnerId == 0) AddError(ValidationErrorType.RequiredPropertyEmpty, "OwnerId"); } //Name required if (string.IsNullOrWhiteSpace(inObj.Name)) AddError(ValidationErrorType.RequiredPropertyEmpty, "Name"); //Name must be less than 255 characters if (inObj.Name.Length > 255) AddError(ValidationErrorType.LengthExceeded, "Name", "255 max"); //If name is otherwise OK, check that name is unique if (!PropertyHasErrors("Name")) { //Use Any command is efficient way to check existance, it doesn't return the record, just a true or false if (ct.User.Any(m => m.Name == inObj.Name && m.Id != inObj.Id)) { AddError(ValidationErrorType.NotUnique, "Name"); } } //Start date AND end date must both be null or both contain values if (inObj.StartDate == null && inObj.EndDate != null) AddError(ValidationErrorType.RequiredPropertyEmpty, "StartDate"); if (inObj.StartDate != null && inObj.EndDate == null) AddError(ValidationErrorType.RequiredPropertyEmpty, "EndDate"); //Start date before end date if (inObj.StartDate != null && inObj.EndDate != null) if (inObj.StartDate > inObj.EndDate) AddError(ValidationErrorType.StartDateMustComeBeforeEndDate, "StartDate"); //Enum is valid value if (!inObj.Roles.IsValid()) { AddError(ValidationErrorType.InvalidValue, "Roles"); } return; } //Can delete? private void ValidateCanDelete(User inObj) { //whatever needs to be check to delete this object } //////////////////////////////////////////////////////////////////////////////////////////////// //JOB / OPERATIONS // public async Task HandleJobAsync(OpsJob job) { //Hand off the particular job to the corresponding processing code //NOTE: If this code throws an exception the caller (JobsBiz::ProcessJobsAsync) will automatically set the job to failed and log the exeption so //basically any error condition during job processing should throw up an exception if it can't be handled switch (job.JobType) { case JobType.TestUserJob: await ProcessTestJobAsync(job); break; default: throw new System.ArgumentOutOfRangeException($"UserBiz.HandleJob-> Invalid job type{job.JobType.ToString()}"); } } /// /// /// Handle the test job /// /// private async Task ProcessTestJobAsync(OpsJob job) { var sleepTime = 30 * 1000; //Simulate a long running job here JobsBiz.UpdateJobStatus(job.GId, JobStatus.Running, ct); JobsBiz.LogJob(job.GId, $"UserBiz::ProcessTestJob started, sleeping for {sleepTime} seconds...", ct); //Uncomment this to test if the job prevents other routes from running //result is NO it doesn't prevent other requests, so we are a-ok for now await Task.Delay(sleepTime); JobsBiz.LogJob(job.GId, "UserBiz::ProcessTestJob done sleeping setting job to finished", ct); JobsBiz.UpdateJobStatus(job.GId, JobStatus.Completed, ct); } //Other job handlers here... ///////////////////////////////////////////////////////////////////// }//eoc }//eons