From 64337d1e42794f48b4e803cc70fa515e7c52ba1b Mon Sep 17 00:00:00 2001 From: John Cardinal Date: Tue, 17 Jan 2023 01:58:09 +0000 Subject: [PATCH] --- server/Controllers/EnumListController.cs | 4 +- server/Controllers/OrderController.cs | 84 ++++++++++++++++++++++++ server/Controllers/RvrController.cs | 4 +- server/biz/LicenseBiz.cs | 4 +- server/biz/ProductGroup.cs | 3 +- server/models/Purchase.cs | 7 +- server/util/AutoOrderProcessingUtil.cs | 27 ++++++++ server/util/AySchema.cs | 6 +- todo.txt | 25 +++++-- 9 files changed, 148 insertions(+), 16 deletions(-) create mode 100644 server/Controllers/OrderController.cs create mode 100644 server/util/AutoOrderProcessingUtil.cs diff --git a/server/Controllers/EnumListController.cs b/server/Controllers/EnumListController.cs index f0fdfa0..c64f310 100644 --- a/server/Controllers/EnumListController.cs +++ b/server/Controllers/EnumListController.cs @@ -569,11 +569,13 @@ namespace Sockeye.Api.Controllers } else if (keyNameInLowerCase == StringUtil.TrimTypeName(typeof(ProductGroup).ToString()).ToLowerInvariant()) { + TranslationKeysToFetch.Add("ProductGroupNotSet"); TranslationKeysToFetch.Add("ProductGroupMisc"); TranslationKeysToFetch.Add("ProductGroupAyaNova7"); TranslationKeysToFetch.Add("ProductGroupRavenPerpetual"); TranslationKeysToFetch.Add("ProductGroupRavenSubscription"); - var LT = await TranslationBiz.GetSubsetStaticAsync(TranslationKeysToFetch, translationId); + var LT = await TranslationBiz.GetSubsetStaticAsync(TranslationKeysToFetch, translationId); + ReturnList.Add(new NameIdItem() { Name = LT["ProductGroupNotSet"], Id = (long)ProductGroup.NotSet }); ReturnList.Add(new NameIdItem() { Name = LT["ProductGroupMisc"], Id = (long)ProductGroup.Misc }); ReturnList.Add(new NameIdItem() { Name = LT["ProductGroupAyaNova7"], Id = (long)ProductGroup.AyaNova7 }); ReturnList.Add(new NameIdItem() { Name = LT["ProductGroupRavenPerpetual"], Id = (long)ProductGroup.RavenPerpetual }); diff --git a/server/Controllers/OrderController.cs b/server/Controllers/OrderController.cs new file mode 100644 index 0000000..034c113 --- /dev/null +++ b/server/Controllers/OrderController.cs @@ -0,0 +1,84 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.Logging; +using Sockeye.Models; +using Sockeye.Api.ControllerHelpers; +using Newtonsoft.Json.Linq; +using System; + +namespace Sockeye.Api.Controllers +{ + + [Produces("application/json")] + [Route("api/order")] + public class OrderController : ControllerBase + { + + private readonly AyContext ct; + private readonly ILogger log; + private readonly ApiServerState serverState; + + /// + /// ctor + /// + /// + /// + /// + public OrderController(AyContext dbcontext, ILogger logger, ApiServerState apiServerState) + { + ct = dbcontext; + log = logger; + serverState = apiServerState; + } + //Receive an order notification from ShareIt + //https://account.mycommerce.com/home/wiki/7479805 + [HttpPost("shareit")] + public async Task Post([FromHeader] string Authorization, [FromBody] JObject j) + { + if (!ModelState.IsValid) + { + return BadRequest(ModelState); + } + try + { + //do stuff with the notification + (string username, string password) = Sockeye.Util.AutoOrderProcessingUtil.GetUsernameAndPasswordFromAuthorizeHeader(Authorization); + // Now use username and password with whatever authentication process you want + if (username == "Y24PYYDQSA1L12905N5MKU" && password == "MA8GMQK2PC3FDNT1RTR68R") + { + //put the jobject notification into the db as json string freeform notification information + //to be processed by other code later. i.e. just capture it as is cleanly and don't bother trying to do anything fancy here this should be tight and focused and side effect free + +TODO +//although...on second thought it could just make a purchase record with some sort of state of unprocessed so a job could swing by and process it or we could +//do that manually so then there would be no need for extra UI and stuff, and it wont' conflict with the need to process immediately and lightly here +//i.e. two stage: one is to make the record barebones for purchase then a job comes along and turns it into a real purchase +//also add error handling here iwth proper notification see trial request code so we get notified on error +//and don't return OK / 200 unless the order is stored successfully in db as shareit will retry until it's successful several times, see the link below for deets + + //save it to the database + var VendorNotification = new VendorNotification(); + VendorNotification.Vendor = "shareit"; + VendorNotification.Data = j.ToString(); + VendorNotification.Processed = false; + await ct.VendorNotification.AddAsync(VendorNotification); + await ct.SaveChangesAsync(); + } + + + } + catch (Exception ex) + { + //todo Log this here + System.Diagnostics.Debug.WriteLine($"order/shareit - Exception processing request: {ex.Message}"); + } + + return Ok("ok");//shareit robot looks for an OK response to know if it should resend or not https://account.mycommerce.com/home/wiki/7479805 + + } + //------------ + + + }//eoc +}//eons \ No newline at end of file diff --git a/server/Controllers/RvrController.cs b/server/Controllers/RvrController.cs index 2903be8..25c164f 100644 --- a/server/Controllers/RvrController.cs +++ b/server/Controllers/RvrController.cs @@ -17,7 +17,7 @@ namespace Sockeye.Api.Controllers { private readonly AyContext ct; - private readonly ILogger log; + private readonly ILogger log; private readonly ApiServerState serverState; /// @@ -26,7 +26,7 @@ namespace Sockeye.Api.Controllers /// /// /// - public RvrController(AyContext dbcontext, ILogger logger, ApiServerState apiServerState) + public RvrController(AyContext dbcontext, ILogger logger, ApiServerState apiServerState) { ct = dbcontext; log = logger; diff --git a/server/biz/LicenseBiz.cs b/server/biz/LicenseBiz.cs index 6490fc3..8adc45e 100644 --- a/server/biz/LicenseBiz.cs +++ b/server/biz/LicenseBiz.cs @@ -652,8 +652,8 @@ namespace Sockeye.Biz return; } - //MISC product group is not valid for keys - if (proposedObj.PGroup == ProductGroup.Misc) + //MISC / NOTSET product group are not valid for keys + if (proposedObj.PGroup == ProductGroup.Misc || proposedObj.PGroup == ProductGroup.NotSet ) { AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "pGroup"); return; diff --git a/server/biz/ProductGroup.cs b/server/biz/ProductGroup.cs index ab6e1be..90aa660 100644 --- a/server/biz/ProductGroup.cs +++ b/server/biz/ProductGroup.cs @@ -5,7 +5,8 @@ namespace Sockeye.Biz Misc = 0, AyaNova7 = 1, RavenPerpetual = 2, - RavenSubscription = 3 + RavenSubscription = 3, + NotSet = 4 } }//eons \ No newline at end of file diff --git a/server/models/Purchase.cs b/server/models/Purchase.cs index cc2ecdd..e116f68 100644 --- a/server/models/Purchase.cs +++ b/server/models/Purchase.cs @@ -10,7 +10,7 @@ namespace Sockeye.Models public class Purchase : ICoreBizObjectModel { public long Id { get; set; } - public uint Concurrency { get; set; } + public uint Concurrency { get; set; } public long? CustomerId { get; set; } [NotMapped] public string CustomerViz { get; set; } @@ -18,10 +18,9 @@ namespace Sockeye.Models public long VendorId { get; set; } [NotMapped] public string VendorViz { get; set; } + public long? ProductId { get; set; }//optional because a new vendor notification won't have a product set yet also pgroup will be NotSet [Required] - public long ProductId { get; set; } - [Required] - public ProductGroup PGroup {get;set;} + public ProductGroup PGroup { get; set; } [NotMapped] public string ProductViz { get; set; } public string SalesOrderNumber { get; set; } diff --git a/server/util/AutoOrderProcessingUtil.cs b/server/util/AutoOrderProcessingUtil.cs new file mode 100644 index 0000000..04189b5 --- /dev/null +++ b/server/util/AutoOrderProcessingUtil.cs @@ -0,0 +1,27 @@ +using System; +using System.Text; + +namespace Sockeye.Util +{ + + public static class AutoOrderProcessingUtil + { + // static System.Text.Encoding ISO_8859_1_ENCODING = System.Text.Encoding.GetEncoding("ISO-8859-1"); + static Encoding ISO_8859_1_ENCODING = System.Text.Encoding.GetEncoding("ISO-8859-1"); + public static (string, string) GetUsernameAndPasswordFromAuthorizeHeader(string authorizeHeader) + { + if (authorizeHeader == null || !authorizeHeader.Contains("Basic ")) + return (null, null); + + string encodedUsernamePassword = authorizeHeader.Substring("Basic ".Length).Trim(); + string usernamePassword = ISO_8859_1_ENCODING.GetString(Convert.FromBase64String(encodedUsernamePassword)); + + string username = usernamePassword.Split(':')[0]; + string password = usernamePassword.Split(':')[1]; + + return (username, password); + } + + }//eoc + +}//eons \ No newline at end of file diff --git a/server/util/AySchema.cs b/server/util/AySchema.cs index 72f3dfc..3351e63 100644 --- a/server/util/AySchema.cs +++ b/server/util/AySchema.cs @@ -898,7 +898,7 @@ $BODY$ LANGUAGE PLPGSQL STABLE"); await ExecQueryAsync("CREATE TABLE apurchase (id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, customerid BIGINT REFERENCES acustomer(id) ON DELETE CASCADE, " - + "vendorid BIGINT NOT NULL REFERENCES avendor(id), productid BIGINT NOT NULL REFERENCES aproduct(id), pgroup INTEGER NOT NULL DEFAULT 0, salesordernumber TEXT NOT NULL, " + + "vendorid BIGINT NOT NULL REFERENCES avendor(id), productid BIGINT REFERENCES aproduct(id), pgroup INTEGER NOT NULL DEFAULT 4, salesordernumber TEXT NOT NULL, " + "purchasedate TIMESTAMPTZ NOT NULL, expiredate TIMESTAMPTZ, canceldate TIMESTAMPTZ, couponcode text, notes text, " + "renewnoticesent BOOL NOT NULL DEFAULT false, quantity INTEGER NOT NULL DEFAULT 1, " + "vendordata TEXT, processeddate TIMESTAMPTZ, " @@ -1341,6 +1341,7 @@ $BODY$ LANGUAGE PLPGSQL STABLE"); LogUpdateMessage(log); //english translations + await ExecQueryAsync("INSERT INTO atranslationitem(translationid,key,display) SELECT t.id, 'ProductGroupNotSet', 'Not set' FROM atranslation t where t.baselanguage = 'en'"); await ExecQueryAsync("INSERT INTO atranslationitem(translationid,key,display) SELECT t.id, 'ProductGroupMisc', 'Misc.' FROM atranslation t where t.baselanguage = 'en'"); await ExecQueryAsync("INSERT INTO atranslationitem(translationid,key,display) SELECT t.id, 'ProductGroupAyaNova7', 'V7' FROM atranslation t where t.baselanguage = 'en'"); await ExecQueryAsync("INSERT INTO atranslationitem(translationid,key,display) SELECT t.id, 'ProductGroupRavenPerpetual', 'Raven perpetual' FROM atranslation t where t.baselanguage = 'en'"); @@ -1353,6 +1354,7 @@ $BODY$ LANGUAGE PLPGSQL STABLE"); await ExecQueryAsync("INSERT INTO atranslationitem(translationid,key,display) SELECT t.id, 'GlobalRavenTrialApproved', 'Raven trial approved message' FROM atranslation t where t.baselanguage = 'en'"); await ExecQueryAsync("INSERT INTO atranslationitem(translationid,key,display) SELECT t.id, 'GlobalRavenTrialRejected', 'Raven trial rejected message' FROM atranslation t where t.baselanguage = 'en'"); //spanish translations + await ExecQueryAsync("INSERT INTO atranslationitem(translationid,key,display) SELECT t.id, 'ProductGroupNotSet', 'Not set' FROM atranslation t where t.baselanguage = 'es'"); await ExecQueryAsync("INSERT INTO atranslationitem(translationid,key,display) SELECT t.id, 'ProductGroupMisc', 'Misc.' FROM atranslation t where t.baselanguage = 'es'"); await ExecQueryAsync("INSERT INTO atranslationitem(translationid,key,display) SELECT t.id, 'ProductGroupAyaNova7', 'V7' FROM atranslation t where t.baselanguage = 'es'"); await ExecQueryAsync("INSERT INTO atranslationitem(translationid,key,display) SELECT t.id, 'ProductGroupRavenPerpetual', 'Raven perpetual' FROM atranslation t where t.baselanguage = 'es'"); @@ -1366,6 +1368,7 @@ $BODY$ LANGUAGE PLPGSQL STABLE"); await ExecQueryAsync("INSERT INTO atranslationitem(translationid,key,display) SELECT t.id, 'GlobalRavenTrialRejected', 'Raven trial rejected message' FROM atranslation t where t.baselanguage = 'es'"); //french translations + await ExecQueryAsync("INSERT INTO atranslationitem(translationid,key,display) SELECT t.id, 'ProductGroupNotSet', 'Not set' FROM atranslation t where t.baselanguage = 'fr'"); await ExecQueryAsync("INSERT INTO atranslationitem(translationid,key,display) SELECT t.id, 'ProductGroupMisc', 'Misc.' FROM atranslation t where t.baselanguage = 'fr'"); await ExecQueryAsync("INSERT INTO atranslationitem(translationid,key,display) SELECT t.id, 'ProductGroupAyaNova7', 'V7' FROM atranslation t where t.baselanguage = 'fr'"); await ExecQueryAsync("INSERT INTO atranslationitem(translationid,key,display) SELECT t.id, 'ProductGroupRavenPerpetual', 'Raven perpetual' FROM atranslation t where t.baselanguage = 'fr'"); @@ -1379,6 +1382,7 @@ $BODY$ LANGUAGE PLPGSQL STABLE"); await ExecQueryAsync("INSERT INTO atranslationitem(translationid,key,display) SELECT t.id, 'GlobalRavenTrialRejected', 'Raven trial rejected message' FROM atranslation t where t.baselanguage = 'fr'"); //german translations + await ExecQueryAsync("INSERT INTO atranslationitem(translationid,key,display) SELECT t.id, 'ProductGroupNotSet', 'Not set' FROM atranslation t where t.baselanguage = 'de'"); await ExecQueryAsync("INSERT INTO atranslationitem(translationid,key,display) SELECT t.id, 'ProductGroupMisc', 'Misc.' FROM atranslation t where t.baselanguage = 'de'"); await ExecQueryAsync("INSERT INTO atranslationitem(translationid,key,display) SELECT t.id, 'ProductGroupAyaNova7', 'V7' FROM atranslation t where t.baselanguage = 'de'"); await ExecQueryAsync("INSERT INTO atranslationitem(translationid,key,display) SELECT t.id, 'ProductGroupRavenPerpetual', 'Raven perpetual' FROM atranslation t where t.baselanguage = 'de'"); diff --git a/todo.txt b/todo.txt index 91c9c67..3a44f5a 100644 --- a/todo.txt +++ b/todo.txt @@ -23,20 +23,35 @@ Once it's up and running update docs to mention that perpetual trial period is t -- v7 license fetch route -- v8 license fetch route -- v8 trial request route - shareit payment notification route triggers purchase if of that type, needs to analyze it and need to test it out somehow here first - trial server request route that contact form can trigger - JOB: notify user active license -- JOB: purchase to license +- JOB: vendor notification to purchase, create customer if necessary from purchase notification or add to existing + - sub or part of above JOB: purchase to license - JOB: Ping / check health route of subscription server flag last health check trigger event notification if fails serverstate set so that it maybe has an OneFail then a TwoFail then a FAILED state where it notifies me so I don't get transient alerts also server state used for other things like pending but not commissioned yet, decommissioned etc -- notify me trial request +- JOB: "SOCKBOT" virtual staff member, + sockbot - does everything we have to do manually on a delay if it hasn't been done by us yet + or tees up as much as possible whatever we need so we can just ok it or if too much time elapses just goes ahead with some things + will require a bit of a UI for config and stuff but that's ok + i.e. if a day goes by without intervention then just process + should cover every customer need possible: + Trial license request + this could potentially just be done automatically now becuase they can't use it to continually use raven so were not protecting anything + Purchase + v8 subscription renewal + v8 perpetual new or renewal + v7 renewal or new + Tech support email + if a support request is received or email to support@ayanova.com + it should check if they are a known email address of a customer and auto-reply if still sitting in inbox for certain length of time + i.e. if it's outside biz hours then auto-respond with biz hours will get back to them here's a manual link just in case their question is already answered etc + + - manually simulate v7 fetch from rockfish compare to manual test locally in sockey confirm Same data shape and format