using System.Linq; using System.Threading.Tasks; using System.Collections.Generic; using Microsoft.EntityFrameworkCore; using Newtonsoft.Json.Linq; using AyaNova.Util; using AyaNova.Api.ControllerHelpers; using AyaNova.Models; namespace AyaNova.Biz { //## NOTE this is a *GLOBAL* form custom that applies to all users as configured by someone with rights to do so //this is *not* a personal customization system internal class FormCustomBiz : BizObject { internal FormCustomBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles UserRoles) { ct = dbcontext; UserId = currentUserId; UserTranslationId = userTranslationId; CurrentUserRoles = UserRoles; BizType = AyaType.FormCustom; } internal static FormCustomBiz GetBiz(AyContext ct, Microsoft.AspNetCore.Http.HttpContext httpContext = null) { if (httpContext != null) return new FormCustomBiz(ct, UserIdFromContext.Id(httpContext.Items), UserTranslationIdFromContext.Id(httpContext.Items), UserRolesFromContext.Roles(httpContext.Items)); else//when called internally for internal ops there will be no context so need to set default values for that return new FormCustomBiz(ct, 1, ServerBootConfig.AYANOVA_DEFAULT_TRANSLATION_ID, AuthorizationRoles.BizAdmin); } //////////////////////////////////////////////////////////////////////////////////////////////// //EXISTS internal async Task ExistsAsync(string formKey) { return await ct.FormCustom.AnyAsync(z => z.FormKey == formKey); } //////////////////////////////////////////////////////////////////////////////////////////////// //CREATE internal async Task CreateAsync(FormCustom inObj) { await ValidateAsync(inObj, true); if (HasErrors) return null; else { // FormCustom outObj = inObj; outObj.Template = JsonUtil.CompactJson(outObj.Template); await ct.FormCustom.AddAsync(outObj); await ct.SaveChangesAsync(); //Handle child and associated items: //EVENT LOG await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, outObj.Id, BizType, AyaEvent.Created), ct); return outObj; } } //////////////////////////////////////////////////////////////////////////////////////////////// /// GET //Get one internal async Task GetAsync(string formKey) { //Step 1: check if exists, if it does then just return it if (await ExistsAsync(formKey)) { return await ct.FormCustom.SingleOrDefaultAsync(z => z.FormKey == formKey); } //If it doesn't exist, vet the form key name is ok by checking with this list if (!FormFieldOptionalCustomizableReference.FormFieldKeys.Contains(formKey)) { //Nope, whatever it is, it's not valid return null; } // Name is valid, make a new formcustom for the name specified, save to db and then return it //NOTE: This assumes that the client will make it a legal form custom and so it doesn't include any pre-defined stock fields that are required etc //this just makes an empty template suitable for the client to work with var fc = new FormCustom() { FormKey = formKey, Template = @"[]" }; //Create and save to db return await CreateAsync(fc); } //////////////////////////////////////////////////////////////////////////////////////////////// //UPDATE // //put internal async Task PutAsync(FormCustom putObject) { var dbObject = await ct.FormCustom.AsNoTracking().SingleOrDefaultAsync(z => z.FormKey == putObject.FormKey); if (dbObject == null) { AddError(ApiErrorCode.NOT_FOUND, "formKey"); return null; } if (dbObject.Concurrency != putObject.Concurrency) { AddError(ApiErrorCode.CONCURRENCY_CONFLICT); return null; } dbObject.Template = JsonUtil.CompactJson(putObject.Template); putObject.Id=dbObject.Id;//weird workaround needed because ID is not sent with the putobject for...reasons 🤷? await ValidateAsync(putObject, false); if (HasErrors) return null; ct.Replace(dbObject, putObject); try { await ct.SaveChangesAsync(); } catch (DbUpdateConcurrencyException) { if (!await ExistsAsync(putObject.FormKey)) AddError(ApiErrorCode.NOT_FOUND); else AddError(ApiErrorCode.CONCURRENCY_CONFLICT); return null; } //Log modification and save context await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.Id, BizType, AyaEvent.Modified), ct); return putObject; // //todo: replace with new put methodology // //Replace the db object with the PUT object // CopyObject.Copy(putObject, dbObject, "Id"); // //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, false); // if (HasErrors) // return false; // dbObject.Template = JsonUtil.CompactJson(dbObject.Template); // await ct.SaveChangesAsync(); // //Log modification and save context // await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.Id, BizType, AyaEvent.Modified), ct); // return true; } //NO DELETE, ONLY EDIT //////////////////////////////////////////////////////////////////////////////////////////////// //VALIDATION // //Can save or update? private async Task ValidateAsync(FormCustom inObj, bool isNew) { //FormKey required and must be valid if (string.IsNullOrWhiteSpace(inObj.FormKey)) AddError(ApiErrorCode.VALIDATION_REQUIRED, "FormKey"); else { if (!FormFieldOptionalCustomizableReference.IsValidFormFieldKey(inObj.FormKey)) { AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "FormKey"); } } //FormKey must be less than 255 characters if (inObj.FormKey.Length > 255) AddError(ApiErrorCode.VALIDATION_LENGTH_EXCEEDED, "FormKey", "255 max"); //If name is otherwise OK, check that name is unique if (!PropertyHasErrors("FormKey") && isNew) { //Use Any command is efficient way to check existance, it doesn't return the record, just a true or false if (await ct.FormCustom.AnyAsync(z => z.FormKey == inObj.FormKey && z.Id != inObj.Id)) { AddError(ApiErrorCode.VALIDATION_NOT_UNIQUE, "FormKey"); } } //Template json must parse if ((!PropertyHasErrors("FormKey") && !string.IsNullOrWhiteSpace(inObj.Template))) { var ValidCustomFieldTypes = CustomFieldType.ValidCustomFieldTypes; var ValidFormFields = FormFieldOptionalCustomizableReference.FormFieldReferenceList(inObj.FormKey); try { //Parse the json, expecting something like this: //[{fld:"ltkeyfieldname",hide:"true/false",required:"true/false", type:"bool"},{fld:"ltkeyfieldname",hide:"true/false",required:"true/false", type:"text"] //Array at root is valid json and saves a bit of bandwidth so minimal is best //Only Custom fields that are to be displayed need to be in this fragment. //fields that are not chosen to display don't need to be in the fragment to be valid var v = JArray.Parse(inObj.Template); for (int i = 0; i < v.Count; i++) { FormField MasterFormField = null; var formFieldItem = v[i]; if (formFieldItem["fld"] == null) AddError(ApiErrorCode.VALIDATION_REQUIRED, "Template", $"Template array item {i}, object is missing required \"fld\" property "); else { var fldKey = formFieldItem["fld"].Value(); if (string.IsNullOrWhiteSpace(fldKey)) AddError(ApiErrorCode.VALIDATION_REQUIRED, "Template", $"Template array item {i}, \"fld\" property exists but is empty, a value is required"); //validate the field name if we can if (ValidFormFields != null) { if (!ValidFormFields.Exists(z => z.FieldKey == fldKey)) { AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "Template", $"Template array item {i}, fld property value \"{fldKey}\" is not a valid form field value for formKey specified"); } else { MasterFormField = ValidFormFields.FirstOrDefault(z => z.FieldKey == fldKey); } } } if (MasterFormField != null) { //removed due to removal of hidden property in ff reference since only customizable fields are hideable by default // if (formFieldItem["hide"] != null) // { // var fieldHideValue = formFieldItem["hide"].Value(); // if (!MasterFormField.Hideable && fieldHideValue == true) // AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "Template", $"Template array item {i} (\"{MasterFormField.FieldKey}\"), \"hide\" property value of \"{fieldHideValue}\" is not valid, this field is core and cannot be hidden"); // } //validate if it's a custom field that it has a type specified if (MasterFormField.IsCustomField && formFieldItem["type"] == null) { AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "Template", $"Template array item {i} (\"{MasterFormField.FieldKey}\"), \"type\" property value is MISSING for custom field, Custom fields MUST have types specified"); } if (formFieldItem["type"] != null) { if (!MasterFormField.IsCustomField) AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "Template", $"Template array item {i} (\"{MasterFormField.FieldKey}\"), \"type\" property value is not valid, only Custom fields can have types specified"); else {//It is a custom field, is it a valid type value var templateFieldCustomTypeValue = formFieldItem["type"].Value(); if (!ValidCustomFieldTypes.Contains(templateFieldCustomTypeValue)) AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "Template", $"Template array item {i} (\"{MasterFormField.FieldKey}\"), \"type\" property value of \"{templateFieldCustomTypeValue}\" is not a valid custom field type"); } } } //other code depends on seeing the required value even if it's not set to true if (formFieldItem["required"] == null) AddError(ApiErrorCode.VALIDATION_REQUIRED, "Template", $"Template array item {i}, object is missing \"required\" property. All items must contain this property. "); //NOTE: value of nothing, null or empty is a valid value so no checking for it here } } catch (Newtonsoft.Json.JsonReaderException ex) { AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "Template", "Template is not valid JSON string: " + ex.Message); } } return; } // //Can delete? // private void ValidateCanDelete(FormCustom inObj) // { // //Leaving this off for now // } ///////////////////////////////////////////////////////////////////// }//eoc }//eons