diff --git a/.vscode/launch.json b/.vscode/launch.json
index 33155498..396abc9b 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -45,7 +45,7 @@
//"AYANOVA_LOG_LEVEL": "Debug",
"AYANOVA_DEFAULT_TRANSLATION": "en",
//TRANSLATION MUST BE en for Integration TESTING
- "AYANOVA_PERMANENTLY_ERASE_DATABASE": "true",
+ //"AYANOVA_PERMANENTLY_ERASE_DATABASE": "true",
"AYANOVA_DB_CONNECTION": "Server=localhost;Username=postgres;Password=raven;Database=AyaNova;",
"AYANOVA_USE_URLS": "http://*:7575;",
"AYANOVA_FOLDER_USER_FILES": "c:\\temp\\RavenTestData\\userfiles",
diff --git a/server/AyaNova/Controllers/TranslationController.cs b/server/AyaNova/Controllers/TranslationController.cs
index f52682fb..f8f7cc2b 100644
--- a/server/AyaNova/Controllers/TranslationController.cs
+++ b/server/AyaNova/Controllers/TranslationController.cs
@@ -48,7 +48,7 @@ namespace AyaNova.Api.Controllers
serverState = apiServerState;
}
-
+
///
@@ -84,23 +84,52 @@ namespace AyaNova.Api.Controllers
///
- /// Get Translations list
- ///
- /// List in alphabetical order of all Translations
- [HttpGet("list")]
- public async Task TranslationList()
+ /// Put (update) Translation
+ ///
+ ///
+ ///
+ ///
+ [HttpPut]
+ public async Task PutTranslation([FromBody] Translation updatedObject)
{
- if (serverState.IsClosed)
+ if (!serverState.IsOpen)
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
-
- //Instantiate the business object handler
+ if (!ModelState.IsValid)
+ return BadRequest(new ApiErrorResponse(ModelState));
TranslationBiz biz = TranslationBiz.GetBiz(ct, HttpContext);
-
- var l = await biz.GetTranslationListAsync();
- return Ok(ApiOkResponse.Response(l));
+ 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)
+ {
+ if (biz.Errors.Exists(z => z.Code == ApiErrorCode.CONCURRENCY_CONFLICT))
+ return StatusCode(409, new ApiErrorResponse(biz.Errors));
+ else
+ return BadRequest(new ApiErrorResponse(biz.Errors));
+ }
+ return Ok(ApiOkResponse.Response(new { Concurrency = o.Concurrency })); ;
}
+
+ // ///
+ // /// Get Translations list
+ // ///
+ // /// List in alphabetical order of all Translations
+ // [HttpGet("list")]
+ // public async Task TranslationList()
+ // {
+ // if (serverState.IsClosed)
+ // return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
+
+ // //Instantiate the business object handler
+ // TranslationBiz biz = TranslationBiz.GetBiz(ct, HttpContext);
+
+ // var l = await biz.GetTranslationListAsync();
+ // return Ok(ApiOkResponse.Response(l));
+ // }
+
+
#if (DEBUG)
///
/// Get a coverage report of translation keys used versus unused
@@ -146,222 +175,32 @@ namespace AyaNova.Api.Controllers
}
+
///
- /// Duplicates an existing translation with a new name
+ /// Duplicate
///
- /// NameIdItem object containing source translation Id and new name
+ /// Source object id
/// From route path
- /// Error response or newly created translation
- [HttpPost("duplicate")]
- public async Task Duplicate([FromBody] NameIdItem inObj, ApiVersion apiVersion)
+ /// Duplicate
+ [HttpPost("duplicate/{id}")]
+ public async Task DuplicateTranslation([FromRoute] long id, ApiVersion apiVersion)
{
- if (serverState.IsClosed)
+ if (!serverState.IsOpen)
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
-
- //Instantiate the business object handler
TranslationBiz biz = TranslationBiz.GetBiz(ct, HttpContext);
-
- var o = await biz.DuplicateAsync(inObj);
-
+ if (!Authorized.HasCreateRole(HttpContext.Items, biz.BizType))
+ return StatusCode(403, new ApiNotAuthorizedResponse());
+ if (!ModelState.IsValid)
+ return BadRequest(new ApiErrorResponse(ModelState));
+ Translation o = await biz.DuplicateAsync(id);
if (o == null)
- {
- //error return
return BadRequest(new ApiErrorResponse(biz.Errors));
- }
else
- {
return CreatedAtAction(nameof(TranslationController.GetTranslation), new { id = o.Id, version = apiVersion.ToString() }, new ApiCreatedResponse(o));
- }
}
- ///
- /// Put (UpdateTranslationItemDisplayText)
- /// Update a single key with new display text
- ///
- /// NewText/Id/Concurrency token object. NewText is new display text, Id is TranslationItem Id, concurrency token is required
- ///
- [HttpPut("updatetranslationitemdisplaytext")]
- public async Task PutTranslationItemDisplayText([FromBody] NewTextIdConcurrencyTokenItem inObj)
- {
- if (serverState.IsClosed)
- return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
-
- if (!ModelState.IsValid)
- {
- return BadRequest(new ApiErrorResponse(ModelState));
- }
-
- var oFromDb = await ct.TranslationItem.SingleOrDefaultAsync(z => z.Id == inObj.Id);
-
- if (oFromDb == null)
- {
- return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
- }
-
- //Now fetch translation for rights and to ensure not stock
- var oDbParent = await ct.Translation.SingleOrDefaultAsync(z => z.Id == oFromDb.TranslationId);
- if (oDbParent == null)
- {
- return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
- }
-
- if (!Authorized.HasModifyRole(HttpContext.Items, AyaType.Translation))
- {
- return StatusCode(403, new ApiNotAuthorizedResponse());
- }
-
- //Instantiate the business object handler
- TranslationBiz biz = TranslationBiz.GetBiz(ct, HttpContext);
-
- try
- {
- if (!await biz.PutTranslationItemDisplayTextAsync(oFromDb, inObj, oDbParent))
- {
- return BadRequest(new ApiErrorResponse(biz.Errors));
- }
- }
- catch (DbUpdateConcurrencyException)
- {
- if (!await biz.TranslationItemExistsAsync(inObj.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 = oFromDb.Concurrency }));
- }
-
-
-
- ///
- /// Put (UpdateTranslationItemDisplayText)
- /// Update a list of items with new display text
- ///
- /// Array of NewText/Id/Concurrency token objects. NewText is new display text, Id is TranslationItem Id, concurrency token is required
- ///
- [HttpPut("updatetranslationitemsdisplaytext")]
- public async Task PutTranslationItemsDisplayText([FromBody] List inObj)
- {
- if (serverState.IsClosed)
- return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
-
- if (!ModelState.IsValid)
- {
- return BadRequest(new ApiErrorResponse(ModelState));
- }
-
- var oFromDb = await ct.TranslationItem.AsNoTracking().SingleOrDefaultAsync(z => z.Id == inObj[0].Id);
-
- if (oFromDb == null)
- {
- return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
- }
-
- //Now fetch translation for rights and to ensure not stock
- var oDbParent = await ct.Translation.SingleOrDefaultAsync(z => z.Id == oFromDb.TranslationId);
- if (oDbParent == null)
- {
- return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
- }
-
- if (!Authorized.HasModifyRole(HttpContext.Items, AyaType.Translation))
- {
- return StatusCode(403, new ApiNotAuthorizedResponse());
- }
-
- //Instantiate the business object handler
- TranslationBiz biz = TranslationBiz.GetBiz(ct, HttpContext);
-
- try
- {
- if (!await biz.PutTranslationItemsDisplayTextAsync(inObj, oDbParent))
- {
- return BadRequest(new ApiErrorResponse(biz.Errors));
- }
- }
- catch (DbUpdateConcurrencyException)
- {
-
- //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 NoContent();
- }
-
-
-
- ///
- /// Put (UpdateTranslationName)
- /// Update a translation to change the name (non-stock Translations only)
- ///
- /// NewText/Id/Concurrency token object. NewText is new translation name, Id is Translation Id, concurrency token is required
- ///
- [HttpPut("updatetranslationname")]
- public async Task PutTranslationName([FromBody] NewTextIdConcurrencyTokenItem inObj)
- {
- if (serverState.IsClosed)
- return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
-
- if (!ModelState.IsValid)
- {
- return BadRequest(new ApiErrorResponse(ModelState));
- }
-
- var oFromDb = await ct.Translation.SingleOrDefaultAsync(z => z.Id == inObj.Id);
-
- if (oFromDb == null)
- {
- return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
- }
-
-
- if (!Authorized.HasModifyRole(HttpContext.Items, AyaType.Translation))
- {
- return StatusCode(403, new ApiNotAuthorizedResponse());
- }
-
- //Instantiate the business object handler
- TranslationBiz biz = TranslationBiz.GetBiz(ct, HttpContext);
-
- try
- {
- if (!await biz.PutTranslationNameAsync(oFromDb, inObj))
- {
- return BadRequest(new ApiErrorResponse(biz.Errors));
- }
-
- }
- catch (DbUpdateConcurrencyException)
- {
- if (!await biz.TranslationExistsAsync(inObj.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 = oFromDb.Concurrency }));
- }
-
-
///
/// Delete Translation
///
diff --git a/server/AyaNova/biz/TranslationBiz.cs b/server/AyaNova/biz/TranslationBiz.cs
index d9390021..7a1d3948 100644
--- a/server/AyaNova/biz/TranslationBiz.cs
+++ b/server/AyaNova/biz/TranslationBiz.cs
@@ -32,50 +32,95 @@ namespace AyaNova.Biz
return new TranslationBiz(ct, UserIdFromContext.Id(httpContext.Items), UserTranslationIdFromContext.Id(httpContext.Items), UserRolesFromContext.Roles(httpContext.Items));
else
return new TranslationBiz(ct, 1, ServerBootConfig.AYANOVA_DEFAULT_TRANSLATION_ID, AuthorizationRoles.BizAdminFull);
-
}
+ ////////////////////////////////////////////////////////////////////////////////////////////////
+ //EXISTS
+ internal async Task ExistsAsync(long id)
+ {
+ return await ct.Translation.AnyAsync(z => z.Id == id);
+ }
////////////////////////////////////////////////////////////////////////////////////////////////
- //DUPLICATE - only way to create a new translation
- //
- internal async Task DuplicateAsync(NameIdItem inObj)
+ //UPDATE
+ //
+ internal async Task PutAsync(Translation putObject)
{
-
- //make sure sourceid exists
- if (!await TranslationExistsAsync(inObj.Id))
- AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "Id", "Source translation id does not exist");
-
- //Ensure name is unique and not too long and not empty
- await ValidateAsync(inObj.Name, true);
-
- if (HasErrors)
- return null;
-
- //fetch the existing translation for duplication
- var SourceTranslation = await ct.Translation.Include(z => z.TranslationItems).SingleOrDefaultAsync(z => z.Id == inObj.Id);
-
- //replicate the source to a new dest and save
- Translation NewTranslation = new Translation();
- NewTranslation.Name = inObj.Name;
-
- NewTranslation.Stock = false;
- NewTranslation.CjkIndex = false;
- foreach (TranslationItem i in SourceTranslation.TranslationItems)
+ Translation dbObject = await ct.Translation.SingleOrDefaultAsync(z => z.Id == putObject.Id);
+ if (dbObject == null)
{
- NewTranslation.TranslationItems.Add(new TranslationItem() { Key = i.Key, Display = i.Display });
+ AddError(ApiErrorCode.NOT_FOUND, "id");
+ return null;
+ }
+ Translation SnapshotOfOriginalDBObj = new Translation();
+ CopyObject.Copy(dbObject, SnapshotOfOriginalDBObj);
+ CopyObject.Copy(putObject, dbObject, "Id");
+
+ ct.Entry(dbObject).OriginalValues["Concurrency"] = putObject.Concurrency;
+ //maybe validate that there are no empty values?
+ 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);
+
+ return dbObject;
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////////////////////
+ //DUPLICATE
+ //
+ internal async Task DuplicateAsync(long id)
+ {
+ Translation dbObject = await ct.Translation.SingleOrDefaultAsync(z => z.Id == id);
+
+ if (dbObject == null)
+ {
+ AddError(ApiErrorCode.NOT_FOUND, "id");
+ return null;
+ }
+ Translation newObject = new Translation();
+ //CopyObject.Copy(dbObject, newObject, "Id, Salt, Login, Password, CurrentAuthToken, DlKey, DlKeyExpire, Wiki, Serial");
+ string newUniqueName = string.Empty;
+ bool NotUnique = true;
+ long l = 1;
+ do
+ {
+ newUniqueName = Util.StringUtil.UniqueNameBuilder(dbObject.Name, l++, 255);
+ NotUnique = await ct.Translation.AnyAsync(z => z.Name == newUniqueName);
+ } while (NotUnique);
+ newObject.Name = newUniqueName;
+
+ newObject.Stock = false;
+ newObject.CjkIndex = false;
+ foreach (TranslationItem i in dbObject.TranslationItems)
+ {
+ newObject.TranslationItems.Add(new TranslationItem() { Key = i.Key, Display = i.Display });
}
- //Add it to the context so the controller can save it
- await ct.Translation.AddAsync(NewTranslation);
+ newObject.Id = 0;
+ newObject.Concurrency = 0;
+ await ct.Translation.AddAsync(newObject);
await ct.SaveChangesAsync();
- //Log
- await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, NewTranslation.Id, AyaType.Translation, AyaEvent.Created), ct);
- return NewTranslation;
-
+ await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, newObject.Id, BizType, AyaEvent.Created), ct);
+ // await SearchIndexAsync(newObject, true);
+ // await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, newObject.Tags, null);
+ return newObject;
}
+
+
////////////////////////////////////////////////////////////////////////////////////////////////
/// GET
@@ -206,106 +251,6 @@ namespace AyaNova.Biz
}
- ////////////////////////////////////////////////////////////////////////////////////////////////
- //UPDATE
- //
-
-
- internal async Task PutTranslationItemDisplayTextAsync(TranslationItem dbObj, NewTextIdConcurrencyTokenItem inObj, Translation dbParent)
- {
-
- if (dbParent.Stock == true)
- {
- AddError(ApiErrorCode.INVALID_OPERATION, "object", "TranslationItem is from a Stock translation and cannot be modified");
- return false;
- }
-
- //Replace the db object with the PUT object
- //CopyObject.Copy(inObj, dbObj, "Id");
- dbObj.Display = inObj.NewText;
- //Set "original" value of concurrency token to input token
- //this will allow EF to check it out
- ct.Entry(dbObj).OriginalValues["Concurrency"] = inObj.Concurrency;
-
- //Only thing to validate is if it has data at all in it
- if (string.IsNullOrWhiteSpace(inObj.NewText))
- AddError(ApiErrorCode.VALIDATION_REQUIRED, "Display (NewText)");
-
- if (HasErrors)
- return false;
- await ct.SaveChangesAsync();
-
- //Log
- await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbParent.Id, AyaType.Translation, AyaEvent.Modified), ct);
-
- return true;
- }
-
- internal async Task PutTranslationItemsDisplayTextAsync(List inObj, Translation dbParent)
- {
-
- if (dbParent.Stock == true)
- {
- AddError(ApiErrorCode.INVALID_OPERATION, "object", "TranslationItem is from a Stock translation and cannot be modified");
- return false;
- }
-
- foreach (NewTextIdConcurrencyTokenItem tit in inObj)
- {
- var titem = await ct.TranslationItem.SingleOrDefaultAsync(z => z.Id == tit.Id);
- if (titem == null)
- {
- AddError(ApiErrorCode.NOT_FOUND, $"Translation item ID {tit.Id}");
- return false;
- }
- //Replace the db object with the PUT object
- //CopyObject.Copy(inObj, dbObj, "Id");
- titem.Display = tit.NewText;
-
- //Set "original" value of concurrency token to input token
- //this will allow EF to check it out
- ct.Entry(titem).OriginalValues["Concurrency"] = tit.Concurrency;
-
- //Only thing to validate is if it has data at all in it
- if (string.IsNullOrWhiteSpace(tit.NewText))
- AddError(ApiErrorCode.VALIDATION_REQUIRED, $"Display (NewText) for Id: {tit.Id}");
- }
- if (HasErrors)
- return false;
- await ct.SaveChangesAsync();
-
- //Log
- await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbParent.Id, AyaType.Translation, AyaEvent.Modified), ct);
-
- return true;
- }
-
-
- internal async Task PutTranslationNameAsync(Translation dbObj, NewTextIdConcurrencyTokenItem inObj)
- {
- if (dbObj.Stock == true)
- {
- AddError(ApiErrorCode.INVALID_OPERATION, "object", "Translation is a Stock translation and cannot be modified");
- return false;
- }
-
- dbObj.Name = inObj.NewText;
-
- //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.Name, false);
-
- if (HasErrors)
- return false;
-
- await ct.SaveChangesAsync();
- //Log
- await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObj.Id, AyaType.Translation, AyaEvent.Modified), ct);
-
- return true;
- }
@@ -333,22 +278,28 @@ namespace AyaNova.Biz
//
//Can save or update?
- private async Task ValidateAsync(string inObjName, bool isNew)
+ private async Task ValidateAsync(Translation proposedObj, Translation currentObj)
{
//run validation and biz rules
//Name required
- if (string.IsNullOrWhiteSpace(inObjName))
+ if (string.IsNullOrWhiteSpace(proposedObj.Name))
AddError(ApiErrorCode.VALIDATION_REQUIRED, "Name");
//Name must be less than 255 characters
- if (inObjName.Length > 255)
+ if (proposedObj.Name.Length > 255)
AddError(ApiErrorCode.VALIDATION_LENGTH_EXCEEDED, "Name", "255 char max");
//Name must be unique
- if (await ct.Translation.AnyAsync(z => z.Name == inObjName))
+ if (await ct.Translation.AnyAsync(z => z.Name == proposedObj.Name))
AddError(ApiErrorCode.VALIDATION_NOT_UNIQUE, "Name");
+ //Ensure there are no empty keys
+ if (proposedObj.TranslationItems.Where(z => z.Display.Length < 1).Any())
+ {
+ AddError(ApiErrorCode.VALIDATION_REQUIRED, "Display", "One or more items are missing a display value");
+ }
+
return;
}