This commit is contained in:
2018-08-29 22:52:14 +00:00
parent 35e5183543
commit c91ed15689
7 changed files with 507 additions and 7 deletions

View File

@@ -0,0 +1,315 @@
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<User> 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<User> 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<ApiPagedResponse<User>> 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<User> pr = new ApiPagedResponse<User>(items, pageLinks);
return pr;
}
//get picklist (paged)
internal async Task<ApiPagedResponse<NameIdItem>> 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<NameIdItem> pr = new ApiPagedResponse<NameIdItem>(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<User> 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;
}
/// <summary>
/// Delete child objects like tags and attachments and etc
/// </summary>
/// <param name="dbObj"></param>
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()}");
}
}
/// <summary>
/// /// Handle the test job
/// </summary>
/// <param name="job"></param>
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

View File

@@ -0,0 +1,72 @@
namespace AyaNova.Biz
{
/// <summary>
/// All AyaNova user types
/// </summary>
public enum UserType : int
{
Administrator = 1,
Schedulable = 2,
NonSchedulable = 3,
Client = 4,
HeadOffice = 5,
Utility = 6,
Subcontractor = 7
}
}//eons
/*
///////////////////////////////////////////////////////////
// UserTypes.cs
// Implementation of Class UserTypes
// CSLA type: enumeration
// Created on: 07-Jun-2004 8:41:44 AM
// Object design: Joyce
///////////////////////////////////////////////////////////
using System.ComponentModel;
namespace GZTW.AyaNova.BLL {
/// <summary>
/// Variations of <see cref="User"/> types
/// </summary>
public enum UserTypes : int {
/// <summary>
/// This is the special account used only for updates, for customizing layout, etc
/// </summary>
[Description("LT:UserTypes.Label.Administrator")] Administrator = 1,
/// <summary>
/// This is a user that can be assigned to a workorder or schedule and consumes an AyaNova license
/// </summary>
[Description("LT:UserTypes.Label.Schedulable")] Schedulable = 2,
/// <summary>
/// Standard user that can use all aspects of AyaNova but can not be chosen to assign to a work order or schedule
/// Does not consume an AyaNova license
/// </summary>
[Description("LT:UserTypes.Label.NonSchedulable")] NonSchedulable = 3,
/// <summary>
/// Client's login user account for WBI
/// </summary>
[Description("LT:UserTypes.Label.Client")] Client = 4,
/// <summary>
/// HeadOffice's login user account for WBI
/// </summary>
[Description("LT:UserTypes.Label.HeadOffice")] HeadOffice = 5,
/// <summary>
/// Utility user type for internal processing with limited functionality
/// ATTN. API users: Do not create or set a user to this type in your code, you will be dissapointed and will break things. :)
/// </summary>
[Description("UTILITY")] Utility = 6
//*** IF MORE ADDED USER OBJECT USERTYPE PROPERTIES
// BROKEN RULES MUST BE UPDATED AND SQL SERVER USER TABLE CHECK CONSTRAINT
// FOR USERTYPE VALUE ***
}//end UserTypes
}//end namespace GZTW.AyaNova.BLL
*/

View File

@@ -11,14 +11,107 @@ namespace AyaNova.Models
public uint ConcurrencyToken { get; set; }
[Required]
public long OwnerId { get; set; }
[Required]
public bool Active { get; set; }
[Required]
public string Name { get; set; }
[Required]
public string Login { get; set; }
[Required]
public string Password { get; set; }
[Required]
public string Salt { get; set; }
public AuthorizationRoles Roles { get; set; }
[Required]
public AuthorizationRoles Roles { get; set; }
[Required]
public long LocaleId { get; set; }
public string DlKey { get; set; }
public DateTime? DlKeyExpire { get; set; }
public long LocaleId { get; set; }
[Required]
public UserType UserType { get; set; }
public string EmployeeNumber { get; set; }
public string Notes { get; set; }
public long? ClientId { get; set; }
public long? HeadOfficeId { get; set; }
public long? SubVendorId { get; set; }
}
}
/*
v7 export record sample
{
"DefaultLanguage": "Custom English",
"DefaultServiceTemplateID": "ca83a7b8-4e5f-4a7b-a02b-9cf78d5f983f",
"UserType": 2,
"Active": true,
"ClientID": "00000000-0000-0000-0000-000000000000",
"HeadOfficeID": "00000000-0000-0000-0000-000000000000",
"MemberOfGroup": "0f8a80ff-4b03-4114-ae51-2d13b812dd65",
"Created": "03/21/2005 07:19 AM",
"Modified": "09/15/2015 12:22 PM",
"Creator": "2ecc77fc-69e2-4a7e-b88d-bd0ecaf36aed",
"Modifier": "1d859264-3f32-462a-9b0c-a67dddfdf4d3",
"ID": "1d859264-3f32-462a-9b0c-a67dddfdf4d3",
"FirstName": "Hank",
"LastName": "Rearden",
"Initials": "HR",
"EmployeeNumber": "EMP1236",
"PageAddress": "",
"PageMaxText": 24,
"Phone1": "",
"Phone2": "",
"EmailAddress": "",
"UserCertifications": [
{
"Created": "12/22/2005 02:07 PM",
"Creator": "2ecc77fc-69e2-4a7e-b88d-bd0ecaf36aed",
"Modified": "12/22/2005 02:08 PM",
"Modifier": "2ecc77fc-69e2-4a7e-b88d-bd0ecaf36aed",
"ID": "4492360c-43e4-4209-9f33-30691b0808ed",
"UserCertificationID": "b2f26359-7c42-4218-923a-e949f3ef1f85",
"UserID": "1d859264-3f32-462a-9b0c-a67dddfdf4d3",
"ValidStartDate": "2005-10-11T00:00:00-07:00",
"ValidStopDate": "2006-10-11T00:00:00-07:00"
}
],
"UserSkills": [
{
"Created": "12/22/2005 02:06 PM",
"Creator": "2ecc77fc-69e2-4a7e-b88d-bd0ecaf36aed",
"Modified": "12/22/2005 02:08 PM",
"Modifier": "2ecc77fc-69e2-4a7e-b88d-bd0ecaf36aed",
"ID": "1dc5ce96-f411-4885-856e-5bdb3ad79728",
"UserSkillID": "2e6f8b65-594c-4f6c-9cd6-e14a562daba8",
"UserID": "1d859264-3f32-462a-9b0c-a67dddfdf4d3"
},
{
"Created": "12/22/2005 02:06 PM",
"Creator": "2ecc77fc-69e2-4a7e-b88d-bd0ecaf36aed",
"Modified": "12/22/2005 02:08 PM",
"Modifier": "2ecc77fc-69e2-4a7e-b88d-bd0ecaf36aed",
"ID": "88e476d3-7526-45f5-a0dd-706c8053a63f",
"UserSkillID": "47a4ee94-b0e9-41b5-afe5-4b4f2c981877",
"UserID": "1d859264-3f32-462a-9b0c-a67dddfdf4d3"
}
],
"Notes": "",
"VendorID": "06e502c2-69ba-4e88-8efb-5b53c1687740",
"RegionID": "f856423a-d468-4344-b7b8-121e466738c6",
"DispatchZoneID": "00000000-0000-0000-0000-000000000000",
"SubContractor": false,
"DefaultWarehouseID": "d45eab37-b6e6-4ad2-9163-66d7ba83a98c",
"Custom1": "",
"Custom2": "",
"Custom3": "",
"Custom4": "",
"Custom5": "",
"Custom6": "",
"Custom7": "",
"Custom8": "",
"Custom9": "",
"Custom0": "",
"ScheduleBackColor": -2097216,
"TimeZoneOffset": null
}
*/

View File

@@ -139,8 +139,8 @@ namespace AyaNova.Util
AyaNova.Biz.PrimeData.PrimeLocales(ct);
//Add user table
exec("CREATE TABLE auser (id BIGSERIAL PRIMARY KEY, ownerid bigint not null, name varchar(255) not null, " +
"login text not null, password text not null, salt text not null, roles integer not null, localeid bigint REFERENCES alocale (id), " +
exec("CREATE TABLE auser (id BIGSERIAL PRIMARY KEY, ownerid bigint not null, active bool not null, name varchar(255) not null, " +
"login text not null, password text not null, salt text not null, roles integer not null, localeid bigint not null REFERENCES alocale (id), " +
"dlkey text, dlkeyexpire timestamp)");
//Prime the db with the default MANAGER account