diff --git a/server/AyaNova/Controllers/UserController.cs b/server/AyaNova/Controllers/UserController.cs
index 9ceca15f..da6e67f0 100644
--- a/server/AyaNova/Controllers/UserController.cs
+++ b/server/AyaNova/Controllers/UserController.cs
@@ -83,65 +83,92 @@ namespace AyaNova.Api.Controllers
}
-
///
/// Put (update) User
/// (Login and / or Password are not changed if set to null / omitted)
- ///
- ///
- ///
+ ///
+ ///
///
- [HttpPut("{id}")]
- public async Task PutUser([FromRoute] long id, [FromBody] User inObj)
+ [HttpPut]
+ public async Task PutUser([FromBody] User updatedObject)
{
- if (!serverState.IsOpen)
+ if (!serverState.IsOpen)
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
-
if (!ModelState.IsValid)
- {
return BadRequest(new ApiErrorResponse(ModelState));
- }
-
- var o = await ct.User.SingleOrDefaultAsync(m => m.Id == id);
-
+ UserBiz biz = UserBiz.GetBiz(ct, HttpContext);
+ if (!Authorized.HasModifyRole(HttpContext.Items, biz.BizType))
+ return StatusCode(403, new ApiNotAuthorizedResponse());
+ var o = await biz.PutAsync(updatedObject);//In future may need to return entire object, for now just concurrency token
if (o == null)
{
- return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
- }
-
- //Instantiate the business object handler
- UserBiz biz = UserBiz.GetBiz(ct, HttpContext);
-
- if (!Authorized.HasModifyRole(HttpContext.Items, biz.BizType))
- {
- return StatusCode(403, new ApiNotAuthorizedResponse());
- }
-
-
-
- try
- {
- if (!await biz.PutAsync(o, inObj))
+ if (biz.Errors.Exists(m => m.Code == ApiErrorCode.CONCURRENCY_CONFLICT))
+ return StatusCode(409, new ApiErrorResponse(biz.Errors));
+ else
return BadRequest(new ApiErrorResponse(biz.Errors));
}
- catch (DbUpdateConcurrencyException)
- {
- if (!UserExists(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(ApiOkResponse.Response(new { Concurrency = o.Concurrency }, true));
+ return Ok(ApiOkResponse.Response(new { Concurrency = o.Concurrency }, true)); ;
}
+ // ///
+ // /// Put (update) User
+ // /// (Login and / or Password are not changed if set to null / omitted)
+ // ///
+ // ///
+ // ///
+ // ///
+ // [HttpPut("{id}")]
+ // public async Task PutUser([FromRoute] long id, [FromBody] User inObj)
+ // {
+ // if (!serverState.IsOpen)
+ // return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
+
+ // if (!ModelState.IsValid)
+ // {
+ // return BadRequest(new ApiErrorResponse(ModelState));
+ // }
+
+ // var o = await ct.User.SingleOrDefaultAsync(m => m.Id == id);
+
+ // if (o == null)
+ // {
+ // return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
+ // }
+
+ // //Instantiate the business object handler
+ // UserBiz biz = UserBiz.GetBiz(ct, HttpContext);
+
+ // if (!Authorized.HasModifyRole(HttpContext.Items, biz.BizType))
+ // {
+ // return StatusCode(403, new ApiNotAuthorizedResponse());
+ // }
+
+
+
+ // try
+ // {
+ // if (!await biz.PutAsync(o, inObj))
+ // return BadRequest(new ApiErrorResponse(biz.Errors));
+ // }
+ // catch (DbUpdateConcurrencyException)
+ // {
+ // if (!UserExists(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(ApiOkResponse.Response(new { Concurrency = o.Concurrency }, true));
+ // }
+
+
///
/// Create User
@@ -152,7 +179,7 @@ namespace AyaNova.Api.Controllers
[HttpPost]
public async Task PostUser([FromBody] User inObj, ApiVersion apiVersion)
{
- if (!serverState.IsOpen)
+ if (!serverState.IsOpen)
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
//Instantiate the business object handler
@@ -200,7 +227,7 @@ namespace AyaNova.Api.Controllers
[HttpDelete("{id}")]
public async Task DeleteUser([FromRoute] long id)
{
- if (!serverState.IsOpen)
+ if (!serverState.IsOpen)
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
if (!ModelState.IsValid)
diff --git a/server/AyaNova/biz/UserBiz.cs b/server/AyaNova/biz/UserBiz.cs
index 49d2d9eb..0ef5ec31 100644
--- a/server/AyaNova/biz/UserBiz.cs
+++ b/server/AyaNova/biz/UserBiz.cs
@@ -1,11 +1,13 @@
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
-
using EnumsNET;
using AyaNova.Util;
using AyaNova.Api.ControllerHelpers;
using AyaNova.Models;
+using System;
+using Newtonsoft.Json.Linq;
+using System.Collections.Generic;
namespace AyaNova.Biz
{
@@ -43,7 +45,12 @@ namespace AyaNova.Biz
return new UserBiz(ct, 1, ServerBootConfig.AYANOVA_DEFAULT_TRANSLATION_ID, AuthorizationRoles.BizAdminFull);
}
-
+ ////////////////////////////////////////////////////////////////////////////////////////////////
+ //EXISTS
+ internal async Task ExistsAsync(long id)
+ {
+ return await ct.User.AnyAsync(e => e.Id == id);
+ }
////////////////////////////////////////////////////////////////////////////////////////////////
//CREATE
@@ -114,14 +121,16 @@ namespace AyaNova.Biz
/// GET
//Get one
- internal async Task GetAsync(long fetchId)
+ internal async Task GetAsync(long Id, bool logTheGetEvent = true)
{
+
//This is simple so nothing more here, but often will be copying to a different output object or some other ops
- var dbFullUser = await ct.User.SingleOrDefaultAsync(m => m.Id == fetchId);
+ var dbFullUser = await ct.User.SingleOrDefaultAsync(m => m.Id == Id);
if (dbFullUser != null)
{
//Log
- await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, fetchId, BizType, AyaEvent.Retrieved), ct);
+ if (logTheGetEvent)
+ await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, Id, BizType, AyaEvent.Retrieved), ct);
dtUser retUser = new dtUser();
@@ -136,65 +145,137 @@ namespace AyaNova.Biz
////////////////////////////////////////////////////////////////////////////////////////////////
//UPDATE
//
-
- //put
- internal async Task PutAsync(User dbObj, User inObj)
+ internal async Task PutAsync(User putObject)
{
- //Get a snapshot of the original db value object before changes
+ User dbObject = await ct.User.SingleOrDefaultAsync(m => m.Id == putObject.Id);
+ if (dbObject == null)
+ {
+ AddError(ApiErrorCode.NOT_FOUND, "id");
+ return null;
+ }
User SnapshotOfOriginalDBObj = new User();
- CopyObject.Copy(dbObj, SnapshotOfOriginalDBObj);
-
- //Update the db object with the PUT object values
- CopyObject.Copy(inObj, dbObj, "Id, Salt, CurrentAuthToken, DlKey, DlKeyExpire");
- dbObj.Tags = TagBiz.NormalizeTags(dbObj.Tags);
- dbObj.CustomFields = JsonUtil.CompactJson(dbObj.CustomFields);
+ CopyObject.Copy(dbObject, SnapshotOfOriginalDBObj);
+ CopyObject.Copy(putObject, dbObject, "Id, Salt, CurrentAuthToken, DlKey, DlKeyExpire");
+ dbObject.Tags = TagBiz.NormalizeTags(dbObject.Tags);
+ dbObject.CustomFields = JsonUtil.CompactJson(dbObject.CustomFields);
//NOTE: It's valid to call this without intending to change login or password (null values)
//Is the user updating the password?
- if (!string.IsNullOrWhiteSpace(inObj.Password) && SnapshotOfOriginalDBObj.Password != inObj.Password)
+ if (!string.IsNullOrWhiteSpace(putObject.Password) && SnapshotOfOriginalDBObj.Password != putObject.Password)
{
//YES password is being updated:
- dbObj.Password = Hasher.hash(SnapshotOfOriginalDBObj.Salt, inObj.Password);
+ dbObject.Password = Hasher.hash(SnapshotOfOriginalDBObj.Salt, putObject.Password);
}
else
{
//No, use the snapshot password value
- dbObj.Password = SnapshotOfOriginalDBObj.Password;
- dbObj.Salt = SnapshotOfOriginalDBObj.Salt;
+ dbObject.Password = SnapshotOfOriginalDBObj.Password;
+ dbObject.Salt = SnapshotOfOriginalDBObj.Salt;
}
//Updating login?
- if (!string.IsNullOrWhiteSpace(inObj.Login))
+ if (!string.IsNullOrWhiteSpace(putObject.Login))
{
//YES Login is being updated:
- dbObj.Login = inObj.Login;
+ dbObject.Login = putObject.Login;
}
else
{
//No, use the original value
- dbObj.Login = SnapshotOfOriginalDBObj.Login;
+ dbObject.Login = SnapshotOfOriginalDBObj.Login;
}
- //Set "original" value of concurrency token to input token
- //this will allow EF to check it out
- ct.Entry(dbObj).OriginalValues["Concurrency"] = inObj.Concurrency;
-
- await ValidateAsync(dbObj, SnapshotOfOriginalDBObj);
- if (HasErrors)
- return false;
- await ct.SaveChangesAsync();
-
- //Log modification and save context
- await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObj.Id, BizType, AyaEvent.Modified), ct);
-
- await SearchIndexAsync(dbObj, false);
-
- await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, dbObj.Tags, SnapshotOfOriginalDBObj.Tags);
-
- return true;
+ ct.Entry(dbObject).OriginalValues["Concurrency"] = putObject.Concurrency;
+ await ValidateAsync(dbObject, SnapshotOfOriginalDBObj);
+ if (HasErrors) return null;
+ try
+ {
+ await ct.SaveChangesAsync();
+ }
+ catch (DbUpdateConcurrencyException)
+ {
+ if (!await ExistsAsync(putObject.Id))
+ AddError(ApiErrorCode.NOT_FOUND);
+ else
+ AddError(ApiErrorCode.CONCURRENCY_CONFLICT);
+ return null;
+ }
+ await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.Id, BizType, AyaEvent.Modified), ct);
+ await SearchIndexAsync(dbObject, false);
+ await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, dbObject.Tags, SnapshotOfOriginalDBObj.Tags);
+ return dbObject;
}
-
- //put
+
+
+
+
+ // //put
+ // internal async Task PutAsync(User putObject)
+ // {
+ // User dbObject = await ct.User.SingleOrDefaultAsync(m => m.Id == putObject.Id);
+ // if (dbObject == null)
+ // {
+ // AddError(ApiErrorCode.NOT_FOUND, "id");
+ // return null;
+ // }
+
+ // //Get a snapshot of the original db value object before changes
+ // User SnapshotOfOriginalDBObj = new User();
+ // CopyObject.Copy(dbObject, SnapshotOfOriginalDBObj);
+
+ // //Update the db object with the PUT object values
+ // CopyObject.Copy(putObject, dbObject, "Id, Salt, CurrentAuthToken, DlKey, DlKeyExpire");
+ // dbObject.Tags = TagBiz.NormalizeTags(dbObject.Tags);
+ // dbObject.CustomFields = JsonUtil.CompactJson(dbObject.CustomFields);
+
+ // //NOTE: It's valid to call this without intending to change login or password (null values)
+ // //Is the user updating the password?
+ // if (!string.IsNullOrWhiteSpace(putObject.Password) && SnapshotOfOriginalDBObj.Password != putObject.Password)
+ // {
+ // //YES password is being updated:
+ // dbObject.Password = Hasher.hash(SnapshotOfOriginalDBObj.Salt, putObject.Password);
+ // }
+ // else
+ // {
+ // //No, use the snapshot password value
+ // dbObject.Password = SnapshotOfOriginalDBObj.Password;
+ // dbObject.Salt = SnapshotOfOriginalDBObj.Salt;
+ // }
+ // //Updating login?
+ // if (!string.IsNullOrWhiteSpace(putObject.Login))
+ // {
+ // //YES Login is being updated:
+ // dbObject.Login = putObject.Login;
+ // }
+ // else
+ // {
+ // //No, use the original value
+ // dbObject.Login = SnapshotOfOriginalDBObj.Login;
+ // }
+
+
+ // //Set "original" value of concurrency token to input token
+ // //this will allow EF to check it out
+ // ct.Entry(dbObject).OriginalValues["Concurrency"] = putObject.Concurrency;
+
+ // await ValidateAsync(dbObject, SnapshotOfOriginalDBObj);
+ // if (HasErrors)
+ // return false;
+ // await ct.SaveChangesAsync();
+
+ // //Log modification and save context
+ // await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.Id, BizType, AyaEvent.Modified), ct);
+
+ // await SearchIndexAsync(dbObject, false);
+
+ // await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, dbObject.Tags, SnapshotOfOriginalDBObj.Tags);
+
+ // return true;
+ // }
+
+ /////////////////////////////////////////////
+ //PASSWORD
+ //
internal async Task ChangePasswordAsync(long userId, string newPassword)
{
User dbObj = await ct.User.FirstOrDefaultAsync(m => m.Id == userId);
@@ -485,14 +566,11 @@ namespace AyaNova.Biz
//
public async Task HandleJobAsync(OpsJob job)
{
- //just to hide compiler warning for now
- await Task.CompletedTask;
- //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.BulkCoreBizObjectOperation:
+ await ProcessBulkJobAsync(job);
+ break;
default:
throw new System.ArgumentOutOfRangeException($"UserBiz.HandleJob-> Invalid job type{job.JobType.ToString()}");
}
@@ -500,7 +578,107 @@ namespace AyaNova.Biz
}
- //Other job handlers here...
+
+
+ private async Task ProcessBulkJobAsync(OpsJob job)
+ {
+ await JobsBiz.UpdateJobStatusAsync(job.GId, JobStatus.Running, ct);
+ await JobsBiz.LogJobAsync(job.GId, $"Bulk job {job.SubType} started...", ct);
+
+ List idList = new List();
+ long ProcessedObjectCount = 0;
+ JObject jobData = JObject.Parse(job.JobInfo);
+
+
+ //TAGS?
+ string ToTag = null;
+ string Tag = null;
+ if (jobData.ContainsKey("tag"))
+ Tag = (string)jobData["tag"];
+
+ if (jobData.ContainsKey("toTag"))
+ ToTag = (string)jobData["toTag"];
+
+ if (jobData.ContainsKey("idList"))
+ {
+ idList = ((JArray)jobData["idList"]).ToObject>();
+ // jobData["idList"].Value>();
+ }
+ else
+ {
+ //we need to fetch a list of them all because ALL items was selected
+ //If they wanted to filter (say by Active=true) they can do that from a grid list mass op
+ idList = await ct.User.Select(m => m.Id).ToListAsync();
+ }
+
+
+ bool SaveIt = false;
+ //Iterate the list fetching each in turn and sending to the processor to handle
+ foreach (long id in idList)
+ {
+ try
+ {
+ SaveIt = false;
+ //errors would pile up here if not cleared on each iteration
+ ClearErrors();
+
+ //Fetch
+ var o = await GetAsync(id, false);
+ //call out processor for each item in turn
+ switch (job.SubType)
+ {
+ case JobSubType.TagAddAny:
+ case JobSubType.TagAdd:
+ if (!o.Tags.Contains(Tag))
+ {
+ o.Tags.Add(Tag);
+ SaveIt = true;
+ }
+ break;
+ case JobSubType.TagRemoveAny:
+ case JobSubType.TagRemove:
+ SaveIt = o.Tags.Remove(Tag);
+ break;
+ case JobSubType.TagReplaceAny:
+ case JobSubType.TagReplace:
+ int index = o.Tags.IndexOf(Tag);
+ if (index != -1)
+ {
+ o.Tags[index] = ToTag;
+ SaveIt = true;
+ }
+ break;
+ default:
+ throw new System.ArgumentOutOfRangeException($"ProcessBulkJob -> Invalid job Subtype{job.SubType}");
+ }
+
+ if (SaveIt)
+ {
+ User u = new User();
+ CopyObject.Copy(o, u, "Id, Salt, CurrentAuthToken, DlKey, DlKeyExpire");
+ u = await PutAsync(u);
+ if (u == null)
+ await JobsBiz.LogJobAsync(job.GId, $"Error processing item {id}: {GetErrorsAsString()}", ct);
+ else
+ ProcessedObjectCount++;
+ }
+ else
+ {
+
+ ProcessedObjectCount++;
+ }
+
+ }
+ catch (Exception ex)
+ {
+ await JobsBiz.LogJobAsync(job.GId, $"Error processing item {id}", ct);
+ await JobsBiz.LogJobAsync(job.GId, ExceptionUtil.ExtractAllExceptionMessages(ex), ct);
+ }
+ }
+ await JobsBiz.LogJobAsync(job.GId, $"Bulk job {job.SubType} processed {ProcessedObjectCount} of {idList.Count}", ct);
+ await JobsBiz.UpdateJobStatusAsync(job.GId, JobStatus.Completed, ct);
+ }
+
/////////////////////////////////////////////////////////////////////
diff --git a/server/AyaNova/biz/WidgetBiz.cs b/server/AyaNova/biz/WidgetBiz.cs
index ea594f24..c63ba008 100644
--- a/server/AyaNova/biz/WidgetBiz.cs
+++ b/server/AyaNova/biz/WidgetBiz.cs
@@ -324,9 +324,7 @@ namespace AyaNova.Biz
private async Task ProcessBulkJobAsync(OpsJob job)
- {
-
- //Simulate a long running job here
+ {
await JobsBiz.UpdateJobStatusAsync(job.GId, JobStatus.Running, ct);
await JobsBiz.LogJobAsync(job.GId, $"Bulk job {job.SubType} started...", ct);