Ready to Test

This commit is contained in:
2022-07-26 09:34:09 -07:00
commit 2afec95704
1004 changed files with 164796 additions and 0 deletions

View File

@ -0,0 +1,50 @@
using Microsoft.AspNetCore.Mvc;
using OI.Metrology.Shared.DataModels;
using OI.Metrology.Shared.Repositories;
using OI.Metrology.Shared.Services;
using System;
using System.IO;
namespace OI.Metrology.Archive.ApiControllers;
public class AttachmentsController : Controller
{
protected IMetrologyRepo _Repo;
protected IAttachmentsService _AttachmentsService;
public AttachmentsController(IMetrologyRepo repo, IAttachmentsService attachmentsService)
{
_Repo = repo;
_AttachmentsService = attachmentsService;
}
// this endpoint was created in hope that it would make retrieving attachments to display in OpenInsight easier
// url would be like /api/attachments/mercuryprobe/header/HgProbe_66-232268-4329_20180620052640032/data.pdf
[HttpGet("/api/attachments/{toolTypeName}/{tabletype}/{title}/{filename}")]
public IActionResult GetAttachment(
string toolTypeName,
string tabletype,
string title,
string filename)
{
ToolType tt = _Repo.GetToolTypeByName(toolTypeName);
bool header = !string.Equals(tabletype.Trim(), "data", StringComparison.OrdinalIgnoreCase);
try
{
string contenttype = "application/pdf";
if (filename.ToLower().TrimEnd().EndsWith(".txt"))
contenttype = "text/plain";
Stream fs = _AttachmentsService.GetAttachmentStreamByTitle(tt, header, title, filename);
return File(fs, contenttype);
}
catch (Exception ex)
{
return Content(ex.Message);
}
}
}

View File

@ -0,0 +1,46 @@
using Microsoft.AspNetCore.Mvc;
namespace OI.Metrology.Archive.ApiContollers;
using OI.Metrology.Shared.Repositories;
using System.Text.Json;
// this controller is for the Awaiting Dispo functionality
public class AwaitingDispoController : Controller
{
protected IMetrologyRepo _Repo;
public AwaitingDispoController(IMetrologyRepo repo) => _Repo = repo;
// returns the data to show in the Awaiting Dispo grid
// marked no-cache, just-in-case since igniteUI automatically adds a query string parameter to prevent caching
[HttpGet("/api/awaitingdispo")]
[ResponseCache(NoStore = true)]
public IActionResult Index()
{
var r = new
{
Results = _Repo.GetAwaitingDispo()
};
return Json(r, new JsonSerializerOptions { PropertyNamingPolicy = null, WriteIndented = true });
}
// this endpoint is used to set the ReviewDate column, causing the header to no longer show in Awaiting Dispo
[HttpPost("/api/awaitingdispo/markasreviewed")]
public IActionResult MarkAsReviewed([FromQuery] long headerid, [FromQuery] int tooltypeid)
{
_ = _Repo.UpdateReviewDate(tooltypeid, headerid, false);
return Ok();
}
// this endpoint is used to clear the ReviewDate column, causing the header to show up again
[HttpPost("/api/awaitingdispo/markasawaiting")]
public IActionResult MarkAsAwaiting([FromQuery] long headerid, [FromQuery] int tooltypeid)
{
if (_Repo.UpdateReviewDate(tooltypeid, headerid, true) <= 1)
return Ok();
else
return StatusCode(444);
}
}

View File

@ -0,0 +1,169 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json.Linq;
using OI.Metrology.Shared.DataModels;
using OI.Metrology.Shared.Repositories;
using OI.Metrology.Shared.Services;
using System;
using System.Collections.Generic;
using System.Linq;
namespace OI.Metrology.Archive.ApiContollers;
[ApiController]
public class InboundController : ControllerBase
{
private IConfiguration Config { get; }
private ILogger Logger { get; }
protected IMetrologyRepo _Repo;
protected IAttachmentsService _AttachmentService;
protected IInboundDataService _InboundDataService;
public InboundController(IConfiguration config, ILogger<InboundController> logger, IMetrologyRepo repo, IInboundDataService inboundDataService, IAttachmentsService attachmentService)
{
Config = config;
Logger = logger;
_Repo = repo;
_InboundDataService = inboundDataService;
_AttachmentService = attachmentService;
}
// this class represents the API response back to the client
public class DataResponse
{
public bool Success { get; set; }
public long HeaderID { get; set; }
public List<string> Errors { get; set; }
public List<string> Warnings { get; set; }
}
// this is the main endpoint, it accepts a JSON message that contains both the header and data records together
// tooltype is the ToolTypeName column from the ToolType table
// JToken is how you can accept a JSON message without deserialization.
// Using "string" doesn't work because ASP.NET Core will expect a json encoded string, not give you the actual string.
[HttpPost("/api/inbound/{tooltype}")]
public IActionResult Data(string tooltype, [FromBody] JToken jsonbody)
{
DataResponse r = new()
{
Success = false,
HeaderID = -1,
Errors = new List<string>(),
Warnings = new List<string>()
};
if (!IsIPAddressAllowed())
{
Logger.LogInformation($"Rejected remote IP: {HttpContext.Connection.RemoteIpAddress}");
r.Errors.Add("Remote IP is not on allowed list");
return Unauthorized(r);
}
ToolType toolType = _Repo.GetToolTypeByName(tooltype);
if (toolType == null)
{
r.Errors.Add("Invalid tool type: " + tooltype);
return BadRequest(r);
}
// get metadata
List<ToolTypeMetadata> metaData = _Repo.GetToolTypeMetadataByToolTypeID(toolType.ID).ToList();
if (metaData == null)
{
r.Errors.Add("Invalid metadata for tool type: " + tooltype);
return BadRequest(r);
}
// validate fields
if (jsonbody != null)
_InboundDataService.ValidateJSONFields(jsonbody, 0, metaData, r.Errors, r.Warnings);
else
r.Errors.Add("Invalid json");
if (r.Errors.Count == 0)
{
try
{
r.HeaderID = _InboundDataService.DoSQLInsert(jsonbody, toolType, metaData);
r.Success = r.HeaderID > 0;
}
catch (Exception ex)
{
r.Errors.Add(ex.Message);
}
return Ok(r);
}
else
{
return BadRequest(r);
}
}
// this is the endpoint for attaching a field. It is not JSON, it is form-data/multipart like an HTML form because that's the normal way.
// header ID is the ID value from the Header table
// datauniqueid is the Title value from the Data/Detail table
[HttpPost("/api/inbound/{tooltype}/attachment")]
public IActionResult AttachFile(string tooltype, [FromQuery] long headerid, [FromQuery] string datauniqueid = "")
{
if (!IsIPAddressAllowed())
{
Logger.LogInformation($"Rejected remote IP: {HttpContext.Connection.RemoteIpAddress}");
return Unauthorized("Remote IP is not on allowed list");
}
ToolType toolType = _Repo.GetToolTypeByName(tooltype);
if (toolType == null)
return BadRequest($"Invalid tool type: {tooltype}");
if (Request.Form == null)
return BadRequest($"Invalid form");
if (Request.Form.Files.Count != 1)
return BadRequest($"Invalid file count");
string filename = System.IO.Path.GetFileName(Request.Form.Files[0].FileName);
if (string.IsNullOrWhiteSpace(filename))
return BadRequest("Empty filename");
if (filename.IndexOfAny(System.IO.Path.GetInvalidFileNameChars()) >= 0)
return BadRequest("Invalid filename");
_AttachmentService.SaveAttachment(toolType, headerid, datauniqueid, filename, Request.Form.Files[0]);
return Ok();
}
protected bool IsIPAddressAllowed()
{
string allowedList = Config[Constants.InboundApiAllowedIPList];
if (string.IsNullOrWhiteSpace(allowedList))
return true;
System.Net.IPAddress remoteIP = HttpContext.Connection.RemoteIpAddress;
byte[] remoteIPBytes = remoteIP.GetAddressBytes();
string[] allowedIPs = allowedList.Split(';');
foreach (string ip in allowedIPs)
{
System.Net.IPAddress parsedIP;
if (System.Net.IPAddress.TryParse(ip, out parsedIP))
{
if (parsedIP.GetAddressBytes().SequenceEqual(remoteIPBytes))
return true;
}
}
return false;
}
}

View File

@ -0,0 +1,276 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Newtonsoft.Json;
using System;
using System.IO;
using System.Linq;
namespace OI.Metrology.Archive.ApiContollers;
using OI.Metrology.Shared.DataModels;
using OI.Metrology.Shared.Repositories;
using OI.Metrology.Shared.Services;
using System.Collections.Generic;
using System.Text.Json;
// using System.Data.Common;
public class ToolTypesController : Controller
{
// this controller powers the bulk of the UI
// it is named after the /api/tooltypes prefix
// the URL pattern is RESTful and the tool type is the root of every request
private IConfiguration Config { get; }
protected IMetrologyRepo _Repo;
protected IAttachmentsService _AttachmentsService;
public ToolTypesController(IConfiguration config, IMetrologyRepo repo, IAttachmentsService attachmentsService)
{
Config = config;
_Repo = repo;
_AttachmentsService = attachmentsService;
}
// Get a list of tooltypes, returns just Name and ID
[HttpGet("/api/tooltypes")]
public IActionResult Index()
{
var r = new
{
Results = _Repo.GetToolTypes().Select(tt => new { tt.ToolTypeName, tt.ID })
};
return Json(r, new JsonSerializerOptions { PropertyNamingPolicy = null, WriteIndented = true });
}
// Gets the metadata for a tooltype, accepts a parameter which sorts the results
// This is used to setup the grid displays on the UI
[HttpGet("/api/tooltypes/{id}")]
public IActionResult GetToolTypeMetadata(int id, string sortby = "")
{
ToolType tt = _Repo.GetToolTypeByID(id);
IEnumerable<ToolTypeMetadata> md = _Repo.GetToolTypeMetadataByToolTypeID(id);
if (string.Equals(sortby, "grid", StringComparison.OrdinalIgnoreCase))
md = md.OrderBy(f => f.GridDisplayOrder).ToList();
if (string.Equals(sortby, "table", StringComparison.OrdinalIgnoreCase))
md = md.OrderBy(f => f.GridDisplayOrder).ToList();
var r = new
{
Results = new
{
ToolType = tt,
Metadata = md
}
};
return Json(r, new JsonSerializerOptions { PropertyNamingPolicy = null, WriteIndented = true });
}
//Just Changed here
// Gets headers, request/response format is to allow paging with igniteUI
// The headerid parameter is used for navigating directly to a header, when the button is clicked in Awaiting Dispo
[HttpGet("/api/tooltypes/{id}/headers")]
public IActionResult GetHeaders(
int id,
[FromQuery] DateTime? datebegin,
[FromQuery] DateTime? dateend,
[FromQuery] int? page,
[FromQuery] int? pagesize,
[FromQuery] long? headerid, bool isSharePoint)
{
long totalRecs;
System.Data.DataTable dt = _Repo.GetHeaders(id, datebegin, dateend, page, pagesize, headerid, out totalRecs, isSharePoint);
var r = new
{
Results = dt,
TotalRows = totalRecs,
};
string json = JsonConvert.SerializeObject(r);
return Content(json);
}
// Gets header titles, used in the Run Headers UI
[HttpGet("/api/tooltypes/{id}/headertitles")]
public IActionResult GetHeaderTitles(
int id,
[FromQuery] int? page,
[FromQuery] int? pagesize, bool isArchive)
{
long totalRecs;
IEnumerable<HeaderCommon> dt = _Repo.GetHeaderTitles(id, page, pagesize, out totalRecs, isArchive);
var r = new
{
Results = dt,
TotalRows = totalRecs,
};
string json = JsonConvert.SerializeObject(r);
return Content(json);
}
// Get all of the fields for a header, used with the Run Header UI
[HttpGet("/api/tooltypes/{id}/headers/{headerid}/fields")]
public IActionResult GetHeaderFields(
int id,
long headerid, bool isArchive)
{
var r = new
{
Results = _Repo.GetHeaderFields(id, headerid, isArchive).Select(x => new { Column = x.Key, x.Value }).ToList()
};
string json = JsonConvert.SerializeObject(r);
return Content(json);
}
// Get the data for a header, used with the Run Info UI
[HttpGet("/api/tooltypes/{id}/headers/{title}/data/isSharePoint")]
public IActionResult GetData(
int id,
string title)
{
var r = new
{
Results = _Repo.GetDataSharePoint(id, title)
};
string json = JsonConvert.SerializeObject(r);
return Content(json);
}
// Get the data for a header, used with the Run Info UI
[HttpGet("/api/tooltypes/{id}/headers/{headerid}/data")]
public IActionResult GetData(
int id,
long headerid, bool isSharePoint)
{
var r = new
{
Results = _Repo.GetData(id, headerid, isSharePoint)
};
string json = JsonConvert.SerializeObject(r);
return Content(json);
}
// Display an attachment, used for Run Info - note it is by tool type ID and attachment GUID, so it is best for internal use
[HttpGet("/api/tooltypes/{toolTypeId}/{tabletype}/files/{attachmentId}/{filename}")]
public IActionResult GetAttachment(
int toolTypeId,
string tabletype,
string attachmentId,
string filename, bool isArchive)
{
ToolType tt = _Repo.GetToolTypeByID(toolTypeId);
bool header = !string.Equals(tabletype.Trim(), "data", StringComparison.OrdinalIgnoreCase);
Guid attachmentIdParsed;
if (!Guid.TryParse(attachmentId, out attachmentIdParsed))
return Content("Invalid attachment id");
//try
// {
// figure out what content type to use. this is very simple because there are only two types being used
string contenttype = "application/pdf";
if (filename.ToLower().TrimEnd().EndsWith(".txt"))
contenttype = "text/plain";
// Get attachment stream and feed it to the client
Stream fs = _AttachmentsService.GetAttachmentStreamByAttachmentId(tt, header, attachmentIdParsed, filename);
/*if (isArchive)
{
fs = attachmentsService.GetAttachmentStreamByAttachmentIdArchive(tt, header, attachmentIdParsed, filename);
//fs = attachmentsService.GetAttachmentStreamByAttachmentId
}*/
return File(fs, contenttype);
//}
/*catch (Exception ex)
{
return Content(ex.Message);
}*/
}
// This endpoint triggers writing of the OI Export file
[HttpPost("/api/tooltypes/{toolTypeId}/headers/{headerid}/oiexport")]
public IActionResult OIExport(int toolTypeId, long headerid)
{
// Call the export stored procedure
System.Data.DataSet ds = _Repo.GetOIExportData(toolTypeId, headerid);
try
{
// The SP must return 3 result tables
if (ds.Tables.Count != 3)
throw new Exception("Error exporting, invalid results");
// The first table has just one row, which is the export filename
if (ds.Tables[0].Rows.Count != 1)
throw new Exception("Error exporting, invalid filename");
string filename = Convert.ToString(ds.Tables[0].Rows[0][0]);
// The second table has the header data
if (ds.Tables[1].Rows.Count != 1)
throw new Exception("Error exporting, invalid header data");
System.Text.StringBuilder sb = new();
foreach (object o in ds.Tables[1].Rows[0].ItemArray)
{
if ((o != null) && (!Convert.IsDBNull(o)))
_ = sb.Append(Convert.ToString(o));
_ = sb.Append('\t');
}
// The third table has the detail data
foreach (System.Data.DataRow dr in ds.Tables[2].Rows)
{
foreach (object o in dr.ItemArray)
{
if ((o != null) && (!Convert.IsDBNull(o)))
_ = sb.Append(Convert.ToString(o));
_ = sb.Append('\t');
}
}
_ = sb.AppendLine();
// The output file will only have one line, the header columns are output first
// Then each detail rows has it's columns appended
// H1, H2, H3, D1.1, D1.2, D1.3, D2.1, D2.2, D2.3, etc
// Get the configured export path
string exportRootPath = Config[Constants.OIExportPathKey];
// Write the file
System.IO.File.WriteAllText(
Path.Join(exportRootPath, filename),
sb.ToString());
}
catch (Exception ex)
{
string json = JsonConvert.SerializeObject(new
{
ex.Message,
});
return BadRequest(json);
}
var r = new
{
Message = "OK",
};
return Ok(r);
}
}