This commit is contained in:
2023-01-13 22:06:41 +00:00
parent 650ae96987
commit e47fa443f7
10 changed files with 228 additions and 196 deletions

View File

@@ -202,20 +202,10 @@ namespace Sockeye.Biz
private async Task ValidateAsync(GZCase proposedObj, GZCase currentObj)
{
bool isNew = currentObj == null;
await Task.CompletedTask;
// bool isNew = currentObj == null;
//Any form customizations to validate?
var FormCustomization = await ct.FormCustom.AsNoTracking().SingleOrDefaultAsync(z => z.FormKey == SockType.GZCase.ToString());
if (FormCustomization != null)
{
//Yeppers, do the validation, there are two, the custom fields and the regular fields that might be set to required
//validate users choices for required non custom fields
RequiredFieldsValidator.Validate(this, FormCustomization, proposedObj);
}
}

View File

@@ -240,24 +240,7 @@ namespace Sockeye.Biz
private static string GenFetchCode()
{
//sufficient for this purpose
//https://stackoverflow.com/a/1344258/8939
var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
var stringChars = new char[10];
var random = new Random();
for (int i = 0; i < stringChars.Length; i++)
{
stringChars[i] = chars[random.Next(chars.Length)];
}
var finalString = new String(stringChars);
return finalString;
}
private static Dictionary<string, DateTime> LicenseToPluginsArray(License l)
@@ -309,7 +292,7 @@ namespace Sockeye.Biz
StringBuilder sbKey = new StringBuilder();
StringWriter sw = new StringWriter(sbKey);
l.FetchCode = GenFetchCode();
l.FetchCode = StringUtil.GenFetchCode();
using (Newtonsoft.Json.JsonWriter w = new Newtonsoft.Json.JsonTextWriter(sw))
{
w.Formatting = Newtonsoft.Json.Formatting.Indented;

View File

@@ -500,6 +500,12 @@ namespace Sockeye.Biz
try
{
await ct.SaveChangesAsync();
//TODO: NOTIFICATION ON NEW KEY
//BUT NOT if REVOKED token is the regto, we don't want them to know, just let AyaNova install it
//this is for non-payment scenarios or chargebacks after license was sent
}
catch (DbUpdateConcurrencyException)
{
@@ -761,19 +767,7 @@ namespace Sockeye.Biz
}
//Any form customizations to validate?
var FormCustomization = await ct.FormCustom.AsNoTracking().SingleOrDefaultAsync(z => z.FormKey == SockType.License.ToString());
if (FormCustomization != null)
{
//Yeppers, do the validation, there are two, the custom fields and the regular fields that might be set to required
//validate users choices for required non custom fields
RequiredFieldsValidator.Validate(this, FormCustomization, proposedObj);
}
await Task.CompletedTask;
}
@@ -998,7 +992,7 @@ namespace Sockeye.Biz
if (ayaEvent == SockEvent.Created || ayaEvent == SockEvent.Modified)
{
}
}//end of process notifications

View File

@@ -202,20 +202,7 @@ namespace Sockeye.Biz
private async Task ValidateAsync(Product proposedObj, Product currentObj)
{
bool isNew = currentObj == null;
//Any form customizations to validate?
var FormCustomization = await ct.FormCustom.AsNoTracking().SingleOrDefaultAsync(z => z.FormKey == SockType.Product.ToString());
if (FormCustomization != null)
{
//Yeppers, do the validation, there are two, the custom fields and the regular fields that might be set to required
//validate users choices for required non custom fields
RequiredFieldsValidator.Validate(this, FormCustomization, proposedObj);
}
await Task.CompletedTask;
}

View File

@@ -203,20 +203,7 @@ namespace Sockeye.Biz
private async Task ValidateAsync(Purchase proposedObj, Purchase currentObj)
{
bool isNew = currentObj == null;
//Any form customizations to validate?
var FormCustomization = await ct.FormCustom.AsNoTracking().SingleOrDefaultAsync(z => z.FormKey == SockType.Purchase.ToString());
if (FormCustomization != null)
{
//Yeppers, do the validation, there are two, the custom fields and the regular fields that might be set to required
//validate users choices for required non custom fields
RequiredFieldsValidator.Validate(this, FormCustomization, proposedObj);
}
await Task.CompletedTask;
}

View File

@@ -16,10 +16,12 @@ namespace Sockeye.Biz
//Key generator controller
public static class RavenKeyFactory
{
//Sept 2022 decided all trial periods are 7 days, they can keep renewing a new trial by erasing data or request a longer period case by case
public const int TRIAL_PERIOD_DAYS = 7;
//Jan 2023 values
public const int TRIAL_KEY_SUBSCRIPTION_PERIOD_DAYS = 7;
public const int TRIAL_KEY_SUBSCRIPTION_CUSTOMER_USERS = 25555;//SEEDING required
public const int TRIAL_KEY_SUBSCRIPTION_MAX_DATA_GB = 20;
public const int TRIAL_KEY_PERPETUAL_PERIOD_DAYS = 14;
public const int TRIAL_KEY_USERS = 5000;//SEEDING required minimum
//Unlicensed token
private const string UNLICENSED_TOKEN = "UNLICENSED";
@@ -29,8 +31,6 @@ namespace Sockeye.Biz
//LICENSE USER COUNT FEATURES
//SUBSCRIPTION
private const string ACTIVE_INTERNAL_USERS_FEATURE_NAME = "ActiveInternalUsers";
private const string ACTIVE_CUSTOMER_USERS_FEATURE_NAME = "ActiveCustomerUsers";

View File

@@ -208,20 +208,7 @@ namespace Sockeye.Biz
private async Task ValidateAsync(SubscriptionServer proposedObj, SubscriptionServer currentObj)
{
bool isNew = currentObj == null;
//Any form customizations to validate?
var FormCustomization = await ct.FormCustom.AsNoTracking().SingleOrDefaultAsync(z => z.FormKey == SockType.SubscriptionServer.ToString());
if (FormCustomization != null)
{
//Yeppers, do the validation, there are two, the custom fields and the regular fields that might be set to required
//validate users choices for required non custom fields
RequiredFieldsValidator.Validate(this, FormCustomization, proposedObj);
}
await Task.CompletedTask;
}

View File

@@ -49,11 +49,50 @@ namespace Sockeye.Biz
else
{
newObject.Tags = TagBiz.NormalizeTags(newObject.Tags);
//Process a new request / Generate an email confirm code
newObject.EmailConfirmCode = StringUtil.GenFetchCode();
newObject.EmailValidated = false;
newObject.Status = TrialRequestStatus.AwaitingEmailValidation;
await ct.TrialLicenseRequest.AddAsync(newObject);
await ct.SaveChangesAsync();
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, newObject.Id, BizType, SockEvent.Created), ct);
await EventLogProcessor.LogEventToDatabaseAsync(new Event(1, newObject.Id, BizType, SockEvent.Created), ct);
await SearchIndexAsync(newObject, true);
await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, newObject.Tags, null);
//## ------------------ DEFAULT NOTIFICATIONS TO CUSTOMER ----------------
//ValidateEmail request message,RavenTrialApproved (which sends pending manual generation message) and RavenTrialRejected messages are sent in this block
//
//Send verification request
var verifyUrl = ServerGlobalOpsSettingsCache.Notify.SockeyeServerURL.Trim().TrimEnd('/') + $"/trial-license-request/verify/{newObject.EmailConfirmCode}";
var body = ServerGlobalBizSettings.Cache.ValidateEmail.Replace("{verifyUrl}", verifyUrl);//$"Please verify your email address by clicking the link below or copy and pasting into a browser\r\n{verifyUrl}\r\nOnce your email is verified the request will be processed manually during business hours.\r\n(If you did not request this you can ignore this message)";
var notifyDirectSMTP = new Sockeye.Api.Controllers.NotifyController.NotifyDirectSMTP()
{
ToAddress = newObject.Email,
Subject = "AyaNova trial request email verification",
TextBody = body
};
IMailer m = Sockeye.Util.ServiceProviderProvider.Mailer;
try
{
await m.SendEmailAsync(notifyDirectSMTP.ToAddress, notifyDirectSMTP.Subject, notifyDirectSMTP.TextBody, ServerGlobalOpsSettingsCache.Notify, null, null, null);
await EventLogProcessor.LogEventToDatabaseAsync(new Event(1, notifyDirectSMTP.ObjectId, notifyDirectSMTP.SockType, SockEvent.DirectSMTP, $"\"{notifyDirectSMTP.Subject}\"->{notifyDirectSMTP.ToAddress}"), ct);
}
catch (Exception ex)
{
var err = "TrialLicenseRequest sending email confirmation request: SMTP direct message failed";
await NotifyEventHelper.AddOpsProblemEvent(err, ex);
AddError(ApiErrorCode.API_SERVER_ERROR, null, ExceptionUtil.ExtractAllExceptionMessages(ex));
return null;
}
await HandlePotentialNotificationEvent(SockEvent.Created, newObject);
return newObject;
}
@@ -92,10 +131,58 @@ namespace Sockeye.Biz
putObject.Tags = TagBiz.NormalizeTags(putObject.Tags);
await ValidateAsync(putObject, dbObject);
if (HasErrors) return null;
//Automated processint
//## ------------------ DEFAULT NOTIFICATIONS TO CUSTOMER ----------------
//RavenTrialApproved (which sends pending manual generation message) and RavenTrialRejected messages are sent in this block
Sockeye.Api.Controllers.NotifyController.NotifyDirectSMTP notifyDirectSMTP = null;
if (dbObject.Status == TrialRequestStatus.AwaitingApproval && putObject.Status == TrialRequestStatus.Approved)
{
//APPROVED, generate and save key to be approved for release by us
putObject.Processed = DateTime.UtcNow;
License l = new License();
l.Active = false;//not released to customer until we set to active and save
l.DbId = putObject.DbId;
l.PGroup = putObject.PGroup;
l.TrialMode = true;
l.RegTo = putObject.CompanyName;
//perpet and sub both same number of users, covers seeded data values
l.Users = RavenKeyFactory.TRIAL_KEY_USERS;
if (putObject.PGroup == ProductGroup.RavenPerpetual)
{
l.LicenseExpire = l.MaintenanceExpire = DateTime.UtcNow.AddDays(RavenKeyFactory.TRIAL_KEY_PERPETUAL_PERIOD_DAYS);
}
if (putObject.PGroup == ProductGroup.RavenSubscription)
{
l.LicenseExpire = l.MaintenanceExpire = DateTime.UtcNow.AddDays(RavenKeyFactory.TRIAL_KEY_SUBSCRIPTION_PERIOD_DAYS);
l.CustomerUsers = RavenKeyFactory.TRIAL_KEY_SUBSCRIPTION_CUSTOMER_USERS;
l.MaxDataGB = RavenKeyFactory.TRIAL_KEY_SUBSCRIPTION_MAX_DATA_GB;
}
}
if (dbObject.Status == TrialRequestStatus.AwaitingApproval && putObject.Status == TrialRequestStatus.Rejected)
{
//REJECTED, send email
putObject.Processed = DateTime.UtcNow;
}
ct.Replace(dbObject, putObject);
try
{
await ct.SaveChangesAsync();
//EMAIL?
if (notifyDirectSMTP != null)
{
//send an email
}
}
catch (DbUpdateConcurrencyException)
{
@@ -204,20 +291,12 @@ namespace Sockeye.Biz
private async Task ValidateAsync(TrialLicenseRequest proposedObj, TrialLicenseRequest currentObj)
{
bool isNew = currentObj == null;
await Task.CompletedTask;
// bool isNew = currentObj == null;
//no need to validate required fields, they are set to required in the model with an attribute
//Any form customizations to validate?
var FormCustomization = await ct.FormCustom.AsNoTracking().SingleOrDefaultAsync(z => z.FormKey == SockType.TrialLicenseRequest.ToString());
if (FormCustomization != null)
{
//Yeppers, do the validation, there are two, the custom fields and the regular fields that might be set to required
//validate users choices for required non custom fields
RequiredFieldsValidator.Validate(this, FormCustomization, proposedObj);
}
}
@@ -449,103 +528,101 @@ namespace Sockeye.Biz
//## ------------------ DEFAULT NOTIFICATIONS ----------------
//ValidateEmail request message,RavenTrialApproved (which sends pending manual generation message) and RavenTrialRejected messages are sent in this block
//
//
var custInfo = await ct.Customer.AsNoTracking().Where(x => x.Id == o.CustomerId).Select(x => new { x.Active, x.Tags, x.EmailAddress }).FirstOrDefaultAsync();
if (custInfo != null && custInfo.Active && !string.IsNullOrWhiteSpace(custInfo.EmailAddress))//can this customer receive *any* customer notifications?
{
//-------- Customer is notifiable ------
// //# STATUS CHANGE (create new status)
// {
// //Conditions: must match specific status id value and also tags below
// //delivery is immediate so no need to remove old ones of this kind
// var subs = await ct.CustomerNotifySubscription.AsNoTracking().Where(z => z.EventType == NotifyEventType.WorkorderStatusChange && z.IdValue == oProposed.WorkOrderStatusId).OrderBy(z => z.Id).ToListAsync();
// foreach (var sub in subs)
// {
// //Object tags must match and Customer tags must match
// if (NotifyEventHelper.ObjectHasAllSubscriptionTags(WorkorderInfo.Tags, sub.Tags) && NotifyEventHelper.ObjectHasAllSubscriptionTags(custInfo.Tags, sub.CustomerTags))
// {
// CustomerNotifyEvent n = new CustomerNotifyEvent()
// {
// EventType = NotifyEventType.WorkorderStatusChange,
// CustomerId = WorkorderInfo.CustomerId,
// AyaType = AyaType.WorkOrder,
// ObjectId = oProposed.WorkOrderId,
// CustomerNotifySubscriptionId = sub.Id,
// Name = WorkorderInfo.Serial.ToString()
// };
// await ct.CustomerNotifyEvent.AddAsync(n);
// log.LogDebug($"Adding CustomerNotifyEvent: [{n.ToString()}]");
// await ct.SaveChangesAsync();
// break;//we have a match no need to process any further subs for this event
// }
// }
// }//workorder status change event
// todo: maybe this should be a direct smtp message, not bother with the notification system at all as that's how it was done in rockfish and it's kind of orthogonal
// if ( == !string.IsNullOrWhiteSpace(o.Email))//can this customer receive *any* customer notifications?
// {
// //-------- Customer is notifiable ------
// // //# STATUS CHANGE (create new status)
// // {
// // //Conditions: must match specific status id value and also tags below
// // //delivery is immediate so no need to remove old ones of this kind
// // var subs = await ct.CustomerNotifySubscription.AsNoTracking().Where(z => z.EventType == NotifyEventType.WorkorderStatusChange && z.IdValue == oProposed.WorkOrderStatusId).OrderBy(z => z.Id).ToListAsync();
// // foreach (var sub in subs)
// // {
// // //Object tags must match and Customer tags must match
// // if (NotifyEventHelper.ObjectHasAllSubscriptionTags(WorkorderInfo.Tags, sub.Tags) && NotifyEventHelper.ObjectHasAllSubscriptionTags(custInfo.Tags, sub.CustomerTags))
// // {
// // CustomerNotifyEvent n = new CustomerNotifyEvent()
// // {
// // EventType = NotifyEventType.WorkorderStatusChange,
// // CustomerId = WorkorderInfo.CustomerId,
// // AyaType = AyaType.WorkOrder,
// // ObjectId = oProposed.WorkOrderId,
// // CustomerNotifySubscriptionId = sub.Id,
// // Name = WorkorderInfo.Serial.ToString()
// // };
// // await ct.CustomerNotifyEvent.AddAsync(n);
// // log.LogDebug($"Adding CustomerNotifyEvent: [{n.ToString()}]");
// // await ct.SaveChangesAsync();
// // break;//we have a match no need to process any further subs for this event
// // }
// // }
// // }//workorder status change event
// //# STATUS AGE
// {
// //WorkorderStatusAge = 24,//* Workorder STATUS unchanged for set time (stuck in state), conditional on: Duration (how long stuck), exact status selected IdValue, Tags. Advance notice can NOT be set
// //Always clear any old ones for this object as they are all irrelevant the moment the state has changed:
// await NotifyEventHelper.ClearPriorCustomerNotifyEventsForObject(ct, proposedObj.AyaType, proposedObj.Id, NotifyEventType.WorkorderStatusAge);
// var subs = await ct.CustomerNotifySubscription.AsNoTracking().Where(z => z.EventType == NotifyEventType.WorkorderStatusAge && z.IdValue == oProposed.WorkOrderStatusId).OrderBy(z => z.Id).ToListAsync();
// foreach (var sub in subs)
// {
// //Object tags must match and Customer tags must match
// if (NotifyEventHelper.ObjectHasAllSubscriptionTags(WorkorderInfo.Tags, sub.Tags) && NotifyEventHelper.ObjectHasAllSubscriptionTags(custInfo.Tags, sub.CustomerTags))
// {
// CustomerNotifyEvent n = new CustomerNotifyEvent()
// {
// EventType = NotifyEventType.WorkorderStatusAge,
// CustomerId = WorkorderInfo.CustomerId,
// AyaType = AyaType.WorkOrder,
// ObjectId = oProposed.WorkOrderId,
// CustomerNotifySubscriptionId = sub.Id,
// Name = WorkorderInfo.Serial.ToString()
// };
// await ct.CustomerNotifyEvent.AddAsync(n);
// log.LogDebug($"Adding CustomerNotifyEvent: [{n.ToString()}]");
// await ct.SaveChangesAsync();
// break;//we have a match no need to process any further subs for this event
// }
// }
// }//workorder status age event
// // //# STATUS AGE
// // {
// // //WorkorderStatusAge = 24,//* Workorder STATUS unchanged for set time (stuck in state), conditional on: Duration (how long stuck), exact status selected IdValue, Tags. Advance notice can NOT be set
// // //Always clear any old ones for this object as they are all irrelevant the moment the state has changed:
// // await NotifyEventHelper.ClearPriorCustomerNotifyEventsForObject(ct, proposedObj.AyaType, proposedObj.Id, NotifyEventType.WorkorderStatusAge);
// // var subs = await ct.CustomerNotifySubscription.AsNoTracking().Where(z => z.EventType == NotifyEventType.WorkorderStatusAge && z.IdValue == oProposed.WorkOrderStatusId).OrderBy(z => z.Id).ToListAsync();
// // foreach (var sub in subs)
// // {
// // //Object tags must match and Customer tags must match
// // if (NotifyEventHelper.ObjectHasAllSubscriptionTags(WorkorderInfo.Tags, sub.Tags) && NotifyEventHelper.ObjectHasAllSubscriptionTags(custInfo.Tags, sub.CustomerTags))
// // {
// // CustomerNotifyEvent n = new CustomerNotifyEvent()
// // {
// // EventType = NotifyEventType.WorkorderStatusAge,
// // CustomerId = WorkorderInfo.CustomerId,
// // AyaType = AyaType.WorkOrder,
// // ObjectId = oProposed.WorkOrderId,
// // CustomerNotifySubscriptionId = sub.Id,
// // Name = WorkorderInfo.Serial.ToString()
// // };
// // await ct.CustomerNotifyEvent.AddAsync(n);
// // log.LogDebug($"Adding CustomerNotifyEvent: [{n.ToString()}]");
// // await ct.SaveChangesAsync();
// // break;//we have a match no need to process any further subs for this event
// // }
// // }
// // }//workorder status age event
// //# WorkorderCompleted
// {
// if (wos.Completed)
// {
// var subs = await ct.CustomerNotifySubscription.AsNoTracking().Where(z => z.EventType == NotifyEventType.WorkorderCompleted).OrderBy(z => z.Id).ToListAsync();
// foreach (var sub in subs)
// {
// //Object tags must match and Customer tags must match
// if (NotifyEventHelper.ObjectHasAllSubscriptionTags(WorkorderInfo.Tags, sub.Tags) && NotifyEventHelper.ObjectHasAllSubscriptionTags(custInfo.Tags, sub.CustomerTags))
// {
// // //# WorkorderCompleted
// // {
// // if (wos.Completed)
// // {
// // var subs = await ct.CustomerNotifySubscription.AsNoTracking().Where(z => z.EventType == NotifyEventType.WorkorderCompleted).OrderBy(z => z.Id).ToListAsync();
// // foreach (var sub in subs)
// // {
// // //Object tags must match and Customer tags must match
// // if (NotifyEventHelper.ObjectHasAllSubscriptionTags(WorkorderInfo.Tags, sub.Tags) && NotifyEventHelper.ObjectHasAllSubscriptionTags(custInfo.Tags, sub.CustomerTags))
// // {
// CustomerNotifyEvent n = new CustomerNotifyEvent()
// {
// EventType = NotifyEventType.WorkorderCompleted,
// CustomerId = WorkorderInfo.CustomerId,
// AyaType = AyaType.WorkOrder,
// ObjectId = oProposed.WorkOrderId,
// CustomerNotifySubscriptionId = sub.Id,
// Name = WorkorderInfo.Serial.ToString()
// };
// await ct.CustomerNotifyEvent.AddAsync(n);
// log.LogDebug($"Adding CustomerNotifyEvent: [{n.ToString()}]");
// await ct.SaveChangesAsync();
// break;//we have a match no need to process any further subs for this event
// }
// }
// }
// }//WorkorderCompleted
// // CustomerNotifyEvent n = new CustomerNotifyEvent()
// // {
// // EventType = NotifyEventType.WorkorderCompleted,
// // CustomerId = WorkorderInfo.CustomerId,
// // AyaType = AyaType.WorkOrder,
// // ObjectId = oProposed.WorkOrderId,
// // CustomerNotifySubscriptionId = sub.Id,
// // Name = WorkorderInfo.Serial.ToString()
// // };
// // await ct.CustomerNotifyEvent.AddAsync(n);
// // log.LogDebug($"Adding CustomerNotifyEvent: [{n.ToString()}]");
// // await ct.SaveChangesAsync();
// // break;//we have a match no need to process any further subs for this event
// // }
// // }
// // }
// // }//WorkorderCompleted
//-----------------------
} //------------------ /default notifications ---------------
// //-----------------------
// } //------------------ /default notifications ---------------

View File

@@ -165,6 +165,30 @@ namespace Sockeye.Util
return Encoding.ASCII.GetString(bytes); // returns: "Hello world" for "48656C6C6F20776F726C64"
}
//Generate a random code for email validation and v7 license key fetching
//doesn't have to be perfect, it's only temporary and
//requires knowledge of the customer / trial user
//email address to use it so it's kind of 2 factor
public static string GenFetchCode()
{
//sufficient for this purpose
//https://stackoverflow.com/a/1344258/8939
var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
var stringChars = new char[10];
var random = new Random();
for (int i = 0; i < stringChars.Length; i++)
{
stringChars[i] = chars[random.Next(chars.Length)];
}
var finalString = new String(stringChars);
return finalString;
}
}//eoc
}//eons

View File

@@ -1,5 +1,8 @@
TODO:
DOCS
Once it's up and running update docs to mention that perpetual trial period is two weeks subscription is one week
- Purchase event must trigger notification event subscribable (might already due to created etc)
- figure out and implement default notifications for Customers where appropriate
@@ -30,7 +33,7 @@ TODO:
- TODO: subscription server approved
Need message template and automatic proxy notification to customer
- v7 license fetch route
- v8 license fetch route