This commit is contained in:
2022-06-20 00:39:52 +00:00
parent 7b99160632
commit ca0ce21d60
23 changed files with 74628 additions and 6 deletions

View File

@@ -53,6 +53,9 @@
<EmbedInteropTypes>True</EmbedInteropTypes> <EmbedInteropTypes>True</EmbedInteropTypes>
<HintPath>..\libs\QuickBooks\Interop.QBFC15.dll</HintPath> <HintPath>..\libs\QuickBooks\Interop.QBFC15.dll</HintPath>
</Reference> </Reference>
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>packages\Newtonsoft.Json.13.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Core" /> <Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" /> <Reference Include="System.Xml.Linq" />
@@ -99,6 +102,7 @@
<DependentUpon>Resources.resx</DependentUpon> <DependentUpon>Resources.resx</DependentUpon>
<DesignTime>True</DesignTime> <DesignTime>True</DesignTime>
</Compile> </Compile>
<None Include="packages.config" />
<None Include="Properties\Settings.settings"> <None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator> <Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput> <LastGenOutput>Settings.Designer.cs</LastGenOutput>

View File

@@ -110,6 +110,7 @@
this.btnLogin.TabIndex = 9; this.btnLogin.TabIndex = 9;
this.btnLogin.Text = "Login"; this.btnLogin.Text = "Login";
this.btnLogin.UseVisualStyleBackColor = true; this.btnLogin.UseVisualStyleBackColor = true;
this.btnLogin.Click += new System.EventHandler(this.btnLogin_Click);
// //
// btnCancel // btnCancel
// //

View File

@@ -19,12 +19,84 @@ namespace AyaNovaQBI
private void auth_Load(object sender, EventArgs e) private void auth_Load(object sender, EventArgs e)
{ {
edServerUrl.Text = util.SERVER_URL; edServerUrl.Text = Properties.Settings.Default.serverurl;
} }
private void btnTest_Click(object sender, EventArgs e) private async void btnTest_Click(object sender, EventArgs e)
{ {
if (!ValidateAndCleanServerAddress()) return;
btnLogin.Enabled = btnTest.Enabled = false;
var result = await util.InitAndConfirmAddressAsync(edServerUrl.Text);
btnLogin.Enabled = btnTest.Enabled = true;
if (result == "OK")
{
MessageBox.Show("Server URL is GOOD!");
}
else
{
MessageBox.Show("Server could not be reached at that URL\n" + result);
}
} }
private async void btnLogin_Click(object sender, EventArgs e)
{
if (!ValidateAndCleanServerAddress()) return;
btnLogin.Enabled = btnTest.Enabled = false;
// if (!util.Initialized)
//{
var result = await util.InitAndConfirmAddressAsync(edServerUrl.Text);
if (result != "OK")
{
MessageBox.Show("Server could not be reached at that URL\n" + result);
btnLogin.Enabled = btnTest.Enabled = true;
return;
}
// }
var res = await util.AuthenticateAsync(edUserName.Text, edPassword.Text);
if (!res)
{
MessageBox.Show("AyaNova 8 SuperUser account login failed");
btnLogin.Enabled = btnTest.Enabled = true;
return;
}
btnLogin.Enabled = btnTest.Enabled = true;
this.DialogResult = DialogResult.OK;
this.Close();
}
private bool ValidateAndCleanServerAddress()
{
var serverUrl = edServerUrl.Text;
Uri u;
try
{
u = new Uri(serverUrl);
var scheme = u.Scheme;
var host = u.Host;
var port = u.Port;
edServerUrl.Text = scheme + "://" + host + ":" + port + "/" + util.API_BASE_ROUTE;
//client url for notification default links, help links etc
if ((scheme == "https" && port == 443) || (scheme == "http" && port == 80))
util.GuessClientUrl = scheme + "://" + host + "/";
else
util.GuessClientUrl = scheme + "://" + host + ":" + port + "/";
}
catch (Exception ex)
{
MessageBox.Show("Server address not valid\n" + ex.Message);
return false;
}
//ok, it's some kind of url, now format it for api access
return true;
}
} }
} }

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Newtonsoft.Json" version="13.0.1" targetFramework="net48" />
</packages>

Binary file not shown.

View File

@@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2007 James Newton-King
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

View File

@@ -6,19 +6,346 @@ using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Net.Http; using System.Net.Http;
using System.Net.Http.Headers; using System.Net.Http.Headers;
using Newtonsoft.Json.Linq;
namespace AyaNovaQBI namespace AyaNovaQBI
{ {
internal class util internal class util
{ {
#region API stuff
public const string TEST_ROUTE = "notify/hello"; public const string TEST_ROUTE = "notify/hello";
public const string API_BASE_ROUTE = "api/v8/"; public const string API_BASE_ROUTE = "api/v8/";
private const int MAX_TRIES = 3;//max times to retry an api call before giving up private const int MAX_TRIES = 3;//max times to retry an api call before giving up
private const int API_RETRY_DELAY = 3000;//pause in ms before retrying api call private const int API_RETRY_DELAY = 3000;//pause in ms before retrying api call
public static int HTTPCLIENT_TIMEOUT_SECONDS = 100;//changed by the setting in ops anyway, just a in-case sensible default here public static int HTTPCLIENT_TIMEOUT_SECONDS = 100;//changed by the setting in ops anyway, just a in-case sensible default here
public static HttpClient client = null; public static HttpClient client = null;
internal static string SERVER_URL { get; set; } = Properties.Settings.Default.serverurl; //url once known to be good
internal static string ApiBaseUrl { get; set; }
//auth processes url for api and this is the best guess as to the client url to use for notification / help links etc
internal static string GuessClientUrl { get; set; }
internal static string JWT { get; set; }
public static void InitClient()
{
if (client != null)
{
client.Dispose();
client = null;
}
client = new HttpClient();
client.Timeout = TimeSpan.FromSeconds(HTTPCLIENT_TIMEOUT_SECONDS);
}
/// <summary>
/// Only a return value of "OK" is ok
/// </summary>
/// <param name="serverUrl"></param>
/// <returns></returns>
public static async Task<string> InitAndConfirmAddressAsync(string serverUrl)
{
ApiBaseUrl = serverUrl;
InitClient();
try
{
// TimeSpan tsDefault = client.Timeout;
// client.Timeout = new TimeSpan(0, 0, 20);
HttpResponseMessage response = await client.GetAsync(serverUrl + TEST_ROUTE);
// client.Timeout = tsDefault;
if (response.IsSuccessStatusCode)
return "OK";
else
return "Failed: " + response.StatusCode.ToString();
}
catch (Exception ex)
{
while (ex.InnerException != null)
ex = ex.InnerException;
return "Failed exception: \r\n" + ex.Message;
}
}
public async static Task<bool> AuthenticateAsync(string login, string password = null)
{
InitClient();
if (password == null)
password = login;
dynamic creds = new JObject();
creds.login = login;
creds.password = password;
ApiResponse a = await PostAsync("auth", creds.ToString());
if (a.HttpResponse.IsSuccessStatusCode)
{
JWT = a.ObjectResponse["data"]["token"].Value<string>();
//Must be *the* SuperUser to continue:
a = await GetAsync("user/amsu");
var IsSuperUser = a.ObjectResponse["data"].Value<bool>();
if (!IsSuperUser)
{
JWT = string.Empty;
return false;
}
return true;
}
return false;
}
public async static Task<ApiResponse> GetAsync(string route)
{
Exception FirstException = null;
for (int x = 0; x < MAX_TRIES; x++)
{
try
{
return await TryGetAsync(route);
}
catch (Exception ex)
{
if (FirstException == null)
FirstException = ex;
}
await Task.Delay(API_RETRY_DELAY);
}
//no luck re-throw the exception
throw new Exception("API call failed after " + MAX_TRIES.ToString() + " attempts", FirstException);
}
private async static Task<ApiResponse> TryGetAsync(string route)
{
var requestMessage = new HttpRequestMessage(HttpMethod.Get, ApiBaseUrl + route);
if (!string.IsNullOrWhiteSpace(JWT))
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", JWT);
HttpResponseMessage response = null;
try
{
response = await client.SendAsync(requestMessage);
}
catch (HttpRequestException ex)
{
var Err = ex.Message;
var InnerErr = "";
if (ex.InnerException != null)
InnerErr = ex.InnerException.Message;
throw new Exception("GET error, route: " + route + "\r\nError:" + Err + "\r\nInner error:" + InnerErr + "\r\nStack:" + ex.StackTrace);
}
var responseAsString = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
{
throw new Exception("GET error, code: " + (int)response.StatusCode + ", route: " + route + "\r\n" + responseAsString + "\r\n" + response.ReasonPhrase);
}
else
return new ApiResponse() { HttpResponse = response, ObjectResponse = Parse(responseAsString) };
}
public async static Task<ApiResponse> PostAsync(string route, dynamic d)
{
Exception FirstException = null;
for (int x = 0; x < MAX_TRIES; x++)
{
try
{
return await TryPostAsync(route, d.ToString(Newtonsoft.Json.Formatting.None));
}
catch (Exception ex)
{
if (FirstException == null)
FirstException = ex;
}
await Task.Delay(API_RETRY_DELAY);
}
//no luck re-throw the exception
throw new Exception("API call failed after " + MAX_TRIES.ToString() + " attempts", FirstException);
}
public async static Task<ApiResponse> PostAsync(string route, string s = null)
{
Exception FirstException = null;
for (int x = 0; x < MAX_TRIES; x++)
{
try
{
return await TryPostAsync(route, s);
}
catch (Exception ex)
{
if (FirstException == null)
FirstException = ex;
}
await Task.Delay(API_RETRY_DELAY);
}
//no luck re-throw the exception
throw new Exception("API call failed after " + MAX_TRIES.ToString() + " attempts", FirstException);
}
private async static Task<ApiResponse> TryPostAsync(string route, string postJson = null)
{
var requestMessage = new HttpRequestMessage(HttpMethod.Post, ApiBaseUrl + route);
if (!string.IsNullOrWhiteSpace(JWT))
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", JWT);
if (!string.IsNullOrWhiteSpace(postJson))
requestMessage.Content = new StringContent(postJson, System.Text.Encoding.UTF8, "application/json");
HttpResponseMessage response = null;
try
{
response = await client.SendAsync(requestMessage);
}
catch (HttpRequestException ex)
{
var Err = ex.Message;
var InnerErr = "";
if (ex.InnerException != null)
InnerErr = ex.InnerException.Message;
throw new Exception("POST error, route: " + route + "\r\nError:" + Err + "\r\nInner error:" + InnerErr + "\r\nStack:" + ex.StackTrace + "\r\nPOSTED OBJECT:\r\n" + postJson);
}
var responseAsString = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
{
if (string.IsNullOrWhiteSpace(postJson))
postJson = "n/a";
throw new Exception("POST error, code: " + (int)response.StatusCode + ", route: " + route + "\r\n" + responseAsString + "\r\n" + response.ReasonPhrase + "\r\nPOSTED OBJECT:\r\n" + postJson);
}
else
return new ApiResponse() { HttpResponse = response, ObjectResponse = Parse(responseAsString) };
}
public async static Task<ApiResponse> PutAsync(string route, dynamic d)
{
Exception FirstException = null;
for (int x = 0; x < MAX_TRIES; x++)
{
try
{
return await TryPutAsync(route, d.ToString(Newtonsoft.Json.Formatting.None));
}
catch (Exception ex)
{
if (FirstException == null)
FirstException = ex;
}
await Task.Delay(API_RETRY_DELAY);
}
//no luck re-throw the exception
throw new Exception("API call failed after " + MAX_TRIES.ToString() + " attempts", FirstException);
}
public async static Task<ApiResponse> PutAsync(string route)
{
Exception FirstException = null;
for (int x = 0; x < MAX_TRIES; x++)
{
try
{
return await TryPutAsync(route);
}
catch (Exception ex)
{
if (FirstException == null)
FirstException = ex;
}
await Task.Delay(API_RETRY_DELAY);
}
//no luck re-throw the exception
throw new Exception("API call failed after " + MAX_TRIES.ToString() + " attempts", FirstException);
}
public async static Task<ApiResponse> TryPutAsync(string route, string putJson = null)
{
var requestMessage = new HttpRequestMessage(HttpMethod.Put, ApiBaseUrl + route);
if (!string.IsNullOrWhiteSpace(JWT))
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", JWT);
if (!string.IsNullOrWhiteSpace(putJson))
requestMessage.Content = new StringContent(putJson, System.Text.Encoding.UTF8, "application/json");
HttpResponseMessage response = null;
try
{
response = await client.SendAsync(requestMessage);
}
catch (HttpRequestException ex)
{
var Err = ex.Message;
var InnerErr = "";
if (ex.InnerException != null)
InnerErr = ex.InnerException.Message;
throw new Exception("PUT error, route: " + route + "\r\nError:" + Err + "\r\nInner error:" + InnerErr + "\r\nStack:" + ex.StackTrace + "\r\nPOSTED OBJECT:\r\n" + putJson);
}
var responseAsString = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
{
if (string.IsNullOrWhiteSpace(putJson))
putJson = "n/a";
throw new Exception("PUT error, code: " + (int)response.StatusCode + ", route: " + route + "\r\n" + responseAsString + "\r\n" + response.ReasonPhrase + "\r\nPUT OBJECT:\r\n" + putJson);
}
else
return new ApiResponse() { HttpResponse = response, ObjectResponse = Parse(responseAsString) };
}
public class ApiResponse
{
public HttpResponseMessage HttpResponse { get; set; }
public JObject ObjectResponse { get; set; }
public string CompactResponse
{
get
{
return ObjectResponse.ToString(Newtonsoft.Json.Formatting.None);
}
}
}
#endregion
#region QB STUFF
public static List<InvoiceableItem> GetInvoiceableItems() public static List<InvoiceableItem> GetInvoiceableItems()
{ {
var random = new Random(); var random = new Random();
@@ -66,7 +393,7 @@ namespace AyaNovaQBI
} }
#endregion
} }