46
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
// Use IntelliSense to find out which attributes exist for C# debugging
|
||||
// Use hover for the description of the existing attributes
|
||||
// For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": ".NET Core Launch (web)",
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"preLaunchTask": "build",
|
||||
// If you have changed target frameworks, make sure to update the program path.
|
||||
"program": "${workspaceRoot}/bin/Debug/netcoreapp2.1/rockfishCore.dll",
|
||||
"args": [],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"stopAtEntry": false,
|
||||
"internalConsoleOptions": "openOnSessionStart",
|
||||
"launchBrowser": {
|
||||
"enabled": true,
|
||||
"args": "${auto-detect-url}/",
|
||||
"windows": {
|
||||
"command": "cmd.exe",
|
||||
"args": "/C start http://localhost:5000/"
|
||||
},
|
||||
"osx": {
|
||||
"command": "open"
|
||||
},
|
||||
"linux": {
|
||||
"command": "xdg-open"
|
||||
}
|
||||
},
|
||||
"env": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"sourceFileMap": {
|
||||
"/Views": "${workspaceRoot}/Views"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": ".NET Core Attach",
|
||||
"type": "coreclr",
|
||||
"request": "attach",
|
||||
"processId": "${command:pickProcess}"
|
||||
}
|
||||
]
|
||||
}
|
||||
16
.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"version": "0.1.0",
|
||||
"command": "dotnet",
|
||||
"isShellCommand": true,
|
||||
"args": [],
|
||||
"tasks": [
|
||||
{
|
||||
"taskName": "build",
|
||||
"args": [
|
||||
"${workspaceRoot}/rockfishCore.csproj"
|
||||
],
|
||||
"isBuildCommand": true,
|
||||
"problemMatcher": "$msCompile"
|
||||
}
|
||||
]
|
||||
}
|
||||
38
Controllers/ApiMetaController.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using rockfishCore.Util;
|
||||
|
||||
namespace rockfishCore.Controllers
|
||||
{
|
||||
[Produces("application/json")]
|
||||
[Route("api/meta")]
|
||||
public class ApiMetaController : Controller
|
||||
{
|
||||
//This controller is for fetching information *about* the server and the api itself
|
||||
|
||||
public ApiMetaController()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
// GET: api/meta/serverversion
|
||||
[HttpGet("server_version")]
|
||||
public ActionResult Get()
|
||||
{
|
||||
return Ok(new {server_version=RfVersion.NumberOnly});
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
118
Controllers/AuthController.cs
Normal file
@@ -0,0 +1,118 @@
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using rockfishCore.Models;
|
||||
using rockfishCore.Util;
|
||||
using System.Linq;
|
||||
using System;
|
||||
|
||||
//required to inject configuration in constructor
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace rockfishCore.Controllers
|
||||
{
|
||||
//Authentication controller
|
||||
public class AuthController : Controller
|
||||
{
|
||||
private readonly rockfishContext _context;
|
||||
private readonly IConfiguration _configuration;
|
||||
public AuthController(rockfishContext context, IConfiguration configuration)//these two are injected, see startup.cs
|
||||
{
|
||||
_context = context;
|
||||
_configuration = configuration;
|
||||
}
|
||||
|
||||
//AUTHENTICATE CREDS
|
||||
//RETURN JWT
|
||||
|
||||
[HttpPost("/authenticate")]
|
||||
public JsonResult PostCreds(string login, string password) //if was a json body then //public JsonResult PostCreds([FromBody] string login, [FromBody] string password)
|
||||
{
|
||||
int nFailedAuthDelay = 10000;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(login) || string.IsNullOrWhiteSpace(password))
|
||||
{
|
||||
//Make a failed pw wait
|
||||
System.Threading.Thread.Sleep(nFailedAuthDelay);
|
||||
return Json(new { msg = "authentication failed", error = 1 });
|
||||
}
|
||||
|
||||
//DANGER DANGER - this assumes all logins are different...boo bad code
|
||||
//FIXME
|
||||
//BUGBUG
|
||||
//TODO
|
||||
//ETC
|
||||
/*HOW TO FIX: because of the salt during login it must assume multiple users with the same login and
|
||||
fetch each in turn, get the salt, hash the entered password and compare to the password stored password
|
||||
|
||||
So, instead of singleordefault must be assumed to be a collection of users returned in this code:
|
||||
*/
|
||||
var user = _context.User.SingleOrDefault(m => m.Login == login);
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
//Make a failed pw wait
|
||||
System.Threading.Thread.Sleep(nFailedAuthDelay);
|
||||
return Json(new { msg = "authentication failed", error = 1 });
|
||||
}
|
||||
|
||||
// string pwnew=Hasher.hash(user.Salt,"2df5cc611ee485d4aa897350daa045caa4015147ae34c6b7b363f1def605d305");
|
||||
|
||||
string hashed = Hasher.hash(user.Salt, password);
|
||||
if (hashed == user.Password)
|
||||
{
|
||||
//get teh secret from appsettings.json
|
||||
var secret = _configuration.GetSection("JWT").GetValue<string>("secret");
|
||||
byte[] secretKey = System.Text.Encoding.ASCII.GetBytes(secret);
|
||||
|
||||
var iat = new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds();
|
||||
var exp = new DateTimeOffset(DateTime.Now.AddDays(30)).ToUnixTimeSeconds();
|
||||
|
||||
//Generate a download token and store it with the user account and return it for the client
|
||||
Guid g = Guid.NewGuid();
|
||||
string dlkey = Convert.ToBase64String(g.ToByteArray());
|
||||
dlkey = dlkey.Replace("=", "");
|
||||
dlkey = dlkey.Replace("+", "");
|
||||
|
||||
user.DlKey = dlkey;
|
||||
user.DlKeyExp=exp;
|
||||
_context.User.Update(user);
|
||||
_context.SaveChanges();
|
||||
|
||||
|
||||
var payload = new Dictionary<string, object>()
|
||||
{
|
||||
{ "iat", iat.ToString() },
|
||||
{ "exp", exp.ToString() },
|
||||
{ "iss", "rockfishCore" },
|
||||
{ "id", user.Id.ToString() }
|
||||
};
|
||||
|
||||
|
||||
//NOTE: probably don't need Jose.JWT as am using Microsoft jwt stuff to validate routes so it should also be able to
|
||||
//issue tokens as well, but it looked cmplex and this works so unless need to remove in future keeping it.
|
||||
string token = Jose.JWT.Encode(payload, secretKey, Jose.JwsAlgorithm.HS256);
|
||||
//string jsonDecoded = Jose.JWT.Decode(token, secretKey);
|
||||
|
||||
return Json(new
|
||||
{
|
||||
ok = 1,
|
||||
issued = iat,
|
||||
expires = exp,
|
||||
token = token,
|
||||
dlkey = dlkey,
|
||||
name = user.Name,
|
||||
id = user.Id
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
//Make a failed pw wait
|
||||
System.Threading.Thread.Sleep(nFailedAuthDelay);
|
||||
return Json(new { msg = "authentication failed", error = 1 });
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------
|
||||
|
||||
}//eoc
|
||||
}//eons
|
||||
97
Controllers/AutocompleteController.cs
Normal file
@@ -0,0 +1,97 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using rockfishCore.Models;
|
||||
using rockfishCore.Util;
|
||||
using System.Linq;
|
||||
using System;
|
||||
|
||||
//requried to inject configuration in constructor
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace rockfishCore.Controllers
|
||||
{
|
||||
[Produces("application/json")]
|
||||
[Route("api/autocomplete")]
|
||||
[Authorize]
|
||||
public class AutocompleteController : Controller
|
||||
{
|
||||
private readonly rockfishContext _context;
|
||||
private readonly IConfiguration _configuration;
|
||||
public AutocompleteController(rockfishContext context, IConfiguration configuration)//these two are injected, see startup.cs
|
||||
{
|
||||
_context = context;
|
||||
_configuration = configuration;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
[HttpGet]
|
||||
public JsonResult GetAutocomplete(string acget, string query) //get parameters from url not body
|
||||
{
|
||||
//TODO: if, in future need more might want to make acget a csv string for multi table / field search
|
||||
//ACGET is a collection name and field 'purchase.name'
|
||||
//QUERY is just the characters typed so far, could be one or more
|
||||
|
||||
//default return value, emtpy suggestions
|
||||
var ret = new { suggestions = new string[] { } };
|
||||
|
||||
if (string.IsNullOrWhiteSpace(acget) || string.IsNullOrWhiteSpace(query))
|
||||
{
|
||||
return Json(ret);
|
||||
}
|
||||
|
||||
|
||||
string[] acgetsplit = acget.Split('.');
|
||||
string col = acgetsplit[0];
|
||||
string fld = acgetsplit[1];
|
||||
|
||||
List<string> resultList = new List<string>();
|
||||
|
||||
using (var command = _context.Database.GetDbConnection().CreateCommand())
|
||||
{
|
||||
|
||||
command.CommandText = "SELECT DISTINCT " + fld + " From " + col + " where " + fld + " like @q ORDER BY " + fld + ";";
|
||||
var parameter = command.CreateParameter();
|
||||
parameter.ParameterName = "@q";
|
||||
parameter.Value = "%" + query + "%";
|
||||
command.Parameters.Add(parameter);
|
||||
|
||||
_context.Database.OpenConnection();
|
||||
using (var res = command.ExecuteReader())
|
||||
{
|
||||
|
||||
if (res.HasRows)
|
||||
{
|
||||
while (res.Read())
|
||||
{
|
||||
resultList.Add(res.GetString(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
_context.Database.CloseConnection();
|
||||
}
|
||||
return Json(new { suggestions = resultList });
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//------------------------------------------------------
|
||||
|
||||
}//eoc
|
||||
}//eons
|
||||
|
||||
|
||||
//enter 'a' in purchase product name get:
|
||||
|
||||
/*
|
||||
GET request url:
|
||||
https://rockfish.ayanova.com/api/autocomplete?acget=purchase.name&query=A
|
||||
|
||||
Response:
|
||||
{"suggestions":["CANCELED Up to 5","CANCELED RI","CANCELED WBI","QBI Renewal","OLI Renewal","MBI Renewal","CANCELED Outlook Schedule Export","Quick Notification Renewal","RI Renewal","WBI Renewal","Up to 5 Renewal","Outlook Schedule Export Renewal","Single Renewal","Export to XLS Renewal","Importexport.csv duplicate Renewal","Export To XLS Renewal","Up to 10 Renewal","Up to 20 RENEWAL","CANCELED Single","Quick Notification","Importexport.csv duplicate","CANCELED MBI","CANCELED OLI","CANCELED QBI","CANCELED Quick Notification","Quick Notification ","Key Administration","AyaNova Lite","PTI - Renewal"]} */
|
||||
271
Controllers/CustomerController.cs
Normal file
@@ -0,0 +1,271 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using rockfishCore.Models;
|
||||
using rockfishCore.Util;
|
||||
|
||||
namespace rockfishCore.Controllers
|
||||
{
|
||||
[Produces("application/json")]
|
||||
[Route("api/Customer")]
|
||||
[Authorize]
|
||||
public class CustomerController : Controller
|
||||
{
|
||||
private readonly rockfishContext _context;
|
||||
|
||||
public CustomerController(rockfishContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
//Get api/customer/list
|
||||
[HttpGet("list")]
|
||||
public IEnumerable<dtoNameIdActiveItem> GetList()
|
||||
{
|
||||
|
||||
var res = from c in _context.Customer.OrderBy(c => c.Name)
|
||||
select new dtoNameIdActiveItem
|
||||
{
|
||||
active = c.Active,
|
||||
id = c.Id,
|
||||
name = c.Name
|
||||
};
|
||||
return res.ToList();
|
||||
}
|
||||
|
||||
|
||||
//Get api/customer/77/sitelist
|
||||
[HttpGet("{id}/sitelist")]
|
||||
public IEnumerable<dtoNameIdItem> GetSiteList([FromRoute] long id)
|
||||
{
|
||||
|
||||
var res = from c in _context.Site
|
||||
.Where(c => c.CustomerId.Equals(id)).OrderBy(c => c.Name)
|
||||
select new dtoNameIdItem
|
||||
{
|
||||
id = c.Id,
|
||||
name = c.Name
|
||||
};
|
||||
return res.ToList();
|
||||
}
|
||||
|
||||
|
||||
//Get api/customer/77/activesubbysite
|
||||
[HttpGet("{id}/activesubforsites")]
|
||||
public IEnumerable<dtoNameIdChildrenItem> GetActiveSubsForSites([FromRoute] long id)
|
||||
{
|
||||
|
||||
var res = from c in _context.Site
|
||||
.Where(c => c.CustomerId.Equals(id)).OrderBy(c => c.Name)
|
||||
select new dtoNameIdChildrenItem
|
||||
{
|
||||
id = c.Id,
|
||||
name = c.Name
|
||||
};
|
||||
|
||||
//Force immediate query execution
|
||||
var resList = res.ToList();
|
||||
|
||||
foreach (dtoNameIdChildrenItem child in resList)
|
||||
{
|
||||
var subs = from c in _context.Purchase
|
||||
.Where(c => c.SiteId.Equals(child.id))
|
||||
.Where(c => c.CancelDate == null)
|
||||
.OrderByDescending(c => c.PurchaseDate)
|
||||
select new dtoNameIdItem
|
||||
{
|
||||
id = c.Id,
|
||||
name = c.Name + " exp: " + DateUtil.EpochToString(c.ExpireDate, "d")
|
||||
};
|
||||
|
||||
foreach (dtoNameIdItem sub in subs)
|
||||
{
|
||||
child.children.Add(sub);
|
||||
}
|
||||
}
|
||||
|
||||
return resList;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//Get api/customer/77/sites
|
||||
[HttpGet("{id}/sites")]
|
||||
public IEnumerable<Site> GetSites([FromRoute] long id)
|
||||
{
|
||||
//from https://docs.microsoft.com/en-us/ef/core/querying/basic
|
||||
var sites = _context.Site
|
||||
.Where(b => b.CustomerId.Equals(id))
|
||||
.OrderByDescending(b => b.Id)
|
||||
.ToList();
|
||||
return sites;
|
||||
}
|
||||
|
||||
|
||||
// //Get api/customer/77/contacts
|
||||
// [HttpGet("{id}/contacts")]
|
||||
// public IEnumerable<Contact> GetContacts([FromRoute] long id)
|
||||
// {
|
||||
// var contacts = _context.Contact
|
||||
// .Where(b => b.CustomerId.Equals(id))
|
||||
// .OrderByDescending(b => b.Id)
|
||||
// .ToList();
|
||||
// return contacts;
|
||||
// }
|
||||
|
||||
|
||||
// //Get api/customer/77/notifications
|
||||
// [HttpGet("{id}/notifications")]
|
||||
// public IEnumerable<Notification> GetNotifications([FromRoute] long id)
|
||||
// {
|
||||
// var notifications = _context.Notification
|
||||
// .Where(b => b.CustomerId.Equals(id))
|
||||
// .OrderByDescending(b => b.SentDate)
|
||||
// .ToList();
|
||||
// return notifications;
|
||||
// }
|
||||
|
||||
|
||||
//Get api/customer/77/name
|
||||
[HttpGet("{id}/name")]
|
||||
public async Task<IActionResult> GetName([FromRoute] long id)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
//BUGBUG: this is bombing once in a while
|
||||
//System.InvalidOperationException: Sequence contains no elements
|
||||
//on this client url
|
||||
//http://localhost:5000/default.htm#!/customerSites/85
|
||||
|
||||
var ret = await _context.Customer
|
||||
.Select(r => new { r.Id, r.Name })
|
||||
.Where(r => r.Id == id)
|
||||
.FirstAsync();
|
||||
return Ok(ret);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//-------------
|
||||
//CRUD ROUTES
|
||||
//-------------
|
||||
|
||||
// GET: api/Customer
|
||||
//????? DA FUCK IS THIS ROUTE FOR ?????
|
||||
[HttpGet]
|
||||
public IEnumerable<Customer> GetCustomer()
|
||||
{
|
||||
return _context.Customer;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// GET: api/Customer/5
|
||||
[HttpGet("{id}")]
|
||||
public async Task<IActionResult> GetCustomer([FromRoute] long id)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
var customer = await _context.Customer.SingleOrDefaultAsync(m => m.Id == id);
|
||||
|
||||
if (customer == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
return Ok(customer);
|
||||
}
|
||||
|
||||
// PUT: api/Customer/5
|
||||
[HttpPut("{id}")]
|
||||
public async Task<IActionResult> PutCustomer([FromRoute] long id, [FromBody] Customer customer)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
if (id != customer.Id)
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
_context.Entry(customer).State = EntityState.Modified;
|
||||
|
||||
try
|
||||
{
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
catch (DbUpdateConcurrencyException)
|
||||
{
|
||||
if (!CustomerExists(id))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
// POST: api/Customer
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> PostCustomer([FromBody] Customer customer)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
_context.Customer.Add(customer);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return CreatedAtAction("GetCustomer", new { id = customer.Id }, customer);
|
||||
}
|
||||
|
||||
// DELETE: api/Customer/5
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<IActionResult> DeleteCustomer([FromRoute] long id)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
var customer = await _context.Customer.SingleOrDefaultAsync(m => m.Id == id);
|
||||
if (customer == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
_context.Customer.Remove(customer);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return Ok(customer);
|
||||
}
|
||||
|
||||
private bool CustomerExists(long id)
|
||||
{
|
||||
return _context.Customer.Any(e => e.Id == id);
|
||||
}
|
||||
}
|
||||
}
|
||||
84
Controllers/FetchController.cs
Normal file
@@ -0,0 +1,84 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using rockfishCore.Models;
|
||||
using rockfishCore.Util;
|
||||
|
||||
namespace rockfishCore.Controllers
|
||||
{
|
||||
[Produces("text/plain")]
|
||||
[Route("fetch")]
|
||||
|
||||
public class FetchController : Controller
|
||||
{
|
||||
private readonly rockfishContext _context;
|
||||
|
||||
public FetchController(rockfishContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// GET: fetch/somecode/bob@bob.com
|
||||
[HttpGet("{code}/{email}")]
|
||||
public async Task<IActionResult> Get([FromRoute] string code, [FromRoute] string email)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
var rec = await _context.License.SingleOrDefaultAsync(m => m.Code == code.Trim() && m.Email == email.Trim().ToLowerInvariant() && m.Fetched == false);
|
||||
|
||||
if (rec == null)
|
||||
{
|
||||
//delay, could be someone fishing for a key, make it painful
|
||||
//Have verified this is safe, won't affect other jobs on server
|
||||
//happening concurrently or other requests to server
|
||||
System.Threading.Thread.Sleep(10000);
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
|
||||
rec.Fetched = true;
|
||||
rec.DtFetched = DateUtil.NowAsEpoch();
|
||||
//This might be flaky if behind some other stuff
|
||||
//rec.FetchFrom = HttpContext.Connection.RemoteIpAddress.ToString();
|
||||
_context.Entry(rec).State = EntityState.Modified;
|
||||
|
||||
try
|
||||
{
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
catch (DbUpdateConcurrencyException)
|
||||
{
|
||||
if (!LicenseExists(rec.Id))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(rec.Key);
|
||||
//return Ok(new {key=rec.Key});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private bool LicenseExists(long id)
|
||||
{
|
||||
return _context.License.Any(e => e.Id == id);
|
||||
}
|
||||
}
|
||||
}
|
||||
238
Controllers/LicenseController.cs
Normal file
@@ -0,0 +1,238 @@
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Authorization;//required for authorize attribute
|
||||
using System.Security.Claims;
|
||||
using rockfishCore.Models;
|
||||
using rockfishCore.Util;
|
||||
using System.Linq;
|
||||
using System;
|
||||
|
||||
//case 3233
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
//requried to inject configuration in constructor
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
//This is an 80 character line of text:
|
||||
//##############################################################################
|
||||
namespace rockfishCore.Controllers
|
||||
{
|
||||
//Authentication controller
|
||||
[Produces("application/json")]
|
||||
[Route("api/License")]
|
||||
[Authorize]
|
||||
public class LicenseController : Controller
|
||||
{
|
||||
private readonly rockfishContext _context;
|
||||
private readonly IConfiguration _configuration;
|
||||
public LicenseController(rockfishContext context, IConfiguration configuration)//these two are injected, see startup.cs
|
||||
{
|
||||
_context = context;//Keeping db context here for future where I will be inserting the keys into the db upon generation
|
||||
_configuration = configuration;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
//KEYGEN ROUTES
|
||||
|
||||
//Given key options return the message ready to send to the user
|
||||
//Note this returns a key as plain text content result
|
||||
//called by rockfish client app.license.js (who calls app.api.createLicense)
|
||||
[HttpPost("generate")]
|
||||
public ContentResult Generate([FromBody] dtoKeyOptions ko)
|
||||
{
|
||||
var templates = _context.LicenseTemplates.ToList()[0];
|
||||
ko.authorizedUserKeyGeneratorStamp = GetRFAuthorizedUserStamp();
|
||||
string sKey = KeyFactory.GetKeyReply(ko, templates, _context);
|
||||
return Content(sKey);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//Fetch key request emails
|
||||
[HttpGet("requests")]
|
||||
public JsonResult GetRequests()
|
||||
{
|
||||
return Json(TrialKeyRequestHandler.Requests());
|
||||
}
|
||||
|
||||
//Fetch generated responses
|
||||
//Generate a key from a license key request email
|
||||
//called by rockfish client app.licenseRequestEdit.js (who calls app.api.generateFromRequest)
|
||||
[HttpGet("generateFromRequest/{uid}")]
|
||||
public JsonResult GenerateFromRequest([FromRoute] uint uid)
|
||||
{
|
||||
var templates = _context.LicenseTemplates.ToList()[0];
|
||||
return Json(TrialKeyRequestHandler.GenerateFromRequest(uid, templates, GetRFAuthorizedUserStamp(), _context));
|
||||
}
|
||||
|
||||
// SEND REQUESTED KEY ROUTE
|
||||
//app.post('/api/license/email_response', function (req, res) {
|
||||
[HttpPost("email_response")]
|
||||
public JsonResult EmailResponse([FromBody] dtoKeyRequestResponse k)
|
||||
{
|
||||
return Json(TrialKeyRequestHandler.SendTrialRequestResponse(k));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////
|
||||
// STORED LICENSE KEY CRUD ROUTES
|
||||
//
|
||||
|
||||
//case 3233 Get api/license/list a list of generated licenses
|
||||
[HttpGet("list")]
|
||||
public IEnumerable<dtoLicenseListItem> GetList()
|
||||
{
|
||||
var res = from c in _context.License.OrderByDescending(c => c.DtCreated)
|
||||
select new dtoLicenseListItem
|
||||
{
|
||||
id = c.Id,
|
||||
created = c.DtCreated,
|
||||
regto = c.RegTo,
|
||||
fetched = c.Fetched,
|
||||
trial = (c.CustomerId==0)
|
||||
};
|
||||
return res.ToList();
|
||||
}
|
||||
|
||||
//case 3233 GET: api/License/5
|
||||
[HttpGet("{id}")]
|
||||
public async Task<IActionResult> GetLicense([FromRoute] long id)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
var l = await _context.License.SingleOrDefaultAsync(m => m.Id == id);
|
||||
|
||||
if (l == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
string customerName = "<TRIAL>";
|
||||
|
||||
if (l.CustomerId != 0)
|
||||
{
|
||||
if (_context.Customer.Any(e => e.Id == l.CustomerId))
|
||||
{
|
||||
|
||||
var cust = await _context.Customer
|
||||
.Select(r => new { r.Id, r.Name })
|
||||
.Where(r => r.Id == l.CustomerId)
|
||||
.FirstAsync();
|
||||
customerName=cust.Name;
|
||||
}
|
||||
else
|
||||
{
|
||||
customerName = "< Customer " + l.CustomerId.ToString() + " not found (deleted?) >";
|
||||
}
|
||||
}
|
||||
|
||||
var ret = new
|
||||
{
|
||||
regTo = l.RegTo,
|
||||
customerName = customerName,
|
||||
dtcreated = l.DtCreated,
|
||||
email = l.Email,
|
||||
code = l.Code,
|
||||
fetched = l.Fetched,
|
||||
dtfetched = l.DtFetched,
|
||||
fetchFrom = l.FetchFrom,
|
||||
key = l.Key
|
||||
};
|
||||
|
||||
return Ok(ret);
|
||||
}
|
||||
|
||||
// DELETE: api/License/5
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<IActionResult> DeleteLicense([FromRoute] long id)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
var rec = await _context.License.SingleOrDefaultAsync(m => m.Id == id);
|
||||
if (rec == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
_context.License.Remove(rec);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return Ok(rec);
|
||||
}
|
||||
|
||||
|
||||
// PUT: api/license/5/true
|
||||
//Update a license and set it's fetched property only
|
||||
//used by client to make a license fetchable or not ad-hoc
|
||||
[HttpPut("fetched/{id}/{isFetched}")]
|
||||
public async Task<IActionResult> PutLicenseFetched([FromRoute] long id, [FromRoute] bool isFetched)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
var rec = await _context.License.SingleOrDefaultAsync(m => m.Id == id);
|
||||
if (rec == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
rec.Fetched = isFetched;
|
||||
|
||||
_context.Entry(rec).State = EntityState.Modified;
|
||||
|
||||
try
|
||||
{
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
catch (DbUpdateConcurrencyException)
|
||||
{
|
||||
if (!LicenseExists(id))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
|
||||
|
||||
private bool LicenseExists(long id)
|
||||
{
|
||||
return _context.License.Any(e => e.Id == id);
|
||||
}
|
||||
|
||||
//===================== UTILITY =============
|
||||
private string GetRFAuthorizedUserStamp()
|
||||
{
|
||||
foreach (Claim c in User.Claims)
|
||||
{
|
||||
if (c.Type == "id")
|
||||
{
|
||||
return "RFID" + c.Value;
|
||||
}
|
||||
}
|
||||
return "RFID unknown";
|
||||
}
|
||||
|
||||
//------------------------------------------------------
|
||||
|
||||
}//eoc
|
||||
}//eons
|
||||
127
Controllers/LicenseTemplatesController.cs
Normal file
@@ -0,0 +1,127 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using rockfishCore.Models;
|
||||
|
||||
namespace rockfishCore.Controllers
|
||||
{
|
||||
[Produces("application/json")]
|
||||
[Route("api/LicenseTemplates")]
|
||||
[Authorize]
|
||||
public class LicenseTemplatesController : Controller
|
||||
{
|
||||
private readonly rockfishContext _context;
|
||||
|
||||
public LicenseTemplatesController(rockfishContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
// GET: api/LicenseTemplates
|
||||
[HttpGet]
|
||||
public IEnumerable<LicenseTemplates> GetLicenseTemplates()
|
||||
{
|
||||
return _context.LicenseTemplates;
|
||||
}
|
||||
|
||||
// GET: api/LicenseTemplates/5
|
||||
[HttpGet("{id}")]
|
||||
public async Task<IActionResult> GetLicenseTemplates([FromRoute] long id)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
var licenseTemplates = await _context.LicenseTemplates.SingleOrDefaultAsync(m => m.Id == id);
|
||||
|
||||
if (licenseTemplates == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
return Ok(licenseTemplates);
|
||||
}
|
||||
|
||||
// PUT: api/LicenseTemplates/5
|
||||
[HttpPut("{id}")]
|
||||
public async Task<IActionResult> PutLicenseTemplates([FromRoute] long id, [FromBody] LicenseTemplates licenseTemplates)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
if (id != licenseTemplates.Id)
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
_context.Entry(licenseTemplates).State = EntityState.Modified;
|
||||
|
||||
try
|
||||
{
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
catch (DbUpdateConcurrencyException)
|
||||
{
|
||||
if (!LicenseTemplatesExists(id))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
// POST: api/LicenseTemplates
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> PostLicenseTemplates([FromBody] LicenseTemplates licenseTemplates)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
_context.LicenseTemplates.Add(licenseTemplates);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return CreatedAtAction("GetLicenseTemplates", new { id = licenseTemplates.Id }, licenseTemplates);
|
||||
}
|
||||
|
||||
// DELETE: api/LicenseTemplates/5
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<IActionResult> DeleteLicenseTemplates([FromRoute] long id)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
var licenseTemplates = await _context.LicenseTemplates.SingleOrDefaultAsync(m => m.Id == id);
|
||||
if (licenseTemplates == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
_context.LicenseTemplates.Remove(licenseTemplates);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return Ok(licenseTemplates);
|
||||
}
|
||||
|
||||
private bool LicenseTemplatesExists(long id)
|
||||
{
|
||||
return _context.LicenseTemplates.Any(e => e.Id == id);
|
||||
}
|
||||
}
|
||||
}
|
||||
80
Controllers/MailController.cs
Normal file
@@ -0,0 +1,80 @@
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Authorization;//required for authorize attribute
|
||||
using System.Security.Claims;
|
||||
using rockfishCore.Models;
|
||||
using rockfishCore.Util;
|
||||
using System.Linq;
|
||||
using System;
|
||||
|
||||
//requried to inject configuration in constructor
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
|
||||
|
||||
namespace rockfishCore.Controllers
|
||||
{
|
||||
//Authentication controller
|
||||
[Produces("application/json")]
|
||||
[Route("api/Mail")]
|
||||
[Authorize]
|
||||
public class MailController : Controller
|
||||
{
|
||||
private readonly rockfishContext _context;
|
||||
private readonly IConfiguration _configuration;
|
||||
public MailController(rockfishContext context, IConfiguration configuration)
|
||||
{
|
||||
_context = context;
|
||||
_configuration = configuration;
|
||||
}
|
||||
|
||||
//Fetch inbox emails from sales and support
|
||||
[HttpGet("salesandsupportsummaries")]
|
||||
public JsonResult GetSalesAndSupportSummaries()
|
||||
{
|
||||
return Json(Util.RfMail.GetSalesAndSupportSummaries());
|
||||
}
|
||||
|
||||
//Fetch a preview of a message
|
||||
[HttpGet("preview/{account}/{folder}/{id}")]
|
||||
public JsonResult GetPreview([FromRoute] string account, [FromRoute] string folder, [FromRoute] uint id)
|
||||
{
|
||||
return new JsonResult(Util.RfMail.GetMessagePreview(account, folder, id));
|
||||
//return Json(new { message = Util.RfMail.GetMessagePreview(account, folder, id) });
|
||||
}
|
||||
|
||||
|
||||
[HttpPost("reply/{account}/{id}")]
|
||||
public JsonResult Reply([FromRoute] string account, [FromRoute] uint id, [FromBody] dtoReplyMessageItem m)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(m.composition))
|
||||
{
|
||||
return Json(new { msg = "MailController:Reply->There is no reply text", error = 1 });
|
||||
}
|
||||
RfMail.rfMailAccount acct = RfMail.rfMailAccount.support;
|
||||
if (account.Contains("sales"))
|
||||
{
|
||||
acct = RfMail.rfMailAccount.sales;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
RfMail.ReplyMessage(id, acct, m.composition, true, m.trackDelivery);
|
||||
return Json(new { msg = "message sent", ok = 1 });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Json(new { msg = ex.Message, error = 1 });
|
||||
}
|
||||
}
|
||||
// //------------------------------------------------------
|
||||
|
||||
public class dtoReplyMessageItem
|
||||
{
|
||||
public string composition;
|
||||
public bool trackDelivery;
|
||||
|
||||
}
|
||||
|
||||
}//eoc
|
||||
}//eons
|
||||
129
Controllers/ProductController.cs
Normal file
@@ -0,0 +1,129 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using rockfishCore.Models;
|
||||
|
||||
namespace rockfishCore.Controllers
|
||||
{
|
||||
[Produces("application/json")]
|
||||
[Route("api/Product")]
|
||||
[Authorize]
|
||||
public class ProductController : Controller
|
||||
{
|
||||
private readonly rockfishContext _context;
|
||||
|
||||
public ProductController(rockfishContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// GET: api/Product
|
||||
[HttpGet]
|
||||
public IEnumerable<Product> GetProduct()
|
||||
{
|
||||
return _context.Product;
|
||||
}
|
||||
|
||||
// GET: api/Product/5
|
||||
[HttpGet("{id}")]
|
||||
public async Task<IActionResult> GetProduct([FromRoute] long id)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
var Product = await _context.Product.SingleOrDefaultAsync(m => m.Id == id);
|
||||
|
||||
if (Product == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
return Ok(Product);
|
||||
}
|
||||
|
||||
// PUT: api/Product/5
|
||||
[HttpPut("{id}")]
|
||||
public async Task<IActionResult> PutProduct([FromRoute] long id, [FromBody] Product Product)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
if (id != Product.Id)
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
_context.Entry(Product).State = EntityState.Modified;
|
||||
|
||||
try
|
||||
{
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
catch (DbUpdateConcurrencyException)
|
||||
{
|
||||
if (!ProductExists(id))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
// POST: api/Product
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> PostProduct([FromBody] Product Product)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
_context.Product.Add(Product);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return CreatedAtAction("GetProduct", new { id = Product.Id }, Product);
|
||||
}
|
||||
|
||||
// DELETE: api/Product/5
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<IActionResult> DeleteProduct([FromRoute] long id)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
var Product = await _context.Product.SingleOrDefaultAsync(m => m.Id == id);
|
||||
if (Product == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
_context.Product.Remove(Product);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return Ok(Product);
|
||||
}
|
||||
|
||||
private bool ProductExists(long id)
|
||||
{
|
||||
return _context.Product.Any(e => e.Id == id);
|
||||
}
|
||||
}
|
||||
}
|
||||
248
Controllers/PurchaseController.cs
Normal file
@@ -0,0 +1,248 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using rockfishCore.Models;
|
||||
using System.IO;
|
||||
|
||||
namespace rockfishCore.Controllers
|
||||
{
|
||||
[Produces("application/json")]
|
||||
[Route("api/Purchase")]
|
||||
[Authorize]
|
||||
public class PurchaseController : Controller
|
||||
{
|
||||
private readonly rockfishContext _context;
|
||||
|
||||
public PurchaseController(rockfishContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
//Get unique product code / name combinations
|
||||
[HttpGet("productcodes")]
|
||||
public JsonResult GetUniqueProductCodes()
|
||||
{
|
||||
//Note: will return dupes as comparer sees misspellings as unique in name, but I'm keeping it that way
|
||||
//so the name misspellings are apparent and can be fixed up
|
||||
var l = _context.Purchase.Select(p => new { p.ProductCode, p.Name }).Distinct().OrderBy(p => p.ProductCode);
|
||||
return Json(l);
|
||||
}
|
||||
|
||||
// GET: api/Purchase
|
||||
[HttpGet]
|
||||
public IEnumerable<Purchase> GetPurchase()
|
||||
{
|
||||
return _context.Purchase;
|
||||
}
|
||||
|
||||
// GET: api/Purchase/5
|
||||
[HttpGet("{id}")]
|
||||
public async Task<IActionResult> GetPurchase([FromRoute] long id)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
var purchase = await _context.Purchase.SingleOrDefaultAsync(m => m.Id == id);
|
||||
|
||||
if (purchase == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
return Ok(purchase);
|
||||
}
|
||||
|
||||
// PUT: api/Purchase/5
|
||||
[HttpPut("{id}")]
|
||||
public async Task<IActionResult> PutPurchase([FromRoute] long id, [FromBody] Purchase purchase)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
if (id != purchase.Id)
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
_context.Entry(purchase).State = EntityState.Modified;
|
||||
|
||||
try
|
||||
{
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
catch (DbUpdateConcurrencyException)
|
||||
{
|
||||
if (!PurchaseExists(id))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
updateCustomerActive(purchase.CustomerId);
|
||||
ParseOrderAndUpdateEmail(purchase);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
// POST: api/Purchase
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> PostPurchase([FromBody] Purchase purchase)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
_context.Purchase.Add(purchase);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
updateCustomerActive(purchase.CustomerId);
|
||||
ParseOrderAndUpdateEmail(purchase);
|
||||
|
||||
return CreatedAtAction("GetPurchase", new { id = purchase.Id }, purchase);
|
||||
}
|
||||
|
||||
// DELETE: api/Purchase/5
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<IActionResult> DeletePurchase([FromRoute] long id)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
var purchase = await _context.Purchase.SingleOrDefaultAsync(m => m.Id == id);
|
||||
if (purchase == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
var customerId = purchase.CustomerId;
|
||||
|
||||
_context.Purchase.Remove(purchase);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
updateCustomerActive(customerId);
|
||||
return Ok(purchase);
|
||||
}
|
||||
|
||||
private bool PurchaseExists(long id)
|
||||
{
|
||||
return _context.Purchase.Any(e => e.Id == id);
|
||||
}
|
||||
|
||||
//check if customer has any active subs and flag accordingly
|
||||
private void updateCustomerActive(long customerId)
|
||||
{
|
||||
var cust = _context.Customer.First(m => m.Id == customerId);
|
||||
bool active = hasActiveSubs(customerId);
|
||||
if (cust.Active != active)
|
||||
{
|
||||
cust.Active = active;
|
||||
_context.Customer.Update(cust);
|
||||
_context.SaveChanges();
|
||||
}
|
||||
}
|
||||
|
||||
//check if a customer has active subscriptions
|
||||
private bool hasActiveSubs(long customerId)
|
||||
{
|
||||
return _context.Purchase.Where(m => m.CustomerId == customerId && m.CancelDate == null).Count() > 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void ParseOrderAndUpdateEmail(Purchase purchase)
|
||||
{
|
||||
Dictionary<string, string> d = ParseShareItOrderData(purchase.Notes);
|
||||
if (d.Count < 1)
|
||||
return;
|
||||
|
||||
if (d.ContainsKey("E-Mail"))
|
||||
{
|
||||
string email = d["E-Mail"];
|
||||
if (!string.IsNullOrWhiteSpace(email))
|
||||
{
|
||||
//append or set it to the customer adminAddress if it isn't already present there
|
||||
//in this way the last address listed is the most recent purchase address there
|
||||
var cust = _context.Customer.First(m => m.Id == purchase.CustomerId);
|
||||
Util.CustomerUtils.AddAdminEmailIfNotPresent(cust, email);
|
||||
_context.SaveChanges();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// parse out the x=y values in a ShareIt order document
|
||||
/// </summary>
|
||||
/// <param name="order"></param>
|
||||
/// <returns></returns>
|
||||
Dictionary<string, string> ParseShareItOrderData(string order)
|
||||
{
|
||||
Dictionary<string, string> ret = new Dictionary<string, string>();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(order))
|
||||
{
|
||||
//parse the email address out of the order
|
||||
StringReader sr = new StringReader(order);
|
||||
string aLine = string.Empty;
|
||||
while (true)
|
||||
{
|
||||
aLine = sr.ReadLine();
|
||||
if (aLine != null)
|
||||
{
|
||||
if (aLine.Contains("="))
|
||||
{
|
||||
string[] item = aLine.Split("=");
|
||||
ret.Add(item[0].Trim(), item[1].Trim());
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
/*
|
||||
Original JS code for this from client end of things:
|
||||
// Loop through all lines
|
||||
for (var j = 0; j < lines.length; j++) {
|
||||
var thisLine = lines[j];
|
||||
if (thisLine.includes("=")) {
|
||||
var thisElement = thisLine.split("=");
|
||||
purchaseData[thisElement[0].trim()] = thisElement[1].trim();
|
||||
}
|
||||
}
|
||||
|
||||
//Now have an object with the value pairs in it
|
||||
if (purchaseData["ShareIt Ref #"]) {
|
||||
$("#salesOrderNumber").val(purchaseData["ShareIt Ref #"]);
|
||||
}
|
||||
|
||||
// if (purchaseData["E-Mail"]) {
|
||||
// $("#email").val(purchaseData["E-Mail"]);
|
||||
// }
|
||||
|
||||
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}//eoc
|
||||
}//eons
|
||||
201
Controllers/ReportController.cs
Normal file
@@ -0,0 +1,201 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using rockfishCore.Models;
|
||||
using rockfishCore.Util;
|
||||
using System.Linq;
|
||||
using System;
|
||||
|
||||
//requried to inject configuration in constructor
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace rockfishCore.Controllers
|
||||
{
|
||||
[Produces("application/json")]
|
||||
[Route("api/report")]
|
||||
[Authorize]
|
||||
public class ReportController : Controller
|
||||
{
|
||||
private readonly rockfishContext _context;
|
||||
private readonly IConfiguration _configuration;
|
||||
public ReportController(rockfishContext context, IConfiguration configuration)//these two are injected, see startup.cs
|
||||
{
|
||||
_context = context;
|
||||
_configuration = configuration;
|
||||
}
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////
|
||||
//Get expiring subscriptions list
|
||||
//
|
||||
[HttpGet("expires")]
|
||||
public JsonResult Get()
|
||||
{
|
||||
var customerList = _context.Customer.Select(p => new { p.Id, p.Name });
|
||||
|
||||
var rawExpiresList = _context.Purchase
|
||||
.Where(p => p.ExpireDate != null && p.CancelDate == null)
|
||||
.OrderBy(p => p.ExpireDate)
|
||||
.Select(p => new expiresResultItem { id = p.Id, expireDate = p.ExpireDate, name = p.Name, site_id = p.SiteId, customerId = p.CustomerId })
|
||||
.ToList();
|
||||
|
||||
foreach (expiresResultItem i in rawExpiresList)
|
||||
{
|
||||
i.Customer = customerList.First(p => p.Id == i.customerId).Name;
|
||||
}
|
||||
|
||||
return Json(rawExpiresList);
|
||||
}
|
||||
|
||||
//dto classes for route
|
||||
public class expiresResultItem
|
||||
{
|
||||
public long id;
|
||||
public string name;
|
||||
public string Customer;
|
||||
public long customerId;
|
||||
public long? expireDate;
|
||||
public long site_id;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////
|
||||
//Get unique product code / name combinations
|
||||
//
|
||||
[HttpPost("emailsforproductcodes")]
|
||||
public JsonResult GetUniqueEmailsByProductCodes([FromBody] requestEmailsForProductCodes req)
|
||||
{
|
||||
var customerList = _context.Customer.Select(p => new { p.Id, p.DoNotContact });
|
||||
|
||||
List<long> rawCustomerIds = new List<long>();
|
||||
|
||||
foreach (string pcode in req.products)
|
||||
{
|
||||
//fetch all customer id's from purchase collection that match product codes submitted
|
||||
var l = _context.Purchase.Where(p => p.ProductCode == pcode).Select(p => p.CustomerId).Distinct();
|
||||
rawCustomerIds.AddRange(l.ToList());
|
||||
}
|
||||
|
||||
//uniquify the customer list
|
||||
List<long> uniqueCustomerIds = rawCustomerIds.Distinct().ToList();
|
||||
|
||||
//container for the raw email lists built serially
|
||||
List<string> rawEmails = new List<string>();
|
||||
|
||||
foreach (long cid in uniqueCustomerIds)
|
||||
{
|
||||
//skip if do not contact and not explicitly including do not contact
|
||||
if (customerList.First(p => p.Id == cid).DoNotContact && req.ckNoContact != true)
|
||||
continue;
|
||||
|
||||
//get all raw email values for this client from db
|
||||
//there may be dupes or even multiple in one
|
||||
rawEmails.AddRange(getEmailsForClient(cid));
|
||||
}
|
||||
|
||||
//Now clean up the list and sort and uniquify it
|
||||
List<string> cleanedEmails = cleanupRawEmailList(rawEmails);
|
||||
|
||||
return Json(cleanedEmails);
|
||||
}
|
||||
|
||||
|
||||
//Given a client id find all unique email address in db for that client
|
||||
private List<string> getEmailsForClient(long id)
|
||||
{
|
||||
List<string> ret = new List<string>();
|
||||
|
||||
//New for RF 6
|
||||
var cust = _context.Customer.Where(p => p.Id == id).FirstOrDefault();
|
||||
if (cust != null)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(cust.AdminEmail))
|
||||
ret.Add(cust.AdminEmail);
|
||||
if (!string.IsNullOrWhiteSpace(cust.SupportEmail))
|
||||
ret.Add(cust.SupportEmail);
|
||||
}
|
||||
|
||||
//TOASTED for RF 6
|
||||
//search contact, trial, purchase, incident (optionally)
|
||||
// ret.AddRange(_context.Purchase.Where(p => p.CustomerId == id).Select(p => p.Email).Distinct().ToList());
|
||||
// ret.AddRange(_context.Contact.Where(p => p.CustomerId == id).Select(p => p.Email).Distinct().ToList());
|
||||
// ret.AddRange(_context.Trial.Where(p => p.CustomerId == id).Select(p => p.Email).Distinct().ToList());
|
||||
// if (includeIncidentEmails)
|
||||
// ret.AddRange(_context.Incident.Where(p => p.CustomerId == id).Select(p => p.Email).Distinct().ToList());
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
//break out multiple also trim whitespace and lowercase and uniquify them
|
||||
private List<string> cleanupRawEmailList(List<string> src)
|
||||
{
|
||||
List<string> ret = new List<string>();
|
||||
foreach (string rawAddress in src)
|
||||
{
|
||||
//count the @'s, if there are more than one then get splittn'
|
||||
int count = rawAddress.Count(f => f == '@');
|
||||
|
||||
if (count < 1)
|
||||
continue;//no address, skip this one
|
||||
|
||||
//a little cleanup based on what I had to do in og rockfish
|
||||
string semiRawAddress = rawAddress.Replace(", ", ",").TrimEnd('>').TrimEnd(',').Trim();
|
||||
|
||||
//there's at least one address in there, maybe more
|
||||
if (count == 1)
|
||||
ret.Add(semiRawAddress.ToLowerInvariant());
|
||||
else
|
||||
{
|
||||
//there are multiple so break it apart
|
||||
//determine break character could be a space or a comma
|
||||
char breakChar;
|
||||
if (semiRawAddress.Contains(','))
|
||||
{
|
||||
breakChar = ',';
|
||||
}
|
||||
else if (semiRawAddress.Contains(' '))
|
||||
{
|
||||
breakChar = ' ';
|
||||
}
|
||||
else
|
||||
{
|
||||
//no break character, it's a bad address, highlight it so it can be fixed when seen
|
||||
ret.Add("_BAD_EMAIL_" + semiRawAddress + "_BAD_EMAIL_");
|
||||
continue;
|
||||
}
|
||||
|
||||
//Ok if we made it here then we can split out the emails
|
||||
string[] splits = semiRawAddress.Split(breakChar);
|
||||
foreach (string s in splits)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(s) && s.Contains("@"))
|
||||
ret.Add(s.ToLowerInvariant().Trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//return distinct values only that are ordered by the email domain
|
||||
return ret.Distinct().OrderBy(email => email.Split('@')[1]).ToList();
|
||||
}
|
||||
|
||||
|
||||
//dto classes for route
|
||||
public class requestEmailsForProductCodes
|
||||
{
|
||||
public string[] products;
|
||||
public bool ckIncidental;
|
||||
public bool ckNoContact;
|
||||
}
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
|
||||
}//eoc
|
||||
}//eons
|
||||
|
||||
220
Controllers/RfCaseBlobController.cs
Normal file
@@ -0,0 +1,220 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using rockfishCore.Models;
|
||||
|
||||
namespace rockfishCore.Controllers
|
||||
{
|
||||
[Produces("application/json")]
|
||||
[Route("api/RfCaseBlob")]
|
||||
public class RfCaseBlobController : Controller
|
||||
{
|
||||
private readonly rockfishContext _context;
|
||||
|
||||
public RfCaseBlobController(rockfishContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
// GET: api/RfCaseBlob
|
||||
[HttpGet]
|
||||
[Authorize]
|
||||
public IEnumerable<RfCaseBlob> GetRfCaseBlob()
|
||||
{
|
||||
var c = from s in _context.RfCaseBlob select s;
|
||||
c = c.OrderBy(s => s.Name);
|
||||
return c;
|
||||
}
|
||||
|
||||
|
||||
|
||||
[HttpPost("upload")]
|
||||
public IActionResult UploadFilesAjax([FromQuery] string rfcaseid)
|
||||
{//http://www.binaryintellect.net/articles/f1cee257-378a-42c1-9f2f-075a3aed1d98.aspx
|
||||
|
||||
//need a proper case ID to do this
|
||||
if (string.IsNullOrWhiteSpace(rfcaseid) || rfcaseid == "new")
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
var files = Request.Form.Files;
|
||||
int nCount=0;
|
||||
foreach (var file in files)
|
||||
{
|
||||
if (file.Length > 0)
|
||||
{
|
||||
using (var fileStream = file.OpenReadStream())
|
||||
using (var ms = new System.IO.MemoryStream())
|
||||
{
|
||||
fileStream.CopyTo(ms);
|
||||
var fileBytes = ms.ToArray();
|
||||
RfCaseBlob blob=new RfCaseBlob();
|
||||
blob.RfCaseId=Convert.ToInt64(rfcaseid);
|
||||
blob.Name=file.FileName;
|
||||
blob.File=fileBytes;
|
||||
_context.RfCaseBlob.Add(blob);
|
||||
_context.SaveChanges();
|
||||
nCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
string message = $"{nCount} file(s) uploaded successfully!";
|
||||
|
||||
return Json(message);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
[HttpGet("download/{id}")]
|
||||
public ActionResult Download([FromRoute] long id, [FromQuery] string dlkey)
|
||||
{//https://dotnetcoretutorials.com/2017/03/12/uploading-files-asp-net-core/
|
||||
//https://stackoverflow.com/questions/45763149/asp-net-core-jwt-in-uri-query-parameter/45811270#45811270
|
||||
|
||||
if (string.IsNullOrWhiteSpace(dlkey))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
//get user by key, if not found then reject
|
||||
//If user dlkeyexp has not expired then return file
|
||||
var user = _context.User.SingleOrDefault(m => m.DlKey == dlkey);
|
||||
if (user == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var unixdtnow = new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds();
|
||||
if (user.DlKeyExp < unixdtnow)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
//Ok, user has a valid download key and it's not expired yet so get the file
|
||||
var f = _context.RfCaseBlob.SingleOrDefault(m => m.Id == id);
|
||||
if (f == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var extension = System.IO.Path.GetExtension(f.Name);
|
||||
|
||||
string mimetype = "application/x-msdownload";
|
||||
if (!string.IsNullOrWhiteSpace(extension))
|
||||
{
|
||||
mimetype = Util.MimeTypeMap.GetMimeType(extension);
|
||||
}
|
||||
|
||||
Response.Headers.Add("Content-Disposition", "inline; filename=" + f.Name);
|
||||
return File(f.File, mimetype);//NOTE: if you don't specify a filename here then the above content disposition header takes effect, if you do then the 'File(' method sets it as attachment automatically
|
||||
|
||||
}
|
||||
|
||||
|
||||
// GET: api/RfCaseBlob/5
|
||||
[HttpGet("{id}")]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> GetRfCaseBlob([FromRoute] long id)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
var RfCaseBlob = await _context.RfCaseBlob.SingleOrDefaultAsync(m => m.Id == id);
|
||||
|
||||
if (RfCaseBlob == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
return Ok(RfCaseBlob);
|
||||
}
|
||||
|
||||
// PUT: api/RfCaseBlob/5
|
||||
[HttpPut("{id}")]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> PutRfCaseBlob([FromRoute] long id, [FromBody] RfCaseBlob RfCaseBlob)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
if (id != RfCaseBlob.Id)
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
_context.Entry(RfCaseBlob).State = EntityState.Modified;
|
||||
|
||||
try
|
||||
{
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
catch (DbUpdateConcurrencyException)
|
||||
{
|
||||
if (!RfCaseBlobExists(id))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
// POST: api/RfCaseBlob
|
||||
[HttpPost]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> PostRfCaseBlob([FromBody] RfCaseBlob RfCaseBlob)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
_context.RfCaseBlob.Add(RfCaseBlob);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return CreatedAtAction("GetRfCaseBlob", new { id = RfCaseBlob.Id }, RfCaseBlob);
|
||||
}
|
||||
|
||||
// DELETE: api/RfCaseBlob/5
|
||||
[HttpDelete("{id}")]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> DeleteRfCaseBlob([FromRoute] long id)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
var RfCaseBlob = await _context.RfCaseBlob.SingleOrDefaultAsync(m => m.Id == id);
|
||||
if (RfCaseBlob == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
_context.RfCaseBlob.Remove(RfCaseBlob);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return Ok(RfCaseBlob);
|
||||
}
|
||||
|
||||
private bool RfCaseBlobExists(long id)
|
||||
{
|
||||
return _context.RfCaseBlob.Any(e => e.Id == id);
|
||||
}
|
||||
}
|
||||
}
|
||||
230
Controllers/RfCaseController.cs
Normal file
@@ -0,0 +1,230 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using rockfishCore.Models;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace rockfishCore.Controllers
|
||||
{
|
||||
[Produces("application/json")]
|
||||
[Route("api/RfCase")]
|
||||
[Authorize]
|
||||
public class RfCaseController : Controller
|
||||
{
|
||||
private readonly rockfishContext _context;
|
||||
|
||||
public RfCaseController(rockfishContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
//Get api/rfcase/list
|
||||
[HttpGet("list")]
|
||||
public JsonResult GetList(long? Project, bool? Open, int? Priority, string Search)
|
||||
{
|
||||
//NOTE: Unlike FogBugz, does not open a case directly from the search
|
||||
//uses a separate route (simple get), so this route doesn't need to worry about it.
|
||||
|
||||
|
||||
//FILTERS
|
||||
var cases = _context.RfCase.AsQueryable();
|
||||
|
||||
|
||||
//TODO: this is case sensitive currently
|
||||
//need to figure out a way to make it insensitive
|
||||
if (!string.IsNullOrWhiteSpace(Search))
|
||||
{
|
||||
cases = _context.RfCase.Where(s => s.Notes.Contains(Search)
|
||||
|| s.ReleaseNotes.Contains(Search)
|
||||
|| s.ReleaseVersion.Contains(Search)
|
||||
|| s.Title.Contains(Search)
|
||||
);
|
||||
}
|
||||
|
||||
//project
|
||||
if (Project != null && Project != 0)
|
||||
{
|
||||
cases = cases.Where(s => s.RfCaseProjectId == Project);
|
||||
}
|
||||
|
||||
//open
|
||||
if (Open != null)
|
||||
{
|
||||
if (Open == true)
|
||||
{
|
||||
cases = cases.Where(s => s.DtClosed == null);
|
||||
}
|
||||
else
|
||||
{
|
||||
cases = cases.Where(s => s.DtClosed != null);
|
||||
}
|
||||
}
|
||||
|
||||
//priority
|
||||
if (Priority != null && Priority > 0 && Priority < 6)
|
||||
{
|
||||
cases = cases.Where(s => s.Priority == Priority);
|
||||
|
||||
}
|
||||
|
||||
cases = cases.OrderBy(s => s.Priority).ThenByDescending(s => s.Id);
|
||||
return new JsonResult(cases);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// //Get api/rfcase/77/attachments
|
||||
// [HttpGet("{id}/attachments")]
|
||||
// public IEnumerable<dtoNameIdItem> GetAttachmentList([FromRoute] long id)
|
||||
// {
|
||||
// var res = from c in _context.RfCaseBlob
|
||||
// .Where(c => c.RfCaseId.Equals(id)).OrderBy(c => c.Id)//order by entry order
|
||||
// select new dtoNameIdItem
|
||||
// {
|
||||
// id = c.Id,
|
||||
// name = c.Name
|
||||
// };
|
||||
// return res.ToList();
|
||||
// }
|
||||
|
||||
|
||||
//Get api/rfcase/77/attachments
|
||||
[HttpGet("{id}/attachments")]
|
||||
public ActionResult GetAttachmentList([FromRoute] long id)
|
||||
{
|
||||
var res = from c in _context.RfCaseBlob
|
||||
.Where(c => c.RfCaseId.Equals(id)).OrderBy(c => c.Id)//order by entry order
|
||||
select new dtoNameIdItem
|
||||
{
|
||||
id = c.Id,
|
||||
name = c.Name
|
||||
};
|
||||
|
||||
//Took forever to find this out
|
||||
//How to get user id from jwt token in controller
|
||||
//http://www.jerriepelser.com/blog/aspnetcore-jwt-saving-bearer-token-as-claim/
|
||||
var userId = User.FindFirst("id")?.Value;
|
||||
long luserId=long.Parse(userId);
|
||||
|
||||
|
||||
var user = _context.User.SingleOrDefault(m => m.Id == luserId);
|
||||
if (user == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
return new JsonResult(new { dlkey = user.DlKey, attach = res.ToList() });
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// GET: api/RfCase
|
||||
[HttpGet]
|
||||
public IEnumerable<RfCase> GetRfCase()
|
||||
{
|
||||
return _context.RfCase;
|
||||
}
|
||||
|
||||
|
||||
// GET: api/RfCase/5
|
||||
[HttpGet("{id}")]
|
||||
public async Task<IActionResult> GetRfCase([FromRoute] long id)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
var RfCase = await _context.RfCase.SingleOrDefaultAsync(m => m.Id == id);
|
||||
|
||||
if (RfCase == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
return Ok(RfCase);
|
||||
}
|
||||
|
||||
// PUT: api/RfCase/5
|
||||
[HttpPut("{id}")]
|
||||
public async Task<IActionResult> PutRfCase([FromRoute] long id, [FromBody] RfCase RfCase)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
if (id != RfCase.Id)
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
_context.Entry(RfCase).State = EntityState.Modified;
|
||||
|
||||
try
|
||||
{
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
catch (DbUpdateConcurrencyException)
|
||||
{
|
||||
if (!RfCaseExists(id))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
// POST: api/RfCase
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> PostRfCase([FromBody] RfCase RfCase)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
_context.RfCase.Add(RfCase);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return CreatedAtAction("GetRfCase", new { id = RfCase.Id }, RfCase);
|
||||
}
|
||||
|
||||
// DELETE: api/RfCase/5
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<IActionResult> DeleteRfCase([FromRoute] long id)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
var RfCase = await _context.RfCase.SingleOrDefaultAsync(m => m.Id == id);
|
||||
if (RfCase == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
_context.RfCase.Remove(RfCase);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return Ok(RfCase);
|
||||
}
|
||||
|
||||
private bool RfCaseExists(long id)
|
||||
{
|
||||
return _context.RfCase.Any(e => e.Id == id);
|
||||
}
|
||||
}
|
||||
}
|
||||
130
Controllers/RfCaseProjectController.cs
Normal file
@@ -0,0 +1,130 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using rockfishCore.Models;
|
||||
|
||||
namespace rockfishCore.Controllers
|
||||
{
|
||||
[Produces("application/json")]
|
||||
[Route("api/RfCaseProject")]
|
||||
[Authorize]
|
||||
public class RfCaseProjectController : Controller
|
||||
{
|
||||
private readonly rockfishContext _context;
|
||||
|
||||
public RfCaseProjectController(rockfishContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
// GET: api/RfCaseProject
|
||||
[HttpGet]
|
||||
public IEnumerable<RfCaseProject> GetRfCaseProject()
|
||||
{
|
||||
var c = from s in _context.RfCaseProject select s;
|
||||
c = c.OrderBy(s => s.Name);
|
||||
return c;
|
||||
//return _context.RfCaseProject;
|
||||
}
|
||||
|
||||
// GET: api/RfCaseProject/5
|
||||
[HttpGet("{id}")]
|
||||
public async Task<IActionResult> GetRfCaseProject([FromRoute] long id)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
var RfCaseProject = await _context.RfCaseProject.SingleOrDefaultAsync(m => m.Id == id);
|
||||
|
||||
if (RfCaseProject == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
return Ok(RfCaseProject);
|
||||
}
|
||||
|
||||
// PUT: api/RfCaseProject/5
|
||||
[HttpPut("{id}")]
|
||||
public async Task<IActionResult> PutRfCaseProject([FromRoute] long id, [FromBody] RfCaseProject RfCaseProject)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
if (id != RfCaseProject.Id)
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
_context.Entry(RfCaseProject).State = EntityState.Modified;
|
||||
|
||||
try
|
||||
{
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
catch (DbUpdateConcurrencyException)
|
||||
{
|
||||
if (!RfCaseProjectExists(id))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
// POST: api/RfCaseProject
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> PostRfCaseProject([FromBody] RfCaseProject RfCaseProject)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
_context.RfCaseProject.Add(RfCaseProject);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return CreatedAtAction("GetRfCaseProject", new { id = RfCaseProject.Id }, RfCaseProject);
|
||||
}
|
||||
|
||||
// DELETE: api/RfCaseProject/5
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<IActionResult> DeleteRfCaseProject([FromRoute] long id)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
var RfCaseProject = await _context.RfCaseProject.SingleOrDefaultAsync(m => m.Id == id);
|
||||
if (RfCaseProject == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
_context.RfCaseProject.Remove(RfCaseProject);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return Ok(RfCaseProject);
|
||||
}
|
||||
|
||||
private bool RfCaseProjectExists(long id)
|
||||
{
|
||||
return _context.RfCaseProject.Any(e => e.Id == id);
|
||||
}
|
||||
}
|
||||
}
|
||||
60
Controllers/RvfController.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using rockfishCore.Models;
|
||||
using rockfishCore.Util;
|
||||
|
||||
namespace rockfishCore.Controllers
|
||||
{
|
||||
[Produces("text/plain")]
|
||||
[Route("rvf")]
|
||||
|
||||
public class RvfController : Controller //RAVEN License fetch route
|
||||
{
|
||||
private readonly rockfishContext _context;
|
||||
|
||||
public RvfController(rockfishContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
|
||||
[HttpGet("{dbid}")]
|
||||
public ActionResult Get([FromRoute] Guid dbid)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
//This is to simulate the scenarios where there is no license to return
|
||||
//either due to no current account / canceled or simply no license exists
|
||||
//Changes here must be reflected in RAVEN Util.License.Fetch block
|
||||
bool bTestStatusOtherThanOk = false;
|
||||
if (bTestStatusOtherThanOk)
|
||||
{
|
||||
return Json(new {Status="NONE", Reason="No license"});
|
||||
//return Json(new {Status="Canceled", Reason="Non payment"});
|
||||
}
|
||||
else
|
||||
{
|
||||
return Ok(RavenKeyFactory.GetRavenTestKey(dbid));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private bool LicenseExists(long id)
|
||||
{
|
||||
return _context.License.Any(e => e.Id == id);
|
||||
}
|
||||
}
|
||||
}
|
||||
60
Controllers/RvrController.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using rockfishCore.Models;
|
||||
using rockfishCore.Util;
|
||||
|
||||
namespace rockfishCore.Controllers
|
||||
{
|
||||
[Produces("text/plain")]
|
||||
[Route("rvr")]
|
||||
public class RvrController : Controller //RAVEN trial license request
|
||||
{
|
||||
private readonly rockfishContext _context;
|
||||
|
||||
public RvrController(rockfishContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
|
||||
[HttpGet]
|
||||
public ActionResult Get([FromQuery] Guid dbid, [FromQuery] string email, [FromQuery] string regto)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
if (dbid == Guid.Empty)
|
||||
{
|
||||
return BadRequest("The requested DB ID was empty and not valid");
|
||||
}
|
||||
|
||||
//TODO: closer to release as ROCKFISH might change before then
|
||||
|
||||
//Attempt to match customer by dbid if not then create new customer of trial type for the indicated dbid and regto
|
||||
//ensure this email goes into the adminEmail array if not already there
|
||||
|
||||
//Flag customer record has having an unfulfilled trial request
|
||||
|
||||
//If it's a new customer or an existing one with non-matching email then send a verification email to the customer
|
||||
|
||||
//When a response is spotted in email then Rockfish should see it and flag the customer as verified
|
||||
//Probably some general purpose email Verify code would be helpful here as it would likely be useful for all manner of things
|
||||
|
||||
//When user releases trial in Rockfish a license key will be generated ready for the customers RAVEN to retrieve
|
||||
|
||||
return Ok("Request accepted. Awaiting email verification and approval.");
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}//eoc
|
||||
}//eons
|
||||
184
Controllers/SearchController.cs
Normal file
@@ -0,0 +1,184 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using rockfishCore.Models;
|
||||
using rockfishCore.Util;
|
||||
using System.Linq;
|
||||
using System;
|
||||
|
||||
//requried to inject configuration in constructor
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace rockfishCore.Controllers
|
||||
{
|
||||
[Produces("application/json")]
|
||||
[Route("api/search")]
|
||||
[Authorize]
|
||||
public class SearchController : Controller
|
||||
{
|
||||
private readonly rockfishContext _context;
|
||||
private readonly IConfiguration _configuration;
|
||||
public SearchController(rockfishContext context, IConfiguration configuration)//these two are injected, see startup.cs
|
||||
{
|
||||
_context = context;
|
||||
_configuration = configuration;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
[HttpGet]
|
||||
public JsonResult GetSearchResults(string query) //get parameters from url not body
|
||||
{
|
||||
|
||||
//default return value, emtpy suggestions
|
||||
var noResults = new resultItem[0];
|
||||
|
||||
if (string.IsNullOrWhiteSpace(query))
|
||||
{
|
||||
return Json(noResults);
|
||||
}
|
||||
|
||||
//CACHE CUSTOMER NAME LIST
|
||||
var custData = _context.Customer.Select(p => new { p.Id, p.Name });
|
||||
Dictionary<long, string> customerDict = new Dictionary<long, string>();
|
||||
customerDict.Add(0, "TRIAL CUSTOMER");
|
||||
foreach (var cust in custData)
|
||||
{
|
||||
customerDict.Add(cust.Id, cust.Name);
|
||||
}
|
||||
|
||||
|
||||
// List<SearchItem> l = searchItems;
|
||||
List<resultItem> resultList = new List<resultItem>();
|
||||
using (var command = _context.Database.GetDbConnection().CreateCommand())
|
||||
{
|
||||
_context.Database.OpenConnection();
|
||||
|
||||
foreach (SearchItem searchItem in searchItems)
|
||||
{
|
||||
string getColumns = "id, " + searchItem.field;
|
||||
if (searchItem.getCustomer_id)
|
||||
{
|
||||
//fuckery due to column name not consistent
|
||||
if (searchItem.table == "license")
|
||||
getColumns += ", customerid";
|
||||
else
|
||||
getColumns += ", customer_id";
|
||||
}
|
||||
|
||||
if (searchItem.getSite_id)
|
||||
getColumns += ", site_id";
|
||||
|
||||
command.CommandText = "select distinct " + getColumns + " from " + searchItem.table + " where " + searchItem.field + " like @q;";
|
||||
command.Parameters.Clear();
|
||||
var parameter = command.CreateParameter();
|
||||
parameter.ParameterName = "@q";
|
||||
parameter.Value = "%" + query + "%";
|
||||
command.Parameters.Add(parameter);
|
||||
|
||||
using (var res = command.ExecuteReader())
|
||||
{
|
||||
if (res.HasRows)
|
||||
{
|
||||
while (res.Read())
|
||||
{
|
||||
var r = new resultItem();
|
||||
r.obj = searchItem.table;
|
||||
r.fld = searchItem.field;
|
||||
r.id = Convert.ToInt64(res["id"]);
|
||||
if (searchItem.getCustomer_id)
|
||||
{
|
||||
//fucked up the scheme of customer_id as table name with license
|
||||
//which is customerid instead so have to workaround as don't want to bother
|
||||
//with the considerable amount of rigamarole to rename the column in the db
|
||||
if (searchItem.table == "license")
|
||||
{
|
||||
r.customerId = Convert.ToInt64(res["customerid"]);
|
||||
r.name = customerDict[r.customerId];//get name from customer id
|
||||
}
|
||||
else
|
||||
{
|
||||
r.customerId = Convert.ToInt64(res["customer_id"]);
|
||||
r.name = customerDict[r.customerId];//get name from customer id
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
r.name = customerDict[r.id];//here id is the customer id
|
||||
}
|
||||
if (searchItem.getSite_id)
|
||||
r.site_id = Convert.ToInt64(res["site_id"]);
|
||||
|
||||
resultList.Add(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_context.Database.CloseConnection();
|
||||
}
|
||||
|
||||
var filteredAndSorted = resultList.GroupBy(o => new { o.id, o.obj })
|
||||
.Select(o => o.FirstOrDefault())
|
||||
.OrderBy(o => o.name);
|
||||
return Json(filteredAndSorted);
|
||||
}
|
||||
//------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
//Full text searchable items
|
||||
private static List<SearchItem> searchItems
|
||||
{
|
||||
get
|
||||
{
|
||||
List<SearchItem> l = new List<SearchItem>();
|
||||
|
||||
l.Add(new SearchItem { table = "customer", field = "name", getCustomer_id = false, getSite_id = false });
|
||||
l.Add(new SearchItem { table = "customer", field = "notes", getCustomer_id = false, getSite_id = false });
|
||||
//case 3607
|
||||
l.Add(new SearchItem { table = "customer", field = "supportEmail", getCustomer_id = false, getSite_id = false });
|
||||
l.Add(new SearchItem { table = "customer", field = "adminEmail", getCustomer_id = false, getSite_id = false });
|
||||
l.Add(new SearchItem { table = "license", field = "code", getCustomer_id = true, getSite_id = false });
|
||||
l.Add(new SearchItem { table = "license", field = "key", getCustomer_id = true, getSite_id = false });
|
||||
l.Add(new SearchItem { table = "license", field = "regto", getCustomer_id = true, getSite_id = false });
|
||||
l.Add(new SearchItem { table = "license", field = "email", getCustomer_id = true, getSite_id = false });
|
||||
|
||||
l.Add(new SearchItem { table = "purchase", field = "productCode", getCustomer_id = true, getSite_id = true });
|
||||
l.Add(new SearchItem { table = "purchase", field = "salesOrderNumber", getCustomer_id = true, getSite_id = true });
|
||||
l.Add(new SearchItem { table = "purchase", field = "notes", getCustomer_id = true, getSite_id = true });
|
||||
l.Add(new SearchItem { table = "site", field = "name", getCustomer_id = true, getSite_id = false });
|
||||
l.Add(new SearchItem { table = "site", field = "notes", getCustomer_id = true, getSite_id = false });
|
||||
|
||||
return l;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private class SearchItem
|
||||
{
|
||||
public string table;
|
||||
public string field;
|
||||
public bool getCustomer_id;
|
||||
public bool getSite_id;
|
||||
}
|
||||
|
||||
private class resultItem
|
||||
{
|
||||
public long id;
|
||||
public long site_id;
|
||||
public long customerId;
|
||||
public string obj;//Table name basically
|
||||
public string name;//always customer name
|
||||
public string fld;//the name of the field the value was found in
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}//eoc
|
||||
}//eons
|
||||
|
||||
212
Controllers/SiteController.cs
Normal file
@@ -0,0 +1,212 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using rockfishCore.Models;
|
||||
|
||||
namespace rockfishCore.Controllers
|
||||
{
|
||||
[Produces("application/json")]
|
||||
[Route("api/Site")]
|
||||
[Authorize]
|
||||
public class SiteController : Controller
|
||||
{
|
||||
private readonly rockfishContext _context;
|
||||
|
||||
public SiteController(rockfishContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
|
||||
//Get api/site/77/purchases
|
||||
[HttpGet("{id}/purchases")]
|
||||
public IEnumerable<Purchase> GetPurchases([FromRoute] long id)
|
||||
{
|
||||
var l = _context.Purchase
|
||||
.Where(b => b.SiteId.Equals(id))
|
||||
.OrderByDescending(b => b.PurchaseDate)
|
||||
.ToList();
|
||||
return l;
|
||||
}
|
||||
|
||||
// //Get api/site/77/activepurchases
|
||||
// [HttpGet("{id}/activepurchases")]
|
||||
// public IEnumerable<Purchase> GetActivePurchases([FromRoute] long id)
|
||||
// {
|
||||
// var l = _context.Purchase
|
||||
// .Where(b => b.SiteId.Equals(id))
|
||||
// .Where(b => b.CancelDate==null)
|
||||
// .OrderByDescending(b => b.PurchaseDate)
|
||||
// .ToList();
|
||||
// return l;
|
||||
// }
|
||||
|
||||
//Get api/site/77/activepurchases
|
||||
[HttpGet("{id}/activepurchases")]
|
||||
public IEnumerable<dtoNameIdItem> GetActivePurchases([FromRoute] long id)
|
||||
{
|
||||
var res = from c in _context.Purchase
|
||||
.Where(c => c.SiteId.Equals(id))
|
||||
.Where(c => c.CancelDate==null)
|
||||
.OrderByDescending(c => c.PurchaseDate)
|
||||
select new dtoNameIdItem
|
||||
{
|
||||
id = c.Id,
|
||||
name = c.Name
|
||||
};
|
||||
return res.ToList();
|
||||
}
|
||||
|
||||
// //Get api/site/77/incidents
|
||||
// [HttpGet("{id}/incidents")]
|
||||
// public IEnumerable<Incident> GetIncidents([FromRoute] long id)
|
||||
// {
|
||||
// var l = _context.Incident
|
||||
// .Where(b => b.SiteId.Equals(id))
|
||||
// .OrderByDescending(b => b.Id)//no single suitable date to order by
|
||||
// .ToList();
|
||||
// return l;
|
||||
// }
|
||||
|
||||
|
||||
// //Get api/site/77/trials
|
||||
// [HttpGet("{id}/trials")]
|
||||
// public IEnumerable<Trial> GetTrials([FromRoute] long id)
|
||||
// {
|
||||
// var l = _context.Trial
|
||||
// .Where(b => b.SiteId.Equals(id))
|
||||
// .OrderByDescending(b => b.Id)
|
||||
// .ToList();
|
||||
// return l;
|
||||
// }
|
||||
|
||||
//Get api/site/77/name
|
||||
[HttpGet("{id}/name")]
|
||||
public async Task<IActionResult> GetName([FromRoute] long id)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
var ret = await _context.Site
|
||||
.Select(r => new { r.Id, r.Name, r.CustomerId })
|
||||
.Where(r => r.Id == id)
|
||||
.FirstAsync();
|
||||
return Ok(ret);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// GET: api/Site
|
||||
[HttpGet]
|
||||
public IEnumerable<Site> GetSite()
|
||||
{
|
||||
return _context.Site;
|
||||
}
|
||||
|
||||
// GET: api/Site/5
|
||||
[HttpGet("{id}")]
|
||||
public async Task<IActionResult> GetSite([FromRoute] long id)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
var site = await _context.Site.SingleOrDefaultAsync(m => m.Id == id);
|
||||
|
||||
if (site == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
return Ok(site);
|
||||
}
|
||||
|
||||
// PUT: api/Site/5
|
||||
[HttpPut("{id}")]
|
||||
public async Task<IActionResult> PutSite([FromRoute] long id, [FromBody] Site site)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
if (id != site.Id)
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
_context.Entry(site).State = EntityState.Modified;
|
||||
|
||||
try
|
||||
{
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
catch (DbUpdateConcurrencyException)
|
||||
{
|
||||
if (!SiteExists(id))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
// POST: api/Site
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> PostSite([FromBody] Site site)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
_context.Site.Add(site);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return CreatedAtAction("GetSite", new { id = site.Id }, site);
|
||||
}
|
||||
|
||||
// DELETE: api/Site/5
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<IActionResult> DeleteSite([FromRoute] long id)
|
||||
{
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
|
||||
var site = await _context.Site.SingleOrDefaultAsync(m => m.Id == id);
|
||||
|
||||
if (site == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
|
||||
_context.Site.Remove(site);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return Ok(site);
|
||||
}
|
||||
|
||||
private bool SiteExists(long id)
|
||||
{
|
||||
return _context.Site.Any(e => e.Id == id);
|
||||
}
|
||||
}
|
||||
}
|
||||
128
Controllers/SubscriptionController.cs
Normal file
@@ -0,0 +1,128 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using rockfishCore.Models;
|
||||
using rockfishCore.Util;
|
||||
using System.Linq;
|
||||
using System;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace rockfishCore.Controllers
|
||||
{
|
||||
[Produces("application/json")]
|
||||
[Route("api/subscription")]
|
||||
[Authorize]
|
||||
public class SubscriptionController : Controller
|
||||
{
|
||||
private readonly rockfishContext _context;
|
||||
private readonly IConfiguration _configuration;
|
||||
public SubscriptionController(rockfishContext context, IConfiguration configuration)//these two are injected, see startup.cs
|
||||
{
|
||||
_context = context;
|
||||
_configuration = configuration;
|
||||
}
|
||||
|
||||
//**************************
|
||||
//TODO: ASYNCIFY ALL OF THIS
|
||||
|
||||
///////////////////////////////////////////////////////
|
||||
//Get notification list for expiring subscriptions
|
||||
//
|
||||
[HttpGet("notifylist")]
|
||||
public JsonResult Get()
|
||||
{
|
||||
var customerList = _context.Customer.Select(p => new { p.Id, p.Name });
|
||||
|
||||
/*Query: purchases with no renewal warning flag set, not cancelled and that are expiring subs in next 30 day window grouped by customer and then by purchase date
|
||||
Take that list show in UI, with button beside each customer group, press button, it generates a renewal warning email
|
||||
and puts it into the drafts folder, tags all the purchases with renewal warning sent true. */
|
||||
|
||||
//from three days ago to a month from now
|
||||
long windowStart = DateUtil.DateToEpoch(DateTime.Now.AddDays(-3));
|
||||
long windowEnd = DateUtil.DateToEpoch(DateTime.Now.AddMonths(1));
|
||||
|
||||
var rawExpiresList = _context.Purchase
|
||||
.Where(p => p.RenewNoticeSent == false)
|
||||
.Where(p => p.ExpireDate != null)
|
||||
.Where(p => p.ExpireDate > windowStart)
|
||||
.Where(p => p.ExpireDate < windowEnd)
|
||||
.Where(p => p.CancelDate == null)
|
||||
.OrderBy(p => p.CustomerId)
|
||||
.Select(p => new { id = p.Id, name = p.Name, customerId = p.CustomerId })
|
||||
.ToList();
|
||||
|
||||
|
||||
|
||||
//Initiate an empty list for return
|
||||
List<subnotifyResultItem> retList = new List<subnotifyResultItem>();
|
||||
//Bail if nothing to see here
|
||||
if (rawExpiresList.Count < 1)
|
||||
{
|
||||
return Json(retList);
|
||||
}
|
||||
|
||||
|
||||
long lastCustomerId = -1;
|
||||
subnotifyResultItem sn = new subnotifyResultItem();
|
||||
|
||||
//flatten the list and project it into return format
|
||||
foreach (var i in rawExpiresList)
|
||||
{
|
||||
//first loop?
|
||||
if (lastCustomerId == -1) lastCustomerId = i.customerId;
|
||||
|
||||
//New customer?
|
||||
if (i.customerId != lastCustomerId)
|
||||
{
|
||||
sn.purchasenames = sn.purchasenames.TrimStart(',').Trim();
|
||||
retList.Add(sn);
|
||||
sn = new subnotifyResultItem();
|
||||
lastCustomerId = i.customerId;
|
||||
}
|
||||
|
||||
if (sn.customerId == 0)
|
||||
{
|
||||
//get the full data
|
||||
sn.Customer = customerList.First(p => p.Id == i.customerId).Name;
|
||||
sn.customerId = i.customerId;
|
||||
sn.purchaseidlist = new List<long>();
|
||||
}
|
||||
sn.purchasenames += ", " + i.name;
|
||||
sn.purchaseidlist.Add(i.id);
|
||||
}
|
||||
//Add the last one
|
||||
sn.purchasenames = sn.purchasenames.TrimStart(',').Trim();
|
||||
retList.Add(sn);
|
||||
|
||||
return Json(retList);
|
||||
}
|
||||
|
||||
//dto classes for route
|
||||
public class subnotifyResultItem
|
||||
{
|
||||
public long id;
|
||||
public string Customer;
|
||||
public string purchasenames;
|
||||
public List<long> purchaseidlist;
|
||||
public long customerId;
|
||||
|
||||
}
|
||||
|
||||
//SEND SELECTED RENEWAL NOTIFICATIONS
|
||||
[HttpPost("sendnotify")]
|
||||
public JsonResult Generate([FromBody] List<long> purchaseidlist)
|
||||
{
|
||||
return Json(RfNotify.SendSubscriptionRenewalNotice(_context, purchaseidlist));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
|
||||
}//eoc
|
||||
}//eons
|
||||
|
||||
137
Controllers/TextTemplateController.cs
Normal file
@@ -0,0 +1,137 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using rockfishCore.Models;
|
||||
|
||||
namespace rockfishCore.Controllers
|
||||
{
|
||||
[Produces("application/json")]
|
||||
[Route("api/TextTemplate")]
|
||||
[Authorize]
|
||||
public class TextTemplateController : Controller
|
||||
{
|
||||
private readonly rockfishContext _context;
|
||||
|
||||
public TextTemplateController(rockfishContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
|
||||
//ui ordered list
|
||||
[HttpGet("list")]
|
||||
public IEnumerable<TextTemplate> GetList()
|
||||
{
|
||||
var tt = from s in _context.TextTemplate select s;
|
||||
tt = tt.OrderBy(s => s.Name);
|
||||
return tt;
|
||||
}
|
||||
|
||||
// GET: api/TextTemplate
|
||||
[HttpGet]
|
||||
public IEnumerable<TextTemplate> GetTextTemplate()
|
||||
{
|
||||
return _context.TextTemplate;
|
||||
}
|
||||
|
||||
// GET: api/TextTemplate/5
|
||||
[HttpGet("{id}")]
|
||||
public async Task<IActionResult> GetTextTemplate([FromRoute] long id)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
var TextTemplate = await _context.TextTemplate.SingleOrDefaultAsync(m => m.Id == id);
|
||||
|
||||
if (TextTemplate == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
return Ok(TextTemplate);
|
||||
}
|
||||
|
||||
// PUT: api/TextTemplate/5
|
||||
[HttpPut("{id}")]
|
||||
public async Task<IActionResult> PutTextTemplate([FromRoute] long id, [FromBody] TextTemplate TextTemplate)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
if (id != TextTemplate.Id)
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
_context.Entry(TextTemplate).State = EntityState.Modified;
|
||||
|
||||
try
|
||||
{
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
catch (DbUpdateConcurrencyException)
|
||||
{
|
||||
if (!TextTemplateExists(id))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
// POST: api/TextTemplate
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> PostTextTemplate([FromBody] TextTemplate TextTemplate)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
_context.TextTemplate.Add(TextTemplate);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return CreatedAtAction("GetTextTemplate", new { id = TextTemplate.Id }, TextTemplate);
|
||||
}
|
||||
|
||||
// DELETE: api/TextTemplate/5
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<IActionResult> DeleteTextTemplate([FromRoute] long id)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
var TextTemplate = await _context.TextTemplate.SingleOrDefaultAsync(m => m.Id == id);
|
||||
if (TextTemplate == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
_context.TextTemplate.Remove(TextTemplate);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return Ok(TextTemplate);
|
||||
}
|
||||
|
||||
private bool TextTemplateExists(long id)
|
||||
{
|
||||
return _context.TextTemplate.Any(e => e.Id == id);
|
||||
}
|
||||
}
|
||||
}
|
||||
172
Controllers/UserController.cs
Normal file
@@ -0,0 +1,172 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using rockfishCore.Models;
|
||||
using rockfishCore.Util;
|
||||
|
||||
namespace rockfishCore.Controllers
|
||||
{
|
||||
[Produces("application/json")]
|
||||
[Route("api/User")]
|
||||
[Authorize]
|
||||
public class UserController : Controller
|
||||
{
|
||||
private readonly rockfishContext _context;
|
||||
|
||||
public UserController(rockfishContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
// GET: api/User
|
||||
[HttpGet]
|
||||
public IEnumerable<User> GetUser()
|
||||
{
|
||||
return _context.User;
|
||||
}
|
||||
|
||||
// GET: api/User/5
|
||||
[HttpGet("{id}")]
|
||||
public async Task<IActionResult> GetUser([FromRoute] long id)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
var user = await _context.User.SingleOrDefaultAsync(m => m.Id == id);
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
return Ok(user);
|
||||
}
|
||||
|
||||
// PUT: api/User/5
|
||||
[HttpPut("{id}")]
|
||||
public async Task<IActionResult> PutUser([FromRoute] long id, [FromBody] User user)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
if (id != user.Id)
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
_context.Entry(user).State = EntityState.Modified;
|
||||
|
||||
try
|
||||
{
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
catch (DbUpdateConcurrencyException)
|
||||
{
|
||||
if (!UserExists(id))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
// POST: api/User
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> PostUser([FromBody] User user)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
_context.User.Add(user);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return CreatedAtAction("GetUser", new { id = user.Id }, user);
|
||||
}
|
||||
|
||||
// DELETE: api/User/5
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<IActionResult> DeleteUser([FromRoute] long id)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
var user = await _context.User.SingleOrDefaultAsync(m => m.Id == id);
|
||||
if (user == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
_context.User.Remove(user);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return Ok(user);
|
||||
}
|
||||
|
||||
private bool UserExists(long id)
|
||||
{
|
||||
return _context.User.Any(e => e.Id == id);
|
||||
}
|
||||
|
||||
|
||||
//------------
|
||||
|
||||
[HttpPost("{id}/changepassword")]
|
||||
public JsonResult ChangePassword([FromRoute] long id, [FromBody] dtoChangePassword cp)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(cp.oldpassword) || string.IsNullOrWhiteSpace(cp.newpassword))
|
||||
{
|
||||
return Json(new { msg = "UserController:ChangePassword->A required value is missing", error = 1 });
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var user = _context.User.SingleOrDefault(m => m.Id == id);
|
||||
string oldhash = Hasher.hash(user.Salt, cp.oldpassword);
|
||||
if (oldhash == user.Password)
|
||||
{
|
||||
string newhash = Hasher.hash(user.Salt, cp.newpassword);
|
||||
user.Password = newhash;
|
||||
_context.User.Update(user);
|
||||
_context.SaveChanges();
|
||||
return Json(new { msg = "success", ok = 1 });
|
||||
}
|
||||
else
|
||||
{
|
||||
return Json(new { msg = "UserController:ChangePassword->current password does not match", error = 1 });
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Json(new { msg = ex.Message, error = 1 });
|
||||
}
|
||||
}
|
||||
|
||||
public class dtoChangePassword
|
||||
{
|
||||
public string oldpassword;
|
||||
public string newpassword;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
37
Models/Customer.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace rockfishCore.Models
|
||||
{
|
||||
public partial class Customer
|
||||
{
|
||||
public Customer()
|
||||
{
|
||||
//Contact = new HashSet<Contact>();
|
||||
// Incident = new HashSet<Incident>();
|
||||
// Notification = new HashSet<Notification>();
|
||||
Purchase = new HashSet<Purchase>();
|
||||
Site = new HashSet<Site>();
|
||||
// Trial = new HashSet<Trial>();
|
||||
}
|
||||
|
||||
public long Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Country { get; set; }
|
||||
public string StateProvince { get; set; }
|
||||
public string AffiliateNumber { get; set; }
|
||||
public bool DoNotContact { get; set; }
|
||||
public string Notes { get; set; }
|
||||
public bool Active { get; set; }
|
||||
|
||||
public string SupportEmail { get; set; }
|
||||
public string AdminEmail { get; set; }
|
||||
|
||||
// public virtual ICollection<Contact> Contact { get; set; }
|
||||
// public virtual ICollection<Incident> Incident { get; set; }
|
||||
// public virtual ICollection<Notification> Notification { get; set; }
|
||||
public virtual ICollection<Purchase> Purchase { get; set; }
|
||||
public virtual ICollection<Site> Site { get; set; }
|
||||
// public virtual ICollection<Trial> Trial { get; set; }
|
||||
}
|
||||
}
|
||||
21
Models/License.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
|
||||
namespace rockfishCore.Models
|
||||
{
|
||||
public partial class License
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public long CustomerId { get; set; }
|
||||
public long DtCreated { get; set; }
|
||||
public string RegTo { get; set; }
|
||||
public string Key { get; set; }
|
||||
public string Email { get; set; }
|
||||
public string Code { get; set; }
|
||||
public string FetchFrom { get; set; }
|
||||
public long? DtFetched { get; set; }
|
||||
public bool Fetched { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
18
Models/LicenseTemplates.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace rockfishCore.Models
|
||||
{
|
||||
public partial class LicenseTemplates
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public string FullNew { get; set; }
|
||||
public string FullAddOn { get; set; }
|
||||
public string FullTrial { get; set; }
|
||||
public string FullTrialGreeting { get; set; }
|
||||
public string LiteNew { get; set; }
|
||||
public string LiteAddOn { get; set; }
|
||||
public string LiteTrial { get; set; }
|
||||
public string LiteTrialGreeting { get; set; }
|
||||
}
|
||||
}
|
||||
15
Models/Product.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace rockfishCore.Models
|
||||
{
|
||||
public partial class Product
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string ProductCode { get; set; }
|
||||
public long Price { get; set; }//in cents
|
||||
public long RenewPrice { get; set; }//in cents
|
||||
|
||||
}
|
||||
}
|
||||
29
Models/Purchase.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace rockfishCore.Models
|
||||
{
|
||||
public partial class Purchase
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public long CustomerId { get; set; }
|
||||
public long SiteId { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string VendorName { get; set; }
|
||||
public string Email { get; set; }
|
||||
public string ProductCode { get; set; }
|
||||
public string SalesOrderNumber { get; set; }
|
||||
public long PurchaseDate { get; set; }
|
||||
public long? ExpireDate { get; set; }
|
||||
public long? CancelDate { get; set; }
|
||||
public string CouponCode { get; set; }
|
||||
public string Notes { get; set; }
|
||||
//schema v2
|
||||
public bool RenewNoticeSent { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public virtual Customer Customer { get; set; }
|
||||
public virtual Site Site { get; set; }
|
||||
}
|
||||
}
|
||||
29
Models/RfCase.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace rockfishCore.Models
|
||||
{
|
||||
public partial class RfCase
|
||||
{
|
||||
|
||||
// public RfCase()
|
||||
// {
|
||||
// RfCaseBlob = new HashSet<RfCaseBlob>();
|
||||
// // RfCaseProject = new HashSet<RfCaseProject>();
|
||||
|
||||
// }
|
||||
public long Id { get; set; }
|
||||
public string Title { get; set; }
|
||||
public long RfCaseProjectId { get; set; }
|
||||
public int Priority { get; set; }
|
||||
public string Notes { get; set; }
|
||||
public long? DtCreated { get; set; }
|
||||
public long? DtClosed { get; set; }
|
||||
public string ReleaseVersion { get; set; }
|
||||
public string ReleaseNotes { get; set; }
|
||||
|
||||
|
||||
// public virtual RfCaseProject RfCaseProject { get; set; }
|
||||
//public virtual ICollection<RfCaseBlob> RfCaseBlob { get; set; }
|
||||
}
|
||||
}
|
||||
14
Models/RfCaseBlob.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace rockfishCore.Models
|
||||
{
|
||||
public partial class RfCaseBlob
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public long? RfCaseId { get; set; }
|
||||
public string Name { get; set; }
|
||||
public byte[] File { get; set; }
|
||||
public virtual RfCase RfCase { get; set; }
|
||||
}
|
||||
}
|
||||
19
Models/RfCaseProject.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace rockfishCore.Models
|
||||
{
|
||||
public partial class RfCaseProject
|
||||
{
|
||||
|
||||
// public RfCaseProject()
|
||||
// {
|
||||
// RfCase = new HashSet<RfCase>();
|
||||
// }
|
||||
|
||||
public long Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
|
||||
// public virtual ICollection<RfCase> RfCase { get; set; }
|
||||
}
|
||||
}
|
||||
39
Models/Site.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace rockfishCore.Models
|
||||
{
|
||||
public partial class Site
|
||||
{
|
||||
public Site()
|
||||
{
|
||||
// Incident = new HashSet<Incident>();
|
||||
Purchase = new HashSet<Purchase>();
|
||||
//TrialNavigation = new HashSet<Trial>();
|
||||
}
|
||||
|
||||
public long Id { get; set; }
|
||||
|
||||
public long CustomerId { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Country { get; set; }
|
||||
public string StateProvince { get; set; }
|
||||
public bool Trial { get; set; }
|
||||
public long? TrialStartDate { get; set; }
|
||||
public string TrialNotes { get; set; }
|
||||
public bool Networked { get; set; }
|
||||
public bool Hosted { get; set; }
|
||||
public string HostName { get; set; }
|
||||
public long? HostingStartDate { get; set; }
|
||||
public long? HostingEndDate { get; set; }
|
||||
public string DbType { get; set; }
|
||||
public string ServerOs { get; set; }
|
||||
public string ServerBits { get; set; }
|
||||
public string Notes { get; set; }
|
||||
|
||||
// public virtual ICollection<Incident> Incident { get; set; }
|
||||
public virtual ICollection<Purchase> Purchase { get; set; }
|
||||
// public virtual ICollection<Trial> TrialNavigation { get; set; }
|
||||
public virtual Customer Customer { get; set; }
|
||||
}
|
||||
}
|
||||
12
Models/TextTemplate.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace rockfishCore.Models
|
||||
{
|
||||
public partial class TextTemplate
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Template { get; set; }
|
||||
}
|
||||
}
|
||||
17
Models/User.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace rockfishCore.Models
|
||||
{
|
||||
public partial class User
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Login { get; set; }
|
||||
public string Password { get; set; }
|
||||
public string Salt { get; set; }
|
||||
public string DlKey { get; set; }
|
||||
public long? DlKeyExp { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
68
Models/dtoKeyOptions.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace rockfishCore.Models
|
||||
{
|
||||
public class dtoKeyOptions
|
||||
{
|
||||
|
||||
public bool keyWillLockout { get; set; }
|
||||
public long? lockoutDate { get; set; }
|
||||
public bool isLite { get; set; }
|
||||
public string licenseType { get; set; }
|
||||
public string registeredTo { get; set; }
|
||||
public int users { get; set; }
|
||||
public long? supportExpiresDate { get; set; }
|
||||
public bool wbi { get; set; }
|
||||
public long? wbiSupportExpiresDate { get; set; }
|
||||
public bool mbi { get; set; }
|
||||
public long? mbiSupportExpiresDate { get; set; }
|
||||
public bool ri { get; set; }
|
||||
public long? riSupportExpiresDate { get; set; }
|
||||
public bool qbi { get; set; }
|
||||
public long? qbiSupportExpiresDate { get; set; }
|
||||
public bool qboi { get; set; }
|
||||
public long? qboiSupportExpiresDate { get; set; }
|
||||
public bool pti { get; set; }
|
||||
public long? ptiSupportExpiresDate { get; set; }
|
||||
public bool quickNotification { get; set; }
|
||||
public long? quickNotificationSupportExpiresDate { get; set; }
|
||||
public bool exportToXls { get; set; }
|
||||
public long? exportToXlsSupportExpiresDate { get; set; }
|
||||
public bool outlookSchedule { get; set; }
|
||||
public long? outlookScheduleSupportExpiresDate { get; set; }
|
||||
public bool oli { get; set; }
|
||||
public long? oliSupportExpiresDate { get; set; }
|
||||
public bool importExportCSVDuplicate { get; set; }
|
||||
public long? importExportCSVDuplicateSupportExpiresDate { get; set; }
|
||||
public string key { get; set; }
|
||||
|
||||
//case 3233
|
||||
public string emailAddress{get;set;}
|
||||
public string fetchCode{get;set;}
|
||||
public long customerId{get;set;}//always a valid ID or zero for a trial (no customer) key
|
||||
|
||||
//dynamically generated, not from request
|
||||
public string authorizedUserKeyGeneratorStamp { get; set; }
|
||||
public DateTime installByDate{get;set;}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
|
||||
{"keyWillLockout":"false","lockoutDate":"2017-08-10","isLite":"false","licenseType":"new","registeredTo":"MyTestCompany","users":"1",
|
||||
"supportExpiresDate":"2018-07-10","wbi":"false","wbiSupportExpiresDate":"2018-07-10","mbi":"false","mbiSupportExpiresDate":"2018-07-10",
|
||||
"ri":"false","riSupportExpiresDate":"2018-07-10","qbi":"false","qbiSupportExpiresDate":"2018-07-10","qboi":"false",
|
||||
"qboiSupportExpiresDate":"2018-07-10","pti":"false","ptiSupportExpiresDate":"2018-07-10",
|
||||
"quickNotification":"false","quickNotificationSupportExpiresDate":"2018-07-10","exportToXls":"false","exportToXlsSupportExpiresDate":"2018-07-10",
|
||||
"outlookSchedule":"false","outlookScheduleSupportExpiresDate":"2018-07-10","oli":"false","oliSupportExpiresDate":"2018-07-10",
|
||||
"importExportCSVDuplicate":"false","importExportCSVDuplicateSupportExpiresDate":"2018-07-10","key":""}
|
||||
*/
|
||||
18
Models/dtoKeyRequestResponse.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace rockfishCore.Models
|
||||
{
|
||||
//DTO class for license request key response form submission handling
|
||||
public class dtoKeyRequestResponse
|
||||
{
|
||||
public string greeting { get; set; }
|
||||
public string greetingReplySubject { get; set; }
|
||||
public string keycode { get; set; }
|
||||
public string request { get; set; }
|
||||
public string requestReplyToAddress { get; set; }
|
||||
public string requestFromReplySubject { get; set; }
|
||||
public uint request_email_uid { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
15
Models/dtoLicenseListItem.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
namespace rockfishCore.Models
|
||||
{
|
||||
//Used to populate list routes for return
|
||||
public class dtoLicenseListItem
|
||||
{
|
||||
public long id;
|
||||
public long created;
|
||||
public string regto;
|
||||
public bool fetched;
|
||||
public bool trial;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
10
Models/dtoNameIdActiveItem.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace rockfishCore.Models
|
||||
{
|
||||
//Used to populate list routes for return
|
||||
public class dtoNameIdActiveItem
|
||||
{
|
||||
public bool active;
|
||||
public long id;
|
||||
public string name;
|
||||
}
|
||||
}
|
||||
18
Models/dtoNameIdChildrenItem.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace rockfishCore.Models
|
||||
{
|
||||
//Used to populate list routes for return
|
||||
public class dtoNameIdChildrenItem
|
||||
{
|
||||
public dtoNameIdChildrenItem()
|
||||
{
|
||||
children = new List<dtoNameIdItem>();
|
||||
}
|
||||
public long id;
|
||||
public string name;
|
||||
public List<dtoNameIdItem> children;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
9
Models/dtoNameIdItem.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace rockfishCore.Models
|
||||
{
|
||||
//Used to populate list routes for return
|
||||
public class dtoNameIdItem
|
||||
{
|
||||
public long id;
|
||||
public string name;
|
||||
}
|
||||
}
|
||||
532
Models/rockfishContext.cs
Normal file
@@ -0,0 +1,532 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using rockfishCore.Util;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace rockfishCore.Models
|
||||
{
|
||||
public partial class rockfishContext : DbContext
|
||||
{
|
||||
|
||||
|
||||
public virtual DbSet<Customer> Customer { get; set; }
|
||||
public virtual DbSet<LicenseTemplates> LicenseTemplates { get; set; }
|
||||
public virtual DbSet<Purchase> Purchase { get; set; }
|
||||
public virtual DbSet<Site> Site { get; set; }
|
||||
public virtual DbSet<User> User { get; set; }
|
||||
|
||||
//schema 2
|
||||
public virtual DbSet<Product> Product { get; set; }
|
||||
|
||||
//schema 4
|
||||
public virtual DbSet<TextTemplate> TextTemplate { get; set; }
|
||||
|
||||
//schema 6
|
||||
public virtual DbSet<RfCase> RfCase { get; set; }
|
||||
public virtual DbSet<RfCaseBlob> RfCaseBlob { get; set; }
|
||||
public virtual DbSet<RfCaseProject> RfCaseProject { get; set; }
|
||||
|
||||
//schema 10 case 3233
|
||||
public virtual DbSet<License> License { get; set; }
|
||||
|
||||
|
||||
|
||||
//Note: had to add this constructor to work with the code in startup.cs that gets the connection string from the appsettings.json file
|
||||
//and commented out the above on configuring
|
||||
public rockfishContext(DbContextOptions<rockfishContext> options) : base(options)
|
||||
{ }
|
||||
|
||||
//************************************************
|
||||
//add logging to diagnose sql errors
|
||||
//TODO:comment out this entire method in production
|
||||
// protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||
// {
|
||||
// var lf = new LoggerFactory();
|
||||
// lf.AddProvider(new rfLoggerProvider());
|
||||
// optionsBuilder.UseLoggerFactory(lf);
|
||||
// optionsBuilder.EnableSensitiveDataLogging();//show parameters in log text
|
||||
// }
|
||||
//************************************************
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.Entity<Customer>(entity =>
|
||||
{
|
||||
entity.ToTable("customer");
|
||||
|
||||
entity.Property(e => e.Id).HasColumnName("id");
|
||||
|
||||
entity.Property(e => e.AffiliateNumber)
|
||||
.HasColumnName("affiliateNumber")
|
||||
.HasColumnType("text");
|
||||
|
||||
entity.Property(e => e.Country)
|
||||
.HasColumnName("country")
|
||||
.HasColumnType("text");
|
||||
|
||||
entity.Property(e => e.DoNotContact)
|
||||
.IsRequired()
|
||||
.HasColumnName("doNotContact")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
entity.Property(e => e.Name)
|
||||
.IsRequired()
|
||||
.HasColumnName("name")
|
||||
.HasColumnType("text");
|
||||
|
||||
entity.Property(e => e.Notes)
|
||||
.HasColumnName("notes")
|
||||
.HasColumnType("text");
|
||||
|
||||
entity.Property(e => e.StateProvince)
|
||||
.HasColumnName("stateProvince")
|
||||
.HasColumnType("text");
|
||||
|
||||
entity.Property(e => e.Active)
|
||||
.IsRequired()
|
||||
.HasColumnName("active")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
entity.Property(e => e.AdminEmail)
|
||||
.HasColumnName("adminEmail")
|
||||
.HasColumnType("text");
|
||||
|
||||
entity.Property(e => e.SupportEmail)
|
||||
.HasColumnName("supportEmail")
|
||||
.HasColumnType("text");
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
modelBuilder.Entity<LicenseTemplates>(entity =>
|
||||
{
|
||||
entity.ToTable("licenseTemplates");
|
||||
|
||||
entity.Property(e => e.Id).HasColumnName("id");
|
||||
|
||||
entity.Property(e => e.FullAddOn)
|
||||
.HasColumnName("fullAddOn")
|
||||
.HasColumnType("text");
|
||||
|
||||
entity.Property(e => e.FullNew)
|
||||
.HasColumnName("fullNew")
|
||||
.HasColumnType("text");
|
||||
|
||||
entity.Property(e => e.FullTrial)
|
||||
.HasColumnName("fullTrial")
|
||||
.HasColumnType("text");
|
||||
|
||||
entity.Property(e => e.FullTrialGreeting)
|
||||
.HasColumnName("fullTrialGreeting")
|
||||
.HasColumnType("text");
|
||||
|
||||
entity.Property(e => e.LiteAddOn)
|
||||
.HasColumnName("liteAddOn")
|
||||
.HasColumnType("text");
|
||||
|
||||
entity.Property(e => e.LiteNew)
|
||||
.HasColumnName("liteNew")
|
||||
.HasColumnType("text");
|
||||
|
||||
entity.Property(e => e.LiteTrial)
|
||||
.HasColumnName("liteTrial")
|
||||
.HasColumnType("text");
|
||||
|
||||
entity.Property(e => e.LiteTrialGreeting)
|
||||
.HasColumnName("liteTrialGreeting")
|
||||
.HasColumnType("text");
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
modelBuilder.Entity<Purchase>(entity =>
|
||||
{
|
||||
entity.ToTable("purchase");
|
||||
|
||||
entity.Property(e => e.Id).HasColumnName("id");
|
||||
|
||||
entity.Property(e => e.CancelDate)
|
||||
.HasColumnName("cancelDate")
|
||||
.HasColumnType("integer");
|
||||
|
||||
entity.Property(e => e.CouponCode)
|
||||
.HasColumnName("couponCode")
|
||||
.HasColumnType("text");
|
||||
|
||||
entity.Property(e => e.CustomerId)
|
||||
.HasColumnName("customer_id")
|
||||
.HasColumnType("integer")
|
||||
.IsRequired();
|
||||
|
||||
entity.Property(e => e.Email)
|
||||
.HasColumnName("email")
|
||||
.HasColumnType("text");
|
||||
|
||||
entity.Property(e => e.ExpireDate)
|
||||
.HasColumnName("expireDate")
|
||||
.HasColumnType("integer");
|
||||
|
||||
entity.Property(e => e.Name)
|
||||
.IsRequired()
|
||||
.HasColumnName("name")
|
||||
.HasColumnType("text");
|
||||
|
||||
entity.Property(e => e.Notes)
|
||||
.HasColumnName("notes")
|
||||
.HasColumnType("text");
|
||||
|
||||
entity.Property(e => e.ProductCode)
|
||||
.HasColumnName("productCode")
|
||||
.HasColumnType("text");
|
||||
|
||||
entity.Property(e => e.PurchaseDate)
|
||||
.HasColumnName("purchaseDate")
|
||||
.HasColumnType("integer");
|
||||
|
||||
entity.Property(e => e.SalesOrderNumber)
|
||||
.HasColumnName("salesOrderNumber")
|
||||
.HasColumnType("text");
|
||||
|
||||
entity.Property(e => e.SiteId)
|
||||
.HasColumnName("site_id")
|
||||
.HasColumnType("integer")
|
||||
.IsRequired();
|
||||
|
||||
entity.Property(e => e.VendorName)
|
||||
.HasColumnName("vendorName")
|
||||
.HasColumnType("text");
|
||||
|
||||
entity.Property(e => e.RenewNoticeSent)
|
||||
.IsRequired()
|
||||
.HasColumnName("renewNoticeSent")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
entity.HasOne(d => d.Customer)
|
||||
.WithMany(p => p.Purchase)
|
||||
.HasForeignKey(d => d.CustomerId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
entity.HasOne(d => d.Site)
|
||||
.WithMany(p => p.Purchase)
|
||||
.HasForeignKey(d => d.SiteId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<Site>(entity =>
|
||||
{
|
||||
entity.ToTable("site");
|
||||
|
||||
entity.Property(e => e.Id).HasColumnName("id");
|
||||
|
||||
entity.Property(e => e.Country)
|
||||
.HasColumnName("country")
|
||||
.HasColumnType("text");
|
||||
|
||||
entity.Property(e => e.CustomerId)
|
||||
.HasColumnName("customer_id")
|
||||
.HasColumnType("integer")
|
||||
.IsRequired();
|
||||
|
||||
entity.Property(e => e.DbType)
|
||||
.HasColumnName("dbType")
|
||||
.HasColumnType("text");
|
||||
|
||||
entity.Property(e => e.HostName)
|
||||
.HasColumnName("hostName")
|
||||
.HasColumnType("text");
|
||||
|
||||
entity.Property(e => e.Hosted)
|
||||
.IsRequired()
|
||||
.HasColumnName("hosted")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
entity.Property(e => e.HostingEndDate)
|
||||
.HasColumnName("hostingEndDate")
|
||||
.HasColumnType("integer");
|
||||
|
||||
entity.Property(e => e.HostingStartDate)
|
||||
.HasColumnName("hostingStartDate")
|
||||
.HasColumnType("integer");
|
||||
|
||||
|
||||
entity.Property(e => e.Name)
|
||||
.IsRequired()
|
||||
.HasColumnName("name")
|
||||
.HasColumnType("text");
|
||||
|
||||
entity.Property(e => e.Networked)
|
||||
.IsRequired()
|
||||
.HasColumnName("networked")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
entity.Property(e => e.Notes)
|
||||
.HasColumnName("notes")
|
||||
.HasColumnType("text");
|
||||
|
||||
entity.Property(e => e.ServerBits)
|
||||
.HasColumnName("serverBits")
|
||||
.HasColumnType("text");
|
||||
|
||||
entity.Property(e => e.ServerOs)
|
||||
.HasColumnName("serverOS")
|
||||
.HasColumnType("text");
|
||||
|
||||
entity.Property(e => e.StateProvince)
|
||||
.HasColumnName("stateProvince")
|
||||
.HasColumnType("text");
|
||||
|
||||
entity.Property(e => e.Trial)
|
||||
.IsRequired()
|
||||
.HasColumnName("trial")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
entity.Property(e => e.TrialNotes)
|
||||
.HasColumnName("trialNotes")
|
||||
.HasColumnType("text");
|
||||
|
||||
entity.Property(e => e.TrialStartDate)
|
||||
.HasColumnName("trialStartDate")
|
||||
.HasColumnType("integer");
|
||||
|
||||
entity.HasOne(d => d.Customer)
|
||||
.WithMany(p => p.Site)
|
||||
.HasForeignKey(d => d.CustomerId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
//.WillCascadeOnDelete(true);
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
modelBuilder.Entity<User>(entity =>
|
||||
{
|
||||
entity.ToTable("user");
|
||||
|
||||
entity.Property(e => e.Id).HasColumnName("id");
|
||||
|
||||
entity.Property(e => e.Login)
|
||||
.IsRequired()
|
||||
.HasColumnName("login")
|
||||
.HasColumnType("text");
|
||||
|
||||
entity.Property(e => e.Name)
|
||||
.IsRequired()
|
||||
.HasColumnName("name")
|
||||
.HasColumnType("text");
|
||||
|
||||
entity.Property(e => e.DlKey)
|
||||
.HasColumnName("dlkey")
|
||||
.HasColumnType("text");
|
||||
|
||||
entity.Property(e => e.DlKeyExp)
|
||||
.HasColumnName("dlkeyexp")
|
||||
.HasColumnType("integer");
|
||||
});
|
||||
|
||||
|
||||
|
||||
modelBuilder.Entity<Product>(entity =>
|
||||
{
|
||||
entity.ToTable("Product");
|
||||
|
||||
entity.Property(e => e.Id).HasColumnName("id");
|
||||
|
||||
entity.Property(e => e.Name)
|
||||
.IsRequired()
|
||||
.HasColumnName("name")
|
||||
.HasColumnType("text");
|
||||
|
||||
entity.Property(e => e.ProductCode)
|
||||
.HasColumnName("productCode")
|
||||
.HasColumnType("text");
|
||||
|
||||
entity.Property(e => e.Price)
|
||||
.HasColumnName("price")
|
||||
.HasColumnType("integer");
|
||||
|
||||
entity.Property(e => e.RenewPrice)
|
||||
.HasColumnName("renewPrice")
|
||||
.HasColumnType("integer");
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
modelBuilder.Entity<TextTemplate>(entity =>
|
||||
{
|
||||
entity.ToTable("TextTemplate");
|
||||
|
||||
entity.Property(e => e.Id).HasColumnName("id");
|
||||
|
||||
entity.Property(e => e.Name)
|
||||
.IsRequired()
|
||||
.HasColumnName("name")
|
||||
.HasColumnType("text");
|
||||
|
||||
entity.Property(e => e.Template)
|
||||
.HasColumnName("template")
|
||||
.HasColumnType("text");
|
||||
|
||||
});
|
||||
|
||||
|
||||
//Schema 6 case 3308
|
||||
modelBuilder.Entity<RfCaseProject>(entity =>
|
||||
{
|
||||
entity.ToTable("rfcaseproject");
|
||||
|
||||
entity.Property(e => e.Id).HasColumnName("id");
|
||||
|
||||
entity.Property(e => e.Name)
|
||||
.IsRequired()
|
||||
.HasColumnName("name")
|
||||
.HasColumnType("text");
|
||||
|
||||
// entity.HasOne(cp => cp.RfCase)
|
||||
// .WithMany();
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
modelBuilder.Entity<RfCaseBlob>(entity =>
|
||||
{
|
||||
entity.ToTable("rfcaseblob");
|
||||
|
||||
entity.Property(e => e.Id).HasColumnName("id");
|
||||
|
||||
entity.Property(e => e.RfCaseId)
|
||||
.HasColumnName("rfcaseid")
|
||||
.HasColumnType("integer")
|
||||
.IsRequired();
|
||||
|
||||
entity.Property(e => e.Name)
|
||||
.IsRequired()
|
||||
.HasColumnName("name")
|
||||
.HasColumnType("text");
|
||||
|
||||
entity.Property(e => e.File)
|
||||
.HasColumnName("file")
|
||||
.HasColumnType("blob")
|
||||
.IsRequired();
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
modelBuilder.Entity<RfCase>(entity =>
|
||||
{
|
||||
entity.ToTable("rfcase");
|
||||
|
||||
entity.Property(e => e.Id).HasColumnName("id");
|
||||
|
||||
entity.Property(e => e.Title)
|
||||
.HasColumnName("title")
|
||||
.HasColumnType("text")
|
||||
.IsRequired();
|
||||
|
||||
entity.Property(e => e.RfCaseProjectId)
|
||||
.HasColumnName("rfcaseprojectid")
|
||||
.HasColumnType("integer")
|
||||
.IsRequired();
|
||||
|
||||
entity.Property(e => e.Priority)
|
||||
.IsRequired()
|
||||
.HasColumnName("priority")
|
||||
.HasColumnType("integer");
|
||||
|
||||
entity.Property(e => e.Notes)
|
||||
.HasColumnName("notes")
|
||||
.HasColumnType("text");
|
||||
|
||||
entity.Property(e => e.DtCreated)
|
||||
.HasColumnName("dtcreated")
|
||||
.HasColumnType("integer");
|
||||
|
||||
entity.Property(e => e.DtClosed)
|
||||
.HasColumnName("dtclosed")
|
||||
.HasColumnType("integer");
|
||||
|
||||
entity.Property(e => e.ReleaseVersion)
|
||||
.HasColumnName("releaseversion")
|
||||
.HasColumnType("text");
|
||||
|
||||
entity.Property(e => e.ReleaseNotes)
|
||||
.HasColumnName("releasenotes")
|
||||
.HasColumnType("text");
|
||||
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//case 3233
|
||||
modelBuilder.Entity<License>(entity =>
|
||||
{
|
||||
entity.ToTable("license");
|
||||
|
||||
entity.Property(e => e.Id).HasColumnName("id");
|
||||
|
||||
entity.Property(e => e.DtCreated)
|
||||
.HasColumnName("dtcreated")
|
||||
.HasColumnType("integer")
|
||||
.IsRequired();
|
||||
|
||||
entity.Property(e => e.Code)
|
||||
.HasColumnName("code")
|
||||
.HasColumnType("text")
|
||||
.IsRequired();
|
||||
|
||||
entity.Property(e => e.CustomerId)
|
||||
.HasColumnName("customerid")
|
||||
.HasColumnType("integer")
|
||||
.IsRequired();
|
||||
|
||||
entity.Property(e => e.DtFetched)
|
||||
.HasColumnName("dtfetched")
|
||||
.HasColumnType("integer");
|
||||
|
||||
entity.Property(e => e.Email)
|
||||
.HasColumnName("email")
|
||||
.HasColumnType("text")
|
||||
.IsRequired();
|
||||
|
||||
entity.Property(e => e.RegTo)
|
||||
.HasColumnName("regto")
|
||||
.HasColumnType("text")
|
||||
.IsRequired();
|
||||
|
||||
entity.Property(e => e.Fetched)
|
||||
.HasColumnName("fetched")
|
||||
.HasColumnType("boolean")
|
||||
.IsRequired();
|
||||
|
||||
entity.Property(e => e.FetchFrom)
|
||||
.HasColumnName("fetchfrom")
|
||||
.HasColumnType("text");
|
||||
|
||||
entity.Property(e => e.Key)
|
||||
.HasColumnName("key")
|
||||
.HasColumnType("text")
|
||||
.IsRequired();
|
||||
|
||||
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
//-----------
|
||||
}
|
||||
}
|
||||
}
|
||||
66
Program.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
using Microsoft.AspNetCore;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
|
||||
namespace rockfishCore
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
BuildWebHost(args).Run();
|
||||
}
|
||||
|
||||
public static IWebHost BuildWebHost(string[] args) =>
|
||||
WebHost.CreateDefaultBuilder(args)
|
||||
.UseUrls("http://*:5000")
|
||||
.CaptureStartupErrors(true)
|
||||
.UseSetting("detailedErrors", "true")
|
||||
.UseKestrel()
|
||||
.UseContentRoot(System.IO.Directory.GetCurrentDirectory())
|
||||
.UseIISIntegration()
|
||||
.UseStartup<Startup>()
|
||||
.Build();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//OLD .netCORE 1.1 style
|
||||
// using System;
|
||||
// using System.Collections.Generic;
|
||||
// using System.IO;
|
||||
// using System.Linq;
|
||||
// using System.Threading.Tasks;
|
||||
// using Microsoft.AspNetCore.Builder;
|
||||
|
||||
// using Microsoft.AspNetCore;
|
||||
// using Microsoft.AspNetCore.Hosting;
|
||||
|
||||
|
||||
|
||||
// namespace rockfishCore
|
||||
// {
|
||||
// public class Program
|
||||
// {
|
||||
// public static void Main(string[] args)
|
||||
// {
|
||||
// BuildWebHost(args).Run();
|
||||
|
||||
// public static IWebHost BuildWebHost(string[] args) =>
|
||||
// WebHost.CreateDefaultBuilder(args)
|
||||
// .UseStartup<Startup>()
|
||||
// .Build();
|
||||
|
||||
// // var host = new WebHostBuilder()
|
||||
// // .UseUrls("http://*:5000")
|
||||
// // .CaptureStartupErrors(true)
|
||||
// // .UseSetting("detailedErrors", "true")
|
||||
// // .UseKestrel()
|
||||
// // .UseContentRoot(Directory.GetCurrentDirectory())
|
||||
// // .UseIISIntegration()
|
||||
// // .UseStartup<Startup>()
|
||||
// // .Build();
|
||||
|
||||
// // host.Run();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
105
Startup.cs
Normal file
@@ -0,0 +1,105 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
//manually added
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using rockfishCore.Models;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using rockfishCore.Util;
|
||||
|
||||
//added when upgrade to v2 of .netcore
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
//this comment added in windows with notepad++
|
||||
//this comment added in Linux with vscode
|
||||
namespace rockfishCore
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
public Startup(IHostingEnvironment env)
|
||||
{
|
||||
var builder = new ConfigurationBuilder()
|
||||
.SetBasePath(env.ContentRootPath)
|
||||
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
|
||||
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
|
||||
.AddEnvironmentVariables();
|
||||
Configuration = builder.Build();
|
||||
}
|
||||
|
||||
public IConfigurationRoot Configuration { get; }
|
||||
|
||||
// This method gets called by the runtime. Use this method to add services to the container.
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
|
||||
services.AddDbContext<rockfishContext>(options => options.UseSqlite(Configuration.GetConnectionString("rfdb")));
|
||||
|
||||
//Added this so that can access configuration from anywhere else
|
||||
//See authcontroller for usage
|
||||
services.AddSingleton<IConfiguration>(Configuration);
|
||||
|
||||
services.AddMvc();
|
||||
|
||||
//get the key from the appsettings.json file
|
||||
var secretKey = Configuration.GetSection("JWT").GetValue<string>("secret");
|
||||
var signingKey = new SymmetricSecurityKey(System.Text.Encoding.ASCII.GetBytes(secretKey));
|
||||
|
||||
services.AddAuthentication(options =>
|
||||
{
|
||||
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
}).AddJwtBearer(options =>
|
||||
{
|
||||
// options.AutomaticAuthenticate = true;
|
||||
// options.AutomaticChallenge = true;
|
||||
options.TokenValidationParameters = new TokenValidationParameters
|
||||
{
|
||||
// Token signature will be verified using a private key.
|
||||
ValidateIssuerSigningKey = true,
|
||||
RequireSignedTokens = true,
|
||||
IssuerSigningKey = signingKey,
|
||||
ValidateIssuer = true,
|
||||
ValidIssuer = "rockfishCore",
|
||||
ValidateAudience = false,
|
||||
// ValidAudience = "https://yourapplication.example.com",
|
||||
|
||||
// Token will only be valid if not expired yet, with 5 minutes clock skew.
|
||||
// ValidateLifetime = true,
|
||||
// RequireExpirationTime = true,
|
||||
// ClockSkew = new TimeSpan(0, 5, 0),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, rockfishContext dbContext)
|
||||
{
|
||||
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
|
||||
loggerFactory.AddDebug();
|
||||
app.UseDefaultFiles();
|
||||
app.UseStaticFiles(new StaticFileOptions
|
||||
{
|
||||
OnPrepareResponse = context =>
|
||||
{
|
||||
if (context.File.Name == "default.htm")
|
||||
{
|
||||
context.Context.Response.Headers.Add("Cache-Control", "no-cache, no-store");
|
||||
context.Context.Response.Headers.Add("Expires", "-1");
|
||||
}
|
||||
}
|
||||
});
|
||||
app.UseAuthentication();
|
||||
app.UseMvc();
|
||||
//Check schema
|
||||
RfSchema.CheckAndUpdate(dbContext);
|
||||
|
||||
}//eof
|
||||
}
|
||||
}
|
||||
10
appsettings.Development.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"Logging": {
|
||||
"IncludeScopes": false,
|
||||
"LogLevel": {
|
||||
"Default": "Warning",
|
||||
"System": "Warning",
|
||||
"Microsoft": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
15
appsettings.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"Logging": {
|
||||
"IncludeScopes": false,
|
||||
"LogLevel": {
|
||||
"Default": "Warning"
|
||||
}
|
||||
},
|
||||
"ConnectionStrings": {
|
||||
//"rfdb": "Datasource=../../../db/rockfish.sqlite;"
|
||||
"rfdb": "Datasource=./db/rockfish.sqlite;"
|
||||
},
|
||||
"JWT": {
|
||||
"secret": "-------Nothing8utTheRain!-------"
|
||||
}
|
||||
}
|
||||
28
notes/deploy.txt
Normal file
@@ -0,0 +1,28 @@
|
||||
|
||||
***********************
|
||||
HOW TO DEPLOY TO IIS
|
||||
https://stackify.com/how-to-deploy-asp-net-core-to-iis/
|
||||
|
||||
1) SET VERSION
|
||||
|
||||
SET app.api RFVERSION property
|
||||
RENAME ?RFV5.1 parameter in default.htm to the new version so all files update on mobile
|
||||
|
||||
|
||||
2) PUBLISH
|
||||
publish command line from rockfishCore folder:
|
||||
|
||||
//this will build a release version which is what we use on the server now
|
||||
dotnet publish -c Release -o ./../rfcpublish/
|
||||
|
||||
dotnet publish -f netcoreapp2.1 -c Release -o ./../rfcpublish/
|
||||
|
||||
//if need a debug version
|
||||
dotnet publish -o ./../rfcpublish/
|
||||
|
||||
3) COPY
|
||||
Copy over to production server, only need the .dll and the wwwroot folder contents,
|
||||
remember not to delete the folders on the server only replace their contents because there are Windows file permissions set
|
||||
Backup the database
|
||||
|
||||
4) Delete any test data local here
|
||||
114
notes/notes.txt
Normal file
@@ -0,0 +1,114 @@
|
||||
|
||||
|
||||
b43698c255365ee739c05ba0d42855e96c2365c76bb2f9b9eb149cec7b52174c
|
||||
*********************************************
|
||||
Updating Microsoft CODE editor
|
||||
Download the .deb file for 64 bit
|
||||
execute it
|
||||
sudo dpkg -i code_1.8.1-1482159060_i386.deb
|
||||
** UPDATE: As of October 2017 it now updates when you update Debian apt-get update etc
|
||||
|
||||
|
||||
|
||||
HANDLEBARS
|
||||
=-=-=-=-=-
|
||||
Install:
|
||||
sudo npm install -g handlebars
|
||||
|
||||
Build handlebars template:
|
||||
handlebars -m wwwroot/js/templates/> wwwroot/js/templates/templates.js
|
||||
|
||||
|
||||
|
||||
|
||||
3rd party Packages used:
|
||||
JWT JSON web token support using jose-jwt found here:
|
||||
https://jwt.io/#libraries
|
||||
|
||||
Mailkit (supposedly system.net.mail will be ported in v2.0 of .net core but for now mailkit is the shit):
|
||||
<!-- NOTE: bouncycastle needed for license gen but mailkit brings a slightly different one in so there is a conflict but just removing bouncycastle works because the license code uses teh mailkit bouncycastle-->
|
||||
<!--PackageReference Include="bouncycastle.netcore" Version="1.8.1.3" /-->
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
**************************
|
||||
//command to scaffold the sqlite db:
|
||||
dotnet ef dbcontext scaffold "Datasource=/home/john/Documents/rockfishCore/db/rockfish.sqlite" Microsoft.EntityFrameworkCore.Sqlite -o Models -f
|
||||
|
||||
//scaffold all controllers with these commands:
|
||||
dotnet aspnet-codegenerator controller -api -m rockfishCore.Models.Contact -dc rockfishCore.Models.rockfishContext -name ContactController -outDir ./Controllers
|
||||
dotnet aspnet-codegenerator controller -api -m rockfishCore.Models.Customer -dc rockfishCore.Models.rockfishContext -name CustomerController -outDir ./Controllers
|
||||
dotnet aspnet-codegenerator controller -api -m rockfishCore.Models.Incident -dc rockfishCore.Models.rockfishContext -name IncidentController -outDir ./Controllers
|
||||
dotnet aspnet-codegenerator controller -api -m rockfishCore.Models.LicenseTemplates -dc rockfishCore.Models.rockfishContext -name LicenseTemplatesController -outDir ./Controllers
|
||||
dotnet aspnet-codegenerator controller -api -m rockfishCore.Models.Notification -dc rockfishCore.Models.rockfishContext -name NotificationController -outDir ./Controllers
|
||||
dotnet aspnet-codegenerator controller -api -m rockfishCore.Models.Purchase -dc rockfishCore.Models.rockfishContext -name PurchaseController -outDir ./Controllers
|
||||
dotnet aspnet-codegenerator controller -api -m rockfishCore.Models.Site -dc rockfishCore.Models.rockfishContext -name SiteController -outDir ./Controllers
|
||||
dotnet aspnet-codegenerator controller -api -m rockfishCore.Models.Trial -dc rockfishCore.Models.rockfishContext -name TrialController -outDir ./Controllers
|
||||
dotnet aspnet-codegenerator controller -api -m rockfishCore.Models.User -dc rockfishCore.Models.rockfishContext -name UserController -outDir ./Controllers
|
||||
|
||||
|
||||
Here is the help command for this:
|
||||
dotnet aspnet-codegenerator controller --help
|
||||
|
||||
|
||||
|
||||
***********************************
|
||||
EF CORE STUFF NOTES
|
||||
|
||||
To INCLUDE relatives use like this:
|
||||
|
||||
var site = await _context.Site
|
||||
.Include(m=>m.TrialNavigation)
|
||||
.Include(m=>m.Purchase)
|
||||
.Include(m=>m.Incident)
|
||||
.SingleOrDefaultAsync(m => m.Id == id);
|
||||
|
||||
|
||||
|
||||
=-=-=-=-
|
||||
EF Core include queries
|
||||
|
||||
|
||||
//ad-hoc:
|
||||
// var res = from c in _context.Customer.OrderBy(c => c.Name)
|
||||
// join site in _context.Site.DefaultIfEmpty() on c.Id equals site.CustomerId
|
||||
// join purchase in _context.Purchase.DefaultIfEmpty() on site.Id equals purchase.SiteId
|
||||
// where (purchase.CancelDate == null)
|
||||
// select new infoListCustomer
|
||||
// {
|
||||
// active = c.Active,
|
||||
// id = c.Id,
|
||||
// name = c.Name,
|
||||
// siteId = site.Id,
|
||||
// siteName = site.Name,
|
||||
// purchaseId = purchase.Id,
|
||||
// purchaseName = purchase.Name
|
||||
// };
|
||||
|
||||
|
||||
//Using ef relationships:
|
||||
|
||||
// using (var context = new BloggingContext())
|
||||
// {
|
||||
// var blogs = context.Blogs
|
||||
// .Include(blog => blog.Posts)
|
||||
// .ThenInclude(post => post.Author)
|
||||
// .ThenInclude(author => author.Photo)
|
||||
// .Include(blog => blog.Owner)
|
||||
// .ThenInclude(owner => owner.Photo)
|
||||
// .ToList();
|
||||
// }
|
||||
|
||||
var res = _context.Customer
|
||||
.Include(customer => customer.Site)
|
||||
.ThenInclude(site => site.Purchase)//or...:
|
||||
//.Include(customer => customer.Purchase)//this also due to reference in EF
|
||||
.OrderBy(customer => customer.Name);
|
||||
//.ToList();
|
||||
|
||||
|
||||
var xtest=res.ToList();
|
||||
var xcount=xtest.Count();
|
||||
0
notes/todo
Normal file
17
rockfishCore.csproj
Normal file
@@ -0,0 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="wwwroot\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.App" Version="2.1.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.1.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.0" />
|
||||
<PackageReference Include="jose-jwt" Version="2.4.0" />
|
||||
<PackageReference Include="mailkit" Version="2.0.4" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
88
util/CustomerUtils.cs
Normal file
@@ -0,0 +1,88 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using rockfishCore.Models;
|
||||
using System.IO;
|
||||
|
||||
namespace rockfishCore.Util
|
||||
{
|
||||
|
||||
public static class CustomerUtils
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Adds email if not already present, handles trimming and check for duplicates etc
|
||||
/// **DOES NOT CALL SAVE ON CONTEXT, CALLER IS RESPONSIBLE**
|
||||
/// </summary>
|
||||
/// <param name="cust"></param>
|
||||
/// <param name="newEmail"></param>
|
||||
public static void AddAdminEmailIfNotPresent(Customer cust, string newEmail)
|
||||
{
|
||||
AddEmailIfNotPresent(cust, newEmail, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds email if not already present, handles trimming and check for duplicates etc
|
||||
/// **DOES NOT CALL SAVE ON CONTEXT, CALLER IS RESPONSIBLE**
|
||||
/// </summary>
|
||||
/// <param name="cust"></param>
|
||||
/// <param name="newEmail"></param>
|
||||
public static void AddSupportEmailIfNotPresent(Customer cust, string newEmail)
|
||||
{
|
||||
AddEmailIfNotPresent(cust, newEmail, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds email if not already present, handles trimming and check for duplicates etc
|
||||
/// **DOES NOT CALL SAVE ON CONTEXT, CALLER IS RESPONSIBLE**
|
||||
/// </summary>
|
||||
/// <param name="cust"></param>
|
||||
/// <param name="newEmail"></param>
|
||||
/// <param name="isAdmin"></param>
|
||||
private static void AddEmailIfNotPresent(Customer cust, string newEmail, bool isAdmin)
|
||||
{
|
||||
newEmail = newEmail.Trim();
|
||||
string newEmailAsLower = newEmail.ToLowerInvariant();
|
||||
string compareTo = cust.AdminEmail;
|
||||
if (false == isAdmin)
|
||||
compareTo = cust.SupportEmail;
|
||||
|
||||
bool noPriorAddress = false;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(compareTo))
|
||||
compareTo = compareTo.ToLowerInvariant();
|
||||
else
|
||||
noPriorAddress = true;
|
||||
|
||||
//See if email is already present
|
||||
if (false == noPriorAddress && compareTo.Contains(newEmailAsLower))
|
||||
{
|
||||
return;//skip this one, it's already there
|
||||
}
|
||||
|
||||
//It's not in the field already so add it
|
||||
if (noPriorAddress)
|
||||
{
|
||||
if (isAdmin)
|
||||
cust.AdminEmail = newEmail;
|
||||
else
|
||||
cust.SupportEmail = newEmail;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isAdmin)
|
||||
cust.AdminEmail = cust.AdminEmail + ", " + newEmail;
|
||||
else
|
||||
cust.SupportEmail = cust.SupportEmail + ", " + newEmail;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}//eoc
|
||||
|
||||
}//eons
|
||||
104
util/DateUtil.cs
Normal file
@@ -0,0 +1,104 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
|
||||
namespace rockfishCore.Util
|
||||
{
|
||||
public static class DateUtil
|
||||
{
|
||||
public const string DATE_TIME_FORMAT = "MMMM dd, yyyy h:mm tt";
|
||||
public const string DATE_ONLY_FORMAT = "D";
|
||||
|
||||
//Unix epoch converters
|
||||
public static string EpochToString(long? uepoch, string formatString = null)
|
||||
{
|
||||
if (uepoch == null) return string.Empty;
|
||||
if (formatString == null)
|
||||
formatString = DATE_ONLY_FORMAT;
|
||||
return DateTimeOffset.FromUnixTimeSeconds(uepoch.Value).DateTime.ToString(formatString);
|
||||
}
|
||||
public static DateTime EpochToDate(long? uepoch)
|
||||
{
|
||||
DateTime dt = DateTime.Now;
|
||||
if (uepoch == null) return DateTime.MinValue;
|
||||
return DateTimeOffset.FromUnixTimeSeconds(uepoch.Value).DateTime;
|
||||
}
|
||||
|
||||
public static long DateToEpoch(DateTime dt)
|
||||
{
|
||||
DateTimeOffset dto = new DateTimeOffset(dt);
|
||||
return dto.ToUnixTimeSeconds();
|
||||
}
|
||||
|
||||
public static long? DateTimeOffSetNullableToEpoch(DateTimeOffset? dt)
|
||||
{
|
||||
if (dt == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
DateTimeOffset dto = dt.Value;
|
||||
return dto.ToUnixTimeSeconds();
|
||||
}
|
||||
|
||||
|
||||
public static string ISO8601StringToLocalDateTime(string s)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(s))
|
||||
{
|
||||
return DateTimeOffset.Parse(s).DateTime.ToLocalTime().ToString(DATE_TIME_FORMAT);
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public static long? ISO8601StringToEpoch(string s)
|
||||
{
|
||||
DateTimeOffset? dto = ISO8601StringToDateTimeOffset(s);
|
||||
if (dto == null)
|
||||
return null;
|
||||
return ((DateTimeOffset)dto).ToUnixTimeSeconds();
|
||||
|
||||
}
|
||||
|
||||
|
||||
///////////////////////
|
||||
//This method correctly interprets iso8601 strings to a datetimeoffset
|
||||
public static DateTimeOffset? ISO8601StringToDateTimeOffset(string s)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(s))
|
||||
{
|
||||
DateTimeOffset dto = DateTimeOffset.ParseExact(s, new string[] { "yyyy-MM-dd'T'HH:mm:ss.FFFK" }, System.Globalization.CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.None);
|
||||
return dto;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static long NowAsEpoch()
|
||||
{
|
||||
DateTimeOffset dto = new DateTimeOffset(DateTime.Now);
|
||||
return dto.ToUnixTimeSeconds();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// An internally consistent empty or not relevant date marker:
|
||||
/// January 1st 5555
|
||||
/// Used for RAVEN key generation
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static DateTime EmptyDateValue
|
||||
{
|
||||
get
|
||||
{
|
||||
return new DateTime(5555, 1, 1);
|
||||
//Was going to use MaxValue but apparently that varies depending on culture
|
||||
// and Postgres has issues with year 1 as it interprets as year 2001
|
||||
// so to be on safe side just defining one for all usage
|
||||
}
|
||||
}
|
||||
|
||||
//eoc
|
||||
}
|
||||
//eons
|
||||
}
|
||||
591
util/FBImporter.cs
Normal file
@@ -0,0 +1,591 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using rockfishCore.Models;
|
||||
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
//using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
using System.IO;
|
||||
|
||||
namespace rockfishCore.Util
|
||||
{
|
||||
|
||||
//Import fogbugz stuff into our own db
|
||||
//called by schema update 7
|
||||
//does not modify FB at all
|
||||
//can be called any time, won't re-import
|
||||
public static class FBImporter
|
||||
{
|
||||
|
||||
public const long LAST_EXISTING_CASE_IN_FOGBUGZ = 3315;
|
||||
|
||||
public static void Import(rockfishContext ct)
|
||||
{
|
||||
//fogbugz api token, can get it by using the FB UI, open dev tools, be logged in, do something and watch the token get sent in the network traffic
|
||||
string sToken = "jvmnpkrvc1i7hc6kn6dggqkibktgcc";
|
||||
long nCase = 0;
|
||||
bool bDone = false;
|
||||
while (!bDone)
|
||||
{
|
||||
//increment until we reach the end (case 3309 as of now)
|
||||
nCase++;
|
||||
|
||||
if (nCase > LAST_EXISTING_CASE_IN_FOGBUGZ)
|
||||
{
|
||||
bDone = true;
|
||||
break;
|
||||
}
|
||||
|
||||
//no need for this, we wipe clean before every import now
|
||||
// //do we already have this record?
|
||||
// if (ct.RfCase.Any(e => e.Id == nCase))
|
||||
// {
|
||||
// continue;
|
||||
// }
|
||||
fbCase fb = getCase(nCase, sToken);
|
||||
|
||||
|
||||
//copy over to db
|
||||
portCase(fb, ct);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private static Dictionary<string, long> dictProjects = new Dictionary<string, long>();
|
||||
|
||||
///////////////////////////////////////////////
|
||||
//Port a single case over to Rockfish
|
||||
//
|
||||
private static void portCase(fbCase f, rockfishContext ct)
|
||||
{
|
||||
|
||||
long lProjectId = 0;
|
||||
|
||||
if (f.noRecord)
|
||||
{
|
||||
//save as *DELETED* record
|
||||
//Only setting required fields
|
||||
RfCase c = new RfCase();
|
||||
c.Priority = 5;
|
||||
c.Title = "deleted from FogBugz";
|
||||
c.RfCaseProjectId = 1;//has to be something
|
||||
ct.RfCase.Add(c);
|
||||
ct.SaveChanges();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (dictProjects.ContainsKey(f.project))
|
||||
{
|
||||
lProjectId = dictProjects[f.project];
|
||||
}
|
||||
else
|
||||
{
|
||||
//need to insert it
|
||||
RfCaseProject t = new RfCaseProject();
|
||||
t.Name = f.project;
|
||||
ct.RfCaseProject.Add(t);
|
||||
ct.SaveChanges();
|
||||
dictProjects.Add(f.project, t.Id);
|
||||
lProjectId = t.Id;
|
||||
}
|
||||
|
||||
RfCase c = new RfCase();
|
||||
c.DtClosed = f.closed;
|
||||
c.DtCreated = f.created;
|
||||
c.Notes = f.notes;
|
||||
c.Priority = f.priority;
|
||||
if (c.Priority > 5)
|
||||
c.Priority = 5;
|
||||
c.Title = f.title;
|
||||
c.RfCaseProjectId = lProjectId;
|
||||
ct.RfCase.Add(c);
|
||||
ct.SaveChanges();
|
||||
|
||||
|
||||
|
||||
if (f.attachments.Count > 0)
|
||||
{
|
||||
foreach (fbAttachment att in f.attachments)
|
||||
{
|
||||
|
||||
RfCaseBlob blob = new RfCaseBlob();
|
||||
blob.File = att.blob;
|
||||
blob.Name = att.fileName;
|
||||
blob.RfCaseId = c.Id;
|
||||
ct.RfCaseBlob.Add(blob);
|
||||
ct.SaveChanges();
|
||||
}
|
||||
}
|
||||
}
|
||||
// Console.WriteLine("***************************************************************************");
|
||||
Console.WriteLine("~~~~~~~~~~~~~~~~~~~~ PORTCASE: " + f.id);
|
||||
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////
|
||||
//fetch a single case
|
||||
//
|
||||
private static fbCase getCase(long caseNumber, string sToken)
|
||||
{
|
||||
//Use this url
|
||||
//https://fog.ayanova.com/api.asp?cmd=search&cols=ixBug,ixBugParent,sTitle,sProject,ixPriority, sPriority,fOpen,ixStatus,sStatus,dtOpened,dtResolved,dtClosed,ixRelatedBugs,events&token=jvmnpkrvc1i7hc6kn6dggqkibktgcc&q=3308
|
||||
|
||||
|
||||
//TEST TEST TEST
|
||||
// caseNumber = 3279;
|
||||
|
||||
string url = "https://fog.ayanova.com/api.asp?cmd=search&cols=ixBug,ixBugParent,sTitle,sProject,ixPriority,sPriority,fOpen,ixStatus,sStatus,dtOpened,dtResolved,dtClosed,ixRelatedBugs,events&token=" + sToken + "&q=" + caseNumber.ToString();
|
||||
|
||||
var httpClient = new HttpClient();
|
||||
var result = httpClient.GetAsync(url).Result;
|
||||
var stream = result.Content.ReadAsStreamAsync().Result;
|
||||
var x = XElement.Load(stream);
|
||||
fbCase f = new fbCase();
|
||||
|
||||
//are we done?
|
||||
string scount = (from el in x.DescendantsAndSelf("cases") select el).First().Attribute("count").Value;
|
||||
if (scount == "0")
|
||||
{
|
||||
f.noRecord = true;
|
||||
return f;
|
||||
}
|
||||
|
||||
//Got record, process it...
|
||||
|
||||
f.title = getValue("sTitle", x);
|
||||
f.project = getValue("sProject", x);
|
||||
f.id = getValue("ixBug", x);
|
||||
f.priority = int.Parse(getValue("ixPriority", x));
|
||||
|
||||
f.created = Util.DateUtil.ISO8601StringToEpoch(getValue("dtOpened", x));
|
||||
f.closed = Util.DateUtil.ISO8601StringToEpoch(getValue("dtClosed", x));
|
||||
|
||||
//string DTB4 = getValue("dtOpened", x);
|
||||
//string DTAFTER = Util.DateUtil.ISO8601StringToLocalDateTime(DTB4);
|
||||
|
||||
|
||||
//NOTES / ATTACHMENTS
|
||||
StringBuilder sbNotes = new StringBuilder();
|
||||
//events
|
||||
var events = (from el in x.Descendants("event") select el).ToList();
|
||||
bool bFirstNoteAdded = false;
|
||||
foreach (var e in events)
|
||||
{
|
||||
string sNote = getValue("s", e);
|
||||
if (!string.IsNullOrWhiteSpace(sNote))
|
||||
{
|
||||
if (bFirstNoteAdded)
|
||||
{
|
||||
sbNotes.AppendLine("====================");
|
||||
}
|
||||
bFirstNoteAdded = true;
|
||||
sbNotes.Append(Util.DateUtil.ISO8601StringToLocalDateTime(getValue("dt", e)));
|
||||
sbNotes.Append(" ");
|
||||
sbNotes.AppendLine(getValue("evtDescription", e));
|
||||
sbNotes.AppendLine("____________________");
|
||||
sbNotes.AppendLine(sNote);
|
||||
}
|
||||
|
||||
//GET ATTACHMENTS
|
||||
var attaches = (from l in e.Descendants("attachment") select l).ToList();
|
||||
foreach (var a in attaches)
|
||||
{
|
||||
fbAttachment fbat = new fbAttachment();
|
||||
fbat.fileName = getValue("sFileName", a);
|
||||
|
||||
string fileUrl = "https://fog.ayanova.com/" + System.Net.WebUtility.HtmlDecode(getValue("sURL", a)) + "&token=" + sToken;
|
||||
|
||||
|
||||
/////
|
||||
using (var aclient = new HttpClient())
|
||||
{
|
||||
var aresponse = aclient.GetAsync(fileUrl).Result;
|
||||
|
||||
if (aresponse.IsSuccessStatusCode)
|
||||
{
|
||||
// by calling .Result you are performing a synchronous call
|
||||
var responseContent = aresponse.Content;
|
||||
|
||||
// by calling .Result you are synchronously reading the result
|
||||
var astream = responseContent.ReadAsStreamAsync().Result;
|
||||
|
||||
MemoryStream ams = new MemoryStream();
|
||||
astream.CopyTo(ams);
|
||||
fbat.blob = ams.ToArray();
|
||||
}
|
||||
}
|
||||
/////
|
||||
|
||||
//bugbug: Looks like this is always the same size, maybe it's getting an error page?
|
||||
// result = httpClient.GetAsync(url).Result;
|
||||
// stream = result.Content.ReadAsStreamAsync().Result;
|
||||
// MemoryStream ms = new MemoryStream();
|
||||
// stream.CopyTo(ms);
|
||||
// fbat.blob = ms.ToArray();
|
||||
|
||||
f.attachments.Add(fbat);
|
||||
}
|
||||
}//bottom of events loop
|
||||
if (sbNotes.Length > 0)
|
||||
{
|
||||
f.notes = sbNotes.ToString();
|
||||
}
|
||||
return f;
|
||||
}
|
||||
|
||||
|
||||
/////////////////////////////////
|
||||
// Get header element value
|
||||
//
|
||||
private static string getValue(string sItem, XElement x)
|
||||
{
|
||||
return (string)(from el in x.Descendants(sItem) select el).First();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/////////////////////////////////////
|
||||
//dto object
|
||||
public class fbCase
|
||||
{
|
||||
public fbCase()
|
||||
{
|
||||
attachments = new List<fbAttachment>();
|
||||
}
|
||||
public bool noRecord;
|
||||
public string id;
|
||||
public string title;
|
||||
public string project;
|
||||
public int priority;
|
||||
public string notes;
|
||||
public long? created;
|
||||
public long? closed;
|
||||
public List<fbAttachment> attachments;
|
||||
}
|
||||
|
||||
public class fbAttachment
|
||||
{
|
||||
public string fileName;
|
||||
public byte[] blob;
|
||||
}
|
||||
|
||||
//eoc
|
||||
}
|
||||
//eons
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<response>
|
||||
<cases count="1">
|
||||
<case ixBug="3308" operations="edit,assign,resolve,remind">
|
||||
<ixBug>3308</ixBug>
|
||||
<ixBugParent>0</ixBugParent>
|
||||
<sTitle><![CDATA[Port all Fogbugz functionality we use to Rockfish and decommission FogBugz]]></sTitle>
|
||||
<sProject><![CDATA[RockFish]]></sProject>
|
||||
<ixPriority>1</ixPriority>
|
||||
<fOpen>true</fOpen>
|
||||
<ixStatus>20</ixStatus>
|
||||
<sStatus><![CDATA[Active]]></sStatus>
|
||||
<dtOpened>2017-08-06T19:11:54Z</dtOpened>
|
||||
<dtResolved></dtResolved>
|
||||
<dtClosed></dtClosed>
|
||||
<ixRelatedBugs>3240</ixRelatedBugs>
|
||||
<events>
|
||||
<event ixBugEvent="18150" ixBug="3308">
|
||||
<ixBugEvent>18150</ixBugEvent>
|
||||
<evt>1</evt>
|
||||
<sVerb><![CDATA[Opened]]></sVerb>
|
||||
<ixPerson>3</ixPerson>
|
||||
<ixPersonAssignedTo>0</ixPersonAssignedTo>
|
||||
<dt>2017-08-06T19:11:54Z</dt>
|
||||
<s><![CDATA[http://help.fogcreek.com/the-fogbugz-api
|
||||
And porting all existing data to rf including old closed cases etc.
|
||||
Also must be searchable, generate case numbers, have category / project, and priority, accept file attachments like screenshots etc.
|
||||
]]></s>
|
||||
<fEmail>false</fEmail>
|
||||
<fHTML>false</fHTML>
|
||||
<fExternal>false</fExternal>
|
||||
<sChanges></sChanges>
|
||||
<sFormat></sFormat>
|
||||
<rgAttachments></rgAttachments>
|
||||
<evtDescription><![CDATA[Opened by John Cardinal]]></evtDescription>
|
||||
<bEmail>false</bEmail>
|
||||
<bExternal>false</bExternal>
|
||||
<sPerson><![CDATA[John Cardinal]]></sPerson>
|
||||
<sHtml><![CDATA[http://help.fogcreek.com/the-fogbugz-api<br />And porting all existing data to rf including old closed cases etc.<br />Also must be searchable, generate case numbers, have category / project, and priority, accept file attachments like screenshots etc.<br />]]></sHtml>
|
||||
</event>
|
||||
<event ixBugEvent="18151" ixBug="3308">
|
||||
<ixBugEvent>18151</ixBugEvent>
|
||||
<evt>3</evt>
|
||||
<sVerb><![CDATA[Assigned]]></sVerb>
|
||||
<ixPerson>3</ixPerson>
|
||||
<ixPersonAssignedTo>3</ixPersonAssignedTo>
|
||||
<dt>2017-08-06T19:11:55Z</dt>
|
||||
<s></s>
|
||||
<fEmail>false</fEmail>
|
||||
<fHTML>false</fHTML>
|
||||
<fExternal>false</fExternal>
|
||||
<sChanges></sChanges>
|
||||
<sFormat></sFormat>
|
||||
<rgAttachments></rgAttachments>
|
||||
<evtDescription><![CDATA[Assigned to John Cardinal by John Cardinal]]></evtDescription>
|
||||
<bEmail>false</bEmail>
|
||||
<bExternal>false</bExternal>
|
||||
<sPerson><![CDATA[John Cardinal]]></sPerson>
|
||||
<sHtml></sHtml>
|
||||
</event>
|
||||
<event ixBugEvent="18152" ixBug="3308">
|
||||
<ixBugEvent>18152</ixBugEvent>
|
||||
<evt>2</evt>
|
||||
<sVerb><![CDATA[Edited]]></sVerb>
|
||||
<ixPerson>3</ixPerson>
|
||||
<ixPersonAssignedTo>0</ixPersonAssignedTo>
|
||||
<dt>2017-08-07T21:18:55Z</dt>
|
||||
<s><![CDATA[This query gets all the required data:
|
||||
https://fog.ayanova.com/api.asp?cmd=search&cols=ixBug,sTitle,fOpen,ixStatus,dtOpened,dtResolved,dtClosed,ixRelatedBugs,events&token=jvmnpkrvc1i7hc6kn6dggqkibktgcc&q=3240
|
||||
|
||||
This is for case 3240 (q=3240) and the token I got from just viewing the network traffic using Fogbugz (which works fine).
|
||||
|
||||
|
||||
The response is an xml document with a case/cases/case header and then an events collection under that which contains all the text added and other events like chaging the title etc which we don't care about. Also it contains urls for attachments to download and can use the url directly to get the attachment.
|
||||
|
||||
]]></s>
|
||||
<fEmail>false</fEmail>
|
||||
<fHTML>false</fHTML>
|
||||
<fExternal>false</fExternal>
|
||||
<sChanges></sChanges>
|
||||
<sFormat></sFormat>
|
||||
<rgAttachments></rgAttachments>
|
||||
<evtDescription><![CDATA[Edited by John Cardinal]]></evtDescription>
|
||||
<bEmail>false</bEmail>
|
||||
<bExternal>false</bExternal>
|
||||
<sPerson><![CDATA[John Cardinal]]></sPerson>
|
||||
<sHtml><![CDATA[This query gets all the required data:<br />https://fog.ayanova.com/api.asp?cmd=search&cols=ixBug,sTitle,fOpen,ixStatus,dtOpened,dtResolved,dtClosed,ixRelatedBugs,events&token=jvmnpkrvc1i7hc6kn6dggqkibktgcc&q=3240<br /><br />This is for case 3240 (q=3240) and the token I got from just viewing the network traffic using Fogbugz (which works fine).<br /><br /><br />The response is an xml document with a case/cases/case header and then an events collection under that which contains all the text added and other events like chaging the title etc which we don't care about. Also it contains urls for attachments to download and can use the url directly to get the attachment.<br /><br />]]></sHtml>
|
||||
</event>
|
||||
<event ixBugEvent="18153" ixBug="3308">
|
||||
<ixBugEvent>18153</ixBugEvent>
|
||||
<evt>2</evt>
|
||||
<sVerb><![CDATA[Edited]]></sVerb>
|
||||
<ixPerson>3</ixPerson>
|
||||
<ixPersonAssignedTo>0</ixPersonAssignedTo>
|
||||
<dt>2017-08-07T21:21:02Z</dt>
|
||||
<s><![CDATA[As of right now this is the highest case number in use.
|
||||
|
||||
The procedure will be to start at 1 and fetch every case +1 until we get a response like this:
|
||||
<?xml version="1.0" encoding="UTF-8"?><response><cases count="0"></cases></response>
|
||||
|
||||
Where cases count=0 and would normally be one and the cases branch would have the one case fetched.]]></s>
|
||||
<fEmail>false</fEmail>
|
||||
<fHTML>false</fHTML>
|
||||
<fExternal>false</fExternal>
|
||||
<sChanges></sChanges>
|
||||
<sFormat></sFormat>
|
||||
<rgAttachments></rgAttachments>
|
||||
<evtDescription><![CDATA[Edited by John Cardinal]]></evtDescription>
|
||||
<bEmail>false</bEmail>
|
||||
<bExternal>false</bExternal>
|
||||
<sPerson><![CDATA[John Cardinal]]></sPerson>
|
||||
<sHtml><![CDATA[As of right now this is the highest case number in use.<br /><br />The procedure will be to start at 1 and fetch every case +1 until we get a response like this:<br /><?xml version="1.0" encoding="UTF-8"?><response><cases count="0"></cases></response><br /><br />Where cases count=0 and would normally be one and the cases branch would have the one case fetched.]]></sHtml>
|
||||
</event>
|
||||
<event ixBugEvent="18154" ixBug="3308">
|
||||
<ixBugEvent>18154</ixBugEvent>
|
||||
<evt>2</evt>
|
||||
<sVerb><![CDATA[Edited]]></sVerb>
|
||||
<ixPerson>3</ixPerson>
|
||||
<ixPersonAssignedTo>0</ixPersonAssignedTo>
|
||||
<dt>2017-08-07T21:24:48Z</dt>
|
||||
<s><![CDATA[Fields to keep from old fogbugz:
|
||||
|
||||
PROJECTS - all of these in a table
|
||||
|
||||
CASES - every case:
|
||||
|
||||
CASE HEADER:
|
||||
Case # (id), Title, project, priority
|
||||
CASE EVENTS:
|
||||
- s (string of text (CDATA))
|
||||
- Any attachments
|
||||
|
||||
]]></s>
|
||||
<fEmail>false</fEmail>
|
||||
<fHTML>false</fHTML>
|
||||
<fExternal>false</fExternal>
|
||||
<sChanges></sChanges>
|
||||
<sFormat></sFormat>
|
||||
<rgAttachments></rgAttachments>
|
||||
<evtDescription><![CDATA[Edited by John Cardinal]]></evtDescription>
|
||||
<bEmail>false</bEmail>
|
||||
<bExternal>false</bExternal>
|
||||
<sPerson><![CDATA[John Cardinal]]></sPerson>
|
||||
<sHtml><![CDATA[Fields to keep from old fogbugz:<br /><br />PROJECTS - all of these in a table<br /><br />CASES - every case:<br /><br />CASE HEADER:<br />Case # (id), Title, project, priority<br /> CASE EVENTS:<br /> - s (string of text (CDATA))<br /> - Any attachments<br /><br />]]></sHtml>
|
||||
</event>
|
||||
<event ixBugEvent="18155" ixBug="3308">
|
||||
<ixBugEvent>18155</ixBugEvent>
|
||||
<evt>2</evt>
|
||||
<sVerb><![CDATA[Edited]]></sVerb>
|
||||
<ixPerson>3</ixPerson>
|
||||
<ixPersonAssignedTo>0</ixPersonAssignedTo>
|
||||
<dt>2017-08-07T21:34:27Z</dt>
|
||||
<s></s>
|
||||
<fEmail>false</fEmail>
|
||||
<fHTML>false</fHTML>
|
||||
<fExternal>false</fExternal>
|
||||
<sChanges><![CDATA[Priority changed from '3 – 3' to '1 – 1'.
|
||||
]]></sChanges>
|
||||
<sFormat></sFormat>
|
||||
<rgAttachments></rgAttachments>
|
||||
<evtDescription><![CDATA[Edited by John Cardinal]]></evtDescription>
|
||||
<bEmail>false</bEmail>
|
||||
<bExternal>false</bExternal>
|
||||
<sPerson><![CDATA[John Cardinal]]></sPerson>
|
||||
<sHtml></sHtml>
|
||||
</event>
|
||||
<event ixBugEvent="18156" ixBug="3308">
|
||||
<ixBugEvent>18156</ixBugEvent>
|
||||
<evt>2</evt>
|
||||
<sVerb><![CDATA[Edited]]></sVerb>
|
||||
<ixPerson>3</ixPerson>
|
||||
<ixPersonAssignedTo>0</ixPersonAssignedTo>
|
||||
<dt>2017-08-07T21:37:37Z</dt>
|
||||
<s><![CDATA[New will be primarily a single table, all events in fb now a single text field with date time headered sections for the events and hr lines or something.
|
||||
|
||||
New db tables:
|
||||
CASE
|
||||
CASEPROJECT
|
||||
CASEBLOB
|
||||
|
||||
CASE Fields:
|
||||
=-=-=-=-=-=-
|
||||
ID (integer not null, case # autoincrement),
|
||||
TITLE text not null,
|
||||
PROJECT (fk CASEPROJECT not null),
|
||||
PRIORITY (integer 1-5 not null),
|
||||
NOTES (text nullable),
|
||||
CREATED (date time as integer, nullable),
|
||||
CLOSED (date time as integer, nullable, also serves as closed indicator or open indicator if null),
|
||||
RELEASEVERSION (text nullable, public release version that fixed the item, i.e. if it was qboi 7.5 patch 1 then this would be 7.5.1),
|
||||
RELEASENOTES (text nullable, single string of text that serves as customer description of fix).
|
||||
|
||||
CASEPROJECT fields:
|
||||
=-=-=-=-=-=-=-=-=-=-
|
||||
id (integer not null pk autoincrement),
|
||||
name (text not null, project name i.e. QBOI)
|
||||
|
||||
CASEBLOB fields
|
||||
id (integer not null pk autoincrement)
|
||||
CASEID (fk not null link to case)
|
||||
FILE (blob not null)
|
||||
|
||||
]]></s>
|
||||
<fEmail>false</fEmail>
|
||||
<fHTML>false</fHTML>
|
||||
<fExternal>false</fExternal>
|
||||
<sChanges></sChanges>
|
||||
<sFormat></sFormat>
|
||||
<rgAttachments></rgAttachments>
|
||||
<evtDescription><![CDATA[Edited by John Cardinal]]></evtDescription>
|
||||
<bEmail>false</bEmail>
|
||||
<bExternal>false</bExternal>
|
||||
<sPerson><![CDATA[John Cardinal]]></sPerson>
|
||||
<sHtml><![CDATA[New will be primarily a single table, all events in fb now a single text field with date time headered sections for the events and hr lines or something. <br /><br />New db tables:<br />CASE<br />CASEPROJECT<br />CASEBLOB<br /><br />CASE Fields:<br />=-=-=-=-=-=-<br />ID (integer not null, case # autoincrement), <br />TITLE text not null, <br />PROJECT (fk CASEPROJECT not null), <br />PRIORITY (integer 1-5 not null), <br />NOTES (text nullable), <br />CREATED (date time as integer, nullable), <br />CLOSED (date time as integer, nullable, also serves as closed indicator or open indicator if null), <br />RELEASEVERSION (text nullable, public release version that fixed the item, i.e. if it was qboi 7.5 patch 1 then this would be 7.5.1),<br />RELEASENOTES (text nullable, single string of text that serves as customer description of fix).<br /><br />CASEPROJECT fields:<br />=-=-=-=-=-=-=-=-=-=-<br />id (integer not null pk autoincrement),<br />name (text not null, project name i.e. QBOI)<br /><br />CASEBLOB fields<br />id (integer not null pk autoincrement)<br />CASEID (fk not null link to case)<br />FILE (blob not null)<br /><br />]]></sHtml>
|
||||
</event>
|
||||
<event ixBugEvent="18157" ixBug="3308">
|
||||
<ixBugEvent>18157</ixBugEvent>
|
||||
<evt>2</evt>
|
||||
<sVerb><![CDATA[Edited]]></sVerb>
|
||||
<ixPerson>3</ixPerson>
|
||||
<ixPersonAssignedTo>0</ixPersonAssignedTo>
|
||||
<dt>2017-08-07T22:13:24Z</dt>
|
||||
<s></s>
|
||||
<fEmail>false</fEmail>
|
||||
<fHTML>false</fHTML>
|
||||
<fExternal>false</fExternal>
|
||||
<sChanges><![CDATA[Revised John Cardinal's entry from 8/7/2017 at 9:37 PM UTC
|
||||
]]></sChanges>
|
||||
<sFormat></sFormat>
|
||||
<rgAttachments></rgAttachments>
|
||||
<evtDescription><![CDATA[Edited by John Cardinal]]></evtDescription>
|
||||
<bEmail>false</bEmail>
|
||||
<bExternal>false</bExternal>
|
||||
<sPerson><![CDATA[John Cardinal]]></sPerson>
|
||||
<sHtml></sHtml>
|
||||
</event>
|
||||
<event ixBugEvent="18158" ixBug="3308">
|
||||
<ixBugEvent>18158</ixBugEvent>
|
||||
<evt>2</evt>
|
||||
<sVerb><![CDATA[Edited]]></sVerb>
|
||||
<ixPerson>3</ixPerson>
|
||||
<ixPersonAssignedTo>0</ixPersonAssignedTo>
|
||||
<dt>2017-08-07T22:13:32Z</dt>
|
||||
<s><![CDATA[UI will have option to "Append" and it will auto insert the date/time HR and then the text.]]></s>
|
||||
<fEmail>false</fEmail>
|
||||
<fHTML>false</fHTML>
|
||||
<fExternal>false</fExternal>
|
||||
<sChanges></sChanges>
|
||||
<sFormat></sFormat>
|
||||
<rgAttachments></rgAttachments>
|
||||
<evtDescription><![CDATA[Edited by John Cardinal]]></evtDescription>
|
||||
<bEmail>false</bEmail>
|
||||
<bExternal>false</bExternal>
|
||||
<sPerson><![CDATA[John Cardinal]]></sPerson>
|
||||
<sHtml><![CDATA[UI will have option to "Append" and it will auto insert the date/time HR and then the text.]]></sHtml>
|
||||
</event>
|
||||
<event ixBugEvent="18159" ixBug="3308">
|
||||
<ixBugEvent>18159</ixBugEvent>
|
||||
<evt>2</evt>
|
||||
<sVerb><![CDATA[Edited]]></sVerb>
|
||||
<ixPerson>3</ixPerson>
|
||||
<ixPersonAssignedTo>0</ixPersonAssignedTo>
|
||||
<dt>2017-08-07T22:29:29Z</dt>
|
||||
<s></s>
|
||||
<fEmail>false</fEmail>
|
||||
<fHTML>false</fHTML>
|
||||
<fExternal>false</fExternal>
|
||||
<sChanges><![CDATA[Revised John Cardinal's entry from 8/7/2017 at 9:37 PM UTC
|
||||
]]></sChanges>
|
||||
<sFormat></sFormat>
|
||||
<rgAttachments></rgAttachments>
|
||||
<evtDescription><![CDATA[Edited by John Cardinal]]></evtDescription>
|
||||
<bEmail>false</bEmail>
|
||||
<bExternal>false</bExternal>
|
||||
<sPerson><![CDATA[John Cardinal]]></sPerson>
|
||||
<sHtml></sHtml>
|
||||
</event>
|
||||
<event ixBugEvent="18160" ixBug="3308">
|
||||
<ixBugEvent>18160</ixBugEvent>
|
||||
<evt>2</evt>
|
||||
<sVerb><![CDATA[Edited]]></sVerb>
|
||||
<ixPerson>3</ixPerson>
|
||||
<ixPersonAssignedTo>0</ixPersonAssignedTo>
|
||||
<dt>2017-08-07T22:49:22Z</dt>
|
||||
<s></s>
|
||||
<fEmail>false</fEmail>
|
||||
<fHTML>false</fHTML>
|
||||
<fExternal>false</fExternal>
|
||||
<sChanges><![CDATA[Title changed from 'Look into porting Fogbugz functionality we use to Rockfish' to 'Port all Fogbugz functionality we use to Rockfish and decommission FogBugz'.
|
||||
]]></sChanges>
|
||||
<sFormat></sFormat>
|
||||
<rgAttachments></rgAttachments>
|
||||
<evtDescription><![CDATA[Edited by John Cardinal]]></evtDescription>
|
||||
<bEmail>false</bEmail>
|
||||
<bExternal>false</bExternal>
|
||||
<sPerson><![CDATA[John Cardinal]]></sPerson>
|
||||
<sHtml></sHtml>
|
||||
</event>
|
||||
</events>
|
||||
</case>
|
||||
</cases>
|
||||
</response>
|
||||
*/
|
||||
44
util/HexString.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
|
||||
namespace rockfishCore.Util
|
||||
{
|
||||
|
||||
public static class HexString
|
||||
{
|
||||
|
||||
|
||||
public static string ToHex(string str)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
var bytes = Encoding.ASCII.GetBytes(str);
|
||||
foreach (var t in bytes)
|
||||
{
|
||||
sb.Append(t.ToString("X2"));
|
||||
}
|
||||
|
||||
return sb.ToString(); // returns: "48656C6C6F20776F726C64" for "Hello world"
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static string FromHex(string hexString)
|
||||
{
|
||||
var bytes = new byte[hexString.Length / 2];
|
||||
for (var i = 0; i < bytes.Length; i++)
|
||||
{
|
||||
bytes[i] = Convert.ToByte(hexString.Substring(i * 2, 2), 16);
|
||||
}
|
||||
|
||||
return Encoding.ASCII.GetString(bytes); // returns: "Hello world" for "48656C6C6F20776F726C64"
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//eoc
|
||||
}
|
||||
//eons
|
||||
}
|
||||
521
util/KeyFactory.cs
Normal file
@@ -0,0 +1,521 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Org.BouncyCastle.Security;
|
||||
using Org.BouncyCastle.Crypto;
|
||||
using Org.BouncyCastle.OpenSsl;
|
||||
using rockfishCore.Models;
|
||||
using rockfishCore.Util;
|
||||
|
||||
|
||||
|
||||
namespace rockfishCore.Util
|
||||
{
|
||||
//Key generator controller
|
||||
public static class KeyFactory
|
||||
{
|
||||
|
||||
public const string PLUGIN_MBI_KEY = "MBI - Minimal browser interface";
|
||||
public const string PLUGIN_WBI_KEY = "WBI - Web browser interface";
|
||||
public const string PLUGIN_QBI_KEY = "QBI - QuickBooks interface";
|
||||
public const string PLUGIN_QBOI_KEY = "QBOI - QuickBooks Online interface";
|
||||
public const string PLUGIN_PTI_KEY = "PTI - US Sage 50/Peachtree interface";
|
||||
public const string PLUGIN_QUICK_NOTIFICATION_KEY = "QuickNotification";
|
||||
public const string PLUGIN_EXPORT_TO_XLS_KEY = "ExportToXls";
|
||||
public const string PLUGIN_OUTLOOK_SCHEDULE_KEY = "OutlookSchedule";
|
||||
public const string PLUGIN_OLI_KEY = "AyaNovaOLI";
|
||||
public const string PLUGIN_IMPORT_EXPORT_CSV_DUPLICATE_KEY = "ImportExportCSVDuplicate";
|
||||
public const string PLUGIN_RI_KEY = "RI - Responsive Interface";
|
||||
|
||||
|
||||
private static Dictionary<string, DateTime> _plugins;
|
||||
|
||||
//Generate a key message reply from a key selection object
|
||||
//CALLED BY LicenseController Generate route
|
||||
public static string GetKeyReply(dtoKeyOptions ko, LicenseTemplates t, rockfishContext ct)
|
||||
{
|
||||
|
||||
//case 3542
|
||||
ko.installByDate = System.DateTime.Now.AddYears(1);//changed from one month to one year
|
||||
string sKey = genKey(ko, ct);
|
||||
string sMsg = genMessage(sKey, ko, t);
|
||||
return sMsg;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//called by trialkeyrequesthandler.GenerateFromRequest
|
||||
//get a trial key for named regTo
|
||||
public static string GetTrialKey(string regTo, bool lite, LicenseTemplates t, string authorizedUserKeyGeneratorStamp,
|
||||
string emailAddress, rockfishContext ct)//case 3233
|
||||
{
|
||||
DateTime dtoneMonth = System.DateTime.Now.AddMonths(1);
|
||||
long oneMonth = DateUtil.DateToEpoch(System.DateTime.Now.AddMonths(1));
|
||||
|
||||
dtoKeyOptions ko = new dtoKeyOptions();
|
||||
|
||||
//case 3233
|
||||
ko.emailAddress = emailAddress;
|
||||
ko.customerId = 0; //not a customer so trial 0
|
||||
|
||||
ko.registeredTo = regTo;
|
||||
ko.supportExpiresDate = oneMonth;
|
||||
ko.isLite = lite;
|
||||
ko.installByDate = dtoneMonth;
|
||||
ko.authorizedUserKeyGeneratorStamp = authorizedUserKeyGeneratorStamp;
|
||||
if (lite)
|
||||
{
|
||||
ko.users = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
ko.users = 5;
|
||||
}
|
||||
|
||||
ko.licenseType = "webRequestedTrial";
|
||||
|
||||
ko.qbi = true;
|
||||
ko.qbiSupportExpiresDate = oneMonth;
|
||||
|
||||
ko.qboi = true;
|
||||
ko.qboiSupportExpiresDate = oneMonth;
|
||||
|
||||
ko.pti = true;
|
||||
ko.ptiSupportExpiresDate = oneMonth;
|
||||
|
||||
ko.exportToXls = true;
|
||||
ko.exportToXlsSupportExpiresDate = oneMonth;
|
||||
|
||||
ko.outlookSchedule = true;
|
||||
ko.outlookScheduleSupportExpiresDate = oneMonth;
|
||||
|
||||
ko.oli = true;
|
||||
ko.oliSupportExpiresDate = oneMonth;
|
||||
|
||||
ko.importExportCSVDuplicate = true;
|
||||
ko.importExportCSVDuplicateSupportExpiresDate = oneMonth;
|
||||
|
||||
if (!lite)
|
||||
{
|
||||
ko.quickNotification = true;
|
||||
ko.quickNotificationSupportExpiresDate = oneMonth;
|
||||
|
||||
ko.mbi = true;
|
||||
ko.mbiSupportExpiresDate = oneMonth;
|
||||
|
||||
ko.wbi = true;
|
||||
ko.wbiSupportExpiresDate = oneMonth;
|
||||
|
||||
ko.ri = true;
|
||||
ko.riSupportExpiresDate = oneMonth;
|
||||
}
|
||||
|
||||
string sKey = genKey(ko, ct);
|
||||
string sMsg = genMessage(sKey, ko, t);
|
||||
return sMsg;
|
||||
|
||||
}
|
||||
|
||||
|
||||
//Take the key and the options and make a return message ready to send
|
||||
private static string genMessage(string sKey, dtoKeyOptions ko, LicenseTemplates template)
|
||||
{
|
||||
string sMessage = "";
|
||||
|
||||
if (ko.licenseType == "new")
|
||||
{
|
||||
if (ko.isLite)
|
||||
{
|
||||
sMessage = template.LiteNew;
|
||||
}
|
||||
else
|
||||
{
|
||||
sMessage = template.FullNew;
|
||||
}
|
||||
}
|
||||
else if (ko.licenseType == "addon")
|
||||
{
|
||||
if (ko.isLite)
|
||||
{
|
||||
sMessage = template.LiteAddOn;
|
||||
}
|
||||
else
|
||||
{
|
||||
sMessage = template.FullAddOn;
|
||||
}
|
||||
}
|
||||
else//licensed trial
|
||||
{
|
||||
if (ko.isLite)
|
||||
{
|
||||
sMessage = template.LiteTrial;
|
||||
}
|
||||
else
|
||||
{
|
||||
sMessage = template.FullTrial;
|
||||
}
|
||||
}
|
||||
|
||||
//token substitutions
|
||||
sMessage = sMessage.Replace("[LicenseExpiryDate]", ko.installByDate.ToString("D"));//https://github.com/dotnet/coreclr/issues/2317
|
||||
sMessage = sMessage.Replace("[LicenseDescription]", LicenseInfo(ko));
|
||||
sMessage = sMessage.Replace("[LicenseKey]", sKey);
|
||||
|
||||
return sMessage;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// Extra info to display about key at top of key message
|
||||
private static string LicenseInfo(dtoKeyOptions ko)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.Append("LICENSE DETAILS\r\n");
|
||||
sb.Append("This key must be installed before: ");
|
||||
sb.Append(ko.installByDate.ToString("D"));
|
||||
sb.Append("\r\n");
|
||||
|
||||
//if (kg.SelectedLicenseType == "Web requested trial")
|
||||
//{
|
||||
// sb.Append("*** This temporary license key has been provided for limited evaluation purposes only *** \r\n");
|
||||
// sb.Append("This license will expire and AyaNova usage will be restricted after: " + kg.Expires.ToLongDateString() + "\r\n\r\n");
|
||||
//}
|
||||
|
||||
if (ko.keyWillLockout)
|
||||
{
|
||||
sb.Append("*** This temporary license key is provided for evaluation use only pending payment ***\r\n");
|
||||
sb.Append("This license will expire and AyaNova usage will be restricted after: " + DateUtil.EpochToString(ko.lockoutDate) + "\r\n");
|
||||
sb.Append("\r\n");
|
||||
sb.Append("A permanent license key will be sent to you when payment \r\n" +
|
||||
"has been received and processed. There will be no extensions or \r\n" +
|
||||
"exceptions. Please send in payment early enough to allow for \r\n" +
|
||||
"mail and processing time to ensure uninterrupted use of AyaNova" + (ko.isLite ? " Lite" : "") + ". \r\n\r\n");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
sb.Append("Registered to: ");
|
||||
sb.Append(ko.registeredTo);
|
||||
sb.Append("\r\n");
|
||||
|
||||
//case 3233
|
||||
sb.Append("Fetch address: ");
|
||||
sb.Append(ko.emailAddress);
|
||||
sb.Append("\r\n");
|
||||
|
||||
sb.Append("Fetch code: ");
|
||||
sb.Append(ko.fetchCode);
|
||||
sb.Append("\r\n");
|
||||
|
||||
|
||||
sb.Append("Scheduleable resources: ");
|
||||
switch (ko.users)
|
||||
{
|
||||
case 1:
|
||||
sb.AppendLine("1");
|
||||
break;
|
||||
case 5:
|
||||
sb.AppendLine("Up to 5");
|
||||
break;
|
||||
case 10:
|
||||
sb.AppendLine("Up to 10");
|
||||
break;
|
||||
case 15:
|
||||
sb.AppendLine("Up to 15");//case 3550
|
||||
break;
|
||||
case 20:
|
||||
sb.AppendLine("Up to 20");
|
||||
break;
|
||||
case 50:
|
||||
sb.AppendLine("Up to 50");
|
||||
break;
|
||||
case 999:
|
||||
sb.AppendLine("Up to 999");
|
||||
break;
|
||||
}
|
||||
|
||||
sb.AppendLine("Support and updates until: " + DateUtil.EpochToString(ko.supportExpiresDate) + "\r\n");
|
||||
|
||||
if (_plugins.Count > 0)
|
||||
{
|
||||
sb.Append("\r\n");
|
||||
sb.Append("Plugins:\r\n");
|
||||
foreach (KeyValuePair<string, DateTime> kv in _plugins)
|
||||
{
|
||||
sb.Append("\t");
|
||||
sb.Append(kv.Key);
|
||||
sb.Append(" support and updates until: ");
|
||||
sb.Append(kv.Value.ToString("D"));
|
||||
sb.Append("\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Generate keycode based on passed in data
|
||||
/// This is called by both regular and trial license key routes
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private static string genKey(dtoKeyOptions ko, rockfishContext ct)
|
||||
{
|
||||
_plugins = new Dictionary<string, DateTime>();
|
||||
|
||||
if (ko.registeredTo == null || ko.registeredTo == "")
|
||||
throw new ArgumentException("RegisteredTo is required", "RegisteredTo");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(ko.emailAddress))
|
||||
throw new ArgumentException("Email address is required", "emailAddress");
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
StringBuilder sbKey = new StringBuilder();
|
||||
StringWriter sw = new StringWriter(sbKey);
|
||||
|
||||
//case 3233
|
||||
ko.fetchCode = FetchKeyCode.generate();
|
||||
|
||||
using (Newtonsoft.Json.JsonWriter w = new Newtonsoft.Json.JsonTextWriter(sw))
|
||||
{
|
||||
w.Formatting = Newtonsoft.Json.Formatting.Indented;
|
||||
|
||||
//outer object start
|
||||
w.WriteStartObject();
|
||||
|
||||
if (ko.isLite)
|
||||
w.WritePropertyName("AyaNovaLiteLicenseKey");
|
||||
else
|
||||
w.WritePropertyName("AyaNovaLicenseKey");
|
||||
|
||||
w.WriteStartObject();//start of key object
|
||||
|
||||
w.WritePropertyName("SchemaVersion");
|
||||
w.WriteValue("7");
|
||||
|
||||
//stamp a unique value in the key so it can be revoked later
|
||||
//used to use the digest value of the key for this with xml key
|
||||
//whole unix timestamp seconds but kept as a double to work beyond 2038
|
||||
w.WritePropertyName("Id");
|
||||
var vv = Math.Truncate((DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds);
|
||||
string sId = vv.ToString();
|
||||
if (sId.Contains(","))
|
||||
sId = sId.Split('.')[0];
|
||||
w.WriteValue(sId);
|
||||
|
||||
w.WritePropertyName("Created");
|
||||
w.WriteValue(System.DateTime.Now);
|
||||
|
||||
w.WritePropertyName("Sub");
|
||||
w.WriteValue("true");
|
||||
|
||||
w.WritePropertyName("RegisteredTo");
|
||||
w.WriteValue(ko.registeredTo);//unicode test string
|
||||
|
||||
//case 3233
|
||||
w.WritePropertyName("EmailAddress");
|
||||
w.WriteValue(ko.emailAddress);
|
||||
|
||||
w.WritePropertyName("FetchCode");
|
||||
|
||||
w.WriteValue(ko.fetchCode);
|
||||
|
||||
//case 3187 - Source here
|
||||
//rockfish
|
||||
w.WritePropertyName("Source");
|
||||
w.WriteValue(rockfishCore.Util.HexString.ToHex(ko.authorizedUserKeyGeneratorStamp));
|
||||
|
||||
|
||||
w.WritePropertyName("InstallableUntil");
|
||||
w.WriteValue(ko.installByDate);//case 3542, respect the KO option
|
||||
|
||||
w.WritePropertyName("TotalScheduleableUsers");
|
||||
w.WriteValue(ko.users.ToString());//Needs to be a string to match rockfish format
|
||||
|
||||
w.WritePropertyName("Expires");
|
||||
w.WriteValue(DateUtil.EpochToDate(ko.supportExpiresDate));
|
||||
|
||||
if (ko.keyWillLockout)
|
||||
{
|
||||
w.WritePropertyName("LockDate");
|
||||
w.WriteValue(DateUtil.EpochToDate(ko.lockoutDate));
|
||||
|
||||
}
|
||||
|
||||
w.WritePropertyName("RequestedTrial");
|
||||
bool bRequestedTrial = ko.licenseType == "webRequestedTrial";
|
||||
w.WriteValue(bRequestedTrial.ToString());
|
||||
|
||||
//PLUGINS
|
||||
w.WritePropertyName("Plugins");
|
||||
w.WriteStartObject();//start of key object
|
||||
w.WritePropertyName("Plugin");
|
||||
w.WriteStartArray();
|
||||
|
||||
if (ko.mbi)
|
||||
AddLicensePlugin(w, PLUGIN_MBI_KEY, DateUtil.EpochToDate(ko.mbiSupportExpiresDate));
|
||||
|
||||
if (ko.wbi)
|
||||
AddLicensePlugin(w, PLUGIN_WBI_KEY, DateUtil.EpochToDate(ko.wbiSupportExpiresDate));
|
||||
|
||||
if (ko.qbi)
|
||||
AddLicensePlugin(w, PLUGIN_QBI_KEY, DateUtil.EpochToDate(ko.qbiSupportExpiresDate));
|
||||
|
||||
if (ko.qboi)
|
||||
AddLicensePlugin(w, PLUGIN_QBOI_KEY, DateUtil.EpochToDate(ko.qboiSupportExpiresDate));
|
||||
|
||||
if (ko.pti)
|
||||
AddLicensePlugin(w, PLUGIN_PTI_KEY, DateUtil.EpochToDate(ko.ptiSupportExpiresDate));
|
||||
|
||||
if (ko.quickNotification)
|
||||
AddLicensePlugin(w, PLUGIN_QUICK_NOTIFICATION_KEY, DateUtil.EpochToDate(ko.quickNotificationSupportExpiresDate));
|
||||
|
||||
|
||||
if (ko.exportToXls)
|
||||
AddLicensePlugin(w, PLUGIN_EXPORT_TO_XLS_KEY, DateUtil.EpochToDate(ko.exportToXlsSupportExpiresDate));
|
||||
|
||||
|
||||
if (ko.outlookSchedule)
|
||||
AddLicensePlugin(w, PLUGIN_OUTLOOK_SCHEDULE_KEY, DateUtil.EpochToDate(ko.outlookScheduleSupportExpiresDate));
|
||||
|
||||
|
||||
if (ko.oli)
|
||||
AddLicensePlugin(w, PLUGIN_OLI_KEY, DateUtil.EpochToDate(ko.oliSupportExpiresDate));
|
||||
|
||||
|
||||
if (ko.importExportCSVDuplicate)
|
||||
AddLicensePlugin(w, PLUGIN_IMPORT_EXPORT_CSV_DUPLICATE_KEY, DateUtil.EpochToDate(ko.importExportCSVDuplicateSupportExpiresDate));
|
||||
|
||||
|
||||
if (ko.ri)
|
||||
AddLicensePlugin(w, PLUGIN_RI_KEY, DateUtil.EpochToDate(ko.riSupportExpiresDate));
|
||||
|
||||
|
||||
//end of plugins array
|
||||
w.WriteEnd();
|
||||
|
||||
//end of plugins object
|
||||
w.WriteEndObject();
|
||||
|
||||
//end of AyaNova/AyaNovaLite key object
|
||||
w.WriteEndObject();
|
||||
|
||||
//close outer 'wrapper' object brace }
|
||||
w.WriteEndObject();
|
||||
|
||||
}//end of using statement
|
||||
|
||||
|
||||
// ## CALCULATE SIGNATURE
|
||||
|
||||
//GET JSON as a string with whitespace stripped outside of delimited strings
|
||||
//http://stackoverflow.com/questions/8913138/minify-indented-json-string-in-net
|
||||
string keyNoWS = System.Text.RegularExpressions.Regex.Replace(sbKey.ToString(), "(\"(?:[^\"\\\\]|\\\\.)*\")|\\s+", "$1");
|
||||
|
||||
|
||||
//**** Note this is our real 2016 private key
|
||||
var privatePEM = @"-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpAIBAAKCAQEAz7wrvLDcKVMZ31HFGBnLWL08IodYIV5VJkKy1Z0n2snprhSi
|
||||
u3izxTyz+SLpftvKHJpky027ii7l/pL9Bo3JcjU5rKrxXavnE7TuYPjXn16dNLd0
|
||||
K/ERSU+pXLmUaVN0nUWuGuUMoGJMEXoulS6pJiG11yu3BM9fL2Nbj0C6a+UwzEHF
|
||||
mns3J/daZOb4gAzMUdJfh9OJ0+wRGzR8ZxyC99Na2gDmqYglUkSMjwLTL/HbgwF4
|
||||
OwmoQYJBcET0Wa6Gfb17SaF8XRBV5ZtpCsbStkthGeoXZkFriB9c1eFQLKpBYQo2
|
||||
DW3H1MPG2nAlQZLbkJj5cSh7/t1bRF08m6P+EQIDAQABAoIBAQCGvTpxLRXgB/Kk
|
||||
EtmQBEsMx9EVZEwZeKIqKuDsBP8wvf4/10ql5mhT6kehtK9WhSDW5J2z8DtQKZMs
|
||||
SBKuCZE77qH2CPp9E17SPWzQoRbaW/gDlWpYhgf8URs89XH5zxO4XtXKw/4omRlV
|
||||
zLYiNR2pifv0EHqpOAg5KGzewdEo4VgXgtRWpHZLMpH2Q0/5ZIKMhstI6vFHP1p7
|
||||
jmU4YI6uxiu7rVrZDmIUsAGoTdMabNqK/N8hKaoBiIto0Jn1ck26g+emLg8m160y
|
||||
Xciu5yFUU+PP1SJMUs+k1UnAWf4p46X9jRLQCBRue9o0Ntiq/75aljRoDvgdwDsR
|
||||
mg4ZANqxAoGBAPBoM5KoMZ4sv8ZFv8V+V8hgL5xiLgGoiwQl91mRsHRM/NQU5A/w
|
||||
tH8nmwUrJOrksV7kX9228smKmoliTptyGGyi1NPmSkA7cN9YYnENoOEBHCVNK9vh
|
||||
P+bkbMYUDNMW4fgOj09oXtQtMl5E2B3OTGoNwZ2w13YQJ8RIniLPsX7nAoGBAN01
|
||||
eQNcUzQk9YrFGTznOs8udDLBfigDxaNnawvPueulJdBy6ZXDDrKmkQQA7xxl8YPr
|
||||
dNtBq2lOgnb6+smC15TaAfV/fb8BLmkSwdn4Fy0FApIXIEOnLq+wjkte98nuezl8
|
||||
9KXDzaqNI9hPuk2i36tJuLLMH8hzldveWbWjSlRHAoGBAKRPE7CQtBjfjNL+qOta
|
||||
RrT0yJWhpMANabYUHNJi+K8ET2jEPnuGkFa3wwPtUPYaCABLJhprB9Unnid3wTIM
|
||||
8RSO1ddd9jGgbqy3w9Bw+BvQnmQAMpG9iedNB+r5mSpM4XSgvuIO+4EYwuwbMXpt
|
||||
nVx+um4Eh75xnDxTRYGVYkLRAoGAaZVpUlpR+HSfooHbPv+bSWKB4ewLPCw4vHrT
|
||||
VErtEfW8q9b9eRcmP81TMFcFykc6VN4g47pfh58KlKHM7DwAjDLWdohIy89TiKGE
|
||||
V3acEUfv5y0UoFX+6ara8Ey+9upWdKUY3Lotw3ckoc3EPeQ84DQK7YSSswnAgLaL
|
||||
mS/8fWcCgYBjRefVbEep161d2DGruk4X7eNI9TFJ278h6ydW5kK9aTJuxkrtKIp4
|
||||
CYf6emoB4mLXFPvAmnsalkhN2iB29hUZCXXSUjpKZrpijL54Wdu2S6ynm7aT97NF
|
||||
oArP0E2Vbow3JMxq/oeXmHbrLMLQfYyXwFmciLFigOtkd45bfHdrbA==
|
||||
-----END RSA PRIVATE KEY-----";
|
||||
|
||||
PemReader pr = new PemReader(new StringReader(privatePEM));
|
||||
AsymmetricCipherKeyPair keys = (AsymmetricCipherKeyPair)pr.ReadObject();
|
||||
var encoder = new UTF8Encoding(false, true);
|
||||
var inputData = encoder.GetBytes(keyNoWS);
|
||||
var signer = SignerUtilities.GetSigner("SHA256WITHRSA");
|
||||
signer.Init(true, keys.Private);
|
||||
signer.BlockUpdate(inputData, 0, inputData.Length);
|
||||
var sign = signer.GenerateSignature();
|
||||
var signature = Convert.ToBase64String(sign);
|
||||
|
||||
|
||||
System.Text.StringBuilder sbOut = new StringBuilder();
|
||||
sbOut.AppendLine("[KEY");
|
||||
sbOut.AppendLine(sbKey.ToString());
|
||||
sbOut.AppendLine("KEY]");
|
||||
sbOut.AppendLine("[SIGNATURE");
|
||||
sbOut.AppendLine(signature);
|
||||
sbOut.AppendLine("SIGNATURE]");
|
||||
|
||||
//case 3233 insert into db
|
||||
License l = new License();
|
||||
l.DtCreated = DateUtil.NowAsEpoch();
|
||||
l.Code = ko.fetchCode;
|
||||
l.CustomerId = ko.customerId;
|
||||
l.Email = ko.emailAddress.ToLowerInvariant();
|
||||
l.Key = sbOut.ToString();
|
||||
l.RegTo = ko.registeredTo;
|
||||
ct.License.Add(l);
|
||||
ct.SaveChanges();
|
||||
|
||||
return sbOut.ToString();
|
||||
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return (ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static void AddLicensePlugin(Newtonsoft.Json.JsonWriter w, string pluginName, DateTime pluginExpires)
|
||||
{
|
||||
|
||||
//this dictionary is used by the additional message code to
|
||||
//make the human readable portion of the license
|
||||
_plugins.Add(pluginName, pluginExpires);
|
||||
|
||||
//this is adding it to the actual key
|
||||
w.WriteStartObject();
|
||||
w.WritePropertyName("Item");
|
||||
w.WriteValue(pluginName);
|
||||
|
||||
w.WritePropertyName("SubscriptionExpires");
|
||||
w.WriteValue(pluginExpires);
|
||||
|
||||
w.WriteEndObject();
|
||||
//----------------
|
||||
}
|
||||
|
||||
|
||||
|
||||
//eoc
|
||||
}
|
||||
//eons
|
||||
}
|
||||
733
util/MimeTypeMap.cs
Normal file
@@ -0,0 +1,733 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace rockfishCore.Util
|
||||
{
|
||||
//https://github.com/HelmGlobal/MimeTypeMap/blob/master/src/MimeTypes/MimeTypeMap.cs
|
||||
public static class MimeTypeMap
|
||||
{
|
||||
private static readonly Lazy<IDictionary<string, string>> _mappings = new Lazy<IDictionary<string, string>>(BuildMappings);
|
||||
|
||||
private static IDictionary<string, string> BuildMappings()
|
||||
{
|
||||
var mappings = new Dictionary<string, string>(StringComparer.CurrentCultureIgnoreCase) {
|
||||
|
||||
#region Big freaking list of mime types
|
||||
|
||||
// maps both ways,
|
||||
// extension -> mime type
|
||||
// and
|
||||
// mime type -> extension
|
||||
//
|
||||
// any mime types on left side not pre-loaded on right side, are added automatically
|
||||
// some mime types can map to multiple extensions, so to get a deterministic mapping,
|
||||
// add those to the dictionary specifcially
|
||||
//
|
||||
// combination of values from Windows 7 Registry and
|
||||
// from C:\Windows\System32\inetsrv\config\applicationHost.config
|
||||
// some added, including .7z and .dat
|
||||
//
|
||||
// Some added based on http://www.iana.org/assignments/media-types/media-types.xhtml
|
||||
// which lists mime types, but not extensions
|
||||
//
|
||||
{".323", "text/h323"},
|
||||
{".3g2", "video/3gpp2"},
|
||||
{".3gp", "video/3gpp"},
|
||||
{".3gp2", "video/3gpp2"},
|
||||
{".3gpp", "video/3gpp"},
|
||||
{".7z", "application/x-7z-compressed"},
|
||||
{".aa", "audio/audible"},
|
||||
{".AAC", "audio/aac"},
|
||||
{".aaf", "application/octet-stream"},
|
||||
{".aax", "audio/vnd.audible.aax"},
|
||||
{".ac3", "audio/ac3"},
|
||||
{".aca", "application/octet-stream"},
|
||||
{".accda", "application/msaccess.addin"},
|
||||
{".accdb", "application/msaccess"},
|
||||
{".accdc", "application/msaccess.cab"},
|
||||
{".accde", "application/msaccess"},
|
||||
{".accdr", "application/msaccess.runtime"},
|
||||
{".accdt", "application/msaccess"},
|
||||
{".accdw", "application/msaccess.webapplication"},
|
||||
{".accft", "application/msaccess.ftemplate"},
|
||||
{".acx", "application/internet-property-stream"},
|
||||
{".AddIn", "text/xml"},
|
||||
{".ade", "application/msaccess"},
|
||||
{".adobebridge", "application/x-bridge-url"},
|
||||
{".adp", "application/msaccess"},
|
||||
{".ADT", "audio/vnd.dlna.adts"},
|
||||
{".ADTS", "audio/aac"},
|
||||
{".afm", "application/octet-stream"},
|
||||
{".ai", "application/postscript"},
|
||||
{".aif", "audio/aiff"},
|
||||
{".aifc", "audio/aiff"},
|
||||
{".aiff", "audio/aiff"},
|
||||
{".air", "application/vnd.adobe.air-application-installer-package+zip"},
|
||||
{".amc", "application/mpeg"},
|
||||
{".anx", "application/annodex"},
|
||||
{".apk", "application/vnd.android.package-archive" },
|
||||
{".application", "application/x-ms-application"},
|
||||
{".art", "image/x-jg"},
|
||||
{".asa", "application/xml"},
|
||||
{".asax", "application/xml"},
|
||||
{".ascx", "application/xml"},
|
||||
{".asd", "application/octet-stream"},
|
||||
{".asf", "video/x-ms-asf"},
|
||||
{".ashx", "application/xml"},
|
||||
{".asi", "application/octet-stream"},
|
||||
{".asm", "text/plain"},
|
||||
{".asmx", "application/xml"},
|
||||
{".aspx", "application/xml"},
|
||||
{".asr", "video/x-ms-asf"},
|
||||
{".asx", "video/x-ms-asf"},
|
||||
{".atom", "application/atom+xml"},
|
||||
{".au", "audio/basic"},
|
||||
{".avi", "video/x-msvideo"},
|
||||
{".axa", "audio/annodex"},
|
||||
{".axs", "application/olescript"},
|
||||
{".axv", "video/annodex"},
|
||||
{".bas", "text/plain"},
|
||||
{".bcpio", "application/x-bcpio"},
|
||||
{".bin", "application/octet-stream"},
|
||||
{".bmp", "image/bmp"},
|
||||
{".c", "text/plain"},
|
||||
{".cab", "application/octet-stream"},
|
||||
{".caf", "audio/x-caf"},
|
||||
{".calx", "application/vnd.ms-office.calx"},
|
||||
{".cat", "application/vnd.ms-pki.seccat"},
|
||||
{".cc", "text/plain"},
|
||||
{".cd", "text/plain"},
|
||||
{".cdda", "audio/aiff"},
|
||||
{".cdf", "application/x-cdf"},
|
||||
{".cer", "application/x-x509-ca-cert"},
|
||||
{".cfg", "text/plain"},
|
||||
{".chm", "application/octet-stream"},
|
||||
{".class", "application/x-java-applet"},
|
||||
{".clp", "application/x-msclip"},
|
||||
{".cmd", "text/plain"},
|
||||
{".cmx", "image/x-cmx"},
|
||||
{".cnf", "text/plain"},
|
||||
{".cod", "image/cis-cod"},
|
||||
{".config", "application/xml"},
|
||||
{".contact", "text/x-ms-contact"},
|
||||
{".coverage", "application/xml"},
|
||||
{".cpio", "application/x-cpio"},
|
||||
{".cpp", "text/plain"},
|
||||
{".crd", "application/x-mscardfile"},
|
||||
{".crl", "application/pkix-crl"},
|
||||
{".crt", "application/x-x509-ca-cert"},
|
||||
{".cs", "text/plain"},
|
||||
{".csdproj", "text/plain"},
|
||||
{".csh", "application/x-csh"},
|
||||
{".csproj", "text/plain"},
|
||||
{".css", "text/css"},
|
||||
{".csv", "text/csv"},
|
||||
{".cur", "application/octet-stream"},
|
||||
{".cxx", "text/plain"},
|
||||
{".dat", "application/octet-stream"},
|
||||
{".datasource", "application/xml"},
|
||||
{".dbproj", "text/plain"},
|
||||
{".dcr", "application/x-director"},
|
||||
{".def", "text/plain"},
|
||||
{".deploy", "application/octet-stream"},
|
||||
{".der", "application/x-x509-ca-cert"},
|
||||
{".dgml", "application/xml"},
|
||||
{".dib", "image/bmp"},
|
||||
{".dif", "video/x-dv"},
|
||||
{".dir", "application/x-director"},
|
||||
{".disco", "text/xml"},
|
||||
{".divx", "video/divx"},
|
||||
{".dll", "application/x-msdownload"},
|
||||
{".dll.config", "text/xml"},
|
||||
{".dlm", "text/dlm"},
|
||||
{".doc", "application/msword"},
|
||||
{".docm", "application/vnd.ms-word.document.macroEnabled.12"},
|
||||
{".docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"},
|
||||
{".dot", "application/msword"},
|
||||
{".dotm", "application/vnd.ms-word.template.macroEnabled.12"},
|
||||
{".dotx", "application/vnd.openxmlformats-officedocument.wordprocessingml.template"},
|
||||
{".dsp", "application/octet-stream"},
|
||||
{".dsw", "text/plain"},
|
||||
{".dtd", "text/xml"},
|
||||
{".dtsConfig", "text/xml"},
|
||||
{".dv", "video/x-dv"},
|
||||
{".dvi", "application/x-dvi"},
|
||||
{".dwf", "drawing/x-dwf"},
|
||||
{".dwp", "application/octet-stream"},
|
||||
{".dxr", "application/x-director"},
|
||||
{".eml", "message/rfc822"},
|
||||
{".emz", "application/octet-stream"},
|
||||
{".eot", "application/vnd.ms-fontobject"},
|
||||
{".eps", "application/postscript"},
|
||||
{".etl", "application/etl"},
|
||||
{".etx", "text/x-setext"},
|
||||
{".evy", "application/envoy"},
|
||||
{".exe", "application/octet-stream"},
|
||||
{".exe.config", "text/xml"},
|
||||
{".fdf", "application/vnd.fdf"},
|
||||
{".fif", "application/fractals"},
|
||||
{".filters", "application/xml"},
|
||||
{".fla", "application/octet-stream"},
|
||||
{".flac", "audio/flac"},
|
||||
{".flr", "x-world/x-vrml"},
|
||||
{".flv", "video/x-flv"},
|
||||
{".fsscript", "application/fsharp-script"},
|
||||
{".fsx", "application/fsharp-script"},
|
||||
{".generictest", "application/xml"},
|
||||
{".gif", "image/gif"},
|
||||
{".group", "text/x-ms-group"},
|
||||
{".gsm", "audio/x-gsm"},
|
||||
{".gtar", "application/x-gtar"},
|
||||
{".gz", "application/x-gzip"},
|
||||
{".h", "text/plain"},
|
||||
{".hdf", "application/x-hdf"},
|
||||
{".hdml", "text/x-hdml"},
|
||||
{".hhc", "application/x-oleobject"},
|
||||
{".hhk", "application/octet-stream"},
|
||||
{".hhp", "application/octet-stream"},
|
||||
{".hlp", "application/winhlp"},
|
||||
{".hpp", "text/plain"},
|
||||
{".hqx", "application/mac-binhex40"},
|
||||
{".hta", "application/hta"},
|
||||
{".htc", "text/x-component"},
|
||||
{".htm", "text/html"},
|
||||
{".html", "text/html"},
|
||||
{".htt", "text/webviewhtml"},
|
||||
{".hxa", "application/xml"},
|
||||
{".hxc", "application/xml"},
|
||||
{".hxd", "application/octet-stream"},
|
||||
{".hxe", "application/xml"},
|
||||
{".hxf", "application/xml"},
|
||||
{".hxh", "application/octet-stream"},
|
||||
{".hxi", "application/octet-stream"},
|
||||
{".hxk", "application/xml"},
|
||||
{".hxq", "application/octet-stream"},
|
||||
{".hxr", "application/octet-stream"},
|
||||
{".hxs", "application/octet-stream"},
|
||||
{".hxt", "text/html"},
|
||||
{".hxv", "application/xml"},
|
||||
{".hxw", "application/octet-stream"},
|
||||
{".hxx", "text/plain"},
|
||||
{".i", "text/plain"},
|
||||
{".ico", "image/x-icon"},
|
||||
{".ics", "application/octet-stream"},
|
||||
{".idl", "text/plain"},
|
||||
{".ief", "image/ief"},
|
||||
{".iii", "application/x-iphone"},
|
||||
{".inc", "text/plain"},
|
||||
{".inf", "application/octet-stream"},
|
||||
{".ini", "text/plain"},
|
||||
{".inl", "text/plain"},
|
||||
{".ins", "application/x-internet-signup"},
|
||||
{".ipa", "application/x-itunes-ipa"},
|
||||
{".ipg", "application/x-itunes-ipg"},
|
||||
{".ipproj", "text/plain"},
|
||||
{".ipsw", "application/x-itunes-ipsw"},
|
||||
{".iqy", "text/x-ms-iqy"},
|
||||
{".isp", "application/x-internet-signup"},
|
||||
{".ite", "application/x-itunes-ite"},
|
||||
{".itlp", "application/x-itunes-itlp"},
|
||||
{".itms", "application/x-itunes-itms"},
|
||||
{".itpc", "application/x-itunes-itpc"},
|
||||
{".IVF", "video/x-ivf"},
|
||||
{".jar", "application/java-archive"},
|
||||
{".java", "application/octet-stream"},
|
||||
{".jck", "application/liquidmotion"},
|
||||
{".jcz", "application/liquidmotion"},
|
||||
{".jfif", "image/pjpeg"},
|
||||
{".jnlp", "application/x-java-jnlp-file"},
|
||||
{".jpb", "application/octet-stream"},
|
||||
{".jpe", "image/jpeg"},
|
||||
{".jpeg", "image/jpeg"},
|
||||
{".jpg", "image/jpeg"},
|
||||
{".js", "application/javascript"},
|
||||
{".json", "application/json"},
|
||||
{".jsx", "text/jscript"},
|
||||
{".jsxbin", "text/plain"},
|
||||
{".latex", "application/x-latex"},
|
||||
{".library-ms", "application/windows-library+xml"},
|
||||
{".lit", "application/x-ms-reader"},
|
||||
{".loadtest", "application/xml"},
|
||||
{".lpk", "application/octet-stream"},
|
||||
{".lsf", "video/x-la-asf"},
|
||||
{".lst", "text/plain"},
|
||||
{".lsx", "video/x-la-asf"},
|
||||
{".lzh", "application/octet-stream"},
|
||||
{".m13", "application/x-msmediaview"},
|
||||
{".m14", "application/x-msmediaview"},
|
||||
{".m1v", "video/mpeg"},
|
||||
{".m2t", "video/vnd.dlna.mpeg-tts"},
|
||||
{".m2ts", "video/vnd.dlna.mpeg-tts"},
|
||||
{".m2v", "video/mpeg"},
|
||||
{".m3u", "audio/x-mpegurl"},
|
||||
{".m3u8", "audio/x-mpegurl"},
|
||||
{".m4a", "audio/m4a"},
|
||||
{".m4b", "audio/m4b"},
|
||||
{".m4p", "audio/m4p"},
|
||||
{".m4r", "audio/x-m4r"},
|
||||
{".m4v", "video/x-m4v"},
|
||||
{".mac", "image/x-macpaint"},
|
||||
{".mak", "text/plain"},
|
||||
{".man", "application/x-troff-man"},
|
||||
{".manifest", "application/x-ms-manifest"},
|
||||
{".map", "text/plain"},
|
||||
{".master", "application/xml"},
|
||||
{".mda", "application/msaccess"},
|
||||
{".mdb", "application/x-msaccess"},
|
||||
{".mde", "application/msaccess"},
|
||||
{".mdp", "application/octet-stream"},
|
||||
{".me", "application/x-troff-me"},
|
||||
{".mfp", "application/x-shockwave-flash"},
|
||||
{".mht", "message/rfc822"},
|
||||
{".mhtml", "message/rfc822"},
|
||||
{".mid", "audio/mid"},
|
||||
{".midi", "audio/mid"},
|
||||
{".mix", "application/octet-stream"},
|
||||
{".mk", "text/plain"},
|
||||
{".mmf", "application/x-smaf"},
|
||||
{".mno", "text/xml"},
|
||||
{".mny", "application/x-msmoney"},
|
||||
{".mod", "video/mpeg"},
|
||||
{".mov", "video/quicktime"},
|
||||
{".movie", "video/x-sgi-movie"},
|
||||
{".mp2", "video/mpeg"},
|
||||
{".mp2v", "video/mpeg"},
|
||||
{".mp3", "audio/mpeg"},
|
||||
{".mp4", "video/mp4"},
|
||||
{".mp4v", "video/mp4"},
|
||||
{".mpa", "video/mpeg"},
|
||||
{".mpe", "video/mpeg"},
|
||||
{".mpeg", "video/mpeg"},
|
||||
{".mpf", "application/vnd.ms-mediapackage"},
|
||||
{".mpg", "video/mpeg"},
|
||||
{".mpp", "application/vnd.ms-project"},
|
||||
{".mpv2", "video/mpeg"},
|
||||
{".mqv", "video/quicktime"},
|
||||
{".ms", "application/x-troff-ms"},
|
||||
{".msi", "application/octet-stream"},
|
||||
{".mso", "application/octet-stream"},
|
||||
{".mts", "video/vnd.dlna.mpeg-tts"},
|
||||
{".mtx", "application/xml"},
|
||||
{".mvb", "application/x-msmediaview"},
|
||||
{".mvc", "application/x-miva-compiled"},
|
||||
{".mxp", "application/x-mmxp"},
|
||||
{".nc", "application/x-netcdf"},
|
||||
{".nsc", "video/x-ms-asf"},
|
||||
{".nws", "message/rfc822"},
|
||||
{".ocx", "application/octet-stream"},
|
||||
{".oda", "application/oda"},
|
||||
{".odb", "application/vnd.oasis.opendocument.database"},
|
||||
{".odc", "application/vnd.oasis.opendocument.chart"},
|
||||
{".odf", "application/vnd.oasis.opendocument.formula"},
|
||||
{".odg", "application/vnd.oasis.opendocument.graphics"},
|
||||
{".odh", "text/plain"},
|
||||
{".odi", "application/vnd.oasis.opendocument.image"},
|
||||
{".odl", "text/plain"},
|
||||
{".odm", "application/vnd.oasis.opendocument.text-master"},
|
||||
{".odp", "application/vnd.oasis.opendocument.presentation"},
|
||||
{".ods", "application/vnd.oasis.opendocument.spreadsheet"},
|
||||
{".odt", "application/vnd.oasis.opendocument.text"},
|
||||
{".oga", "audio/ogg"},
|
||||
{".ogg", "audio/ogg"},
|
||||
{".ogv", "video/ogg"},
|
||||
{".ogx", "application/ogg"},
|
||||
{".one", "application/onenote"},
|
||||
{".onea", "application/onenote"},
|
||||
{".onepkg", "application/onenote"},
|
||||
{".onetmp", "application/onenote"},
|
||||
{".onetoc", "application/onenote"},
|
||||
{".onetoc2", "application/onenote"},
|
||||
{".opus", "audio/ogg"},
|
||||
{".orderedtest", "application/xml"},
|
||||
{".osdx", "application/opensearchdescription+xml"},
|
||||
{".otf", "application/font-sfnt"},
|
||||
{".otg", "application/vnd.oasis.opendocument.graphics-template"},
|
||||
{".oth", "application/vnd.oasis.opendocument.text-web"},
|
||||
{".otp", "application/vnd.oasis.opendocument.presentation-template"},
|
||||
{".ots", "application/vnd.oasis.opendocument.spreadsheet-template"},
|
||||
{".ott", "application/vnd.oasis.opendocument.text-template"},
|
||||
{".oxt", "application/vnd.openofficeorg.extension"},
|
||||
{".p10", "application/pkcs10"},
|
||||
{".p12", "application/x-pkcs12"},
|
||||
{".p7b", "application/x-pkcs7-certificates"},
|
||||
{".p7c", "application/pkcs7-mime"},
|
||||
{".p7m", "application/pkcs7-mime"},
|
||||
{".p7r", "application/x-pkcs7-certreqresp"},
|
||||
{".p7s", "application/pkcs7-signature"},
|
||||
{".pbm", "image/x-portable-bitmap"},
|
||||
{".pcast", "application/x-podcast"},
|
||||
{".pct", "image/pict"},
|
||||
{".pcx", "application/octet-stream"},
|
||||
{".pcz", "application/octet-stream"},
|
||||
{".pdf", "application/pdf"},
|
||||
{".pfb", "application/octet-stream"},
|
||||
{".pfm", "application/octet-stream"},
|
||||
{".pfx", "application/x-pkcs12"},
|
||||
{".pgm", "image/x-portable-graymap"},
|
||||
{".pic", "image/pict"},
|
||||
{".pict", "image/pict"},
|
||||
{".pkgdef", "text/plain"},
|
||||
{".pkgundef", "text/plain"},
|
||||
{".pko", "application/vnd.ms-pki.pko"},
|
||||
{".pls", "audio/scpls"},
|
||||
{".pma", "application/x-perfmon"},
|
||||
{".pmc", "application/x-perfmon"},
|
||||
{".pml", "application/x-perfmon"},
|
||||
{".pmr", "application/x-perfmon"},
|
||||
{".pmw", "application/x-perfmon"},
|
||||
{".png", "image/png"},
|
||||
{".pnm", "image/x-portable-anymap"},
|
||||
{".pnt", "image/x-macpaint"},
|
||||
{".pntg", "image/x-macpaint"},
|
||||
{".pnz", "image/png"},
|
||||
{".pot", "application/vnd.ms-powerpoint"},
|
||||
{".potm", "application/vnd.ms-powerpoint.template.macroEnabled.12"},
|
||||
{".potx", "application/vnd.openxmlformats-officedocument.presentationml.template"},
|
||||
{".ppa", "application/vnd.ms-powerpoint"},
|
||||
{".ppam", "application/vnd.ms-powerpoint.addin.macroEnabled.12"},
|
||||
{".ppm", "image/x-portable-pixmap"},
|
||||
{".pps", "application/vnd.ms-powerpoint"},
|
||||
{".ppsm", "application/vnd.ms-powerpoint.slideshow.macroEnabled.12"},
|
||||
{".ppsx", "application/vnd.openxmlformats-officedocument.presentationml.slideshow"},
|
||||
{".ppt", "application/vnd.ms-powerpoint"},
|
||||
{".pptm", "application/vnd.ms-powerpoint.presentation.macroEnabled.12"},
|
||||
{".pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"},
|
||||
{".prf", "application/pics-rules"},
|
||||
{".prm", "application/octet-stream"},
|
||||
{".prx", "application/octet-stream"},
|
||||
{".ps", "application/postscript"},
|
||||
{".psc1", "application/PowerShell"},
|
||||
{".psd", "application/octet-stream"},
|
||||
{".psess", "application/xml"},
|
||||
{".psm", "application/octet-stream"},
|
||||
{".psp", "application/octet-stream"},
|
||||
{".pub", "application/x-mspublisher"},
|
||||
{".pwz", "application/vnd.ms-powerpoint"},
|
||||
{".qht", "text/x-html-insertion"},
|
||||
{".qhtm", "text/x-html-insertion"},
|
||||
{".qt", "video/quicktime"},
|
||||
{".qti", "image/x-quicktime"},
|
||||
{".qtif", "image/x-quicktime"},
|
||||
{".qtl", "application/x-quicktimeplayer"},
|
||||
{".qxd", "application/octet-stream"},
|
||||
{".ra", "audio/x-pn-realaudio"},
|
||||
{".ram", "audio/x-pn-realaudio"},
|
||||
{".rar", "application/x-rar-compressed"},
|
||||
{".ras", "image/x-cmu-raster"},
|
||||
{".rat", "application/rat-file"},
|
||||
{".rc", "text/plain"},
|
||||
{".rc2", "text/plain"},
|
||||
{".rct", "text/plain"},
|
||||
{".rdlc", "application/xml"},
|
||||
{".reg", "text/plain"},
|
||||
{".resx", "application/xml"},
|
||||
{".rf", "image/vnd.rn-realflash"},
|
||||
{".rgb", "image/x-rgb"},
|
||||
{".rgs", "text/plain"},
|
||||
{".rm", "application/vnd.rn-realmedia"},
|
||||
{".rmi", "audio/mid"},
|
||||
{".rmp", "application/vnd.rn-rn_music_package"},
|
||||
{".roff", "application/x-troff"},
|
||||
{".rpm", "audio/x-pn-realaudio-plugin"},
|
||||
{".rqy", "text/x-ms-rqy"},
|
||||
{".rtf", "application/rtf"},
|
||||
{".rtx", "text/richtext"},
|
||||
{".ruleset", "application/xml"},
|
||||
{".s", "text/plain"},
|
||||
{".safariextz", "application/x-safari-safariextz"},
|
||||
{".scd", "application/x-msschedule"},
|
||||
{".scr", "text/plain"},
|
||||
{".sct", "text/scriptlet"},
|
||||
{".sd2", "audio/x-sd2"},
|
||||
{".sdp", "application/sdp"},
|
||||
{".sea", "application/octet-stream"},
|
||||
{".searchConnector-ms", "application/windows-search-connector+xml"},
|
||||
{".setpay", "application/set-payment-initiation"},
|
||||
{".setreg", "application/set-registration-initiation"},
|
||||
{".settings", "application/xml"},
|
||||
{".sgimb", "application/x-sgimb"},
|
||||
{".sgml", "text/sgml"},
|
||||
{".sh", "application/x-sh"},
|
||||
{".shar", "application/x-shar"},
|
||||
{".shtml", "text/html"},
|
||||
{".sit", "application/x-stuffit"},
|
||||
{".sitemap", "application/xml"},
|
||||
{".skin", "application/xml"},
|
||||
{".sldm", "application/vnd.ms-powerpoint.slide.macroEnabled.12"},
|
||||
{".sldx", "application/vnd.openxmlformats-officedocument.presentationml.slide"},
|
||||
{".slk", "application/vnd.ms-excel"},
|
||||
{".sln", "text/plain"},
|
||||
{".slupkg-ms", "application/x-ms-license"},
|
||||
{".smd", "audio/x-smd"},
|
||||
{".smi", "application/octet-stream"},
|
||||
{".smx", "audio/x-smd"},
|
||||
{".smz", "audio/x-smd"},
|
||||
{".snd", "audio/basic"},
|
||||
{".snippet", "application/xml"},
|
||||
{".snp", "application/octet-stream"},
|
||||
{".sol", "text/plain"},
|
||||
{".sor", "text/plain"},
|
||||
{".spc", "application/x-pkcs7-certificates"},
|
||||
{".spl", "application/futuresplash"},
|
||||
{".spx", "audio/ogg"},
|
||||
{".src", "application/x-wais-source"},
|
||||
{".srf", "text/plain"},
|
||||
{".SSISDeploymentManifest", "text/xml"},
|
||||
{".ssm", "application/streamingmedia"},
|
||||
{".sst", "application/vnd.ms-pki.certstore"},
|
||||
{".stl", "application/vnd.ms-pki.stl"},
|
||||
{".sv4cpio", "application/x-sv4cpio"},
|
||||
{".sv4crc", "application/x-sv4crc"},
|
||||
{".svc", "application/xml"},
|
||||
{".svg", "image/svg+xml"},
|
||||
{".swf", "application/x-shockwave-flash"},
|
||||
{".t", "application/x-troff"},
|
||||
{".tar", "application/x-tar"},
|
||||
{".tcl", "application/x-tcl"},
|
||||
{".testrunconfig", "application/xml"},
|
||||
{".testsettings", "application/xml"},
|
||||
{".tex", "application/x-tex"},
|
||||
{".texi", "application/x-texinfo"},
|
||||
{".texinfo", "application/x-texinfo"},
|
||||
{".tgz", "application/x-compressed"},
|
||||
{".thmx", "application/vnd.ms-officetheme"},
|
||||
{".thn", "application/octet-stream"},
|
||||
{".tif", "image/tiff"},
|
||||
{".tiff", "image/tiff"},
|
||||
{".tlh", "text/plain"},
|
||||
{".tli", "text/plain"},
|
||||
{".toc", "application/octet-stream"},
|
||||
{".tr", "application/x-troff"},
|
||||
{".trm", "application/x-msterminal"},
|
||||
{".trx", "application/xml"},
|
||||
{".ts", "video/vnd.dlna.mpeg-tts"},
|
||||
{".tsv", "text/tab-separated-values"},
|
||||
{".ttf", "application/font-sfnt"},
|
||||
{".tts", "video/vnd.dlna.mpeg-tts"},
|
||||
{".txt", "text/plain"},
|
||||
{".u32", "application/octet-stream"},
|
||||
{".uls", "text/iuls"},
|
||||
{".user", "text/plain"},
|
||||
{".ustar", "application/x-ustar"},
|
||||
{".vb", "text/plain"},
|
||||
{".vbdproj", "text/plain"},
|
||||
{".vbk", "video/mpeg"},
|
||||
{".vbproj", "text/plain"},
|
||||
{".vbs", "text/vbscript"},
|
||||
{".vcf", "text/x-vcard"},
|
||||
{".vcproj", "application/xml"},
|
||||
{".vcs", "text/plain"},
|
||||
{".vcxproj", "application/xml"},
|
||||
{".vddproj", "text/plain"},
|
||||
{".vdp", "text/plain"},
|
||||
{".vdproj", "text/plain"},
|
||||
{".vdx", "application/vnd.ms-visio.viewer"},
|
||||
{".vml", "text/xml"},
|
||||
{".vscontent", "application/xml"},
|
||||
{".vsct", "text/xml"},
|
||||
{".vsd", "application/vnd.visio"},
|
||||
{".vsi", "application/ms-vsi"},
|
||||
{".vsix", "application/vsix"},
|
||||
{".vsixlangpack", "text/xml"},
|
||||
{".vsixmanifest", "text/xml"},
|
||||
{".vsmdi", "application/xml"},
|
||||
{".vspscc", "text/plain"},
|
||||
{".vss", "application/vnd.visio"},
|
||||
{".vsscc", "text/plain"},
|
||||
{".vssettings", "text/xml"},
|
||||
{".vssscc", "text/plain"},
|
||||
{".vst", "application/vnd.visio"},
|
||||
{".vstemplate", "text/xml"},
|
||||
{".vsto", "application/x-ms-vsto"},
|
||||
{".vsw", "application/vnd.visio"},
|
||||
{".vsx", "application/vnd.visio"},
|
||||
{".vtx", "application/vnd.visio"},
|
||||
{".wav", "audio/wav"},
|
||||
{".wave", "audio/wav"},
|
||||
{".wax", "audio/x-ms-wax"},
|
||||
{".wbk", "application/msword"},
|
||||
{".wbmp", "image/vnd.wap.wbmp"},
|
||||
{".wcm", "application/vnd.ms-works"},
|
||||
{".wdb", "application/vnd.ms-works"},
|
||||
{".wdp", "image/vnd.ms-photo"},
|
||||
{".webarchive", "application/x-safari-webarchive"},
|
||||
{".webm", "video/webm"},
|
||||
{".webp", "image/webp"}, /* https://en.wikipedia.org/wiki/WebP */
|
||||
{".webtest", "application/xml"},
|
||||
{".wiq", "application/xml"},
|
||||
{".wiz", "application/msword"},
|
||||
{".wks", "application/vnd.ms-works"},
|
||||
{".WLMP", "application/wlmoviemaker"},
|
||||
{".wlpginstall", "application/x-wlpg-detect"},
|
||||
{".wlpginstall3", "application/x-wlpg3-detect"},
|
||||
{".wm", "video/x-ms-wm"},
|
||||
{".wma", "audio/x-ms-wma"},
|
||||
{".wmd", "application/x-ms-wmd"},
|
||||
{".wmf", "application/x-msmetafile"},
|
||||
{".wml", "text/vnd.wap.wml"},
|
||||
{".wmlc", "application/vnd.wap.wmlc"},
|
||||
{".wmls", "text/vnd.wap.wmlscript"},
|
||||
{".wmlsc", "application/vnd.wap.wmlscriptc"},
|
||||
{".wmp", "video/x-ms-wmp"},
|
||||
{".wmv", "video/x-ms-wmv"},
|
||||
{".wmx", "video/x-ms-wmx"},
|
||||
{".wmz", "application/x-ms-wmz"},
|
||||
{".woff", "application/font-woff"},
|
||||
{".wpl", "application/vnd.ms-wpl"},
|
||||
{".wps", "application/vnd.ms-works"},
|
||||
{".wri", "application/x-mswrite"},
|
||||
{".wrl", "x-world/x-vrml"},
|
||||
{".wrz", "x-world/x-vrml"},
|
||||
{".wsc", "text/scriptlet"},
|
||||
{".wsdl", "text/xml"},
|
||||
{".wvx", "video/x-ms-wvx"},
|
||||
{".x", "application/directx"},
|
||||
{".xaf", "x-world/x-vrml"},
|
||||
{".xaml", "application/xaml+xml"},
|
||||
{".xap", "application/x-silverlight-app"},
|
||||
{".xbap", "application/x-ms-xbap"},
|
||||
{".xbm", "image/x-xbitmap"},
|
||||
{".xdr", "text/plain"},
|
||||
{".xht", "application/xhtml+xml"},
|
||||
{".xhtml", "application/xhtml+xml"},
|
||||
{".xla", "application/vnd.ms-excel"},
|
||||
{".xlam", "application/vnd.ms-excel.addin.macroEnabled.12"},
|
||||
{".xlc", "application/vnd.ms-excel"},
|
||||
{".xld", "application/vnd.ms-excel"},
|
||||
{".xlk", "application/vnd.ms-excel"},
|
||||
{".xll", "application/vnd.ms-excel"},
|
||||
{".xlm", "application/vnd.ms-excel"},
|
||||
{".xls", "application/vnd.ms-excel"},
|
||||
{".xlsb", "application/vnd.ms-excel.sheet.binary.macroEnabled.12"},
|
||||
{".xlsm", "application/vnd.ms-excel.sheet.macroEnabled.12"},
|
||||
{".xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"},
|
||||
{".xlt", "application/vnd.ms-excel"},
|
||||
{".xltm", "application/vnd.ms-excel.template.macroEnabled.12"},
|
||||
{".xltx", "application/vnd.openxmlformats-officedocument.spreadsheetml.template"},
|
||||
{".xlw", "application/vnd.ms-excel"},
|
||||
{".xml", "text/xml"},
|
||||
{".xmta", "application/xml"},
|
||||
{".xof", "x-world/x-vrml"},
|
||||
{".XOML", "text/plain"},
|
||||
{".xpm", "image/x-xpixmap"},
|
||||
{".xps", "application/vnd.ms-xpsdocument"},
|
||||
{".xrm-ms", "text/xml"},
|
||||
{".xsc", "application/xml"},
|
||||
{".xsd", "text/xml"},
|
||||
{".xsf", "text/xml"},
|
||||
{".xsl", "text/xml"},
|
||||
{".xslt", "text/xml"},
|
||||
{".xsn", "application/octet-stream"},
|
||||
{".xss", "application/xml"},
|
||||
{".xspf", "application/xspf+xml"},
|
||||
{".xtp", "application/octet-stream"},
|
||||
{".xwd", "image/x-xwindowdump"},
|
||||
{".z", "application/x-compress"},
|
||||
{".zip", "application/zip"},
|
||||
|
||||
{"application/fsharp-script", ".fsx"},
|
||||
{"application/msaccess", ".adp"},
|
||||
{"application/msword", ".doc"},
|
||||
{"application/octet-stream", ".bin"},
|
||||
{"application/onenote", ".one"},
|
||||
{"application/postscript", ".eps"},
|
||||
{"application/vnd.ms-excel", ".xls"},
|
||||
{"application/vnd.ms-powerpoint", ".ppt"},
|
||||
{"application/vnd.ms-works", ".wks"},
|
||||
{"application/vnd.visio", ".vsd"},
|
||||
{"application/x-director", ".dir"},
|
||||
{"application/x-shockwave-flash", ".swf"},
|
||||
{"application/x-x509-ca-cert", ".cer"},
|
||||
{"application/x-zip-compressed", ".zip"},
|
||||
{"application/xhtml+xml", ".xhtml"},
|
||||
{"application/xml", ".xml"}, // anomoly, .xml -> text/xml, but application/xml -> many thingss, but all are xml, so safest is .xml
|
||||
{"audio/aac", ".AAC"},
|
||||
{"audio/aiff", ".aiff"},
|
||||
{"audio/basic", ".snd"},
|
||||
{"audio/mid", ".midi"},
|
||||
{"audio/wav", ".wav"},
|
||||
{"audio/x-m4a", ".m4a"},
|
||||
{"audio/x-mpegurl", ".m3u"},
|
||||
{"audio/x-pn-realaudio", ".ra"},
|
||||
{"audio/x-smd", ".smd"},
|
||||
{"image/bmp", ".bmp"},
|
||||
{"image/jpeg", ".jpg"},
|
||||
{"image/pict", ".pic"},
|
||||
{"image/png", ".png"},
|
||||
{"image/tiff", ".tiff"},
|
||||
{"image/x-macpaint", ".mac"},
|
||||
{"image/x-quicktime", ".qti"},
|
||||
{"message/rfc822", ".eml"},
|
||||
{"text/html", ".html"},
|
||||
{"text/plain", ".txt"},
|
||||
{"text/scriptlet", ".wsc"},
|
||||
{"text/xml", ".xml"},
|
||||
{"video/3gpp", ".3gp"},
|
||||
{"video/3gpp2", ".3gp2"},
|
||||
{"video/mp4", ".mp4"},
|
||||
{"video/mpeg", ".mpg"},
|
||||
{"video/quicktime", ".mov"},
|
||||
{"video/vnd.dlna.mpeg-tts", ".m2t"},
|
||||
{"video/x-dv", ".dv"},
|
||||
{"video/x-la-asf", ".lsf"},
|
||||
{"video/x-ms-asf", ".asf"},
|
||||
{"x-world/x-vrml", ".xof"},
|
||||
|
||||
#endregion
|
||||
|
||||
};
|
||||
|
||||
var cache = mappings.ToList(); // need ToList() to avoid modifying while still enumerating
|
||||
|
||||
foreach (var mapping in cache)
|
||||
{
|
||||
if (!mappings.ContainsKey(mapping.Value))
|
||||
{
|
||||
mappings.Add(mapping.Value, mapping.Key);
|
||||
}
|
||||
}
|
||||
|
||||
return mappings;
|
||||
}
|
||||
|
||||
public static string GetMimeType(string extension)
|
||||
{
|
||||
if (extension == null)
|
||||
{
|
||||
throw new ArgumentNullException("extension");
|
||||
}
|
||||
|
||||
if (!extension.StartsWith("."))
|
||||
{
|
||||
extension = "." + extension;
|
||||
}
|
||||
|
||||
string mime;
|
||||
|
||||
return _mappings.Value.TryGetValue(extension, out mime) ? mime : "application/octet-stream";
|
||||
}
|
||||
|
||||
public static string GetExtension(string mimeType)
|
||||
{
|
||||
if (mimeType == null)
|
||||
{
|
||||
throw new ArgumentNullException("mimeType");
|
||||
}
|
||||
|
||||
if (mimeType.StartsWith("."))
|
||||
{
|
||||
throw new ArgumentException("Requested mime type is not valid: " + mimeType);
|
||||
}
|
||||
|
||||
string extension;
|
||||
|
||||
if (_mappings.Value.TryGetValue(mimeType, out extension))
|
||||
{
|
||||
return extension;
|
||||
}
|
||||
|
||||
throw new ArgumentException("Requested mime type is not registered: " + mimeType);
|
||||
}
|
||||
}
|
||||
}
|
||||
425
util/RavenKeyFactory.cs
Normal file
@@ -0,0 +1,425 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Org.BouncyCastle.Security;
|
||||
using Org.BouncyCastle.Crypto;
|
||||
using Org.BouncyCastle.OpenSsl;
|
||||
using rockfishCore.Models;
|
||||
using rockfishCore.Util;
|
||||
|
||||
|
||||
|
||||
namespace rockfishCore.Util
|
||||
{
|
||||
//Key generator controller
|
||||
public static class RavenKeyFactory
|
||||
{
|
||||
//Scheduleable users
|
||||
private const string SERVICE_TECHS_FEATURE_NAME = "ServiceTechs";
|
||||
|
||||
//Accounting add-on
|
||||
private const string ACCOUNTING_FEATURE_NAME = "Accounting";
|
||||
|
||||
//This feature name means it's a trial key
|
||||
private const string TRIAL_FEATURE_NAME = "TrialMode";
|
||||
|
||||
//This feature name means it's a SAAS or rental mode key for month to month hosted service
|
||||
private const string RENTAL_FEATURE_NAME = "ServiceMode";
|
||||
|
||||
#region license classes
|
||||
|
||||
//DTO object returned on license query
|
||||
internal class LicenseFeature
|
||||
{
|
||||
//name of feature / product
|
||||
public string Feature { get; set; }
|
||||
|
||||
//Optional count for items that require it
|
||||
public long Count { get; set; }
|
||||
|
||||
}
|
||||
|
||||
//DTO object for parsed key
|
||||
internal class AyaNovaLicenseKey
|
||||
{
|
||||
public AyaNovaLicenseKey()
|
||||
{
|
||||
Features = new List<LicenseFeature>();
|
||||
RegisteredTo = "UNLICENSED";
|
||||
Id = RegisteredTo;
|
||||
}
|
||||
|
||||
public bool IsEmpty
|
||||
{
|
||||
get
|
||||
{
|
||||
//Key is empty if it's not registered to anyone or there are no features in it
|
||||
return string.IsNullOrWhiteSpace(RegisteredTo) || (Features == null || Features.Count == 0);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetch the license status of the feature in question
|
||||
/// </summary>
|
||||
/// <param name="Feature"></param>
|
||||
/// <returns>LicenseFeature object or null if there is no license</returns>
|
||||
public LicenseFeature GetLicenseFeature(string Feature)
|
||||
{
|
||||
if (IsEmpty)
|
||||
return null;
|
||||
|
||||
string lFeature = Feature.ToLowerInvariant();
|
||||
|
||||
foreach (LicenseFeature l in Features)
|
||||
{
|
||||
if (l.Feature.ToLowerInvariant() == lFeature)
|
||||
{
|
||||
return l;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Check for the existance of license feature
|
||||
/// </summary>
|
||||
/// <param name="Feature"></param>
|
||||
/// <returns>bool</returns>
|
||||
public bool HasLicenseFeature(string Feature)
|
||||
{
|
||||
if (IsEmpty)
|
||||
return false;
|
||||
|
||||
string lFeature = Feature.ToLowerInvariant();
|
||||
|
||||
foreach (LicenseFeature l in Features)
|
||||
{
|
||||
if (l.Feature.ToLowerInvariant() == lFeature)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public bool WillExpire
|
||||
{
|
||||
get
|
||||
{
|
||||
return LicenseExpiration < DateUtil.EmptyDateValue;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public bool LicenseExpired
|
||||
{
|
||||
get
|
||||
{
|
||||
return LicenseExpiration > DateTime.Now;
|
||||
}
|
||||
}
|
||||
|
||||
public bool MaintenanceExpired
|
||||
{
|
||||
get
|
||||
{
|
||||
return MaintenanceExpiration > DateTime.Now;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public bool TrialLicense
|
||||
{
|
||||
get
|
||||
{
|
||||
return HasLicenseFeature(TRIAL_FEATURE_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
public bool RentalLicense
|
||||
{
|
||||
get
|
||||
{
|
||||
return HasLicenseFeature(RENTAL_FEATURE_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
public string LicenseFormat { get; set; }
|
||||
public string Id { get; set; }
|
||||
public string RegisteredTo { get; set; }
|
||||
public Guid DbId { get; set; }
|
||||
public DateTime LicenseExpiration { get; set; }
|
||||
public DateTime MaintenanceExpiration { get; set; }
|
||||
public List<LicenseFeature> Features { get; set; }
|
||||
|
||||
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region sample v8 key
|
||||
// private static string SAMPLE_KEY = @"[KEY
|
||||
// {
|
||||
// ""Key"": {
|
||||
// ""LicenseFormat"": ""2018"",
|
||||
// ""Id"": ""34-1516288681"", <----Customer id followed by key serial id
|
||||
// ""RegisteredTo"": ""Super TestCo"",
|
||||
// ""DBID"": ""df558559-7f8a-4c7b-955c-959ebcdf71f3"",
|
||||
// ""LicenseExpiration"": ""2019-01-18T07:18:01.2329138-08:00"", <--- UTC,special 1/1/5555 DateTime if perpetual license, applies to all features 1/1/5555 indicates not expiring
|
||||
// ""MaintenanceExpiration"": ""2019-01-18T07:18:01.2329138-08:00"", <-- UTC, DateTime support and updates subscription runs out, applies to all features
|
||||
// ""Features"": { <-- deprecate, collection doesn't need to be inside a property?
|
||||
// ""Feature"": [
|
||||
// {
|
||||
// ""Name"": ""ServiceTechs"",
|
||||
// ""Count"":""10"",
|
||||
// },
|
||||
// {
|
||||
// ""Name"": ""Accounting""
|
||||
// },
|
||||
// {
|
||||
// ""Name"": ""TrialMode""<---means is a trial key
|
||||
// },
|
||||
// {
|
||||
// ""Name"": ""ServiceMode"" <----Means it's an SAAS/Rental key
|
||||
// }
|
||||
|
||||
// ]
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// KEY]
|
||||
// [SIGNATURE
|
||||
// HEcY3JCVwK9HFXEFnldUEPXP4Q7xoZfMZfOfx1cYmfVF3MVWePyZ9dqVZcY7pk3RmR1BbhQdhpljsYLl+ZLTRhNa54M0EFa/bQnBnbwYZ70EQl8fz8WOczYTEBo7Sm5EyC6gSHtYZu7yRwBvhQzpeMGth5uWnlfPb0dMm0DQM7PaqhdWWW9GCSOdZmFcxkFQ8ERLDZhVMbd8PJKyLvZ+sGMrmYTAIoL0tqa7nrxYkM71uJRTAmQ0gEl4bJdxiV825U1J+buNQuTZdacZKEPSjQQkYou10jRbReUmP2vDpvu+nA1xdJe4b5LlyQL+jiIXH17lf93xlCUb0UkDpu8iNQ==
|
||||
// SIGNATURE]\";
|
||||
|
||||
#endregion
|
||||
|
||||
#region RAVEN test code for development
|
||||
|
||||
//Trial key magic number for development and testing, all other guids will be fully licensed
|
||||
private static Guid TEST_TRIAL_KEY_DBID = new Guid("{A6D18A8A-5613-4979-99DA-80D07641A2FE}");
|
||||
|
||||
|
||||
|
||||
public static string GetRavenTestKey(Guid dbid)
|
||||
{
|
||||
|
||||
//Build a sample test key, sign it and return it
|
||||
AyaNovaLicenseKey k = new AyaNovaLicenseKey();
|
||||
k.LicenseFormat = "2018";
|
||||
|
||||
var vv = Math.Truncate((DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds);
|
||||
string sId = vv.ToString();
|
||||
if (sId.Contains(","))
|
||||
sId = sId.Split('.')[0];
|
||||
k.Id = $"00-{sId}";
|
||||
k.RegisteredTo = "Test Testerson Inc.";
|
||||
k.DbId = dbid;
|
||||
|
||||
//add accounting and user features either way
|
||||
k.Features.Add(new LicenseFeature() { Feature = ACCOUNTING_FEATURE_NAME, Count = 0 });
|
||||
k.Features.Add(new LicenseFeature() { Feature = SERVICE_TECHS_FEATURE_NAME, Count = 100 });
|
||||
|
||||
|
||||
//fake trial key or fake licensed key
|
||||
if (dbid == TEST_TRIAL_KEY_DBID)
|
||||
{
|
||||
k.MaintenanceExpiration = k.LicenseExpiration = DateTime.UtcNow.AddMonths(1);
|
||||
k.Features.Add(new LicenseFeature() { Feature = TRIAL_FEATURE_NAME, Count = 0 });
|
||||
}
|
||||
else
|
||||
{
|
||||
k.MaintenanceExpiration = DateTime.UtcNow.AddYears(1);
|
||||
k.LicenseExpiration = DateUtil.EmptyDateValue;//1/1/5555 as per spec
|
||||
}
|
||||
|
||||
return genKey(k);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// New RAVEN key generator, so far just for testing purposes
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private static string genKey(AyaNovaLicenseKey k)
|
||||
{
|
||||
|
||||
|
||||
if (string.IsNullOrWhiteSpace(k.RegisteredTo))
|
||||
throw new ArgumentException("RegisteredTo is required", "RegisteredTo");
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
StringBuilder sbKey = new StringBuilder();
|
||||
StringWriter sw = new StringWriter(sbKey);
|
||||
|
||||
using (Newtonsoft.Json.JsonWriter w = new Newtonsoft.Json.JsonTextWriter(sw))
|
||||
{
|
||||
w.Formatting = Newtonsoft.Json.Formatting.Indented;
|
||||
|
||||
//outer object start
|
||||
w.WriteStartObject();
|
||||
|
||||
w.WritePropertyName("Key");
|
||||
|
||||
w.WriteStartObject();//start of key object
|
||||
|
||||
w.WritePropertyName("LicenseFormat");
|
||||
w.WriteValue(k.LicenseFormat);
|
||||
|
||||
w.WritePropertyName("Id");
|
||||
w.WriteValue(k.Id);
|
||||
|
||||
w.WritePropertyName("RegisteredTo");
|
||||
w.WriteValue(k.RegisteredTo);
|
||||
|
||||
w.WritePropertyName("DBID");
|
||||
w.WriteValue(k.DbId);
|
||||
|
||||
w.WritePropertyName("LicenseExpiration");
|
||||
w.WriteValue(k.LicenseExpiration);
|
||||
|
||||
w.WritePropertyName("MaintenanceExpiration");
|
||||
w.WriteValue(k.MaintenanceExpiration);
|
||||
|
||||
|
||||
|
||||
//FEATURES
|
||||
// w.WritePropertyName("Features");
|
||||
// w.WriteStartObject();
|
||||
w.WritePropertyName("Features");
|
||||
w.WriteStartArray();
|
||||
|
||||
foreach (LicenseFeature lf in k.Features)
|
||||
{
|
||||
|
||||
w.WriteStartObject();
|
||||
|
||||
w.WritePropertyName("Name");
|
||||
w.WriteValue(lf.Feature);
|
||||
|
||||
if (lf.Count > 0)
|
||||
{
|
||||
w.WritePropertyName("Count");
|
||||
w.WriteValue(lf.Count);
|
||||
}
|
||||
|
||||
w.WriteEndObject();
|
||||
|
||||
}
|
||||
|
||||
|
||||
//end of features array
|
||||
w.WriteEnd();
|
||||
|
||||
//end of features object
|
||||
// w.WriteEndObject();
|
||||
|
||||
//end of AyaNova/AyaNovaLite key object
|
||||
w.WriteEndObject();
|
||||
|
||||
//close outer 'wrapper' object brace }
|
||||
w.WriteEndObject();
|
||||
|
||||
}//end of using statement
|
||||
|
||||
|
||||
// ## CALCULATE SIGNATURE
|
||||
|
||||
//GET JSON as a string with whitespace stripped outside of delimited strings
|
||||
//http://stackoverflow.com/questions/8913138/minify-indented-json-string-in-net
|
||||
string keyNoWS = System.Text.RegularExpressions.Regex.Replace(sbKey.ToString(), "(\"(?:[^\"\\\\]|\\\\.)*\")|\\s+", "$1");
|
||||
|
||||
|
||||
//**** Note this is our real 2016 private key
|
||||
var privatePEM = @"-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpAIBAAKCAQEAz7wrvLDcKVMZ31HFGBnLWL08IodYIV5VJkKy1Z0n2snprhSi
|
||||
u3izxTyz+SLpftvKHJpky027ii7l/pL9Bo3JcjU5rKrxXavnE7TuYPjXn16dNLd0
|
||||
K/ERSU+pXLmUaVN0nUWuGuUMoGJMEXoulS6pJiG11yu3BM9fL2Nbj0C6a+UwzEHF
|
||||
mns3J/daZOb4gAzMUdJfh9OJ0+wRGzR8ZxyC99Na2gDmqYglUkSMjwLTL/HbgwF4
|
||||
OwmoQYJBcET0Wa6Gfb17SaF8XRBV5ZtpCsbStkthGeoXZkFriB9c1eFQLKpBYQo2
|
||||
DW3H1MPG2nAlQZLbkJj5cSh7/t1bRF08m6P+EQIDAQABAoIBAQCGvTpxLRXgB/Kk
|
||||
EtmQBEsMx9EVZEwZeKIqKuDsBP8wvf4/10ql5mhT6kehtK9WhSDW5J2z8DtQKZMs
|
||||
SBKuCZE77qH2CPp9E17SPWzQoRbaW/gDlWpYhgf8URs89XH5zxO4XtXKw/4omRlV
|
||||
zLYiNR2pifv0EHqpOAg5KGzewdEo4VgXgtRWpHZLMpH2Q0/5ZIKMhstI6vFHP1p7
|
||||
jmU4YI6uxiu7rVrZDmIUsAGoTdMabNqK/N8hKaoBiIto0Jn1ck26g+emLg8m160y
|
||||
Xciu5yFUU+PP1SJMUs+k1UnAWf4p46X9jRLQCBRue9o0Ntiq/75aljRoDvgdwDsR
|
||||
mg4ZANqxAoGBAPBoM5KoMZ4sv8ZFv8V+V8hgL5xiLgGoiwQl91mRsHRM/NQU5A/w
|
||||
tH8nmwUrJOrksV7kX9228smKmoliTptyGGyi1NPmSkA7cN9YYnENoOEBHCVNK9vh
|
||||
P+bkbMYUDNMW4fgOj09oXtQtMl5E2B3OTGoNwZ2w13YQJ8RIniLPsX7nAoGBAN01
|
||||
eQNcUzQk9YrFGTznOs8udDLBfigDxaNnawvPueulJdBy6ZXDDrKmkQQA7xxl8YPr
|
||||
dNtBq2lOgnb6+smC15TaAfV/fb8BLmkSwdn4Fy0FApIXIEOnLq+wjkte98nuezl8
|
||||
9KXDzaqNI9hPuk2i36tJuLLMH8hzldveWbWjSlRHAoGBAKRPE7CQtBjfjNL+qOta
|
||||
RrT0yJWhpMANabYUHNJi+K8ET2jEPnuGkFa3wwPtUPYaCABLJhprB9Unnid3wTIM
|
||||
8RSO1ddd9jGgbqy3w9Bw+BvQnmQAMpG9iedNB+r5mSpM4XSgvuIO+4EYwuwbMXpt
|
||||
nVx+um4Eh75xnDxTRYGVYkLRAoGAaZVpUlpR+HSfooHbPv+bSWKB4ewLPCw4vHrT
|
||||
VErtEfW8q9b9eRcmP81TMFcFykc6VN4g47pfh58KlKHM7DwAjDLWdohIy89TiKGE
|
||||
V3acEUfv5y0UoFX+6ara8Ey+9upWdKUY3Lotw3ckoc3EPeQ84DQK7YSSswnAgLaL
|
||||
mS/8fWcCgYBjRefVbEep161d2DGruk4X7eNI9TFJ278h6ydW5kK9aTJuxkrtKIp4
|
||||
CYf6emoB4mLXFPvAmnsalkhN2iB29hUZCXXSUjpKZrpijL54Wdu2S6ynm7aT97NF
|
||||
oArP0E2Vbow3JMxq/oeXmHbrLMLQfYyXwFmciLFigOtkd45bfHdrbA==
|
||||
-----END RSA PRIVATE KEY-----";
|
||||
|
||||
PemReader pr = new PemReader(new StringReader(privatePEM));
|
||||
AsymmetricCipherKeyPair keys = (AsymmetricCipherKeyPair)pr.ReadObject();
|
||||
var encoder = new UTF8Encoding(false, true);
|
||||
var inputData = encoder.GetBytes(keyNoWS);
|
||||
var signer = SignerUtilities.GetSigner("SHA256WITHRSA");
|
||||
signer.Init(true, keys.Private);
|
||||
signer.BlockUpdate(inputData, 0, inputData.Length);
|
||||
var sign = signer.GenerateSignature();
|
||||
var signature = Convert.ToBase64String(sign);
|
||||
|
||||
|
||||
System.Text.StringBuilder sbOut = new StringBuilder();
|
||||
sbOut.AppendLine("[KEY");
|
||||
sbOut.AppendLine(sbKey.ToString());
|
||||
sbOut.AppendLine("KEY]");
|
||||
sbOut.AppendLine("[SIGNATURE");
|
||||
sbOut.AppendLine(signature);
|
||||
sbOut.AppendLine("SIGNATURE]");
|
||||
|
||||
|
||||
return sbOut.ToString();
|
||||
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return (ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// private static void AddLicensePlugin(Newtonsoft.Json.JsonWriter w, string pluginName, DateTime pluginExpires)
|
||||
// {
|
||||
|
||||
// //this dictionary is used by the additional message code to
|
||||
// //make the human readable portion of the license
|
||||
// _plugins.Add(pluginName, pluginExpires);
|
||||
|
||||
// //this is adding it to the actual key
|
||||
// w.WriteStartObject();
|
||||
// w.WritePropertyName("Item");
|
||||
// w.WriteValue(pluginName);
|
||||
|
||||
// w.WritePropertyName("SubscriptionExpires");
|
||||
// w.WriteValue(pluginExpires);
|
||||
|
||||
// w.WriteEndObject();
|
||||
// //----------------
|
||||
// }
|
||||
|
||||
|
||||
|
||||
//eoc
|
||||
}
|
||||
//eons
|
||||
}
|
||||
563
util/RfMail.cs
Normal file
@@ -0,0 +1,563 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
|
||||
using MailKit.Net.Smtp;
|
||||
using MailKit.Net.Imap;
|
||||
using MailKit.Search;
|
||||
using MailKit;
|
||||
using MimeKit;
|
||||
|
||||
namespace rockfishCore.Util
|
||||
{
|
||||
//http://www.mimekit.net/
|
||||
public static class RfMail
|
||||
{
|
||||
|
||||
public const string MAIL_SMPT_ADDRESS = "smtp.ayanova.com";
|
||||
public const int MAIL_SMPT_PORT = 465;
|
||||
public const string MAIL_IMAP_ADDRESS = "mail.ayanova.com";
|
||||
public const int MAIL_IMAP_PORT = 993;
|
||||
|
||||
public const string MAIL_ACCOUNT_SUPPORT = "support@ayanova.com";
|
||||
public const string MAIL_ACCOUNT_PASSWORD_SUPPORT = "e527b6c5a00c27bb61ca694b3de0ee178cbe3f1541a772774762ed48e9caf5ce";
|
||||
|
||||
public const string MAIL_ACCOUNT_SALES = "sales@ayanova.com";
|
||||
public const string MAIL_ACCOUNT_PASSWORD_SALES = "6edbae5eb616a975abf86bcd9f45616f5c70c4f05189af60a1caaa62b406149d";
|
||||
|
||||
public enum rfMailAccount
|
||||
{
|
||||
support = 1,
|
||||
sales = 2
|
||||
}
|
||||
|
||||
|
||||
public class rfMailMessage
|
||||
{
|
||||
public MimeMessage message;
|
||||
public uint uid;
|
||||
}
|
||||
|
||||
|
||||
class DeliverStatusSmtpClient : SmtpClient
|
||||
{
|
||||
protected override DeliveryStatusNotification? GetDeliveryStatusNotifications(MimeMessage message, MailboxAddress mailbox)
|
||||
{
|
||||
return DeliveryStatusNotification.Delay |
|
||||
DeliveryStatusNotification.Failure |
|
||||
DeliveryStatusNotification.Success;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////
|
||||
//
|
||||
// Do the sending with optional deliver status receipt
|
||||
//
|
||||
public static void DoSend(MimeMessage message, string MailAccount, string MailAccountPassword, bool trackDeliveryStatus)
|
||||
{
|
||||
|
||||
if (trackDeliveryStatus)
|
||||
{
|
||||
//set the return receipt and disposition to headers
|
||||
message.Headers.Add("Return-Receipt-To", "<" + MailAccount + ">");
|
||||
message.Headers.Add("Disposition-Notification-To", "<" + MailAccount + ">");
|
||||
using (var client = new DeliverStatusSmtpClient())
|
||||
{
|
||||
//Accept all SSL certificates (in case the server supports STARTTLS)
|
||||
//(we have a funky cert on the mail server)
|
||||
client.ServerCertificateValidationCallback = (s, c, h, e) => true;
|
||||
client.Connect(MAIL_SMPT_ADDRESS, MAIL_SMPT_PORT, true);
|
||||
|
||||
// Note: since we don't have an OAuth2 token, disable
|
||||
// the XOAUTH2 authentication mechanism.
|
||||
client.AuthenticationMechanisms.Remove("XOAUTH2");
|
||||
|
||||
// Note: only needed if the SMTP server requires authentication
|
||||
client.Authenticate(MAIL_ACCOUNT_SUPPORT, MAIL_ACCOUNT_PASSWORD_SUPPORT);
|
||||
|
||||
client.Send(message);
|
||||
client.Disconnect(true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
using (var client = new SmtpClient())
|
||||
{
|
||||
//Accept all SSL certificates (in case the server supports STARTTLS)
|
||||
//(we have a funky cert on the mail server)
|
||||
client.ServerCertificateValidationCallback = (s, c, h, e) => true;
|
||||
client.Connect(MAIL_SMPT_ADDRESS, MAIL_SMPT_PORT, true);
|
||||
|
||||
// Note: since we don't have an OAuth2 token, disable
|
||||
// the XOAUTH2 authentication mechanism.
|
||||
client.AuthenticationMechanisms.Remove("XOAUTH2");
|
||||
|
||||
// Note: only needed if the SMTP server requires authentication
|
||||
client.Authenticate(MAIL_ACCOUNT_SUPPORT, MAIL_ACCOUNT_PASSWORD_SUPPORT);
|
||||
|
||||
client.Send(message);
|
||||
client.Disconnect(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void SendMessage(string MessageFrom, string MessageTo, string MessageSubject, string MessageBody,
|
||||
bool trackDeliveryStatus = false, string CustomHeader = "", string CustomHeaderValue = "")
|
||||
{
|
||||
var message = new MimeMessage();
|
||||
message.From.Add(new MailboxAddress(MessageFrom));
|
||||
message.To.Add(new MailboxAddress(MessageTo));
|
||||
message.Subject = MessageSubject;
|
||||
|
||||
message.Body = new TextPart("plain")
|
||||
{
|
||||
Text = MessageBody
|
||||
};
|
||||
|
||||
message.Headers["X-Mailer"] = RfVersion.Full;
|
||||
|
||||
if (CustomHeader != "" && CustomHeaderValue != "")
|
||||
{
|
||||
message.Headers["X-Rockfish-" + CustomHeader] = CustomHeaderValue;
|
||||
}
|
||||
|
||||
//send with optional tracking
|
||||
DoSend(message, MAIL_ACCOUNT_SUPPORT, MAIL_ACCOUNT_PASSWORD_SUPPORT, trackDeliveryStatus);
|
||||
|
||||
// using (var client = new SmtpClient())
|
||||
// {
|
||||
// //Accept all SSL certificates (in case the server supports STARTTLS)
|
||||
// //(we have a funky cert on the mail server)
|
||||
// client.ServerCertificateValidationCallback = (s, c, h, e) => true;
|
||||
// client.Connect(MAIL_SMPT_ADDRESS, MAIL_SMPT_PORT, true);
|
||||
|
||||
// // Note: since we don't have an OAuth2 token, disable
|
||||
// // the XOAUTH2 authentication mechanism.
|
||||
// client.AuthenticationMechanisms.Remove("XOAUTH2");
|
||||
|
||||
// // Note: only needed if the SMTP server requires authentication
|
||||
// client.Authenticate(MAIL_ACCOUNT_SUPPORT, MAIL_ACCOUNT_PASSWORD_SUPPORT);
|
||||
|
||||
// client.Send(message);
|
||||
// client.Disconnect(true);
|
||||
// }
|
||||
}//send message
|
||||
|
||||
|
||||
public static void ReplyMessage(uint replyToMessageId, rfMailAccount replyFromAccount, string replyBody,
|
||||
bool replyToAll, bool trackDeliveryStatus = false, string CustomHeader = "", string CustomHeaderValue = "")
|
||||
{
|
||||
//get the original to reply to it:
|
||||
MimeMessage message = GetMessage(replyToMessageId, replyFromAccount);
|
||||
if (message == null)
|
||||
{
|
||||
throw new System.ArgumentException("RfMail:ReplyMessage->source message not found (id=" + replyToMessageId.ToString() + ")");
|
||||
}
|
||||
//construct the new message
|
||||
|
||||
var reply = new MimeMessage();
|
||||
MailboxAddress from = null;
|
||||
string from_account = "";
|
||||
string from_account_password = "";
|
||||
switch (replyFromAccount)
|
||||
{
|
||||
case rfMailAccount.sales:
|
||||
from_account = MAIL_ACCOUNT_SALES;
|
||||
from_account_password = MAIL_ACCOUNT_PASSWORD_SALES;
|
||||
from = new MailboxAddress(from_account);
|
||||
break;
|
||||
default:
|
||||
from_account = MAIL_ACCOUNT_SUPPORT;
|
||||
from_account_password = MAIL_ACCOUNT_PASSWORD_SUPPORT;
|
||||
from = new MailboxAddress(from_account);
|
||||
break;
|
||||
}
|
||||
reply.From.Add(from);
|
||||
|
||||
// reply to the sender of the message
|
||||
if (message.ReplyTo.Count > 0)
|
||||
{
|
||||
reply.To.AddRange(message.ReplyTo);
|
||||
}
|
||||
else if (message.From.Count > 0)
|
||||
{
|
||||
reply.To.AddRange(message.From);
|
||||
}
|
||||
else if (message.Sender != null)
|
||||
{
|
||||
reply.To.Add(message.Sender);
|
||||
}
|
||||
|
||||
if (replyToAll)
|
||||
{
|
||||
// include all of the other original recipients (removing ourselves from the list)
|
||||
reply.To.AddRange(message.To.Mailboxes.Where(x => x.Address != from.Address));
|
||||
reply.Cc.AddRange(message.Cc.Mailboxes.Where(x => x.Address != from.Address));
|
||||
}
|
||||
|
||||
|
||||
// set the reply subject
|
||||
if (!message.Subject.StartsWith("Re:", StringComparison.OrdinalIgnoreCase))
|
||||
reply.Subject = "Re: " + message.Subject;
|
||||
else
|
||||
reply.Subject = message.Subject;
|
||||
|
||||
// construct the In-Reply-To and References headers
|
||||
if (!string.IsNullOrEmpty(message.MessageId))
|
||||
{
|
||||
reply.InReplyTo = message.MessageId;
|
||||
foreach (var id in message.References)
|
||||
reply.References.Add(id);
|
||||
reply.References.Add(message.MessageId);
|
||||
}
|
||||
|
||||
// quote the original message text
|
||||
using (var quoted = new StringWriter())
|
||||
{
|
||||
var sender = message.Sender ?? message.From.Mailboxes.FirstOrDefault();
|
||||
var name = sender != null ? (!string.IsNullOrEmpty(sender.Name) ? sender.Name : sender.Address) : "someone";
|
||||
|
||||
quoted.WriteLine("On {0}, {1} wrote:", message.Date.ToString("f"), name);
|
||||
using (var reader = new StringReader(message.TextBody))
|
||||
{
|
||||
string line;
|
||||
|
||||
while ((line = reader.ReadLine()) != null)
|
||||
{
|
||||
quoted.Write("> ");
|
||||
quoted.WriteLine(line);
|
||||
}
|
||||
}
|
||||
|
||||
reply.Body = new TextPart("plain")
|
||||
{
|
||||
Text = replyBody + "\r\n\r\n\r\n" + quoted.ToString()
|
||||
};
|
||||
}
|
||||
|
||||
reply.Headers["X-Mailer"] = RfVersion.Full;
|
||||
|
||||
if (CustomHeader != "" && CustomHeaderValue != "")
|
||||
{
|
||||
reply.Headers["X-Rockfish-" + CustomHeader] = CustomHeaderValue;
|
||||
}
|
||||
|
||||
|
||||
//send with optional tracking
|
||||
DoSend(reply, from_account, from_account_password, trackDeliveryStatus);
|
||||
|
||||
// using (var client = new SmtpClient())
|
||||
// {
|
||||
// //Accept all SSL certificates (in case the server supports STARTTLS)
|
||||
// //(we have a funky cert on the mail server)
|
||||
// client.ServerCertificateValidationCallback = (s, c, h, e) => true;
|
||||
// client.Connect(MAIL_SMPT_ADDRESS, MAIL_SMPT_PORT, true);
|
||||
|
||||
// // Note: since we don't have an OAuth2 token, disable
|
||||
// // the XOAUTH2 authentication mechanism.
|
||||
// client.AuthenticationMechanisms.Remove("XOAUTH2");
|
||||
|
||||
// // Note: only needed if the SMTP server requires authentication
|
||||
// client.Authenticate(from_account, from_account_password);
|
||||
|
||||
// client.Send(reply);
|
||||
// client.Disconnect(true);
|
||||
// }
|
||||
|
||||
//flag the message as having been replied to
|
||||
FlagInboxMessageSeenReplied(replyToMessageId, replyFromAccount);
|
||||
|
||||
}//send message
|
||||
|
||||
|
||||
//Fetch message by UID
|
||||
public static MimeMessage GetMessage(uint uid, rfMailAccount fromAccount = rfMailAccount.support)
|
||||
{
|
||||
using (var client = new ImapClient())
|
||||
{
|
||||
// Accept all SSL certificates
|
||||
client.ServerCertificateValidationCallback = (s, c, h, e) => true;
|
||||
client.Connect(MAIL_IMAP_ADDRESS, MAIL_IMAP_PORT, true);
|
||||
// Note: since we don't have an OAuth2 token, disable
|
||||
// the XOAUTH2 authentication mechanism.
|
||||
client.AuthenticationMechanisms.Remove("XOAUTH2");
|
||||
if (fromAccount == rfMailAccount.support)
|
||||
{
|
||||
client.Authenticate(MAIL_ACCOUNT_SUPPORT, MAIL_ACCOUNT_PASSWORD_SUPPORT);
|
||||
}
|
||||
else
|
||||
{
|
||||
client.Authenticate(MAIL_ACCOUNT_SALES, MAIL_ACCOUNT_PASSWORD_SALES);
|
||||
}
|
||||
|
||||
var inbox = client.Inbox;
|
||||
inbox.Open(FolderAccess.ReadOnly);
|
||||
var m = inbox.GetMessage(new UniqueId(uid));
|
||||
client.Disconnect(true);
|
||||
return m;
|
||||
}
|
||||
}//get message
|
||||
|
||||
|
||||
|
||||
//Fetch messages by Search query
|
||||
public static List<rfMailMessage> GetMessages(SearchQuery query)
|
||||
{
|
||||
List<rfMailMessage> ret = new List<rfMailMessage>();
|
||||
using (var client = new ImapClient())
|
||||
{
|
||||
// Accept all SSL certificates
|
||||
client.ServerCertificateValidationCallback = (s, c, h, e) => true;
|
||||
client.Connect(MAIL_IMAP_ADDRESS, MAIL_IMAP_PORT, true);
|
||||
// Note: since we don't have an OAuth2 token, disable
|
||||
// the XOAUTH2 authentication mechanism.
|
||||
client.AuthenticationMechanisms.Remove("XOAUTH2");
|
||||
client.Authenticate(MAIL_ACCOUNT_SUPPORT, MAIL_ACCOUNT_PASSWORD_SUPPORT);
|
||||
|
||||
var inbox = client.Inbox;
|
||||
inbox.Open(FolderAccess.ReadOnly);
|
||||
foreach (var uid in inbox.Search(query))
|
||||
{
|
||||
ret.Add(new rfMailMessage { message = inbox.GetMessage(uid), uid = uid.Id });
|
||||
}
|
||||
client.Disconnect(true);
|
||||
}
|
||||
return ret;
|
||||
}//get message
|
||||
|
||||
|
||||
//Flag message as seen and replied by UID
|
||||
public static bool FlagInboxMessageSeenReplied(uint uid, rfMailAccount inAccount = rfMailAccount.support)
|
||||
{
|
||||
using (var client = new ImapClient())
|
||||
{
|
||||
// Accept all SSL certificates
|
||||
client.ServerCertificateValidationCallback = (s, c, h, e) => true;
|
||||
client.Connect(MAIL_IMAP_ADDRESS, MAIL_IMAP_PORT, true);
|
||||
// Note: since we don't have an OAuth2 token, disable
|
||||
// the XOAUTH2 authentication mechanism.
|
||||
client.AuthenticationMechanisms.Remove("XOAUTH2");
|
||||
|
||||
if (inAccount == rfMailAccount.support)
|
||||
{
|
||||
client.Authenticate(MAIL_ACCOUNT_SUPPORT, MAIL_ACCOUNT_PASSWORD_SUPPORT);
|
||||
}
|
||||
else
|
||||
{
|
||||
client.Authenticate(MAIL_ACCOUNT_SALES, MAIL_ACCOUNT_PASSWORD_SALES);
|
||||
}
|
||||
|
||||
var inbox = client.Inbox;
|
||||
inbox.Open(FolderAccess.ReadWrite);
|
||||
inbox.AddFlags(new UniqueId(uid), MessageFlags.Seen, true);
|
||||
inbox.AddFlags(new UniqueId(uid), MessageFlags.Answered, true);
|
||||
client.Disconnect(true);
|
||||
return true;
|
||||
}
|
||||
}//get message
|
||||
|
||||
|
||||
////////////////////////////////////////////////////
|
||||
//Put a message in the drafts folder of support
|
||||
//
|
||||
public static void DraftMessage(string MessageFrom, string MessageTo, string MessageSubject, string MessageBody,
|
||||
string CustomHeader = "", string CustomHeaderValue = "")
|
||||
{
|
||||
var message = new MimeMessage();
|
||||
message.From.Add(new MailboxAddress(MessageFrom));
|
||||
|
||||
|
||||
//case 3512 handle more than one email in the address
|
||||
if (MessageTo.Contains(","))
|
||||
{
|
||||
List<MailboxAddress> mbAll = new List<MailboxAddress>();
|
||||
var addrs = MessageTo.Split(',');
|
||||
foreach (string addr in addrs)
|
||||
{
|
||||
mbAll.Add(new MailboxAddress(addr.Trim()));
|
||||
}
|
||||
message.To.AddRange(mbAll);
|
||||
}
|
||||
else
|
||||
{
|
||||
message.To.Add(new MailboxAddress(MessageTo));
|
||||
}
|
||||
|
||||
|
||||
message.Subject = MessageSubject;
|
||||
|
||||
message.Body = new TextPart("plain")
|
||||
{
|
||||
Text = MessageBody
|
||||
};
|
||||
|
||||
if (CustomHeader != "" && CustomHeaderValue != "")
|
||||
{
|
||||
message.Headers["X-Rockfish-" + CustomHeader] = CustomHeaderValue;
|
||||
}
|
||||
|
||||
//adapted from https://stackoverflow.com/questions/33365072/mailkit-sending-drafts
|
||||
using (var client = new ImapClient())
|
||||
{
|
||||
try
|
||||
{
|
||||
// Accept all SSL certificates
|
||||
client.ServerCertificateValidationCallback = (s, c, h, e) => true;
|
||||
client.Connect(MAIL_IMAP_ADDRESS, MAIL_IMAP_PORT, true);
|
||||
// Note: since we don't have an OAuth2 token, disable
|
||||
// the XOAUTH2 authentication mechanism.
|
||||
client.AuthenticationMechanisms.Remove("XOAUTH2");
|
||||
client.Authenticate(MAIL_ACCOUNT_SUPPORT, MAIL_ACCOUNT_PASSWORD_SUPPORT);
|
||||
|
||||
|
||||
var draftFolder = client.GetFolder("Drafts");//Our surgemail server works with this other servers in future might not
|
||||
if (draftFolder != null)
|
||||
{
|
||||
draftFolder.Open(FolderAccess.ReadWrite);
|
||||
draftFolder.Append(message, MessageFlags.Draft);
|
||||
//draftFolder.Expunge();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new System.Exception("RfMail->DraftMessage() - Exception has occured: " + ex.Message);
|
||||
}
|
||||
|
||||
client.Disconnect(true);
|
||||
}
|
||||
|
||||
}//draft message
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////
|
||||
//Fetch summaries of unread messages in sales and support
|
||||
//inboxes
|
||||
//
|
||||
public static List<rfMessageSummary> GetSalesAndSupportSummaries()
|
||||
{
|
||||
List<rfMessageSummary> ret = new List<rfMessageSummary>();
|
||||
|
||||
ret.AddRange(getInboxSummariesFor(MAIL_ACCOUNT_SUPPORT, MAIL_ACCOUNT_PASSWORD_SUPPORT));
|
||||
ret.AddRange(getInboxSummariesFor(MAIL_ACCOUNT_SALES, MAIL_ACCOUNT_PASSWORD_SALES));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static List<rfMessageSummary> getInboxSummariesFor(string sourceAccount, string sourcePassword)
|
||||
{
|
||||
List<rfMessageSummary> ret = new List<rfMessageSummary>();
|
||||
using (var client = new ImapClient())
|
||||
{
|
||||
// Accept all SSL certificates
|
||||
client.ServerCertificateValidationCallback = (s, c, h, e) => true;
|
||||
client.Connect(MAIL_IMAP_ADDRESS, MAIL_IMAP_PORT, true);
|
||||
// Note: since we don't have an OAuth2 token, disable
|
||||
// the XOAUTH2 authentication mechanism.
|
||||
client.AuthenticationMechanisms.Remove("XOAUTH2");
|
||||
|
||||
client.Authenticate(sourceAccount, sourcePassword);
|
||||
|
||||
var inbox = client.Inbox;
|
||||
inbox.Open(FolderAccess.ReadOnly);
|
||||
|
||||
var summaries = inbox.Fetch(0, -1, MessageSummaryItems.Full | MessageSummaryItems.UniqueId);
|
||||
client.Disconnect(true);
|
||||
foreach (var summary in summaries)
|
||||
{
|
||||
//Sometimes bad hombres don't set a from address so don't expect one
|
||||
string sFrom = "UNKNOWN / NOT SET";
|
||||
if (summary.Envelope.From.Count > 0)
|
||||
{
|
||||
sFrom = summary.Envelope.From[0].ToString().Replace("\"", "").Replace("<", "").Replace(">", "").Trim();
|
||||
}
|
||||
ret.Add(new rfMessageSummary
|
||||
{
|
||||
account = sourceAccount,
|
||||
id = summary.UniqueId.Id,
|
||||
subject = summary.Envelope.Subject,
|
||||
sent = DateUtil.DateTimeOffSetNullableToEpoch(summary.Envelope.Date),
|
||||
from = sFrom,
|
||||
flags = summary.Flags.ToString().ToLowerInvariant()
|
||||
});
|
||||
}
|
||||
}
|
||||
//reverse the results array as emails come in oldest first order but we want oldest last
|
||||
ret.Reverse();
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
public class rfMessageSummary
|
||||
{
|
||||
public string account;
|
||||
public uint id;
|
||||
public string from;
|
||||
public string subject;
|
||||
public long? sent;
|
||||
public string flags;
|
||||
}
|
||||
|
||||
//Fetch a single string preview of message by Account / folder / UID
|
||||
public static rfMessagePreview GetMessagePreview(string mailAccount, string mailFolder, uint uid)
|
||||
{
|
||||
using (var client = new ImapClient())
|
||||
{
|
||||
// Accept all SSL certificates
|
||||
client.ServerCertificateValidationCallback = (s, c, h, e) => true;
|
||||
client.Connect(MAIL_IMAP_ADDRESS, MAIL_IMAP_PORT, true);
|
||||
// Note: since we don't have an OAuth2 token, disable
|
||||
// the XOAUTH2 authentication mechanism.
|
||||
client.AuthenticationMechanisms.Remove("XOAUTH2");
|
||||
|
||||
//TODO: make accounts reside in dictionary in future
|
||||
if (mailAccount == "support@ayanova.com")
|
||||
{
|
||||
client.Authenticate(MAIL_ACCOUNT_SUPPORT, MAIL_ACCOUNT_PASSWORD_SUPPORT);
|
||||
}
|
||||
if (mailAccount == "sales@ayanova.com")
|
||||
{
|
||||
client.Authenticate(MAIL_ACCOUNT_SALES, MAIL_ACCOUNT_PASSWORD_SALES);
|
||||
}
|
||||
|
||||
var fldr = client.GetFolder(mailFolder);
|
||||
fldr.Open(FolderAccess.ReadOnly);
|
||||
var m = fldr.GetMessage(new UniqueId(uid));
|
||||
client.Disconnect(true);
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.Append("From: ");
|
||||
sb.AppendLine(m.From[0].ToString().Replace("\"", "").Replace("<", "").Replace(">", "").Trim());
|
||||
sb.Append("To: ");
|
||||
sb.AppendLine(mailAccount);
|
||||
sb.Append("Subject: ");
|
||||
sb.AppendLine(m.Subject);
|
||||
sb.AppendLine();
|
||||
sb.AppendLine();
|
||||
sb.AppendLine(m.GetTextBody(MimeKit.Text.TextFormat.Plain));
|
||||
rfMessagePreview preview = new rfMessagePreview();
|
||||
preview.id = uid;
|
||||
preview.preview = sb.ToString();
|
||||
preview.isKeyRequest = m.Subject.StartsWith("Request for 30 day temporary");
|
||||
return preview;
|
||||
}
|
||||
}//get message
|
||||
|
||||
public class rfMessagePreview
|
||||
{
|
||||
public bool isKeyRequest;
|
||||
public string preview;
|
||||
public uint id;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//----------------
|
||||
}//eoc
|
||||
}//eons
|
||||
128
util/RfNotify.cs
Normal file
@@ -0,0 +1,128 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;//now there's at least 'two problems' :)
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using rockfishCore.Models;
|
||||
|
||||
namespace rockfishCore.Util
|
||||
{
|
||||
/*
|
||||
This class handles formatting and sending various notifications
|
||||
*/
|
||||
|
||||
public static class RfNotify
|
||||
{
|
||||
//template key magic strings
|
||||
public const string KEY_SUB_RENEW_NOTICE_TEMPLATE = "SubRenewNotice";
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////
|
||||
//SUBSCRIPTION RENEWAL NOTICE
|
||||
//
|
||||
public static object SendSubscriptionRenewalNotice(rockfishContext ct, List<long> purchaseidlist)
|
||||
{
|
||||
|
||||
try
|
||||
{
|
||||
var template = ct.TextTemplate.SingleOrDefault(m => m.Name == KEY_SUB_RENEW_NOTICE_TEMPLATE);
|
||||
|
||||
if (template == null)
|
||||
{
|
||||
return RfResponse.fail("couldn't find template SubRenewNotice");
|
||||
}
|
||||
|
||||
|
||||
//parse out tokens
|
||||
//we expect TITLE and BODY tokens here
|
||||
Dictionary<string, string> tempTokens = parseTemplateTokens(template.Template);
|
||||
|
||||
//get a list of all the products
|
||||
var products = ct.Product.ToList();
|
||||
|
||||
//Get a list of all purchases that are in the list of purchase id's
|
||||
var purchases = ct.Purchase.Where(c => purchaseidlist.Contains(c.Id)).ToList();
|
||||
|
||||
//rf6
|
||||
// string emailTO = purchases[0].Email;
|
||||
var cust = ct.Customer.AsNoTracking().First(r => r.Id == purchases[0].CustomerId);
|
||||
string emailTO = cust.AdminEmail;
|
||||
|
||||
//get company name
|
||||
//rf6
|
||||
// string companyName = ct.Customer.Select(r => new { r.Id, r.Name })
|
||||
// .Where(r => r.Id == purchases[0].CustomerId)
|
||||
// .First().Name;
|
||||
string companyName=cust.Name;
|
||||
|
||||
//TAGS EXPECTED: {{BODY=}}, {{TITLE=}}
|
||||
//REPLACEMENT TOKENS EXPECTED: {{SUBLIST}}
|
||||
StringBuilder sublist = new StringBuilder();
|
||||
foreach (Purchase pc in purchases)
|
||||
{
|
||||
var pr = products.Where(p => p.ProductCode == pc.ProductCode).First();
|
||||
decimal dRenew = Convert.ToDecimal(pr.RenewPrice) / 100m;
|
||||
decimal dMonthly = Convert.ToDecimal(pr.RenewPrice) / 1200m;
|
||||
string sRenew = String.Format("{0:C}", dRenew);
|
||||
string sMonthly = String.Format("{0:C}", dMonthly);
|
||||
string sRenewDate = DateUtil.EpochToDate(pc.ExpireDate).ToString("D");
|
||||
|
||||
sublist.Append("\t- ");
|
||||
sublist.Append(pr.Name);
|
||||
sublist.Append(" will renew on ");
|
||||
sublist.Append(sRenewDate);
|
||||
sublist.Append(" at US");
|
||||
sublist.Append(sRenew);
|
||||
sublist.Append(" for the year + taxes (which works out to only ");
|
||||
sublist.Append(sMonthly);
|
||||
sublist.Append(" per month)");
|
||||
sublist.AppendLine();
|
||||
}
|
||||
|
||||
|
||||
string emailFrom = "support@ayanova.com";
|
||||
string emailSubject = companyName + " " + tempTokens["TITLE"];
|
||||
string emailBody = tempTokens["BODY"].Replace("<<SUBLIST>>", sublist.ToString());
|
||||
|
||||
//put email in drafts
|
||||
RfMail.DraftMessage(emailFrom, emailTO, emailSubject, emailBody, "MsgType", "SubRenewNotice");
|
||||
|
||||
//tag purchase as notified
|
||||
foreach (Purchase pc in purchases)
|
||||
{
|
||||
pc.RenewNoticeSent = true;
|
||||
}
|
||||
ct.SaveChanges();
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return RfResponse.fail(ex.Message);
|
||||
}
|
||||
|
||||
return RfResponse.ok();
|
||||
}
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
//parse out all tokens and values in the template
|
||||
private static Dictionary<string, string> parseTemplateTokens(string src)
|
||||
{
|
||||
Dictionary<string, string> ret = new Dictionary<string, string>();
|
||||
var roughmatches = Regex.Matches(src, @"{{.*?}}", RegexOptions.Singleline);
|
||||
foreach (Match roughmatch in roughmatches)
|
||||
{
|
||||
//capture the token name which is a regex group between {{ and =
|
||||
var token = Regex.Match(roughmatch.Value, @"{{(.*?)=", RegexOptions.Singleline).Groups[1].Value;
|
||||
//capture the token value which is a regex group between {{TOKENNAME= and }}
|
||||
var tokenValue = Regex.Match(roughmatch.Value, @"{{" + token + "=(.*?)}}", RegexOptions.Singleline).Groups[1].Value;
|
||||
ret.Add(token, tokenValue);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
//eoc
|
||||
}
|
||||
//eons
|
||||
}
|
||||
21
util/RfResponse.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
|
||||
namespace rockfishCore.Util
|
||||
{
|
||||
public static class RfResponse
|
||||
{
|
||||
public static object fail(string msg)
|
||||
{
|
||||
return new { error = 1, msg = msg };
|
||||
}
|
||||
|
||||
public static object ok()
|
||||
{
|
||||
return new { ok = 1 };
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
389
util/RfSchema.cs
Normal file
@@ -0,0 +1,389 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Linq;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using rockfishCore.Models;
|
||||
|
||||
namespace rockfishCore.Util
|
||||
{
|
||||
//Key generator controller
|
||||
public static class RfSchema
|
||||
{
|
||||
private static rockfishContext ctx;
|
||||
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/////////// CHANGE THIS ON NEW SCHEMA UPDATE ////////////////////
|
||||
public const int DESIRED_SCHEMA_LEVEL = 15;
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
static int startingSchema = -1;
|
||||
static int currentSchema = -1;
|
||||
|
||||
//check and update schema
|
||||
public static void CheckAndUpdate(rockfishContext context)
|
||||
{
|
||||
ctx = context;
|
||||
bool rfSetExists = false;
|
||||
|
||||
//update schema here?
|
||||
using (var command = ctx.Database.GetDbConnection().CreateCommand())
|
||||
{
|
||||
//first of all, do we have a schema table yet (v0?)
|
||||
command.CommandText = "SELECT name FROM sqlite_master WHERE type='table' AND name='rfset';";
|
||||
ctx.Database.OpenConnection();
|
||||
using (var result = command.ExecuteReader())
|
||||
{
|
||||
if (result.HasRows)
|
||||
{
|
||||
rfSetExists = true;
|
||||
}
|
||||
ctx.Database.CloseConnection();
|
||||
}
|
||||
}
|
||||
//Create schema table (v1)
|
||||
if (!rfSetExists)
|
||||
{
|
||||
//nope, no schema table, add it now and set to v1
|
||||
using (var cmCreateRfSet = ctx.Database.GetDbConnection().CreateCommand())
|
||||
{
|
||||
context.Database.OpenConnection();
|
||||
//first of all, do we have a schema table yet (v0?)
|
||||
cmCreateRfSet.CommandText = "CREATE TABLE rfset (id INTEGER PRIMARY KEY, schema INTEGER NOT NULL);";
|
||||
cmCreateRfSet.ExecuteNonQuery();
|
||||
|
||||
cmCreateRfSet.CommandText = "insert into rfset (schema) values (1);";
|
||||
cmCreateRfSet.ExecuteNonQuery();
|
||||
|
||||
context.Database.CloseConnection();
|
||||
startingSchema = 1;
|
||||
currentSchema = 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//get current schema level
|
||||
using (var cm = ctx.Database.GetDbConnection().CreateCommand())
|
||||
{
|
||||
cm.CommandText = "SELECT schema FROM rfset WHERE id=1;";
|
||||
ctx.Database.OpenConnection();
|
||||
using (var result = cm.ExecuteReader())
|
||||
{
|
||||
if (result.HasRows)
|
||||
{
|
||||
result.Read();
|
||||
currentSchema = startingSchema = result.GetInt32(0);
|
||||
ctx.Database.CloseConnection();
|
||||
}
|
||||
else
|
||||
{
|
||||
ctx.Database.CloseConnection();
|
||||
throw new System.Exception("rockfish->RfSchema->CheckAndUpdate: Error reading schema version");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Bail early no update?
|
||||
if (currentSchema == DESIRED_SCHEMA_LEVEL)
|
||||
return;
|
||||
|
||||
|
||||
|
||||
//************* SCHEMA UPDATES ******************
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
//schema 2 case 3283
|
||||
if (currentSchema < 2)
|
||||
{
|
||||
//add renewal flag to purchase
|
||||
exec("alter table purchase add renewNoticeSent boolean default 0 NOT NULL CHECK (renewNoticeSent IN (0,1))");
|
||||
//Add product table with prices
|
||||
exec("CREATE TABLE product (id INTEGER PRIMARY KEY, name text not null, productCode text, price integer, renewPrice integer )");
|
||||
currentSchema = 2;
|
||||
setSchemaLevel(currentSchema);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
//schema 3 case 3283
|
||||
if (currentSchema < 3)
|
||||
{
|
||||
//add products current as of 2017-July-18
|
||||
exec("insert into product (name, productCode, price, renewPrice) values ('Key administration','300093112',3500, 3500);");
|
||||
exec("insert into product (name, productCode, price, renewPrice) values ('Custom work','300151145',0, 0);");
|
||||
exec("insert into product (name, productCode, price, renewPrice) values ('AyaNova RI sub (old / unused?)','300740314',19900, 6965);");
|
||||
exec("insert into product (name, productCode, price, renewPrice) values ('Single AyaNova schedulable resource 1 year subscription license','300740315',15900, 5565);");
|
||||
exec("insert into product (name, productCode, price, renewPrice) values ('Single AyaNova Lite 1 year subscription license','300740316',6900, 2415);");
|
||||
exec("insert into product (name, productCode, price, renewPrice) values ('Up to 5 AyaNova schedulable resource 1 year subscription license','300740317', 69500, 24325);");
|
||||
exec("insert into product (name, productCode, price, renewPrice) values ('Up to 10 AyaNova schedulable resource 1 year subscription license','300740318',119000, 41650);");
|
||||
exec("insert into product (name, productCode, price, renewPrice) values ('Up to 20 AyaNova schedulable resource 1 year subscription license','300740319',198000, 69300);");
|
||||
exec("insert into product (name, productCode, price, renewPrice) values ('AyaNova WBI (web browser interface) 1 year subscription license','300740321',9900, 3465);");
|
||||
exec("insert into product (name, productCode, price, renewPrice) values ('AyaNova MBI (minimal browser interface) 1 year subscription license','300740322', 9900, 3465);");
|
||||
exec("insert into product (name, productCode, price, renewPrice) values ('AyaNova QBI(QuickBooks interface) 1 year subscription license','300740323',9900, 3465);");
|
||||
exec("insert into product (name, productCode, price, renewPrice) values ('AyaNova PTI(US Peachtree/Sage 50 interface) 1 year subscription license','300740324',9900, 3465);");
|
||||
exec("insert into product (name, productCode, price, renewPrice) values ('AyaNova OLI(Outlook interface) 1 year subscription license','300740325',9900, 3465);");
|
||||
exec("insert into product (name, productCode, price, renewPrice) values ('Plug-in Outlook Schedule Export 1 year subscription license','300740326',1900, 665);");
|
||||
exec("insert into product (name, productCode, price, renewPrice) values ('Plug-in Export to XLS 1 year subscription license','300740327',1900, 665);");
|
||||
exec("insert into product (name, productCode, price, renewPrice) values ('Plug-in Quick Notification 1 year subscription license','300740328',1900, 665);");
|
||||
exec("insert into product (name, productCode, price, renewPrice) values ('Plug-in importexport.csv duplicate 1 year subscription license','300740329',1900, 665);");
|
||||
exec("insert into product (name, productCode, price, renewPrice) values ('Up to 999 AyaNova schedulable resource 1 year subscription license','300741264',15000, 5250);");
|
||||
|
||||
currentSchema = 3;
|
||||
setSchemaLevel(currentSchema);
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
//schema 4 case 3283
|
||||
if (currentSchema < 4)
|
||||
{
|
||||
//Add template table to store email and other templates
|
||||
exec("CREATE TABLE texttemplate (id INTEGER PRIMARY KEY, name text not null, template text)");
|
||||
currentSchema = 4;
|
||||
setSchemaLevel(currentSchema);
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
//schema 5 case 3253
|
||||
if (currentSchema < 5)
|
||||
{
|
||||
exec("alter table customer add active boolean default 1 NOT NULL CHECK (active IN (0,1))");
|
||||
currentSchema = 5;
|
||||
setSchemaLevel(currentSchema);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
//schema 6 case 3308
|
||||
if (currentSchema < 6)
|
||||
{
|
||||
|
||||
exec("CREATE TABLE rfcaseproject (id INTEGER PRIMARY KEY, name text not null)");
|
||||
|
||||
exec("CREATE TABLE rfcase (id INTEGER PRIMARY KEY, title text not null, rfcaseprojectid integer not null, " +
|
||||
"priority integer default 3 NOT NULL CHECK (priority IN (1,2,3,4,5)), notes text, dtcreated integer, dtclosed integer, " +
|
||||
"releaseversion text, releasenotes text, " +
|
||||
"FOREIGN KEY (rfcaseprojectid) REFERENCES rfcaseproject(id))");
|
||||
|
||||
exec("CREATE TABLE rfcaseblob (id INTEGER PRIMARY KEY, rfcaseid integer not null, name text not null, file blob not null, " +
|
||||
"FOREIGN KEY (rfcaseid) REFERENCES rfcase(id) ON DELETE CASCADE )");
|
||||
|
||||
currentSchema = 6;
|
||||
setSchemaLevel(currentSchema);
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
//schema 7 case 3308
|
||||
if (currentSchema < 7)
|
||||
{
|
||||
|
||||
//empty any prior import data
|
||||
exec("delete from rfcaseblob");
|
||||
exec("delete from rfcase");
|
||||
exec("delete from rfcaseproject");
|
||||
|
||||
//Trigger import of all fogbugz cases into rockfish
|
||||
Util.FBImporter.Import(ctx);
|
||||
|
||||
//now get rid of the delete records
|
||||
exec("delete from rfcase where title='deleted from FogBugz'");
|
||||
|
||||
currentSchema = 7;//<<-------------------- TESTING, CHANGE TO 7 BEFORE PRODUCTION
|
||||
setSchemaLevel(currentSchema);
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
//schema 8
|
||||
if (currentSchema < 8)
|
||||
{
|
||||
exec("alter table user add dlkey text");
|
||||
currentSchema = 8;
|
||||
setSchemaLevel(currentSchema);
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
//schema 9
|
||||
if (currentSchema < 9)
|
||||
{
|
||||
exec("alter table user add dlkeyexp integer");
|
||||
currentSchema = 9;
|
||||
setSchemaLevel(currentSchema);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
//schema 10 case 3233
|
||||
if (currentSchema < 10)
|
||||
{
|
||||
|
||||
exec("CREATE TABLE license (" +
|
||||
"id INTEGER PRIMARY KEY, dtcreated integer not null, customerid integer not null, regto text not null, key text not null, code text not null, email text not null, " +
|
||||
"fetchfrom text, dtfetched integer, fetched boolean default 0 NOT NULL CHECK (fetched IN (0,1))" +
|
||||
")");
|
||||
|
||||
currentSchema = 10;
|
||||
setSchemaLevel(currentSchema);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
//schema 11 case 3550
|
||||
if (currentSchema < 11)
|
||||
{
|
||||
//add 15 level product code
|
||||
exec("insert into product (name, productCode, price, renewPrice) values ('Up to 15 AyaNova schedulable resource 1 year subscription license','300807973',165000, 57750);");
|
||||
currentSchema = 11;
|
||||
setSchemaLevel(currentSchema);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
//schema 12
|
||||
if (currentSchema < 12)
|
||||
{
|
||||
exec("alter table customer add supportEmail text");
|
||||
exec("alter table customer add adminEmail text");
|
||||
currentSchema = 12;
|
||||
setSchemaLevel(currentSchema);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
//schema 13
|
||||
if (currentSchema < 13)
|
||||
{
|
||||
//Put all the purchase emails into the customer adminEmail field as CSV
|
||||
//These will get all notification types
|
||||
foreach (var purchase in ctx.Purchase.AsNoTracking())
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(purchase.Email))
|
||||
{
|
||||
var cust = ctx.Customer.SingleOrDefault(m => m.Id == purchase.CustomerId);
|
||||
if (cust == null)
|
||||
{
|
||||
throw new ArgumentNullException($"RFSCHEMA UPDATE 13 (purchases) CUSTOMER {purchase.CustomerId.ToString()} not found!!");
|
||||
}
|
||||
|
||||
var purchaseEmails = purchase.Email.Split(",");
|
||||
foreach (var email in purchaseEmails)
|
||||
{
|
||||
Util.CustomerUtils.AddAdminEmailIfNotPresent(cust, email);
|
||||
}
|
||||
ctx.SaveChanges();
|
||||
}
|
||||
}
|
||||
|
||||
// //Put all the contact emails into the customer adminEmail field and the support field as CSV
|
||||
// //These will get all notification types
|
||||
// foreach (var contact in ctx.Contact.AsNoTracking())
|
||||
// {
|
||||
// if (!string.IsNullOrWhiteSpace(contact.Email))
|
||||
// {
|
||||
// var cust = ctx.Customer.SingleOrDefault(m => m.Id == contact.CustomerId);
|
||||
// if (cust == null)
|
||||
// {
|
||||
// throw new ArgumentNullException($"RFSCHEMA UPDATE 13 (contacts) CUSTOMER {contact.CustomerId.ToString()} not found!!");
|
||||
// }
|
||||
|
||||
// var contactEmails = contact.Email.Split(",");
|
||||
// foreach (var email in contactEmails)
|
||||
// {
|
||||
// Util.CustomerUtils.AddAdminEmailIfNotPresent(cust, email);
|
||||
// }
|
||||
// ctx.SaveChanges();
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
// //Put all the incident emails into the customer supportEmail field unless already in teh admin field or support field field and the support field as CSV
|
||||
// //These will get only support related (updates/bug reports) notification types
|
||||
// foreach (var incident in ctx.Incident.AsNoTracking())
|
||||
// {
|
||||
// if (!string.IsNullOrWhiteSpace(incident.Email))
|
||||
// {
|
||||
// var cust = ctx.Customer.SingleOrDefault(m => m.Id == incident.CustomerId);
|
||||
// if (cust == null)
|
||||
// {
|
||||
// throw new ArgumentNullException($"RFSCHEMA UPDATE 13 (incidents) CUSTOMER {incident.CustomerId.ToString()} not found!!");
|
||||
// }
|
||||
|
||||
// var incidentEmails = incident.Email.Split(",");
|
||||
// foreach (var email in incidentEmails)
|
||||
// {
|
||||
|
||||
// //See if incident email is already in adminEmail field:
|
||||
// if (cust.AdminEmail != null && cust.AdminEmail.ToLowerInvariant().Contains(email.Trim().ToLowerInvariant()))
|
||||
// {
|
||||
// continue;//skip this one, it's already there
|
||||
// }
|
||||
|
||||
// //It's not in the adminEmail field already so add it to the supportEmail
|
||||
// //field (assumption: all incidents are support related and particularly ones that are not already in admin)
|
||||
// Util.CustomerUtils.AddSupportEmailIfNotPresent(cust, email);
|
||||
// }
|
||||
// ctx.SaveChanges();
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
//NOTE: NOTIFICATION AND TRIAL tables have emails but they are dupes, empty or not required based on actual data so not going to import them
|
||||
|
||||
currentSchema = 13;
|
||||
setSchemaLevel(currentSchema);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
//schema 14
|
||||
if (currentSchema < 14)
|
||||
{
|
||||
exec("update license set fetchfrom = 'redacted'");
|
||||
currentSchema = 14;
|
||||
setSchemaLevel(currentSchema);
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
//schema 15
|
||||
if (currentSchema < 15)
|
||||
{
|
||||
exec("drop table contact");
|
||||
exec("drop table incident");
|
||||
exec("drop table notification");
|
||||
exec("drop table trial");
|
||||
currentSchema = 15;
|
||||
setSchemaLevel(currentSchema);
|
||||
}
|
||||
|
||||
//*************************************************************************************
|
||||
|
||||
|
||||
}//eofunction
|
||||
|
||||
|
||||
|
||||
private static void setSchemaLevel(int nCurrentSchema)
|
||||
{
|
||||
exec("UPDATE RFSET SET schema=" + nCurrentSchema.ToString());
|
||||
}
|
||||
|
||||
//execute command query
|
||||
private static void exec(string q)
|
||||
{
|
||||
using (var cmCreateRfSet = ctx.Database.GetDbConnection().CreateCommand())
|
||||
{
|
||||
ctx.Database.OpenConnection();
|
||||
cmCreateRfSet.CommandText = q;
|
||||
cmCreateRfSet.ExecuteNonQuery();
|
||||
ctx.Database.CloseConnection();
|
||||
}
|
||||
}
|
||||
|
||||
//eoclass
|
||||
}
|
||||
//eons
|
||||
}
|
||||
8
util/RfVersion.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace rockfishCore.Util
|
||||
{
|
||||
public static class RfVersion
|
||||
{
|
||||
public const string NumberOnly="6.3";
|
||||
public const string Full = "Rockfish server " + NumberOnly;
|
||||
}
|
||||
}
|
||||
183
util/TrialKeyRequestHandler.cs
Normal file
@@ -0,0 +1,183 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Collections.Generic;
|
||||
using rockfishCore.Models;
|
||||
using rockfishCore.Util;
|
||||
|
||||
using MailKit.Net.Imap;
|
||||
using MailKit.Search;
|
||||
using MailKit;
|
||||
using MimeKit;
|
||||
|
||||
|
||||
|
||||
|
||||
namespace rockfishCore.Util
|
||||
{
|
||||
//Trial Key request handler
|
||||
public static class TrialKeyRequestHandler
|
||||
{
|
||||
//non anonymous return object for array
|
||||
//https://stackoverflow.com/a/3202396
|
||||
private class retObject
|
||||
{
|
||||
public string from;
|
||||
public string subject;
|
||||
public string date;
|
||||
public uint uid;
|
||||
}
|
||||
|
||||
//MAILKIT DOCS
|
||||
//https://github.com/jstedfast/MailKit#using-mailkit
|
||||
|
||||
public static dynamic Requests()
|
||||
{
|
||||
try
|
||||
{
|
||||
List<retObject> ret = new List<retObject>();
|
||||
List<RfMail.rfMailMessage> msgs = RfMail.GetMessages(SearchQuery.SubjectContains("Request for 30 day temporary").And(SearchQuery.NotAnswered).And(SearchQuery.NotDeleted));
|
||||
|
||||
|
||||
foreach (RfMail.rfMailMessage m in msgs)
|
||||
{
|
||||
ret.Add(new retObject()
|
||||
{
|
||||
uid = m.uid,
|
||||
subject = m.message.Subject,
|
||||
date = m.message.Date.LocalDateTime.ToString("g"),
|
||||
from = m.message.From.ToString()
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
return new { ok = 1, requests = ret };
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return new
|
||||
{
|
||||
error = 1,
|
||||
msg = "Error @ TrialKeyRequestHandler->Requests()",
|
||||
error_detail = new { message = e.Message, stack = e.StackTrace }
|
||||
};
|
||||
}
|
||||
}//requests
|
||||
|
||||
|
||||
|
||||
|
||||
public static dynamic GenerateFromRequest(uint uid, LicenseTemplates licenseTemplates, string authUser, rockfishContext ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var client = new ImapClient())
|
||||
{
|
||||
var m = RfMail.GetMessage(uid);
|
||||
|
||||
string greetingReplySubject = "re: " + m.Subject;
|
||||
|
||||
//Extract request message fields
|
||||
string replyTo = m.From.ToString();
|
||||
|
||||
//get request message body
|
||||
string request = m.TextBody;
|
||||
|
||||
//Parse request message for deets
|
||||
if (string.IsNullOrWhiteSpace(request))
|
||||
{
|
||||
throw new System.NotSupportedException("TrialKeyRequestHandler->GenerateFromRequest: error text body of request email is empty");
|
||||
}
|
||||
bool bLite = m.Subject.Contains("AyaNova Lite");
|
||||
int nNameStart = request.IndexOf("Name:\r\n") + 7;
|
||||
int nNameEnd = request.IndexOf("Company:\r\n");
|
||||
int nCompanyStart = nNameEnd + 10;
|
||||
int nCompanyEnd = request.IndexOf("Referrer:\r\n");
|
||||
string sName = request.Substring(nNameStart, nNameEnd - nNameStart).Trim();
|
||||
if (sName.Contains(" "))
|
||||
sName = sName.Split(' ')[0];
|
||||
string sRegTo = request.Substring(nCompanyStart, nCompanyEnd - nCompanyStart).Trim();
|
||||
|
||||
//make keycode
|
||||
string keyCode = KeyFactory.GetTrialKey(sRegTo, bLite, licenseTemplates, authUser, replyTo, ct);//case 3233
|
||||
|
||||
//get greeting and subject
|
||||
string greeting = string.Empty;
|
||||
string keyEmailSubject = string.Empty;
|
||||
|
||||
if (bLite)
|
||||
{
|
||||
//No lite trial greeting but text looks ok to just use full trial greeting so going with that
|
||||
//(the column is in the DB but there is no UI for it and it's null)
|
||||
//greeting = licenseTemplates.LiteTrialGreeting.Replace("[FirstName]", sName);
|
||||
greeting = licenseTemplates.FullTrialGreeting.Replace("[FirstName]", sName);
|
||||
keyEmailSubject = "AyaNova Lite and all add-ons temporary 30 day Activation key";
|
||||
}
|
||||
else
|
||||
{
|
||||
greeting = licenseTemplates.FullTrialGreeting.Replace("[FirstName]", sName);
|
||||
keyEmailSubject = "AyaNova and all add-on's temporary 30 day Activation key";
|
||||
}
|
||||
|
||||
return new
|
||||
{
|
||||
ok = 1,
|
||||
uid = uid,
|
||||
requestReplyToAddress = replyTo,
|
||||
requestFromReplySubject = keyEmailSubject,
|
||||
requestKeyIsLite = bLite,
|
||||
keycode = keyCode,
|
||||
greeting = greeting,
|
||||
greetingReplySubject = greetingReplySubject,
|
||||
request = request
|
||||
};
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
||||
return new
|
||||
{
|
||||
error = 1,
|
||||
ok = 0,
|
||||
msg = "Error @ TrialKeyRequestHandler->GenerateFromRequest()",
|
||||
error_detail = new { message = e.Message, stack = e.StackTrace }
|
||||
};
|
||||
}
|
||||
}//requests
|
||||
|
||||
|
||||
public static dynamic SendTrialRequestResponse(dtoKeyRequestResponse k)
|
||||
{
|
||||
try
|
||||
{
|
||||
//-----------------
|
||||
//Send both responses
|
||||
|
||||
//Send the greeting email
|
||||
RfMail.SendMessage("support@ayanova.com", k.requestReplyToAddress, k.greetingReplySubject, k.greeting, true, "MsgType", "TrialRequestGreet");
|
||||
|
||||
//Send the license key email
|
||||
RfMail.SendMessage("support@ayanova.com", k.requestReplyToAddress, k.requestFromReplySubject, k.keycode, true, "MsgType", "TrialRequestKey");
|
||||
|
||||
// Flag original request message as read and replied
|
||||
RfMail.FlagInboxMessageSeenReplied(k.request_email_uid);
|
||||
|
||||
|
||||
//------------------
|
||||
return new { ok = 1 };
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return new
|
||||
{
|
||||
error = 1,
|
||||
msg = "Error @ TrialKeyRequestHandler->SendTrialRequestResponse()",
|
||||
error_detail = new { message = e.Message, stack = e.StackTrace }
|
||||
};
|
||||
}
|
||||
}//requests
|
||||
|
||||
|
||||
}//eoc
|
||||
|
||||
}//eons
|
||||
35
util/fetchkeycode.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
|
||||
namespace rockfishCore.Util
|
||||
{
|
||||
//Generate a random code for 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 class FetchKeyCode
|
||||
{
|
||||
|
||||
public static string generate()
|
||||
{
|
||||
|
||||
//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
|
||||
38
util/hasher.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.Security.Cryptography;
|
||||
using Microsoft.AspNetCore.Cryptography.KeyDerivation;
|
||||
namespace rockfishCore.Util
|
||||
{
|
||||
//Authentication controller
|
||||
public static class Hasher
|
||||
{
|
||||
|
||||
public static string hash(string Salt, string Password)
|
||||
{
|
||||
|
||||
//adapted from here:
|
||||
//https://docs.microsoft.com/en-us/aspnet/core/security/data-protection/consumer-apis/password-hashing
|
||||
string hashed = Convert.ToBase64String(KeyDerivation.Pbkdf2(
|
||||
password: Password,
|
||||
salt: StringToByteArray(Salt),
|
||||
prf: KeyDerivationPrf.HMACSHA512,
|
||||
iterationCount: 10000,
|
||||
numBytesRequested: 512 / 8));
|
||||
return hashed;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//https://stackoverflow.com/questions/311165/how-do-you-convert-a-byte-array-to-a-hexadecimal-string-and-vice-versa/24343727#24343727
|
||||
public static byte[] StringToByteArray(String hex)
|
||||
{
|
||||
int NumberChars = hex.Length;
|
||||
byte[] bytes = new byte[NumberChars / 2];
|
||||
for (int i = 0; i < NumberChars; i += 2)
|
||||
bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
}//eoc
|
||||
|
||||
}//eons
|
||||
40
util/rfLogger.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace rockfishCore.Util
|
||||
{
|
||||
//from a comment here: https://github.com/aspnet/EntityFramework/issues/6482
|
||||
|
||||
public class rfLoggerProvider : ILoggerProvider
|
||||
{
|
||||
public ILogger CreateLogger(string categoryName)
|
||||
{
|
||||
return new rfLogger();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{ }
|
||||
|
||||
private class rfLogger : ILogger
|
||||
{
|
||||
public bool IsEnabled(LogLevel logLevel)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
|
||||
{
|
||||
// File.AppendAllText(@"log.txt", formatter(state, exception));
|
||||
Console.WriteLine("------------------------------------------------------------");
|
||||
Console.WriteLine(formatter(state, exception));
|
||||
Console.WriteLine("------------------------------------------------------------");
|
||||
}
|
||||
|
||||
public IDisposable BeginScope<TState>(TState state)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
wwwroot/android-chrome-192x192.png
Normal file
|
After Width: | Height: | Size: 8.3 KiB |
BIN
wwwroot/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 7.5 KiB |
9
wwwroot/browserconfig.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<browserconfig>
|
||||
<msapplication>
|
||||
<tile>
|
||||
<square150x150logo src="/mstile-150x150.png"/>
|
||||
<TileColor>#603cba</TileColor>
|
||||
</tile>
|
||||
</msapplication>
|
||||
</browserconfig>
|
||||
29
wwwroot/css/app.css
Normal file
@@ -0,0 +1,29 @@
|
||||
|
||||
|
||||
ul{
|
||||
padding:0;
|
||||
}
|
||||
|
||||
.rf-list, .list-unstyled{
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.rf-content{
|
||||
margin-top:64px;
|
||||
}
|
||||
|
||||
.rf-larger{
|
||||
font-size: larger;
|
||||
}
|
||||
|
||||
.rf-smaller{
|
||||
font-size: smaller;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 576px){ /* bump up font on smaller screen */
|
||||
html{font-size: 20px}
|
||||
|
||||
.rf-content{
|
||||
margin-top:80px;
|
||||
}
|
||||
}
|
||||
2
wwwroot/css/materialdesignicons.min.css
vendored
Normal file
40
wwwroot/css/mdi-bs4-compat.css
Normal file
@@ -0,0 +1,40 @@
|
||||
.alert.mdi::before {
|
||||
margin: 0 3px 0 -3px;
|
||||
}
|
||||
|
||||
.btn.mdi:not(:empty)::before {
|
||||
margin: 0 3px 0 -3px;
|
||||
}
|
||||
|
||||
.breadcrumb-item a.mdi::before, .breadcrumb-item span.mdi::before {
|
||||
margin: 0 2px 0 -2px;
|
||||
}
|
||||
|
||||
.dropdown-item.mdi::before {
|
||||
margin: 0 8px 0 -10px;
|
||||
}
|
||||
|
||||
.list-group-item.mdi::before {
|
||||
margin: 0 6px 0 -6px;
|
||||
}
|
||||
|
||||
.modal-title.mdi::before {
|
||||
margin: 0 4px 0 0;
|
||||
}
|
||||
|
||||
.nav-link.mdi::before {
|
||||
margin: 0 4px 0 -4px;
|
||||
}
|
||||
|
||||
.navbar-brand.mdi::before {
|
||||
margin: 0 4px 0 0;
|
||||
}
|
||||
|
||||
.popover-title.mdi::before {
|
||||
margin: 0 4px 0 -4px;
|
||||
}
|
||||
|
||||
.btn.mdi-chevron-up.collapsed::before {
|
||||
content: '\f140';
|
||||
}
|
||||
|
||||
127
wwwroot/default.htm
Normal file
@@ -0,0 +1,127 @@
|
||||
<!doctype html>
|
||||
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
|
||||
|
||||
<title>Rockfish loading....</title>
|
||||
|
||||
<!-- ICONS / MANIFEST -->
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png?rfv=6.2">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png?rfv=6.2">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png?rfv=6.2">
|
||||
<link rel="manifest" href="/manifest.json?rfv=6.2">
|
||||
<link rel="mask-icon" href="/safari-pinned-tab.svg?rfv=6.2" color="#5bbad5">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
|
||||
|
||||
<!-- 3rd party components fonts and icons -->
|
||||
<link href="css/materialdesignicons.min.css?rfv=6.2" media="all" rel="stylesheet" type="text/css" />
|
||||
|
||||
<!--BOOTSTRAP-->
|
||||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css" integrity="sha384-WskhaSGFgHYWDcbwN70/dfYBj47jz9qbsMId/iRN3ewGhXQFZCSftd1LZCfmhktB"
|
||||
crossorigin="anonymous">
|
||||
<!-- <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/css/bootstrap.min.css" integrity="sha384-PsH8R72JQ3SOdhVi3uxftmaW6Vc51MKb0q5P2rRUpPvrszuE4W1povHYgTpBfshb"
|
||||
crossorigin="anonymous"> -->
|
||||
|
||||
|
||||
<link rel="stylesheet" href="css/app.css?rfv=6.2" type="text/css" />
|
||||
<link rel="stylesheet" href="css/mdi-bs4-compat.css?rfv=6.2" type="text/css" />
|
||||
<!-- <script src="js/lib/jquery-3.2.1.min.js"></script> -->
|
||||
<script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
|
||||
|
||||
|
||||
<!-- third-party javascript -->
|
||||
<script src="js/lib/page.js?rfv=6.2"></script>
|
||||
<script src="js/lib/jquery.event.gevent.js?rfv=6.2"></script>
|
||||
<script src="js/lib/jquery.gzserialize.js?rfv=6.2"></script>
|
||||
<script src="js/lib/handlebars.runtime-v4.0.5.js?rfv=6.2"></script>
|
||||
<script src="js/lib/store.min.js?rfv=6.2"></script>
|
||||
<script src="js/lib/jquery.autocomplete.min.js?rfv=6.2"></script>
|
||||
<script src="js/lib/moment.min.js?rfv=6.2"></script>
|
||||
|
||||
<!-- our javascript -->
|
||||
|
||||
<!-- <script src="js/index.js?rfv=6.2"></script> -->
|
||||
<script src="js/index.js" asp-append-version="true"></script>
|
||||
|
||||
<script src="js/app.util.js?rfv=6.2"></script>
|
||||
<script src="js/app.api.js?rfv=6.2"></script>
|
||||
<script src="js/app.utilB.js?rfv=6.2"></script>
|
||||
<script src="js/app.nav.js?rfv=6.2"></script>
|
||||
<script src="js/app.shell.js?rfv=6.2"></script>
|
||||
<script src="js/app.fourohfour.js?rfv=6.2"></script>
|
||||
<script src="js/app.authenticate.js?rfv=6.2"></script>
|
||||
<script src="js/app.customers.js?rfv=6.2"></script>
|
||||
<script src="js/app.customerEdit.js?rfv=6.2"></script>
|
||||
<script src="js/app.customerSites.js?rfv=6.2"></script>
|
||||
<script src="js/app.customerSiteEdit.js?rfv=6.2"></script>
|
||||
<script src="js/app.purchases.js?rfv=6.2"></script>
|
||||
<script src="js/app.purchaseEdit.js?rfv=6.2"></script>
|
||||
<script src="js/app.license.js?rfv=6.2"></script>
|
||||
<script src="js/app.licenseTemplates.js?rfv=6.2"></script>
|
||||
<script src="js/app.licenseRequestEdit.js?rfv=6.2"></script>
|
||||
<script src="js/app.licenses.js?rfv=6.2"></script>
|
||||
<script src="js/app.licenseView.js?rfv=6.2"></script>
|
||||
<script src="js/app.reportData.js?rfv=6.2"></script>
|
||||
<script src="js/app.reportDataProdEmail.js?rfv=6.2"></script>
|
||||
<script src="js/app.reportDataExpires.js?rfv=6.2"></script>
|
||||
<script src="js/app.search.js?rfv=6.2"></script>
|
||||
<script src="js/app.subscription.js?rfv=6.2"></script>
|
||||
<script src="js/app.subnotify.js?rfv=6.2"></script>
|
||||
<script src="js/app.templates.js?rfv=6.2"></script>
|
||||
<script src="js/app.templateEdit.js?rfv=6.2"></script>
|
||||
<script src="js/app.inbox.js?rfv=6.2"></script>
|
||||
<script src="js/app.mailEdit.js?rfv=6.2"></script>
|
||||
<script src="js/app.rfcaseEdit.js?rfv=6.2"></script>
|
||||
<script src="js/app.rfcases.js?rfv=6.2"></script>
|
||||
<script src="js/app.rfsettings.js?rfv=6.2"></script>
|
||||
|
||||
<!-- handlebars templates -->
|
||||
<script src="js/templates/templates.js?rfv=6.2"></script>
|
||||
|
||||
|
||||
<script>
|
||||
$(function () {
|
||||
app.initModule($('#app'));
|
||||
});
|
||||
</script>
|
||||
|
||||
<!--
|
||||
//Error handling...
|
||||
<script>
|
||||
$(function () {
|
||||
try {
|
||||
app.initModule( $('#app') );
|
||||
} catch ( error ) {
|
||||
// log the error to the console
|
||||
// then send it to a third party logging service
|
||||
}
|
||||
});
|
||||
window.onerror = function ( error ) {
|
||||
// do something with asynchronous errors
|
||||
};
|
||||
</script>-->
|
||||
|
||||
|
||||
</head>
|
||||
|
||||
<body class="rf-body">
|
||||
<div id="app" class="container"></div>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49"
|
||||
crossorigin="anonymous"></script>
|
||||
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/js/bootstrap.min.js" integrity="sha384-smHYKdLADwkXOn1EmN1qk/HfnUcbVRZyYmZ4qpPea6sjB/pTJ0euyQp0Mk8ck+5T"
|
||||
crossorigin="anonymous"></script>
|
||||
<!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.3/umd/popper.min.js" integrity="sha384-vFJXuSJphROIrBnz7yo7oB41mKfc8JzQZiCq4NCceLEaO4IHwicKwpJf9c9IpFgh"
|
||||
crossorigin="anonymous"></script>
|
||||
|
||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/js/bootstrap.min.js" integrity="sha384-alpBpkh1PFOepccYVYDB4do5UnbKysX5WZXm3XxPqe5iKTfUKjNkCk9SaVuEZflJ"
|
||||
crossorigin="anonymous"></script> -->
|
||||
</body>
|
||||
|
||||
</html>
|
||||
BIN
wwwroot/favicon-16x16.png
Normal file
|
After Width: | Height: | Size: 686 B |
BIN
wwwroot/favicon-32x32.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
wwwroot/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
wwwroot/fonts/materialdesignicons-webfont.eot
Normal file
6150
wwwroot/fonts/materialdesignicons-webfont.svg
Normal file
|
After Width: | Height: | Size: 2.1 MiB |
BIN
wwwroot/fonts/materialdesignicons-webfont.ttf
Normal file
BIN
wwwroot/fonts/materialdesignicons-webfont.woff
Normal file
BIN
wwwroot/fonts/materialdesignicons-webfont.woff2
Normal file
BIN
wwwroot/img/flag.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
337
wwwroot/js/app.api.js
Normal file
@@ -0,0 +1,337 @@
|
||||
/*
|
||||
* app.api.js
|
||||
* Ajax api helper module
|
||||
*/
|
||||
|
||||
/*jslint browser : true, continue : true,
|
||||
devel : true, indent : 2, maxerr : 50,
|
||||
newcap : true, nomen : true, plusplus : true,
|
||||
regexp : true, sloppy : true, vars : false,
|
||||
white : true
|
||||
*/
|
||||
/*global $, io, app */
|
||||
|
||||
app.api = (function() {
|
||||
"use strict";
|
||||
var initModule,
|
||||
getAuthHeaderObject,
|
||||
RockFishVersion,
|
||||
get,
|
||||
remove,
|
||||
create,
|
||||
update,
|
||||
uploadFile,
|
||||
putAction,
|
||||
createLicense,
|
||||
getLicenseRequests,
|
||||
generateFromRequest,
|
||||
licenseEmailResponse;
|
||||
|
||||
RockFishVersion = "6.1";
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// NOT AUTHORIZED ERROR HANDLER
|
||||
|
||||
$(document).ajaxError(function(event, jqxhr, settings, thrownError) {
|
||||
//unauthorized? Trigger logout which will trigger login after clearing creds
|
||||
if (jqxhr.status == 401) {
|
||||
window.location.replace("#!/logout");
|
||||
}
|
||||
});
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// UTILITY
|
||||
|
||||
///////////////////////////////////////////////////////////
|
||||
// Return the auth token header
|
||||
//
|
||||
//
|
||||
getAuthHeaderObject = function() {
|
||||
return {
|
||||
Authorization: "Bearer " + app.shell.stateMap.user.token
|
||||
};
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// ROCKFISH CORE ROUTES
|
||||
|
||||
///////////////////////////////////////////////////////////
|
||||
//Create
|
||||
//Route app.post('/api/:obj_type/create', function (req, res) {
|
||||
//
|
||||
create = function(apiRoute, objData, callback) {
|
||||
$.ajax({
|
||||
method: "post",
|
||||
dataType: "json",
|
||||
url: app.shell.stateMap.apiUrl + apiRoute,
|
||||
headers: getAuthHeaderObject(),
|
||||
contentType: "application/json; charset=utf-8",
|
||||
data: JSON.stringify(objData),
|
||||
success: function(data, textStatus) {
|
||||
callback(data);
|
||||
},
|
||||
error: function(jqXHR, textStatus, errorThrown) {
|
||||
callback({
|
||||
error: 1,
|
||||
msg: textStatus + "\n" + errorThrown,
|
||||
error_detail: {}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/////////////////
|
||||
//Get - get anything, the caller provides the route, this should replace most legacy get
|
||||
//
|
||||
get = function(apiRoute, callback) {
|
||||
$.ajax({
|
||||
method: "GET",
|
||||
dataType: "json",
|
||||
url: app.shell.stateMap.apiUrl + apiRoute,
|
||||
headers: getAuthHeaderObject(),
|
||||
|
||||
success: function(data, textStatus) {
|
||||
callback(data);
|
||||
},
|
||||
error: function(jqXHR, textStatus, errorThrown) {
|
||||
callback({
|
||||
error: 1,
|
||||
msg: textStatus + "\n" + errorThrown,
|
||||
error_detail: {}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
////////////////////
|
||||
|
||||
///////////////////////////////////////////////////////////
|
||||
//Update
|
||||
//route: app.post('/api/:obj_type/update/:id', function (req, res) {
|
||||
//
|
||||
update = function(objType, objData, callback) {
|
||||
var theId;
|
||||
if (!objData.id) {
|
||||
return callback({
|
||||
error: 1,
|
||||
msg: "app.api.js::update->Error: missing id field in update document",
|
||||
error_detail: objData
|
||||
});
|
||||
}
|
||||
theId = objData.id;
|
||||
$.ajax({
|
||||
method: "put",
|
||||
dataType: "json",
|
||||
url: app.shell.stateMap.apiUrl + objType + "/" + theId,
|
||||
headers: getAuthHeaderObject(),
|
||||
contentType: "application/json; charset=utf-8",
|
||||
data: JSON.stringify(objData),
|
||||
success: function(data, textStatus) {
|
||||
if (data == null) {
|
||||
data = { ok: 1 };
|
||||
}
|
||||
|
||||
callback(data);
|
||||
},
|
||||
error: function(jqXHR, textStatus, errorThrown) {
|
||||
callback({
|
||||
error: 1,
|
||||
msg: textStatus + "\n" + errorThrown,
|
||||
error_detail: {}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////
|
||||
//remove Item
|
||||
remove = function(apiRoute, callback) {
|
||||
$.ajax({
|
||||
method: "DELETE",
|
||||
dataType: "json",
|
||||
url: app.shell.stateMap.apiUrl + apiRoute,
|
||||
headers: getAuthHeaderObject(),
|
||||
success: function(data, textStatus) {
|
||||
callback(data);
|
||||
},
|
||||
error: function(jqXHR, textStatus, errorThrown) {
|
||||
callback({
|
||||
error: 1,
|
||||
msg: textStatus + "\n" + errorThrown,
|
||||
error_detail: {}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////
|
||||
// uploadFile
|
||||
// (ajax route to upload a file)
|
||||
//
|
||||
uploadFile = function(apiRoute, objData, callback) {
|
||||
$.ajax({
|
||||
method: "post",
|
||||
dataType: "json",
|
||||
url: app.shell.stateMap.apiUrl + apiRoute,
|
||||
headers: getAuthHeaderObject(),
|
||||
contentType: false,
|
||||
processData: false,
|
||||
data: objData,
|
||||
success: function(data, textStatus) {
|
||||
callback(data);
|
||||
},
|
||||
error: function(jqXHR, textStatus, errorThrown) {
|
||||
callback({
|
||||
error: 1,
|
||||
msg: textStatus + "\n" + errorThrown,
|
||||
error_detail: {}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////
|
||||
//putAction - ad-hoc put method used to trigger actions etc
|
||||
//
|
||||
putAction = function(apiRoute, callback) {
|
||||
$.ajax({
|
||||
method: "put",
|
||||
dataType: "json",
|
||||
url: app.shell.stateMap.apiUrl + apiRoute,
|
||||
headers: getAuthHeaderObject(),
|
||||
contentType: "application/json; charset=utf-8",
|
||||
//data: JSON.stringify(objData),
|
||||
success: function(data, textStatus) {
|
||||
if (data == null) {
|
||||
data = { ok: 1 };
|
||||
}
|
||||
|
||||
callback(data);
|
||||
},
|
||||
error: function(jqXHR, textStatus, errorThrown) {
|
||||
callback({
|
||||
error: 1,
|
||||
msg: textStatus + "\n" + errorThrown,
|
||||
error_detail: {}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// LICENSE KEY RELATED API METHODS
|
||||
|
||||
///////////////////////////////////////////////////////////
|
||||
//CreateLicense
|
||||
//Route app.post('/api/license/create', function (req, res) {
|
||||
//
|
||||
createLicense = function(objData, callback) {
|
||||
$.ajax({
|
||||
method: "post",
|
||||
dataType: "text",
|
||||
url: app.shell.stateMap.apiUrl + "license/generate",
|
||||
headers: getAuthHeaderObject(),
|
||||
contentType: "application/json; charset=utf-8",
|
||||
data: JSON.stringify(objData),
|
||||
success: function(data, textStatus) {
|
||||
callback(data);
|
||||
},
|
||||
error: function(jqXHR, textStatus, errorThrown) {
|
||||
callback({
|
||||
error: 1,
|
||||
msg: textStatus + "\n" + errorThrown,
|
||||
error_detail: {}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////
|
||||
//GetLicenseRequests
|
||||
//Fetch license requests
|
||||
//route: app.get('/api/license/requests', function (req, res) {
|
||||
//
|
||||
getLicenseRequests = function(callback) {
|
||||
$.ajax({
|
||||
method: "GET",
|
||||
dataType: "json",
|
||||
url: app.shell.stateMap.apiUrl + "license/requests",
|
||||
headers: getAuthHeaderObject(),
|
||||
success: function(data, textStatus) {
|
||||
callback(data);
|
||||
},
|
||||
error: function(jqXHR, textStatus, errorThrown) {
|
||||
callback({
|
||||
error: 1,
|
||||
msg: textStatus + "\n" + errorThrown,
|
||||
error_detail: {}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////
|
||||
//GenerateFromRequest
|
||||
//Fetch generated response to license request
|
||||
//route: app.get('/api/license/generateFromRequest/:uid', function (req, res) {
|
||||
//
|
||||
generateFromRequest = function(uid, callback) {
|
||||
$.ajax({
|
||||
method: "GET",
|
||||
dataType: "json",
|
||||
url: app.shell.stateMap.apiUrl + "license/generateFromRequest/" + uid,
|
||||
headers: getAuthHeaderObject(),
|
||||
success: function(data, textStatus) {
|
||||
callback(data);
|
||||
},
|
||||
error: function(jqXHR, textStatus, errorThrown) {
|
||||
callback({
|
||||
error: 1,
|
||||
msg: textStatus + "\n" + errorThrown,
|
||||
error_detail: {}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////
|
||||
//Email license request response
|
||||
//app.post('/api/license/email_response', function (req, res) {
|
||||
//
|
||||
licenseEmailResponse = function(objData, callback) {
|
||||
$.ajax({
|
||||
method: "post",
|
||||
dataType: "text",
|
||||
url: app.shell.stateMap.apiUrl + "license/email_response",
|
||||
headers: getAuthHeaderObject(),
|
||||
contentType: "application/json; charset=utf-8",
|
||||
data: JSON.stringify(objData),
|
||||
success: function(data, textStatus) {
|
||||
callback(data);
|
||||
},
|
||||
error: function(jqXHR, textStatus, errorThrown) {
|
||||
callback({
|
||||
error: 1,
|
||||
msg: textStatus + "\n" + errorThrown,
|
||||
error_detail: {}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
initModule = function() {};
|
||||
|
||||
return {
|
||||
initModule: initModule,
|
||||
getAuthHeaderObject: getAuthHeaderObject,
|
||||
RockFishVersion: RockFishVersion,
|
||||
get: get,
|
||||
remove: remove,
|
||||
create: create,
|
||||
update: update,
|
||||
uploadFile: uploadFile,
|
||||
putAction: putAction,
|
||||
createLicense: createLicense,
|
||||
getLicenseRequests: getLicenseRequests,
|
||||
generateFromRequest: generateFromRequest,
|
||||
licenseEmailResponse: licenseEmailResponse
|
||||
};
|
||||
})();
|
||||
103
wwwroot/js/app.authenticate.js
Normal file
@@ -0,0 +1,103 @@
|
||||
/*jslint browser : true, continue : true,
|
||||
devel : true, indent : 2, maxerr : 50,
|
||||
newcap : true, nomen : true, plusplus : true,
|
||||
regexp : true, sloppy : true, vars : false,
|
||||
white : true
|
||||
*/
|
||||
|
||||
/*global $, app */
|
||||
|
||||
app.authenticate = (function() {
|
||||
'use strict';
|
||||
//---------------- BEGIN MODULE SCOPE VARIABLES --------------
|
||||
var
|
||||
stateMap = {},
|
||||
onSubmit,
|
||||
configModule,
|
||||
initModule;
|
||||
//----------------- END MODULE SCOPE VARIABLES ---------------
|
||||
|
||||
//------------------- BEGIN UTILITY METHODS ------------------
|
||||
//-------------------- END UTILITY METHODS -------------------
|
||||
|
||||
//------------------- BEGIN EVENT HANDLERS -------------------
|
||||
onSubmit = function(event) {
|
||||
event.preventDefault();
|
||||
|
||||
//get creds
|
||||
var login = $('#login').val();
|
||||
var password = $('#password').val();
|
||||
|
||||
|
||||
$.ajax({
|
||||
method: "post",
|
||||
dataType: "json",
|
||||
url: app.shell.stateMap.apiUrl.replace('/api/', '/authenticate'),
|
||||
data: {
|
||||
login: login,
|
||||
password: password
|
||||
},
|
||||
success: function(data, textStatus, jqXHR) {
|
||||
if (data.ok == 1) {
|
||||
app.shell.stateMap.user.authenticated = true;
|
||||
app.shell.stateMap.user.token = data.token;
|
||||
app.shell.stateMap.user.name = data.name;
|
||||
app.shell.stateMap.user.id=data.id;
|
||||
//token expiry date
|
||||
app.shell.stateMap.user.expires = data.expires;
|
||||
|
||||
//tell the shell we've logged in successfully
|
||||
$.gevent.publish('app-login', {
|
||||
name: login
|
||||
});
|
||||
} else {
|
||||
if (data.error) {
|
||||
$.gevent.publish('app-show-error',data.error);
|
||||
}
|
||||
app.shell.stateMap.user.authenticated = false;
|
||||
app.shell.stateMap.user.token = '';
|
||||
app.shell.stateMap.user.name = 'please sign in';
|
||||
$.gevent.publish('app-logout');
|
||||
}
|
||||
},
|
||||
error: function(jqXHR, textStatus, errorThrown) {
|
||||
app.shell.stateMap.user.authenticated = false;
|
||||
app.shell.stateMap.user.token = '';
|
||||
app.shell.stateMap.user.name = 'please sign in';
|
||||
$.gevent.publish('app-logout');
|
||||
$.gevent.publish('app-show-error',textStatus + " " + errorThrown);
|
||||
}
|
||||
});
|
||||
return false; //prevent default?
|
||||
};
|
||||
|
||||
//-------------------- END EVENT HANDLERS --------------------
|
||||
|
||||
//------------------- BEGIN PUBLIC METHODS -------------------
|
||||
//CONFIGMODULE
|
||||
//
|
||||
configModule = function(context) {
|
||||
stateMap.context = context.context;
|
||||
if (stateMap.context.params.id) {
|
||||
stateMap.id = stateMap.context.params.id;
|
||||
}
|
||||
};
|
||||
|
||||
//INITMODULE
|
||||
//
|
||||
initModule = function($container) {
|
||||
if (typeof $container === 'undefined') {
|
||||
$container = $('#app-shell-main-content');
|
||||
}
|
||||
$container.html(Handlebars.templates['app.authenticate']({}));
|
||||
$('#btnSubmit').bind('click', onSubmit);
|
||||
};
|
||||
|
||||
|
||||
// return public methods
|
||||
return {
|
||||
configModule: configModule,
|
||||
initModule: initModule
|
||||
};
|
||||
//------------------- END PUBLIC METHODS ---------------------
|
||||
}());
|
||||
143
wwwroot/js/app.customerEdit.js
Normal file
@@ -0,0 +1,143 @@
|
||||
/*jslint browser : true, continue : true,
|
||||
devel : true, indent : 2, maxerr : 50,
|
||||
newcap : true, nomen : true, plusplus : true,
|
||||
regexp : true, sloppy : true, vars : false,
|
||||
white : true
|
||||
*/
|
||||
|
||||
/*global $, app */
|
||||
|
||||
app.customerEdit = (function () {
|
||||
'use strict';
|
||||
//---------------- BEGIN MODULE SCOPE VARIABLES --------------
|
||||
var
|
||||
stateMap = {},
|
||||
onSave, onDelete,
|
||||
configModule, initModule;
|
||||
//----------------- END MODULE SCOPE VARIABLES ---------------
|
||||
|
||||
|
||||
//------------------- BEGIN EVENT HANDLERS -------------------
|
||||
|
||||
//ONSAVE
|
||||
//
|
||||
onSave = function (event) {
|
||||
event.preventDefault();
|
||||
$.gevent.publish('app-clear-error');
|
||||
|
||||
//get form data
|
||||
var formData = $("form").serializeArray({
|
||||
checkboxesAsBools: true
|
||||
});
|
||||
|
||||
var submitData = app.utilB.objectifyFormDataArray(formData);
|
||||
|
||||
//is this a new record?
|
||||
if (stateMap.id != 'new') {
|
||||
//put id into the form data
|
||||
submitData.id = stateMap.id;
|
||||
|
||||
|
||||
app.api.update('customer', submitData, function (res) {
|
||||
if (res.error) {
|
||||
$.gevent.publish('app-show-error',res.msg);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
//it's a new record - create
|
||||
app.api.create('customer', submitData, function (res) {
|
||||
if (res.error) {
|
||||
$.gevent.publish('app-show-error',res.msg);
|
||||
} else {
|
||||
page('#!/customerEdit/' + res.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
return false; //prevent default?
|
||||
};
|
||||
|
||||
//ONDELETE
|
||||
//
|
||||
onDelete = function (event) {
|
||||
event.preventDefault();
|
||||
$.gevent.publish('app-clear-error');
|
||||
var r = confirm("Are you sure you want to delete this record?");
|
||||
if (r == true) {
|
||||
//Delete customer and children
|
||||
app.api.remove('customer/' + stateMap.id, function (res) {
|
||||
if (res.error) {
|
||||
$.gevent.publish('app-show-error',res.msg);
|
||||
} else {
|
||||
//deleted, return to customers list
|
||||
page('#!/customers');
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return false; //prevent default?
|
||||
};
|
||||
|
||||
|
||||
//-------------------- END EVENT HANDLERS --------------------
|
||||
|
||||
//------------------- BEGIN PUBLIC METHODS -------------------
|
||||
|
||||
|
||||
//CONFIGMODULE
|
||||
//
|
||||
configModule = function (context) {
|
||||
stateMap.context = context.context;
|
||||
if (stateMap.context.params.id) {
|
||||
stateMap.id = stateMap.context.params.id;
|
||||
}
|
||||
};
|
||||
|
||||
//INITMODULE
|
||||
//
|
||||
initModule = function ($container) {
|
||||
if (typeof $container === 'undefined') {
|
||||
$container = $('#app-shell-main-content');
|
||||
}
|
||||
|
||||
$container.html(Handlebars.templates['app.customerEdit']({}));
|
||||
|
||||
////app.nav.setContextTitle("Customer");
|
||||
|
||||
//id should always have a value, either a record id or the keyword 'new' for making a new object
|
||||
if (stateMap.id != 'new') {
|
||||
//fetch existing record
|
||||
app.api.get('customer/' + stateMap.id, function (res) {
|
||||
if (res.error) {
|
||||
$.gevent.publish('app-show-error',res.msg);
|
||||
} else {
|
||||
//fill out form
|
||||
app.utilB.formData(res);
|
||||
}
|
||||
});
|
||||
//Context menu
|
||||
app.nav.contextClear();
|
||||
app.nav.contextAddLink("customerSites/" + stateMap.id, "Sites", "city");//url title icon
|
||||
|
||||
|
||||
} else {
|
||||
$('#btn-delete').hide();
|
||||
app.nav.contextClear();
|
||||
}
|
||||
|
||||
// bind actions
|
||||
$('#btn-save').bind('click', onSave);
|
||||
$('#btn-delete').bind('click', onDelete);
|
||||
};
|
||||
|
||||
|
||||
// RETURN PUBLIC METHODS
|
||||
//
|
||||
return {
|
||||
configModule: configModule,
|
||||
initModule: initModule
|
||||
};
|
||||
//------------------- END PUBLIC METHODS ---------------------
|
||||
}());
|
||||
168
wwwroot/js/app.customerSiteEdit.js
Normal file
@@ -0,0 +1,168 @@
|
||||
/*jslint browser : true, continue : true,
|
||||
devel : true, indent : 2, maxerr : 50,
|
||||
newcap : true, nomen : true, plusplus : true,
|
||||
regexp : true, sloppy : true, vars : false,
|
||||
white : true
|
||||
*/
|
||||
|
||||
/*global $, app */
|
||||
|
||||
app.customerSiteEdit = (function () {
|
||||
'use strict';
|
||||
//---------------- BEGIN MODULE SCOPE VARIABLES --------------
|
||||
var
|
||||
stateMap = {},
|
||||
onSave, onDelete, configModule, initModule;
|
||||
//----------------- END MODULE SCOPE VARIABLES ---------------
|
||||
|
||||
//------------------- BEGIN UTILITY METHODS ------------------
|
||||
//-------------------- END UTILITY METHODS -------------------
|
||||
|
||||
//------------------- BEGIN EVENT HANDLERS -------------------
|
||||
|
||||
onSave = function (event) {
|
||||
|
||||
event.preventDefault();
|
||||
$.gevent.publish('app-clear-error');
|
||||
//get form data
|
||||
var formData = $("form").serializeArray({
|
||||
checkboxesAsBools: true
|
||||
});
|
||||
|
||||
var submitData = app.utilB.objectifyFormDataArray(formData);
|
||||
|
||||
|
||||
//is this a new record?
|
||||
if (stateMap.id != 'new') {
|
||||
//put id into the form data
|
||||
submitData.id = stateMap.id;
|
||||
|
||||
app.api.update('site', submitData, function (res) {
|
||||
if (res.error) {
|
||||
$.gevent.publish('app-show-error',res.msg);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
//create new record
|
||||
app.api.create('site', submitData, function (res) {
|
||||
if (res.error) {
|
||||
$.gevent.publish('app-show-error',res.msg);
|
||||
} else {
|
||||
page('#!/customerSiteEdit/' + res.id + '/' + stateMap.context.params.cust_id);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
return false; //prevent default
|
||||
};
|
||||
|
||||
|
||||
//ONDELETE
|
||||
//
|
||||
onDelete = function (event) {
|
||||
event.preventDefault();
|
||||
$.gevent.publish('app-clear-error');
|
||||
|
||||
var r = confirm("Are you sure you want to delete this record?");
|
||||
if (r == true) {
|
||||
//--------------------------------------------
|
||||
//==== DELETE THE site and it's children ====
|
||||
app.api.remove('site/' + stateMap.id, function (res) {
|
||||
if (res.error) {
|
||||
$.gevent.publish('app-show-error',res.msg);
|
||||
} else {
|
||||
//deleted, return to customers list
|
||||
page('#!/customerSites/' + stateMap.context.params.cust_id);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
//--------------------
|
||||
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return false; //prevent default?
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
//-------------------- END EVENT HANDLERS --------------------
|
||||
|
||||
//------------------- BEGIN PUBLIC METHODS -------------------
|
||||
//CONFIGMODULE
|
||||
//
|
||||
configModule = function (context) {
|
||||
stateMap.context = context.context;
|
||||
if (stateMap.context.params.id) {
|
||||
stateMap.id = stateMap.context.params.id;
|
||||
}
|
||||
};
|
||||
|
||||
//INITMODULE
|
||||
//
|
||||
initModule = function ($container) {
|
||||
if (typeof $container === 'undefined') {
|
||||
$container = $('#app-shell-main-content');
|
||||
}
|
||||
|
||||
$container.html(Handlebars.templates['app.customerSiteEdit']({}));
|
||||
var title = "Site";
|
||||
|
||||
if (stateMap.context.params.cust_id) {
|
||||
//Append customer id as a hidden form field for referential integrity
|
||||
$('<input />').attr('type', 'hidden')
|
||||
.attr('name', "customerId")
|
||||
.attr('value', stateMap.context.params.cust_id)
|
||||
.appendTo('#frm');
|
||||
|
||||
//fetch existing record
|
||||
app.api.get('customer/' + stateMap.context.params.cust_id + '/name', function (res) {
|
||||
if (res.error) {
|
||||
$.gevent.publish('app-show-error',res.msg);
|
||||
} else {
|
||||
title = 'Site - ' + res.name;
|
||||
if (stateMap.id != 'new') {
|
||||
//fetch existing record
|
||||
app.api.get('site/' + stateMap.id, function (res) {
|
||||
if (res.error) {
|
||||
$.gevent.publish('app-show-error',res.msg);
|
||||
} else {
|
||||
//fill out form
|
||||
app.utilB.formData(res);
|
||||
}
|
||||
});
|
||||
}
|
||||
//set customer name
|
||||
//app.nav.setContextTitle(title);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// bind actions
|
||||
$('#btn-save').bind('click', onSave);
|
||||
$('#btn-delete').bind('click', onDelete);
|
||||
|
||||
|
||||
//Context menu
|
||||
app.nav.contextClear();
|
||||
app.nav.contextAddLink("customerEdit/" + stateMap.context.params.cust_id, "Customer", "account");
|
||||
app.nav.contextAddLink("customerSites/" + stateMap.context.params.cust_id, "Sites", "city");
|
||||
|
||||
if (stateMap.id != 'new') {
|
||||
app.nav.contextAddLink("purchases/" + stateMap.id, "Purchases", "basket");
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
// return public methods
|
||||
return {
|
||||
configModule: configModule,
|
||||
initModule: initModule
|
||||
};
|
||||
//------------------- END PUBLIC METHODS ---------------------
|
||||
}());
|
||||
131
wwwroot/js/app.customerSites.js
Normal file
@@ -0,0 +1,131 @@
|
||||
/*jslint browser : true, continue : true,
|
||||
devel : true, indent : 2, maxerr : 50,
|
||||
newcap : true, nomen : true, plusplus : true,
|
||||
regexp : true, sloppy : true, vars : false,
|
||||
white : true
|
||||
*/
|
||||
|
||||
/*global $, app */
|
||||
|
||||
app.customerSites = (function () {
|
||||
'use strict';
|
||||
//---------------- BEGIN MODULE SCOPE VARIABLES --------------
|
||||
var
|
||||
|
||||
configMap = {
|
||||
//main_html: '',
|
||||
|
||||
settable_map: {}
|
||||
},
|
||||
stateMap = {
|
||||
$append_target: null
|
||||
},
|
||||
onSubmit,
|
||||
configModule, initModule;
|
||||
//----------------- END MODULE SCOPE VARIABLES ---------------
|
||||
|
||||
//------------------- BEGIN UTILITY METHODS ------------------
|
||||
//-------------------- END UTILITY METHODS -------------------
|
||||
|
||||
//--------------------- BEGIN DOM METHODS --------------------
|
||||
|
||||
|
||||
|
||||
|
||||
// Begin private DOM methods
|
||||
|
||||
|
||||
|
||||
// End private DOM methods
|
||||
//---------------------- END DOM METHODS ---------------------
|
||||
|
||||
//------------------- BEGIN EVENT HANDLERS -------------------
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//-------------------- END EVENT HANDLERS --------------------
|
||||
|
||||
//------------------- BEGIN PUBLIC METHODS -------------------
|
||||
//CONFIGMODULE
|
||||
//
|
||||
configModule = function (context) {
|
||||
stateMap.context = context.context;
|
||||
if (stateMap.context.params.id) {
|
||||
stateMap.id = stateMap.context.params.id;
|
||||
}
|
||||
};
|
||||
|
||||
// Begin public method /initModule/
|
||||
// Example : app.customer.initModule( $('#div_id') );
|
||||
// Purpose : directs the module to being offering features
|
||||
// Arguments : $container - container to use
|
||||
// Action : Provides interface
|
||||
// Returns : none
|
||||
// Throws : none
|
||||
//
|
||||
initModule = function ($container) {
|
||||
if (typeof $container === 'undefined') {
|
||||
$container = $('#app-shell-main-content');
|
||||
}
|
||||
|
||||
$container.html(Handlebars.templates['app.customerSites']({}));
|
||||
|
||||
var title = "Sites";
|
||||
|
||||
if (stateMap.id) {
|
||||
|
||||
|
||||
app.api.get('customer/' + stateMap.id + '/name', function (res) {
|
||||
if (res.error) {
|
||||
$.gevent.publish('app-show-error',res.msg);
|
||||
} else {
|
||||
//set customer name
|
||||
title = 'Sites - ' + res.name;
|
||||
//app.nav.setContextTitle(title);
|
||||
|
||||
|
||||
if (stateMap.id) {
|
||||
|
||||
//fetch sites list
|
||||
|
||||
//fetch existing record
|
||||
app.api.get('customer/' + stateMap.id + '/sites', function (res) {
|
||||
if (res.error) {
|
||||
$.gevent.publish('app-show-error',res.msg);
|
||||
} else {
|
||||
//get the list ul
|
||||
var $appList = $('#rf-list');
|
||||
|
||||
$.each(res, function (i, obj) {
|
||||
$appList.append("<li><a href=\"#!/customerSiteEdit/" + obj.id + "/" + stateMap.id + "\">" +
|
||||
app.utilB.genListColumn(obj.name) +
|
||||
"</a></li>")
|
||||
});
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//Context menu
|
||||
app.nav.contextClear();
|
||||
app.nav.contextAddLink('customerSiteEdit/new/' + stateMap.id, "New", "plus");
|
||||
app.nav.contextAddLink("customerEdit/" + stateMap.id, "Customer", "account");
|
||||
|
||||
|
||||
};
|
||||
// End public method /initModule/
|
||||
|
||||
|
||||
|
||||
// return public methods
|
||||
return {
|
||||
configModule: configModule,
|
||||
initModule: initModule
|
||||
};
|
||||
//------------------- END PUBLIC METHODS ---------------------
|
||||
}());
|
||||
192
wwwroot/js/app.customers.js
Normal file
@@ -0,0 +1,192 @@
|
||||
/*jslint browser : true, continue : true,
|
||||
devel : true, indent : 2, maxerr : 50,
|
||||
newcap : true, nomen : true, plusplus : true,
|
||||
regexp : true, sloppy : true, vars : false,
|
||||
white : true
|
||||
*/
|
||||
|
||||
/*global $, app */
|
||||
|
||||
app.customers = (function() {
|
||||
"use strict";
|
||||
//---------------- BEGIN MODULE SCOPE VARIABLES --------------
|
||||
var stateMap = {},
|
||||
configModule,
|
||||
initModule,
|
||||
generateCard,
|
||||
onShowMore;
|
||||
//----------------- END MODULE SCOPE VARIABLES ---------------
|
||||
|
||||
//------------------- BEGIN UTILITY METHODS ------------------
|
||||
//////////////////
|
||||
//Generate a card with collapsible middle section with more details
|
||||
//
|
||||
generateCard = function(obj) {
|
||||
var editUrl = "#!/customerEdit/" + obj.id;
|
||||
var cardClass = obj.active
|
||||
? "border-primary text-primary"
|
||||
: "border-secondary text-secondary";
|
||||
var urlClass = obj.active ? "" : "text-secondary";
|
||||
|
||||
return (
|
||||
'<div class="card ' +
|
||||
cardClass +
|
||||
' mb-3">' +
|
||||
'<h4 class="card-header"><a class="' +
|
||||
urlClass +
|
||||
'" href=' +
|
||||
editUrl +
|
||||
">" +
|
||||
obj.name +
|
||||
"</a></h4>" +
|
||||
'<div class="collapse" id="card-collapse' +
|
||||
obj.id +
|
||||
'">' +
|
||||
'<div id="card-body' +
|
||||
obj.id +
|
||||
'" class="card-body"/>' +
|
||||
"</div>" +
|
||||
'<div class="card-footer">' +
|
||||
'<button id="btnMore' +
|
||||
obj.id +
|
||||
'" class="btn btn-outline-dark mdi mdi-basket mdi-24px mr-3" type="button"/>' +
|
||||
// '<a href="' + editUrl + '" class="btn btn-outline-success mdi mdi-account-edit mdi-24px "></a>' +
|
||||
"</div>" +
|
||||
"</div>"
|
||||
);
|
||||
};
|
||||
//-------------------- END UTILITY METHODS -------------------
|
||||
|
||||
//------------------- BEGIN EVENT HANDLERS -------------------
|
||||
|
||||
////////////////////////////////////////////
|
||||
//ONMORE
|
||||
//
|
||||
onShowMore = function(event) {
|
||||
event.preventDefault();
|
||||
|
||||
var customerId = event.data;
|
||||
var $cardbody = $("#card-body" + customerId);
|
||||
var $collapseDiv = $("#card-collapse" + customerId);
|
||||
|
||||
var isOpen = $collapseDiv.hasClass("show");
|
||||
|
||||
//either way we don't want the old contents hanging around
|
||||
$cardbody.empty();
|
||||
|
||||
//Reload the data?
|
||||
if (!isOpen) {
|
||||
//===================
|
||||
//Get sites
|
||||
app.api.get("customer/" + customerId + "/activesubforsites", function(
|
||||
sites
|
||||
) {
|
||||
if (sites.error) {
|
||||
$.gevent.publish("app-show-error", sites.msg);
|
||||
} else {
|
||||
var cardDisplay = '<ul class="list-unstyled">';
|
||||
|
||||
//Iterate the sites
|
||||
for (var y = 0; y < sites.length; y++) {
|
||||
//append the site name
|
||||
cardDisplay +=
|
||||
'<li class="font-weight-bold">' + sites[y].name + "</li>";
|
||||
|
||||
//append the active subs
|
||||
//purchase link for future
|
||||
//https://rockfish.ayanova.com/default.htm#!/purchaseEdit/<PURCHASEID>/<SITEID>
|
||||
if (sites[y].children.length > 0) {
|
||||
cardDisplay += '<ul class="mb-2">';
|
||||
for (var x = 0; x < sites[y].children.length; x++) {
|
||||
cardDisplay += "<li>" + sites[y].children[x].name + "</li>";
|
||||
}
|
||||
cardDisplay += "</ul>";
|
||||
} else {
|
||||
cardDisplay +=
|
||||
'<ul class="text-danger"><li>NO ACTIVE SUBS</li></ul>';
|
||||
}
|
||||
}
|
||||
cardDisplay += "</ul>";
|
||||
$cardbody.append(cardDisplay);
|
||||
//Toggle open after populating the card
|
||||
$collapseDiv.collapse("toggle");
|
||||
}
|
||||
});
|
||||
//=========/sites==============
|
||||
} else {
|
||||
//Toggle closed
|
||||
$collapseDiv.collapse("toggle");
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
//-------------------- END EVENT HANDLERS --------------------
|
||||
|
||||
//------------------- BEGIN PUBLIC METHODS -------------------
|
||||
//CONFIGMODULE
|
||||
//
|
||||
configModule = function(context) {
|
||||
stateMap.context = context.context;
|
||||
if (stateMap.context.params.id) {
|
||||
stateMap.id = stateMap.context.params.id;
|
||||
}
|
||||
};
|
||||
|
||||
//INITMODULE
|
||||
//
|
||||
initModule = function($container) {
|
||||
if (typeof $container === "undefined") {
|
||||
$container = $("#app-shell-main-content");
|
||||
}
|
||||
$container.html(Handlebars.templates["app.customers"]({}));
|
||||
|
||||
//===================
|
||||
//Get customers
|
||||
app.api.get("customer/list", function(res) {
|
||||
if (res.error) {
|
||||
$.gevent.publish("app-show-error", res.msg);
|
||||
} else {
|
||||
var $appList = $("#rf-list");
|
||||
|
||||
var activeCount = 0;
|
||||
var inactiveCount = 0;
|
||||
|
||||
$.each(res, function(i, obj) {
|
||||
if (obj.active) {
|
||||
activeCount++;
|
||||
} else {
|
||||
inactiveCount++;
|
||||
}
|
||||
|
||||
$appList.append(generateCard(obj));
|
||||
$("#btnMore" + obj.id).bind("click", obj.id, onShowMore);
|
||||
});
|
||||
|
||||
//Show the count of customers active and inactive
|
||||
$("#rf-list-count")
|
||||
.empty()
|
||||
.append(
|
||||
res.length +
|
||||
" items (" +
|
||||
activeCount +
|
||||
" active, " +
|
||||
inactiveCount +
|
||||
" inactive)"
|
||||
);
|
||||
}
|
||||
});
|
||||
//=========/customers==============
|
||||
|
||||
app.nav.contextClear();
|
||||
app.nav.contextAddLink("customerEdit/new", "New", "plus");
|
||||
app.nav.contextAddLink("search", "Search", "magnify");
|
||||
};
|
||||
|
||||
//PUBLIC METHODS
|
||||
//
|
||||
return {
|
||||
configModule: configModule,
|
||||
initModule: initModule
|
||||
};
|
||||
//------------------- END PUBLIC METHODS ---------------------
|
||||
})();
|
||||
56
wwwroot/js/app.fourohfour.js
Normal file
@@ -0,0 +1,56 @@
|
||||
/*jslint browser : true, continue : true,
|
||||
devel : true, indent : 2, maxerr : 50,
|
||||
newcap : true, nomen : true, plusplus : true,
|
||||
regexp : true, sloppy : true, vars : false,
|
||||
white : true
|
||||
*/
|
||||
|
||||
/*global $, app */
|
||||
|
||||
app.fourohfour = (function() {
|
||||
'use strict';
|
||||
//---------------- BEGIN MODULE SCOPE VARIABLES --------------
|
||||
var
|
||||
|
||||
stateMap = {},
|
||||
configModule,
|
||||
initModule;
|
||||
//----------------- END MODULE SCOPE VARIABLES ---------------
|
||||
|
||||
//------------------- BEGIN UTILITY METHODS ------------------
|
||||
//-------------------- END UTILITY METHODS -------------------
|
||||
|
||||
//------------------- BEGIN EVENT HANDLERS -------------------
|
||||
|
||||
//-------------------- END EVENT HANDLERS --------------------
|
||||
|
||||
//------------------- BEGIN PUBLIC METHODS -------------------
|
||||
//CONFIGMODULE
|
||||
//
|
||||
configModule = function(context) {
|
||||
stateMap.context = context.context;
|
||||
if (stateMap.context.params.id) {
|
||||
stateMap.id = stateMap.context.params.id;
|
||||
}
|
||||
};
|
||||
|
||||
//INITMODULE
|
||||
//
|
||||
initModule = function($container) {
|
||||
if (typeof $container === 'undefined') {
|
||||
$container = $('#app-shell-main-content');
|
||||
}
|
||||
$container.html(Handlebars.templates['app.fourohfour']({}));
|
||||
|
||||
|
||||
|
||||
};
|
||||
|
||||
//PUBLIC METHODS
|
||||
//
|
||||
return {
|
||||
configModule: configModule,
|
||||
initModule: initModule
|
||||
};
|
||||
//------------------- END PUBLIC METHODS ---------------------
|
||||
}());
|
||||
167
wwwroot/js/app.inbox.js
Normal file
@@ -0,0 +1,167 @@
|
||||
/*jslint browser : true, continue : true,
|
||||
devel : true, indent : 2, maxerr : 50,
|
||||
newcap : true, nomen : true, plusplus : true,
|
||||
regexp : true, sloppy : true, vars : false,
|
||||
white : true
|
||||
*/
|
||||
|
||||
/*global $, app */
|
||||
|
||||
app.inbox = (function() {
|
||||
"use strict";
|
||||
//---------------- BEGIN MODULE SCOPE VARIABLES --------------
|
||||
var stateMap = {},
|
||||
configModule,
|
||||
initModule,
|
||||
terminateModule,
|
||||
getMessages,
|
||||
timerVar=null;
|
||||
//----------------- END MODULE SCOPE VARIABLES ---------------
|
||||
|
||||
//------------------- BEGIN UTILITY METHODS ------------------
|
||||
getMessages = function() {
|
||||
stateMap.$appList.html("<h4>Checking...</h4>");
|
||||
|
||||
app.api.get("mail/salesandsupportsummaries", function(res) {
|
||||
if (res.error) {
|
||||
$.gevent.publish("app-show-error", res.msg);
|
||||
} else {
|
||||
stateMap.$appList.empty();
|
||||
var newMessageCount = 0;
|
||||
var lastAccount = "";
|
||||
//The list
|
||||
var displayedItems = 0;
|
||||
var generatedHtml = '<ul class="list-group">';
|
||||
|
||||
//Iterate the results
|
||||
for (var y = 0; y < res.length; y++) {
|
||||
var obj = res[y];
|
||||
if (!obj.flags.includes("deleted")) {
|
||||
if (!obj.flags.includes("seen")) {
|
||||
newMessageCount++;
|
||||
}
|
||||
var displayClass = obj.flags.includes("seen")
|
||||
? "border-secondary text-secondary"
|
||||
: "border-primary text-primary";
|
||||
var answeredIconClass = obj.flags.includes("answered")
|
||||
? " mdi mdi-reply"
|
||||
: "";
|
||||
|
||||
//Make a group on change of account
|
||||
if (lastAccount !== obj.account) {
|
||||
lastAccount = obj.account;
|
||||
//Insert as 'header' in list
|
||||
generatedHtml +=
|
||||
'<li class="list-group-item active">' + lastAccount + "</li>";
|
||||
}
|
||||
|
||||
//LIST ITEM
|
||||
generatedHtml +=
|
||||
'<li class="list-group-item" ><a href="#!/mailEdit/' +
|
||||
obj.account +
|
||||
"/Inbox/" +
|
||||
obj.id +
|
||||
"\"><span class='" +
|
||||
displayClass +
|
||||
answeredIconClass +
|
||||
"'>" +
|
||||
obj.subject +
|
||||
" - " +
|
||||
obj.from +
|
||||
"</span></a></li>";
|
||||
displayedItems++;
|
||||
} //if not deleted
|
||||
} //loop
|
||||
|
||||
//Nothing to display?
|
||||
if (!displayedItems) {
|
||||
generatedHtml +=
|
||||
'<li class="list-group-item">NO MESSAGES - ' +
|
||||
moment().format("YYYY-MM-DD LT") +
|
||||
"</li>";
|
||||
}
|
||||
|
||||
//close list group
|
||||
generatedHtml += "</ul>";
|
||||
|
||||
generatedHtml +=
|
||||
'<div class="mt-5"><h6><small class="text-muted">Last check: ' +
|
||||
moment().format("YYYY-MM-DD LT") +
|
||||
"</small><h6></div>";
|
||||
//SET IT
|
||||
stateMap.$appList.append(generatedHtml);
|
||||
|
||||
//case 3516
|
||||
if (newMessageCount > 0) {
|
||||
document.title =
|
||||
newMessageCount +
|
||||
" NEW message" +
|
||||
(newMessageCount == 1 ? "" : "s");
|
||||
} else {
|
||||
document.title = "No new messages";
|
||||
}
|
||||
}
|
||||
//do it every 5 minutes
|
||||
timerVar=setTimeout(getMessages,5*60*1000);
|
||||
console.log("INBOX.GETMESSAGES - started timer " + timerVar);
|
||||
});
|
||||
};
|
||||
//-------------------- END UTILITY METHODS -------------------
|
||||
|
||||
//------------------- BEGIN EVENT HANDLERS -------------------
|
||||
//-------------------- END EVENT HANDLERS --------------------
|
||||
|
||||
//------------------- BEGIN PUBLIC METHODS -------------------
|
||||
//CONFIGMODULE
|
||||
//
|
||||
configModule = function(context) {
|
||||
stateMap.context = context.context;
|
||||
if (stateMap.context.params.id) {
|
||||
stateMap.id = stateMap.context.params.id;
|
||||
}
|
||||
};
|
||||
|
||||
//INITMODULE
|
||||
//
|
||||
initModule = function($container) {
|
||||
if (typeof $container === "undefined") {
|
||||
$container = $("#app-shell-main-content");
|
||||
}
|
||||
$container.html(Handlebars.templates["app.inbox"]({}));
|
||||
stateMap.$appList = $("#rf-list-div");
|
||||
|
||||
getMessages();
|
||||
|
||||
//auto refresh every 10 minutes
|
||||
|
||||
// intervalRef = setInterval(function() {
|
||||
// getMessages();
|
||||
// }, 10 * 60 * 1000);
|
||||
|
||||
app.nav.contextClear();
|
||||
////app.nav.setContextTitle("inbox");
|
||||
};
|
||||
|
||||
// TERMINATE MODULE
|
||||
//
|
||||
terminateModule = function() {
|
||||
|
||||
if(timerVar!=null){
|
||||
clearTimeout(timerVar);
|
||||
console.log("INBOX.TERMINATEMODULE - cleared timer" + timerVar);
|
||||
}
|
||||
//clear up event handler
|
||||
// clearInterval(intervalRef);
|
||||
// intervalRef=null;
|
||||
//console.log("INBOX.TERMINATEMODULE");
|
||||
};
|
||||
|
||||
//PUBLIC METHODS
|
||||
//
|
||||
return {
|
||||
configModule: configModule,
|
||||
initModule: initModule,
|
||||
terminateModule: terminateModule
|
||||
};
|
||||
//------------------- END PUBLIC METHODS ---------------------
|
||||
})();
|
||||
156
wwwroot/js/app.license.js
Normal file
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
* app.license.js
|
||||
* License key generator
|
||||
*/
|
||||
|
||||
/*jslint browser : true, continue : true,
|
||||
devel : true, indent : 2, maxerr : 50,
|
||||
newcap : true, nomen : true, plusplus : true,
|
||||
regexp : true, sloppy : true, vars : false,
|
||||
white : true
|
||||
*/
|
||||
|
||||
/*global $, app */
|
||||
|
||||
app.license = (function () {
|
||||
'use strict';
|
||||
//---------------- BEGIN MODULE SCOPE VARIABLES --------------
|
||||
var
|
||||
|
||||
stateMap = {},
|
||||
configModule, initModule, onGenerate, onSelectAllAddOns;
|
||||
//----------------- END MODULE SCOPE VARIABLES ---------------
|
||||
|
||||
//------------------- BEGIN UTILITY METHODS ------------------
|
||||
//-------------------- END UTILITY METHODS -------------------
|
||||
|
||||
|
||||
//------------------- BEGIN EVENT HANDLERS -------------------
|
||||
onGenerate = function (event) {
|
||||
|
||||
event.preventDefault();
|
||||
$.gevent.publish('app-clear-error');
|
||||
//get form data
|
||||
var formData = $("form").serializeArray({
|
||||
checkboxesAsBools: true
|
||||
});
|
||||
|
||||
var submitData = app.utilB.objectifyFormDataArray(formData);
|
||||
|
||||
app.api.createLicense(submitData, function (res) {
|
||||
if (res.error) {
|
||||
$.gevent.publish('app-show-error', res.msg);
|
||||
} else {
|
||||
$('#key').val(res);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
return false; //prevent default
|
||||
};
|
||||
|
||||
|
||||
onSelectAllAddOns = function (event) {
|
||||
event.preventDefault();
|
||||
$('#wbi').prop('checked', true);
|
||||
$('#mbi').prop('checked', true);
|
||||
$('#ri').prop('checked', true);
|
||||
$('#qbi').prop('checked', true);
|
||||
$('#qboi').prop('checked', true);
|
||||
$('#pti').prop('checked', true);
|
||||
$('#quickNotification').prop('checked', true);
|
||||
$('#exportToXls').prop('checked', true);
|
||||
$('#outlookSchedule').prop('checked', true);
|
||||
$('#oli').prop('checked', true);
|
||||
$('#importExportCSVDuplicate').prop('checked', true);
|
||||
|
||||
|
||||
|
||||
return false; //prevent default
|
||||
};
|
||||
|
||||
// onTemplates = function(event) {
|
||||
// event.preventDefault();
|
||||
// alert("STUB: templates");
|
||||
|
||||
|
||||
// return false; //prevent default
|
||||
// };
|
||||
|
||||
|
||||
//-------------------- END EVENT HANDLERS --------------------
|
||||
|
||||
//------------------- BEGIN PUBLIC METHODS -------------------
|
||||
//CONFIGMODULE
|
||||
//
|
||||
configModule = function (context) {
|
||||
stateMap.context = context.context;
|
||||
if (stateMap.context.params.id) {
|
||||
stateMap.id = stateMap.context.params.id;
|
||||
}
|
||||
};
|
||||
|
||||
//INITMODULE
|
||||
//
|
||||
initModule = function ($container) {
|
||||
if (typeof $container === 'undefined') {
|
||||
$container = $('#app-shell-main-content');
|
||||
}
|
||||
$container.html(Handlebars.templates['app.license']({}));
|
||||
|
||||
|
||||
//case 3233 customer list
|
||||
//Fill customer list combo
|
||||
var customerList = {};
|
||||
|
||||
|
||||
//get customers
|
||||
app.api.get('customer/list', function (res) {
|
||||
if (res.error) {
|
||||
$.gevent.publish('app-show-error', res.msg);
|
||||
} else {
|
||||
|
||||
var html = '<option value="0"><TRIAL></option>';
|
||||
|
||||
for (var i = 0, len = res.length; i < len; ++i) {
|
||||
html += ('<option value="' + res[i]['id'] + '">' + res[i]['name'] + '</option>');
|
||||
customerList[res[i]['id']] = res[i]['name'];
|
||||
}
|
||||
$('#customerId').append(html);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
//Context menu
|
||||
app.nav.contextClear();
|
||||
|
||||
////app.nav.setContextTitle("License");
|
||||
|
||||
//make context menu
|
||||
|
||||
|
||||
//Context menu
|
||||
app.nav.contextClear();
|
||||
app.nav.contextAddButton('btn-generate', 'Make', 'key', onGenerate);
|
||||
app.nav.contextAddButton('btn-select-all-addons', 'All', 'check-all', onSelectAllAddOns);
|
||||
// app.nav.contextAddLink("licenseRequests/", "Requests", "voice");
|
||||
app.nav.contextAddLink("licenseTemplates/", "", "layers");
|
||||
//case 3233
|
||||
app.nav.contextAddLink("licenses/", "List", "");
|
||||
|
||||
|
||||
//set all date inputs to today plus one year
|
||||
var oneYearFromNow = moment().add(1, 'years').toISOString().substring(0, 10);
|
||||
var oneMonthFromNow = moment().add(1, 'months').toISOString().substring(0, 10);
|
||||
$('input[type="date"]').val(oneYearFromNow);
|
||||
$('#lockoutDate').val(oneMonthFromNow);
|
||||
|
||||
};
|
||||
|
||||
// return public methods
|
||||
return {
|
||||
configModule: configModule,
|
||||
initModule: initModule
|
||||
};
|
||||
//------------------- END PUBLIC METHODS ---------------------
|
||||
}());
|
||||
138
wwwroot/js/app.licenseRequestEdit.js
Normal file
@@ -0,0 +1,138 @@
|
||||
/*jslint browser : true, continue : true,
|
||||
devel : true, indent : 2, maxerr : 50,
|
||||
newcap : true, nomen : true, plusplus : true,
|
||||
regexp : true, sloppy : true, vars : false,
|
||||
white : true
|
||||
*/
|
||||
|
||||
/*global $, app */
|
||||
|
||||
app.licenseRequestEdit = (function () {
|
||||
'use strict';
|
||||
//---------------- BEGIN MODULE SCOPE VARIABLES --------------
|
||||
var
|
||||
stateMap = {},
|
||||
onSend, onCancel, onRegenerate, configModule, initModule;
|
||||
//----------------- END MODULE SCOPE VARIABLES ---------------
|
||||
|
||||
//------------------- BEGIN UTILITY METHODS ------------------
|
||||
//-------------------- END UTILITY METHODS -------------------
|
||||
|
||||
//------------------- BEGIN EVENT HANDLERS -------------------
|
||||
|
||||
////////////////////
|
||||
//
|
||||
onSend = function (event) {
|
||||
|
||||
event.preventDefault();
|
||||
$.gevent.publish('app-clear-error');
|
||||
//get form data
|
||||
var formData = $("form").serializeArray({
|
||||
checkboxesAsBools: true
|
||||
});
|
||||
|
||||
var submitData = app.utilB.objectifyFormDataArray(formData);
|
||||
|
||||
app.api.licenseEmailResponse(submitData, function (res) {
|
||||
if (res.error) {
|
||||
$.gevent.publish('app-show-error',res.msg);
|
||||
} else {
|
||||
//navigate back to licenseRequests
|
||||
//alert("key has been sent!");
|
||||
window.location.href = "#!/inbox/";
|
||||
|
||||
//$('#key').val(res);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
return false; //prevent default
|
||||
};
|
||||
|
||||
|
||||
//-------------------- END EVENT HANDLERS --------------------
|
||||
|
||||
//------------------- BEGIN PUBLIC METHODS -------------------
|
||||
//CONFIGMODULE
|
||||
//
|
||||
configModule = function (context) {
|
||||
stateMap.context = context.context;
|
||||
if (stateMap.context.params.id) {
|
||||
stateMap.id = stateMap.context.params.id;
|
||||
}
|
||||
};
|
||||
|
||||
//INITMODULE
|
||||
//
|
||||
initModule = function ($container) {
|
||||
if (typeof $container === 'undefined') {
|
||||
$container = $('#app-shell-main-content');
|
||||
}
|
||||
|
||||
$container.html(Handlebars.templates['app.licenseRequestEdit']({}));
|
||||
////app.nav.setContextTitle("Request");
|
||||
|
||||
//Append key hidden values for submit and processing by server
|
||||
//This is set from the original click to open this form
|
||||
$('<input />').attr('type', 'hidden')
|
||||
.attr('name', "request_email_uid")
|
||||
.attr('value', stateMap.id)
|
||||
.appendTo('#frm');
|
||||
|
||||
//These are empty but will be filled in when the server responds with the "record"
|
||||
//They are for re-submission back to the server to save a step of refetching the original info
|
||||
//and recalculating stuff that was already done
|
||||
|
||||
$('<input />').attr('type', 'hidden')
|
||||
.attr('name', "requestReplyToAddress")
|
||||
.attr('value', '')
|
||||
.appendTo('#frm');
|
||||
|
||||
$('<input />').attr('type', 'hidden')
|
||||
.attr('name', "requestFromReplySubject")
|
||||
.attr('value', '')
|
||||
.appendTo('#frm');
|
||||
|
||||
//rfcore unused?
|
||||
$('<input />').attr('type', 'hidden')
|
||||
.attr('name', "greetingReplySubject")
|
||||
.attr('value', '')
|
||||
.appendTo('#frm');
|
||||
|
||||
$('<input />').attr('type', 'hidden')
|
||||
.attr('name', "requestFromReplySubject")
|
||||
.attr('value', '')
|
||||
.appendTo('#frm');
|
||||
|
||||
|
||||
|
||||
|
||||
//fetch existing record
|
||||
app.api.generateFromRequest(stateMap.id, function (res) {
|
||||
if (res.error) {
|
||||
$.gevent.publish('app-show-error',res.msg);
|
||||
} else {
|
||||
//fill out form
|
||||
app.utilB.formData(res);
|
||||
}
|
||||
});
|
||||
|
||||
//Context menu
|
||||
app.nav.contextClear();
|
||||
app.nav.contextAddButton('btn-generate', 'Send', 'send', onSend);
|
||||
app.nav.contextAddLink("inbox/", "Inbox", "inbox");
|
||||
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
// return public methods
|
||||
return {
|
||||
configModule: configModule,
|
||||
initModule: initModule
|
||||
};
|
||||
//------------------- END PUBLIC METHODS ---------------------
|
||||
}());
|
||||
102
wwwroot/js/app.licenseTemplates.js
Normal file
@@ -0,0 +1,102 @@
|
||||
/*jslint browser : true, continue : true,
|
||||
devel : true, indent : 2, maxerr : 50,
|
||||
newcap : true, nomen : true, plusplus : true,
|
||||
regexp : true, sloppy : true, vars : false,
|
||||
white : true
|
||||
*/
|
||||
|
||||
/*global $, app */
|
||||
|
||||
app.licenseTemplates = (function () {
|
||||
|
||||
'use strict';
|
||||
//---------------- BEGIN MODULE SCOPE VARIABLES --------------
|
||||
var
|
||||
stateMap = {},
|
||||
onSave, configModule, initModule;
|
||||
//----------------- END MODULE SCOPE VARIABLES ---------------
|
||||
|
||||
//------------------- BEGIN UTILITY METHODS ------------------
|
||||
//-------------------- END UTILITY METHODS -------------------
|
||||
|
||||
//------------------- BEGIN EVENT HANDLERS -------------------
|
||||
|
||||
|
||||
//different than the other edit routes because it's global and there is only one
|
||||
//so no ID
|
||||
onSave = function (event) {
|
||||
|
||||
event.preventDefault();
|
||||
$.gevent.publish('app-clear-error');
|
||||
//get form data
|
||||
var formData = $("form").serializeArray({
|
||||
checkboxesAsBools: true
|
||||
});
|
||||
|
||||
var submitData = app.utilB.objectifyFormDataArray(formData);
|
||||
submitData["id"] = '1';
|
||||
|
||||
app.api.update('licenseTemplates', submitData, function (res) {
|
||||
if (res.error) {
|
||||
$.gevent.publish('app-show-error',res.msg);
|
||||
}
|
||||
});
|
||||
|
||||
return false; //prevent default
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
//-------------------- END EVENT HANDLERS --------------------
|
||||
|
||||
//------------------- BEGIN PUBLIC METHODS -------------------
|
||||
//CONFIGMODULE
|
||||
//
|
||||
configModule = function (context) {
|
||||
stateMap.context = context.context;
|
||||
// if (stateMap.context.params.id) {
|
||||
// stateMap.id = stateMap.context.params.id;
|
||||
// }
|
||||
};
|
||||
|
||||
//INITMODULE
|
||||
//
|
||||
initModule = function ($container) {
|
||||
if (typeof $container === 'undefined') {
|
||||
$container = $('#app-shell-main-content');
|
||||
}
|
||||
|
||||
$container.html(Handlebars.templates['app.licenseTemplates']({}));
|
||||
|
||||
//fetch existing record
|
||||
//Note license templates record id is always 1 as there is only ever one record in db
|
||||
app.api.get('licenseTemplates/' + '1', function (res) {
|
||||
if (res.error) {
|
||||
$.gevent.publish('app-show-error',res.msg);
|
||||
} else {
|
||||
//fill out form
|
||||
app.utilB.formData(res);
|
||||
}
|
||||
});
|
||||
|
||||
//set title
|
||||
// var title = "License message templates";
|
||||
// //app.nav.setContextTitle(title);
|
||||
|
||||
// bind actions
|
||||
$('#btn-save').bind('click', onSave);
|
||||
|
||||
//Context menu
|
||||
app.nav.contextClear();
|
||||
app.nav.contextAddLink("license/", "License", "key");
|
||||
};
|
||||
|
||||
|
||||
// return public methods
|
||||
return {
|
||||
configModule: configModule,
|
||||
initModule: initModule
|
||||
};
|
||||
//------------------- END PUBLIC METHODS ---------------------
|
||||
}());
|
||||
116
wwwroot/js/app.licenseView.js
Normal file
@@ -0,0 +1,116 @@
|
||||
/*jslint browser : true, continue : true,
|
||||
devel : true, indent : 2, maxerr : 50,
|
||||
newcap : true, nomen : true, plusplus : true,
|
||||
regexp : true, sloppy : true, vars : false,
|
||||
white : true
|
||||
*/
|
||||
|
||||
/*global $, app */
|
||||
|
||||
app.licenseView = (function () {
|
||||
'use strict';
|
||||
//---------------- BEGIN MODULE SCOPE VARIABLES --------------
|
||||
var
|
||||
stateMap = {},
|
||||
onSave, onDelete,
|
||||
configModule, initModule;
|
||||
//----------------- END MODULE SCOPE VARIABLES ---------------
|
||||
|
||||
|
||||
//------------------- BEGIN EVENT HANDLERS -------------------
|
||||
|
||||
//ONSAVE
|
||||
//
|
||||
onSave = function (event) {
|
||||
event.preventDefault();
|
||||
$.gevent.publish('app-clear-error');
|
||||
|
||||
var isFetched=$('#fetched').prop('checked')
|
||||
|
||||
// var submitData = { isFetched: isFetched };
|
||||
app.api.putAction('license/fetched/' + stateMap.id + "/" + isFetched, function (res) {
|
||||
if (res.error) {
|
||||
$.gevent.publish('app-show-error', res.msg);
|
||||
}
|
||||
});
|
||||
|
||||
return false; //prevent default?
|
||||
};
|
||||
|
||||
//ONDELETE
|
||||
//
|
||||
onDelete = function (event) {
|
||||
event.preventDefault();
|
||||
$.gevent.publish('app-clear-error');
|
||||
var r = confirm("Are you sure you want to delete this record?");
|
||||
if (r == true) {
|
||||
app.api.remove('license/' + stateMap.id, function (res) {
|
||||
if (res.error) {
|
||||
$.gevent.publish('app-show-error', res.msg);
|
||||
} else {
|
||||
page('#!/licenses');
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return false; //prevent default?
|
||||
};
|
||||
|
||||
|
||||
//-------------------- END EVENT HANDLERS --------------------
|
||||
|
||||
//------------------- BEGIN PUBLIC METHODS -------------------
|
||||
|
||||
|
||||
//CONFIGMODULE
|
||||
//
|
||||
configModule = function (context) {
|
||||
stateMap.context = context.context;
|
||||
if (stateMap.context.params.id) {
|
||||
stateMap.id = stateMap.context.params.id;
|
||||
}
|
||||
};
|
||||
|
||||
//INITMODULE
|
||||
//
|
||||
initModule = function ($container) {
|
||||
if (typeof $container === 'undefined') {
|
||||
$container = $('#app-shell-main-content');
|
||||
}
|
||||
|
||||
$container.html(Handlebars.templates['app.licenseView']({}));
|
||||
|
||||
document.title = 'License ';
|
||||
|
||||
//fetch existing record
|
||||
app.api.get('license/' + stateMap.id, function (res) {
|
||||
if (res.error) {
|
||||
$.gevent.publish('app-show-error', res.msg);
|
||||
} else {
|
||||
//fill out form
|
||||
app.utilB.formData(res);
|
||||
}
|
||||
});
|
||||
//Context menu
|
||||
app.nav.contextClear();
|
||||
app.nav.contextAddLink("licenses/", "List", "");
|
||||
|
||||
|
||||
|
||||
// bind actions
|
||||
$('#btn-save').bind('click', onSave);
|
||||
$('#btn-delete').bind('click', onDelete);
|
||||
};
|
||||
|
||||
|
||||
// RETURN PUBLIC METHODS
|
||||
//
|
||||
return {
|
||||
configModule: configModule,
|
||||
initModule: initModule
|
||||
};
|
||||
//------------------- END PUBLIC METHODS ---------------------
|
||||
}());
|
||||
108
wwwroot/js/app.licenses.js
Normal file
@@ -0,0 +1,108 @@
|
||||
/*jslint browser : true, continue : true,
|
||||
devel : true, indent : 2, maxerr : 50,
|
||||
newcap : true, nomen : true, plusplus : true,
|
||||
regexp : true, sloppy : true, vars : false,
|
||||
white : true
|
||||
*/
|
||||
|
||||
/*global $, app */
|
||||
|
||||
app.licenses = (function() {
|
||||
"use strict";
|
||||
//---------------- BEGIN MODULE SCOPE VARIABLES --------------
|
||||
var stateMap = {},
|
||||
configModule,
|
||||
initModule;
|
||||
//----------------- END MODULE SCOPE VARIABLES ---------------
|
||||
|
||||
//------------------- BEGIN UTILITY METHODS ------------------
|
||||
|
||||
//-------------------- END UTILITY METHODS -------------------
|
||||
|
||||
//------------------- BEGIN EVENT HANDLERS -------------------
|
||||
|
||||
//-------------------- END EVENT HANDLERS --------------------
|
||||
|
||||
//------------------- BEGIN PUBLIC METHODS -------------------
|
||||
//CONFIGMODULE
|
||||
//
|
||||
configModule = function(context) {
|
||||
stateMap.context = context.context;
|
||||
if (stateMap.context.params.id) {
|
||||
stateMap.id = stateMap.context.params.id;
|
||||
}
|
||||
};
|
||||
|
||||
//INITMODULE
|
||||
//
|
||||
initModule = function($container) {
|
||||
if (typeof $container === "undefined") {
|
||||
$container = $("#app-shell-main-content");
|
||||
}
|
||||
$container.html(Handlebars.templates["app.licenses"]({}));
|
||||
|
||||
//case 3513
|
||||
document.title = "Licenses";
|
||||
|
||||
//===================
|
||||
//Get licenses
|
||||
app.api.get("license/list", function(res) {
|
||||
if (res.error) {
|
||||
$.gevent.publish("app-show-error", res.msg);
|
||||
} else {
|
||||
var $appList = $("#rf-list");
|
||||
|
||||
$appList.append('<ul class="list-group">');
|
||||
|
||||
$.each(res, function(i, obj) {
|
||||
if (obj.fetched) {
|
||||
$appList.append(
|
||||
"<li class='rfc-list-item-inactive list-group-item'><a href=\"#!/licenseView/" +
|
||||
obj.id +
|
||||
'">' +
|
||||
"<span class='text-muted'>" +
|
||||
(obj.trial
|
||||
? "Trial "
|
||||
: "") +
|
||||
app.utilB.genListColumn(obj.regto) +
|
||||
" " +
|
||||
app.utilB.genListColumn(
|
||||
app.utilB.epochToShortDate(obj.created)
|
||||
) +
|
||||
"</span>" +
|
||||
"</a></li>"
|
||||
);
|
||||
} else {
|
||||
$appList.append(
|
||||
"<li class='list-group-item'><a href=\"#!/licenseView/" +
|
||||
obj.id +
|
||||
'">' +
|
||||
(obj.trial
|
||||
? "Trial "
|
||||
: "") +
|
||||
app.utilB.genListColumn(obj.regto) +
|
||||
" " +
|
||||
app.utilB.genListColumn(
|
||||
app.utilB.epochToShortDate(obj.created)
|
||||
) +
|
||||
"</a></li>"
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
$appList.append("</ul>");
|
||||
}
|
||||
});
|
||||
//=========/licenses==============
|
||||
|
||||
app.nav.contextClear();
|
||||
};
|
||||
|
||||
//PUBLIC METHODS
|
||||
//
|
||||
return {
|
||||
configModule: configModule,
|
||||
initModule: initModule
|
||||
};
|
||||
//------------------- END PUBLIC METHODS ---------------------
|
||||
})();
|
||||
160
wwwroot/js/app.mailEdit.js
Normal file
@@ -0,0 +1,160 @@
|
||||
/*jslint browser : true, continue : true,
|
||||
devel : true, indent : 2, maxerr : 50,
|
||||
newcap : true, nomen : true, plusplus : true,
|
||||
regexp : true, sloppy : true, vars : false,
|
||||
white : true
|
||||
*/
|
||||
|
||||
/*global $, app */
|
||||
|
||||
app.mailEdit = (function () {
|
||||
'use strict';
|
||||
//---------------- BEGIN MODULE SCOPE VARIABLES --------------
|
||||
var
|
||||
stateMap = {},
|
||||
onSave, onDelete, onSend,
|
||||
configModule, initModule;
|
||||
//----------------- END MODULE SCOPE VARIABLES ---------------
|
||||
|
||||
|
||||
//------------------- BEGIN EVENT HANDLERS -------------------
|
||||
|
||||
///////////////////////////////////
|
||||
// SEND A REPLY OR NEW MESSAGE
|
||||
//
|
||||
onSend = function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
$.gevent.publish('app-clear-error');
|
||||
|
||||
//get form data
|
||||
var formData = $("form").serializeArray({
|
||||
checkboxesAsBools: true
|
||||
});
|
||||
|
||||
//var submitData = {composition:$("#composition").val()};
|
||||
var submitData = app.utilB.objectifyFormDataArray(formData);
|
||||
|
||||
//is this a new record?
|
||||
if (stateMap.id != 'new') {
|
||||
//put id into the form data
|
||||
// submitData.id = stateMap.id;
|
||||
|
||||
app.api.create('mail/reply/' +
|
||||
stateMap.context.params.mail_account +
|
||||
'/' + stateMap.context.params.mail_id, submitData, function (res) {
|
||||
if (res.error) {
|
||||
$.gevent.publish('app-show-error', res.msg);
|
||||
} else {
|
||||
page('#!/inbox');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
alert("STUB: New message composition not implemented yet");
|
||||
// //it's a new record - create
|
||||
// app.api.create('customer', submitData, function (res) {
|
||||
// if (res.error) {
|
||||
// $.gevent.publish('app-show-error',res.msg);
|
||||
// } else {
|
||||
// page('#!/inbox');
|
||||
// }
|
||||
// });
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
//ONDELETE
|
||||
//
|
||||
//removed
|
||||
|
||||
//-------------------- END EVENT HANDLERS --------------------
|
||||
|
||||
//------------------- BEGIN PUBLIC METHODS -------------------
|
||||
|
||||
|
||||
//CONFIGMODULE
|
||||
//
|
||||
configModule = function (context) {
|
||||
stateMap.context = context.context;
|
||||
if (stateMap.context.params.id) {
|
||||
stateMap.id = stateMap.context.params.id;
|
||||
}
|
||||
};
|
||||
|
||||
//INITMODULE
|
||||
//
|
||||
initModule = function ($container) {
|
||||
if (typeof $container === 'undefined') {
|
||||
$container = $('#app-shell-main-content');
|
||||
}
|
||||
|
||||
$container.html(Handlebars.templates['app.mailEdit']({}));
|
||||
// stateMap.replyMode = false;
|
||||
// if (stateMap.context.querystring.endsWith('reply')) {
|
||||
// stateMap.replyMode = true;
|
||||
// }
|
||||
|
||||
app.nav.contextClear();
|
||||
app.nav.contextAddLink("inbox/", "Inbox", "inbox");
|
||||
|
||||
//id should always have a value, either a record id or the keyword 'new' for making a new object
|
||||
if (stateMap.id != 'new') {
|
||||
|
||||
//fetch existing record
|
||||
app.api.get(
|
||||
'mail/preview/' +
|
||||
stateMap.context.params.mail_account +
|
||||
'/' + stateMap.context.params.mail_folder +
|
||||
'/' + stateMap.context.params.mail_id, function (res) {
|
||||
if (res.error) {
|
||||
// app.nav.contextClear();
|
||||
// app.nav.contextAddLink("inbox/", "Inbox", "inbox");
|
||||
$.gevent.publish('app-show-error', res.msg);
|
||||
} else {
|
||||
//fill out form
|
||||
// app.nav.contextClear();
|
||||
//app.nav.contextAddLink("inbox/", "Inbox", "inbox");
|
||||
$('#message').text(res.preview);
|
||||
if(res.isKeyRequest){
|
||||
app.nav.contextAddLink("licenseRequestEdit/" + res.id, "Make", "key");
|
||||
}else{
|
||||
$('#composition').text("\n\n- John\nwww.ayanova.com");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$('#btn-send').bind('click', onSend);
|
||||
|
||||
//Context menu
|
||||
|
||||
//app.nav.contextAddButton('btn-send', 'Send reply', 'send', onSend);
|
||||
|
||||
// app.nav.contextAddButton('btn-generate', 'Build', 'key', onReply);
|
||||
// app.nav.contextAddLink("customerSites/" + stateMap.id, "Sites", "city");//url title icon
|
||||
|
||||
|
||||
|
||||
} else {
|
||||
//NEW email options
|
||||
var $group = $("#sendToGroup");
|
||||
$group.removeClass("invisible");
|
||||
|
||||
// app.nav.contextClear();
|
||||
// app.nav.contextAddLink("inbox/", "Inbox", "inbox");
|
||||
// app.nav.contextAddButton('btn-send', 'Send', 'send', onSend);
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
// RETURN PUBLIC METHODS
|
||||
//
|
||||
return {
|
||||
configModule: configModule,
|
||||
initModule: initModule
|
||||
};
|
||||
//------------------- END PUBLIC METHODS ---------------------
|
||||
}());
|
||||
102
wwwroot/js/app.nav.js
Normal file
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* app.nav.js
|
||||
* Handle dynamic navigation related operations
|
||||
* toobar, menu, fab etc
|
||||
*
|
||||
*/
|
||||
|
||||
/*jslint browser : true, continue : true,
|
||||
devel : true, indent : 2, maxerr : 50,
|
||||
newcap : true, nomen : true, plusplus : true,
|
||||
regexp : true, sloppy : true, vars : false,
|
||||
white : true
|
||||
*/
|
||||
/*global $, app */
|
||||
|
||||
app.nav = (function () {
|
||||
var contextClear, contextAddButton, contextAddLink, backUrl, backRemove, moreMenuInitialized,
|
||||
setContextTitle, setSelectedMenuItem;
|
||||
|
||||
|
||||
|
||||
// ///////////////////////////////////////////
|
||||
// //Set context menu title
|
||||
// //
|
||||
// var setContextTitle = function (title) {
|
||||
// $("#rf-context-title").html(title + "...");
|
||||
// }
|
||||
|
||||
////////////////////////////////////////////////
|
||||
//Clear the contents of the context menu (reset it)
|
||||
//
|
||||
contextClear = function () {
|
||||
$("#rf-context-group").empty();
|
||||
$("#rf-context-group").addClass("invisible");
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
////////////////////////////////////////////////
|
||||
//Add a non nav button item to the context menu
|
||||
//
|
||||
contextAddButton = function (id, title, icon, clickHandler) {
|
||||
var $group=$("#rf-context-group");
|
||||
$group.removeClass("invisible");
|
||||
|
||||
var iconClass = '';
|
||||
if (icon) {
|
||||
iconClass = "mdi mdi-" + icon;
|
||||
}
|
||||
$group.append(
|
||||
'<button type="button" id="' + id + '" class="btn btn-outline-dark ' + iconClass + '" href="#">' + title + '</a>'
|
||||
);
|
||||
|
||||
$('#' + id).bind('click', clickHandler);
|
||||
|
||||
};
|
||||
|
||||
// <button type="button" class="btn btn-outline-dark mdi mdi-key">Left</button>
|
||||
|
||||
////////////////////////////////////////////////
|
||||
//Add an url item to the context menu handling
|
||||
//phone vs larger sizes
|
||||
//
|
||||
contextAddLink = function (url, title, icon) {
|
||||
var $group=$("#rf-context-group");
|
||||
$group.removeClass("invisible");
|
||||
var iconClass = '';
|
||||
if (icon) {
|
||||
iconClass = "mdi mdi-" + icon;
|
||||
}
|
||||
$group.append(
|
||||
'<a class="btn btn-outline-dark ' + iconClass + '" href="#!/' + encodeURI(url) + '">' + title + '</a>'
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
////////////////////////////////////////////////
|
||||
//Set the active class on the selected menu item
|
||||
//
|
||||
setSelectedMenuItem = function (selectedMenuItem) {
|
||||
$(".nav-item").removeClass("active");
|
||||
if (selectedMenuItem) {
|
||||
$("#" + selectedMenuItem).addClass("active");
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
return {
|
||||
contextClear: contextClear,
|
||||
contextAddButton: contextAddButton,
|
||||
contextAddLink: contextAddLink,
|
||||
setContextTitle: setContextTitle,
|
||||
setSelectedMenuItem: setSelectedMenuItem
|
||||
|
||||
};
|
||||
}());
|
||||
266
wwwroot/js/app.purchaseEdit.js
Normal file
@@ -0,0 +1,266 @@
|
||||
/*jslint browser : true, continue : true,
|
||||
devel : true, indent : 2, maxerr : 50,
|
||||
newcap : true, nomen : true, plusplus : true,
|
||||
regexp : true, sloppy : true, vars : false,
|
||||
white : true
|
||||
*/
|
||||
|
||||
/*global $, app */
|
||||
|
||||
app.purchaseEdit = (function() {
|
||||
"use strict";
|
||||
//---------------- BEGIN MODULE SCOPE VARIABLES --------------
|
||||
var stateMap = {},
|
||||
onSave,
|
||||
onDelete,
|
||||
onRenew,
|
||||
configModule,
|
||||
initModule,
|
||||
onPasteNotes;
|
||||
//----------------- END MODULE SCOPE VARIABLES ---------------
|
||||
|
||||
//------------------- BEGIN UTILITY METHODS ------------------
|
||||
//-------------------- END UTILITY METHODS -------------------
|
||||
|
||||
//------------------- BEGIN EVENT HANDLERS -------------------
|
||||
|
||||
onSave = function(event) {
|
||||
event.preventDefault();
|
||||
$.gevent.publish("app-clear-error");
|
||||
//get form data
|
||||
var formData = $("form").serializeArray({
|
||||
checkboxesAsBools: true
|
||||
});
|
||||
|
||||
var submitData = app.utilB.objectifyFormDataArray(formData);
|
||||
|
||||
//is this a new record?
|
||||
if (stateMap.id != "new") {
|
||||
//put id into the form data
|
||||
submitData.id = stateMap.id;
|
||||
|
||||
app.api.update("purchase", submitData, function(res) {
|
||||
if (res.error) {
|
||||
$.gevent.publish("app-show-error", res.msg);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
//create new record
|
||||
app.api.create("purchase", submitData, function(res) {
|
||||
if (res.error) {
|
||||
$.gevent.publish("app-show-error", res.msg);
|
||||
} else {
|
||||
page(
|
||||
"#!/purchaseEdit/" + res.id + "/" + stateMap.context.params.site_id
|
||||
);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
return false; //prevent default
|
||||
};
|
||||
|
||||
onRenew = function(event) {
|
||||
event.preventDefault();
|
||||
$.gevent.publish("app-clear-error");
|
||||
|
||||
if (stateMap.id == "new") {
|
||||
$.gevent.publish(
|
||||
"app-show-error",
|
||||
"Save this record before attempting to renew it"
|
||||
);
|
||||
return false;
|
||||
}
|
||||
stateMap.id = "new";
|
||||
|
||||
//case 3396, no more renewal or dupe names
|
||||
// var nm = $('#name').val();
|
||||
// nm = "DUPE-" + nm;
|
||||
// $('#name').val(nm);
|
||||
|
||||
//case 3396, set values accordingly
|
||||
|
||||
//Clear salesOrderNumber
|
||||
$("#salesOrderNumber").val("");
|
||||
|
||||
//set purchaseDate to today
|
||||
$("#purchaseDate").val(
|
||||
moment()
|
||||
.toISOString()
|
||||
.substring(0, 10)
|
||||
);
|
||||
|
||||
//set expireDate to plus one year from today
|
||||
$("#expireDate").val(
|
||||
moment()
|
||||
.add(1, "years")
|
||||
.toISOString()
|
||||
.substring(0, 10)
|
||||
);
|
||||
|
||||
//clear the couponCode
|
||||
$("#couponCode").val("");
|
||||
|
||||
//clear the notes
|
||||
$("#notes").val("");
|
||||
|
||||
$("#renewNoticeSent").prop("checked", false);
|
||||
$("#cancelDate").val("");
|
||||
|
||||
return false; //prevent default
|
||||
};
|
||||
|
||||
//ONDELETE
|
||||
//
|
||||
onDelete = function(event) {
|
||||
event.preventDefault();
|
||||
$.gevent.publish("app-clear-error");
|
||||
|
||||
var r = confirm("Are you sure you want to delete this record?");
|
||||
if (r == true) {
|
||||
//==== DELETE ====
|
||||
app.api.remove("purchase/" + stateMap.id, function(res) {
|
||||
if (res.error) {
|
||||
$.gevent.publish("app-show-error", res.msg);
|
||||
} else {
|
||||
//deleted, return to master list
|
||||
page("#!/purchases/" + stateMap.context.params.site_id);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return false; //prevent default?
|
||||
};
|
||||
|
||||
onPasteNotes = function(event) {
|
||||
var clipboardData, pastedData;
|
||||
var e = event.originalEvent;
|
||||
|
||||
// // Stop data actually being pasted into div
|
||||
// e.stopPropagation();
|
||||
// e.preventDefault();
|
||||
|
||||
// Get pasted data via clipboard API
|
||||
clipboardData = e.clipboardData || window.clipboardData;
|
||||
pastedData = clipboardData.getData("Text");
|
||||
|
||||
//Iterate through the lines looking for the SHareIt name=value lines (they all contain equal signs)
|
||||
var lines = pastedData.split("\n"); // lines is an array of strings
|
||||
var purchaseData = {};
|
||||
|
||||
// Loop through all lines
|
||||
for (var j = 0; j < lines.length; j++) {
|
||||
var thisLine = lines[j];
|
||||
if (thisLine.includes("=")) {
|
||||
var thisElement = thisLine.split("=");
|
||||
purchaseData[thisElement[0].trim()] = thisElement[1].trim();
|
||||
}
|
||||
}
|
||||
|
||||
//Now have an object with the value pairs in it
|
||||
if (purchaseData["ShareIt Ref #"]) {
|
||||
$("#salesOrderNumber").val(purchaseData["ShareIt Ref #"]);
|
||||
}
|
||||
|
||||
// if (purchaseData["E-Mail"]) {
|
||||
// $("#email").val(purchaseData["E-Mail"]);
|
||||
// }
|
||||
};
|
||||
|
||||
//-------------------- END EVENT HANDLERS --------------------
|
||||
|
||||
//------------------- BEGIN PUBLIC METHODS -------------------
|
||||
//CONFIGMODULE
|
||||
//
|
||||
configModule = function(context) {
|
||||
stateMap.context = context.context;
|
||||
if (stateMap.context.params.id) {
|
||||
stateMap.id = stateMap.context.params.id;
|
||||
}
|
||||
};
|
||||
|
||||
//INITMODULE
|
||||
//
|
||||
initModule = function($container) {
|
||||
if (typeof $container === "undefined") {
|
||||
$container = $("#app-shell-main-content");
|
||||
}
|
||||
|
||||
$container.html(Handlebars.templates["app.purchaseEdit"]({}));
|
||||
var title = "Purchase";
|
||||
|
||||
if (!stateMap.context.params.site_id) {
|
||||
throw "app.purchaseEdit.js::initModule - There is no stateMap.context.params.site_id!";
|
||||
}
|
||||
|
||||
//Append master record id as a hidden form field for referential integrity
|
||||
$("<input />")
|
||||
.attr("type", "hidden")
|
||||
.attr("name", "siteId")
|
||||
.attr("value", stateMap.context.params.site_id)
|
||||
.appendTo("#frm");
|
||||
|
||||
//fetch entire site record to get name *and* customer id which is required for redundancy
|
||||
|
||||
//RFC - get site name and customer name for form
|
||||
|
||||
app.api.get("site/" + stateMap.context.params.site_id, function(res) {
|
||||
if (res.error) {
|
||||
$.gevent.publish("app-show-error", res.msg);
|
||||
} else {
|
||||
//Also append customer ID redundantly
|
||||
$("<input />")
|
||||
.attr("type", "hidden")
|
||||
.attr("name", "customerId")
|
||||
.attr("value", res.customerId)
|
||||
.appendTo("#frm");
|
||||
|
||||
title = "Purchase - " + res.name;
|
||||
if (stateMap.id != "new") {
|
||||
//fetch existing record
|
||||
app.api.get("purchase/" + stateMap.id, function(res) {
|
||||
if (res.error) {
|
||||
$.gevent.publish("app-show-error", res.msg);
|
||||
} else {
|
||||
//fill out form
|
||||
app.utilB.formData(res);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
//it's a new record, set default
|
||||
$("#purchaseDate").val(new Date().toISOString().substring(0, 10));
|
||||
}
|
||||
//set title
|
||||
//app.nav.setContextTitle(title);
|
||||
}
|
||||
});
|
||||
|
||||
//Context menu
|
||||
app.nav.contextClear();
|
||||
app.nav.contextAddLink(
|
||||
"purchases/" + stateMap.context.params.site_id,
|
||||
"Purchases",
|
||||
"basket"
|
||||
);
|
||||
|
||||
// bind actions
|
||||
$("#btn-save").bind("click", onSave);
|
||||
$("#btn-delete").bind("click", onDelete);
|
||||
$("#btn-renew").bind("click", onRenew);
|
||||
$("#notes").bind("paste", onPasteNotes);
|
||||
|
||||
//Autocomplete
|
||||
app.utilB.autoComplete("name", "purchase.name");
|
||||
app.utilB.autoComplete("productCode", "purchase.productCode");
|
||||
app.utilB.autoComplete("vendorName", "purchase.vendorName");
|
||||
};
|
||||
|
||||
// return public methods
|
||||
return {
|
||||
configModule: configModule,
|
||||
initModule: initModule
|
||||
};
|
||||
//------------------- END PUBLIC METHODS ---------------------
|
||||
})();
|
||||
145
wwwroot/js/app.purchases.js
Normal file
@@ -0,0 +1,145 @@
|
||||
/*jslint browser : true, continue : true,
|
||||
devel : true, indent : 2, maxerr : 50,
|
||||
newcap : true, nomen : true, plusplus : true,
|
||||
regexp : true, sloppy : true, vars : false,
|
||||
white : true
|
||||
*/
|
||||
|
||||
/*global $, app */
|
||||
|
||||
app.purchases = (function () {
|
||||
'use strict';
|
||||
//---------------- BEGIN MODULE SCOPE VARIABLES --------------
|
||||
var
|
||||
|
||||
configMap = {
|
||||
//main_html: '',
|
||||
|
||||
settable_map: {}
|
||||
},
|
||||
stateMap = {
|
||||
$append_target: null
|
||||
},
|
||||
onSubmit, loadList, configModule, initModule;
|
||||
//----------------- END MODULE SCOPE VARIABLES ---------------
|
||||
|
||||
//------------------- BEGIN UTILITY METHODS ------------------
|
||||
loadList = function () {
|
||||
//----
|
||||
//fetch
|
||||
app.api.get('site/' + stateMap.id + '/purchases', function (res) {
|
||||
if (res.error) {
|
||||
$.gevent.publish('app-show-error',res.msg);
|
||||
} else {
|
||||
//get the list ul
|
||||
var $appList = $('#rf-list');
|
||||
$appList.empty();
|
||||
$.each(res, function (i, obj) {
|
||||
|
||||
if (obj.cancelDate) {
|
||||
$appList.append("<li class='rfc-list-item-inactive'><a href=\"#!/purchaseEdit/" + obj.id + "/" + stateMap.id + "\">" +
|
||||
// app.utilB.genListColumn(app.utilB.epochToShortDate(obj.purchaseDate)) +
|
||||
// ' ' +
|
||||
"<span class='text-muted'>"+
|
||||
app.utilB.genListColumn(obj.name) +
|
||||
" cancelled " +
|
||||
app.utilB.genListColumn(app.utilB.epochToShortDate(obj.cancelDate)) +
|
||||
"</span>"+
|
||||
"</a></li>");
|
||||
} else {
|
||||
$appList.append("<li><a href=\"#!/purchaseEdit/" + obj.id + "/" + stateMap.id + "\">" +
|
||||
// app.utilB.genListColumn(app.utilB.epochToShortDate(obj.purchaseDate)) +
|
||||
// ' ' +
|
||||
app.utilB.genListColumn(obj.name) +
|
||||
" expires " +
|
||||
app.utilB.genListColumn(app.utilB.epochToShortDate(obj.expireDate)) +
|
||||
"</a></li>");
|
||||
|
||||
//
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
});
|
||||
//------
|
||||
}
|
||||
//-------------------- END UTILITY METHODS -------------------
|
||||
|
||||
//--------------------- BEGIN DOM METHODS --------------------
|
||||
|
||||
|
||||
|
||||
|
||||
// Begin private DOM methods
|
||||
|
||||
|
||||
|
||||
// End private DOM methods
|
||||
//---------------------- END DOM METHODS ---------------------
|
||||
|
||||
//------------------- BEGIN EVENT HANDLERS -------------------
|
||||
|
||||
|
||||
|
||||
|
||||
//-------------------- END EVENT HANDLERS --------------------
|
||||
|
||||
//------------------- BEGIN PUBLIC METHODS -------------------
|
||||
//CONFIGMODULE
|
||||
//
|
||||
configModule = function (context) {
|
||||
stateMap.context = context.context;
|
||||
if (stateMap.context.params.id) {
|
||||
stateMap.id = stateMap.context.params.id;
|
||||
}
|
||||
};
|
||||
|
||||
// Begin public method /initModule/
|
||||
// Example : app.customer.initModule( $('#div_id') );
|
||||
// Purpose : directs the module to being offering features
|
||||
// Arguments : $container - container to use
|
||||
// Action : Provides interface
|
||||
// Returns : none
|
||||
// Throws : none
|
||||
//
|
||||
initModule = function ($container) {
|
||||
if (typeof $container === 'undefined') {
|
||||
$container = $('#app-shell-main-content');
|
||||
}
|
||||
stateMap.sortOrder = 'd';
|
||||
$container.html(Handlebars.templates['app.purchases']({}));
|
||||
|
||||
if (!stateMap.id) {
|
||||
throw ('app.purchases.js::initModule - There is no stateMap.id!');
|
||||
}
|
||||
|
||||
app.api.get('site/' + stateMap.id, function (res) {
|
||||
if (res.error) {
|
||||
$.gevent.publish('app-show-error',res.msg);
|
||||
} else {
|
||||
|
||||
//Context menu
|
||||
app.nav.contextClear();
|
||||
app.nav.contextAddLink('purchaseEdit/new/' + stateMap.id, "New", "plus");
|
||||
app.nav.contextAddLink("customerEdit/" + res.customerId, "Customer", "account");
|
||||
app.nav.contextAddLink("customerSiteEdit/" + stateMap.id + '/' + res.customerId, "Site", "city");
|
||||
if (stateMap.id) {
|
||||
loadList();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
// End public method /initModule/
|
||||
|
||||
|
||||
|
||||
// return public methods
|
||||
return {
|
||||
configModule: configModule,
|
||||
initModule: initModule
|
||||
};
|
||||
//------------------- END PUBLIC METHODS ---------------------
|
||||
}());
|
||||
67
wwwroot/js/app.reportData.js
Normal file
@@ -0,0 +1,67 @@
|
||||
/*jslint browser : true, continue : true,
|
||||
devel : true, indent : 2, maxerr : 50,
|
||||
newcap : true, nomen : true, plusplus : true,
|
||||
regexp : true, sloppy : true, vars : false,
|
||||
white : true
|
||||
*/
|
||||
|
||||
/*global $, app */
|
||||
|
||||
app.reportData = (function () {
|
||||
'use strict';
|
||||
//---------------- BEGIN MODULE SCOPE VARIABLES --------------
|
||||
var
|
||||
|
||||
stateMap = {},
|
||||
configModule,
|
||||
initModule;
|
||||
//----------------- END MODULE SCOPE VARIABLES ---------------
|
||||
|
||||
//------------------- BEGIN UTILITY METHODS ------------------
|
||||
//-------------------- END UTILITY METHODS -------------------
|
||||
|
||||
//------------------- BEGIN EVENT HANDLERS -------------------
|
||||
|
||||
//-------------------- END EVENT HANDLERS --------------------
|
||||
|
||||
//------------------- BEGIN PUBLIC METHODS -------------------
|
||||
//CONFIGMODULE
|
||||
//
|
||||
configModule = function (context) {
|
||||
stateMap.context = context.context;
|
||||
if (stateMap.context.params.id) {
|
||||
stateMap.id = stateMap.context.params.id;
|
||||
}
|
||||
};
|
||||
|
||||
//INITMODULE
|
||||
//
|
||||
initModule = function ($container) {
|
||||
if (typeof $container === 'undefined') {
|
||||
$container = $('#app-shell-main-content');
|
||||
}
|
||||
$container.html(Handlebars.templates['app.reportData']({}));
|
||||
|
||||
//Context menu
|
||||
app.nav.contextClear();
|
||||
////app.nav.setContextTitle("Reports");
|
||||
app.nav.contextAddLink("templates", "Templates", "widgets");//url title icon
|
||||
|
||||
|
||||
|
||||
|
||||
var $appList = $('#rf-list');
|
||||
$appList.empty();
|
||||
$appList.append("<li><a href=\"#!/reportDataProdEmails/\">" + app.utilB.genListColumn("Get emails CSV by product code") + "</a></li>");
|
||||
$appList.append("<li><a href=\"#!/reportDataExpires/\">" + app.utilB.genListColumn("Expiring subscriptions") + "</a></li>");
|
||||
|
||||
};
|
||||
|
||||
//PUBLIC METHODS
|
||||
//
|
||||
return {
|
||||
configModule: configModule,
|
||||
initModule: initModule
|
||||
};
|
||||
//------------------- END PUBLIC METHODS ---------------------
|
||||
}());
|
||||
79
wwwroot/js/app.reportDataExpires.js
Normal file
@@ -0,0 +1,79 @@
|
||||
/*jslint browser : true, continue : true,
|
||||
devel : true, indent : 2, maxerr : 50,
|
||||
newcap : true, nomen : true, plusplus : true,
|
||||
regexp : true, sloppy : true, vars : false,
|
||||
white : true
|
||||
*/
|
||||
|
||||
/*global $, app */
|
||||
|
||||
app.reportDataExpires = (function () {
|
||||
|
||||
'use strict';
|
||||
//---------------- BEGIN MODULE SCOPE VARIABLES --------------
|
||||
var
|
||||
stateMap = {},
|
||||
configModule,
|
||||
initModule;
|
||||
//----------------- END MODULE SCOPE VARIABLES ---------------
|
||||
|
||||
//------------------- BEGIN UTILITY METHODS ------------------
|
||||
//-------------------- END UTILITY METHODS -------------------
|
||||
|
||||
//------------------- BEGIN EVENT HANDLERS -------------------
|
||||
|
||||
|
||||
|
||||
|
||||
//-------------------- END EVENT HANDLERS --------------------
|
||||
|
||||
//------------------- BEGIN PUBLIC METHODS -------------------
|
||||
//CONFIGMODULE
|
||||
//
|
||||
configModule = function (context) {
|
||||
stateMap.context = context.context;
|
||||
};
|
||||
|
||||
//INITMODULE
|
||||
//
|
||||
initModule = function ($container) {
|
||||
if (typeof $container === 'undefined') {
|
||||
$container = $('#app-shell-main-content');
|
||||
}
|
||||
|
||||
$container.html(Handlebars.templates['app.reportDataExpires']({}));
|
||||
|
||||
//fetch data
|
||||
app.api.get('report/expires', function (res) {
|
||||
if (res.error) {
|
||||
$.gevent.publish('app-show-error',res.msg);
|
||||
} else {
|
||||
|
||||
//get the list ul
|
||||
var $appList = $('#rf-list');
|
||||
$appList.empty();
|
||||
$.each(res, function (i, obj) {
|
||||
$appList.append("<li><a href=\"#!/purchaseEdit/" + obj.id + "/" + obj.site_id + "\">" +
|
||||
app.utilB.genListColumn(app.utilB.epochToShortDate(obj.expireDate)) +
|
||||
app.utilB.genListColumn(obj.customer) +
|
||||
app.utilB.genListColumn(obj.name) +
|
||||
"</a></li>")
|
||||
});
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
//Context menu
|
||||
app.nav.contextClear();
|
||||
app.nav.contextAddLink("reportData/", "Reports", "book-open-variant");//url title icon
|
||||
|
||||
};
|
||||
|
||||
|
||||
// return public methods
|
||||
return {
|
||||
configModule: configModule,
|
||||
initModule: initModule
|
||||
};
|
||||
//------------------- END PUBLIC METHODS ---------------------
|
||||
}());
|
||||