Viewer to Server
This commit is contained in:
138
Server/Services/AttachmentsService.cs
Normal file
138
Server/Services/AttachmentsService.cs
Normal file
@ -0,0 +1,138 @@
|
||||
using System.Globalization;
|
||||
|
||||
namespace OI.Metrology.Server.Services;
|
||||
|
||||
using OI.Metrology.Server.Models;
|
||||
using OI.Metrology.Shared.DataModels;
|
||||
using OI.Metrology.Shared.Models.Stateless;
|
||||
using OI.Metrology.Shared.Services;
|
||||
|
||||
public class AttachmentsService : IAttachmentsService
|
||||
{
|
||||
private readonly AppSettings _AppSettings;
|
||||
private readonly IMetrologyRepository _MetrologyRepository;
|
||||
|
||||
public AttachmentsService(AppSettings appSettings, IMetrologyRepository metrologyRepository)
|
||||
{
|
||||
_AppSettings = appSettings;
|
||||
_MetrologyRepository = metrologyRepository;
|
||||
}
|
||||
|
||||
protected Stream GetAttachmentStream(string? tableName, Guid attachmentId, string filename)
|
||||
{
|
||||
if (attachmentId.Equals(Guid.Empty))
|
||||
throw new Exception("No attachments found");
|
||||
|
||||
if (tableName is null)
|
||||
throw new NullReferenceException(nameof(tableName));
|
||||
|
||||
DateTime insertDate = Convert.ToDateTime(_MetrologyRepository.GetAttachmentInsertDateByGUID(tableName, attachmentId));
|
||||
int year = insertDate.Year;
|
||||
DateTime d = insertDate;
|
||||
CultureInfo cul = CultureInfo.CurrentCulture;
|
||||
int weekNum = cul.Calendar.GetWeekOfYear(d, CalendarWeekRule.FirstDay, DayOfWeek.Sunday);
|
||||
string workWeek = "WW" + weekNum.ToString("00");
|
||||
string dateDir = year + @"\" + workWeek;
|
||||
|
||||
string fullPath = Path.Combine(_AppSettings.AttachmentPath, tableName + "_", dateDir, attachmentId.ToString(), filename);
|
||||
|
||||
// Check to see if file exists in the "New" directory structure, if not change the path back to the old. and check there
|
||||
if (!File.Exists(fullPath))
|
||||
{
|
||||
fullPath = Path.Combine(_AppSettings.AttachmentPath, tableName, attachmentId.ToString(), filename);
|
||||
}
|
||||
|
||||
if (!File.Exists(fullPath))
|
||||
throw new Exception("File not found");
|
||||
|
||||
return new FileStream(fullPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
}
|
||||
|
||||
public Stream GetAttachmentStreamByTitle(ToolType toolType, bool header, string title, string filename)
|
||||
{
|
||||
if (toolType is null)
|
||||
throw new Exception("Invalid tool type");
|
||||
Guid attachmentId;
|
||||
string? tableName;
|
||||
if (header)
|
||||
{
|
||||
tableName = toolType.HeaderTableName;
|
||||
attachmentId = _MetrologyRepository.GetHeaderAttachmentIDByTitle(toolType.ID, title);
|
||||
}
|
||||
else
|
||||
{
|
||||
tableName = toolType.DataTableName;
|
||||
attachmentId = _MetrologyRepository.GetDataAttachmentIDByTitle(toolType.ID, title);
|
||||
}
|
||||
return GetAttachmentStream(tableName, attachmentId, filename);
|
||||
}
|
||||
|
||||
public Stream GetAttachmentStreamByAttachmentId(ToolType toolType, bool header, Guid attachmentId, string filename)
|
||||
{
|
||||
if (toolType is null)
|
||||
throw new Exception("Invalid tool type");
|
||||
string? tableName;
|
||||
if (header)
|
||||
tableName = toolType.HeaderTableName;
|
||||
else
|
||||
tableName = toolType.DataTableName;
|
||||
return GetAttachmentStream(tableName, attachmentId, filename);
|
||||
}
|
||||
|
||||
private void SaveAttachment(ToolType toolType, long headerId, string dataUniqueId, string filename, IFormFile uploadedFile)
|
||||
{
|
||||
if (toolType is null)
|
||||
throw new Exception("Invalid tool type");
|
||||
|
||||
using System.Transactions.TransactionScope trans = _MetrologyRepository.StartTransaction();
|
||||
Guid attachmentId = Guid.Empty;
|
||||
DateTime insertDate = new();
|
||||
string? tableName = "";
|
||||
|
||||
if (string.IsNullOrWhiteSpace(dataUniqueId))
|
||||
{
|
||||
attachmentId = _MetrologyRepository.GetHeaderAttachmentID(toolType.ID, headerId);
|
||||
insertDate = Convert.ToDateTime(_MetrologyRepository.GetHeaderInsertDate(toolType.ID, headerId));
|
||||
tableName = toolType.HeaderTableName;
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
attachmentId = _MetrologyRepository.GetDataAttachmentID(toolType.ID, headerId, dataUniqueId);
|
||||
insertDate = Convert.ToDateTime(_MetrologyRepository.GetDataInsertDate(toolType.ID, headerId, dataUniqueId));
|
||||
// Get Date for new directory name
|
||||
tableName = toolType.DataTableName;
|
||||
|
||||
}
|
||||
|
||||
int year = insertDate.Year;
|
||||
DateTime d = insertDate;
|
||||
CultureInfo cul = CultureInfo.CurrentCulture;
|
||||
|
||||
int weekNum = cul.Calendar.GetWeekOfYear(d, CalendarWeekRule.FirstDay, DayOfWeek.Sunday);
|
||||
|
||||
string workWeek = "WW" + weekNum.ToString("00");
|
||||
string dateDir = year + @"\" + workWeek;
|
||||
|
||||
if (Equals(attachmentId, Guid.Empty))
|
||||
throw new Exception("Invalid attachment ID");
|
||||
string directoryPathSecondary = Path.Combine(_AppSettings.AttachmentPath, tableName + "_", dateDir, attachmentId.ToString());
|
||||
if (!Directory.Exists(directoryPathSecondary))
|
||||
_ = Directory.CreateDirectory(directoryPathSecondary);
|
||||
|
||||
string fullPathSecondary = Path.Combine(directoryPathSecondary, filename);
|
||||
|
||||
using (FileStream s = new(fullPathSecondary, FileMode.Create, FileAccess.ReadWrite, FileShare.None))
|
||||
{
|
||||
uploadedFile.CopyTo(s);
|
||||
}
|
||||
trans.Complete();
|
||||
}
|
||||
|
||||
public void SaveAttachment(ToolType toolType, long headerId, string dataUniqueId, string filename, object uploadedFile)
|
||||
{
|
||||
IFormFile formFile = (IFormFile)uploadedFile;
|
||||
SaveAttachment(toolType, headerId, dataUniqueId, filename, formFile);
|
||||
}
|
||||
|
||||
}
|
235
Server/Services/InboundDataService.cs
Normal file
235
Server/Services/InboundDataService.cs
Normal file
@ -0,0 +1,235 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OI.Metrology.Shared.DataModels;
|
||||
using OI.Metrology.Shared.Models.Stateless;
|
||||
using OI.Metrology.Shared.Services;
|
||||
|
||||
#pragma warning disable CS8600, CS8602, CS8603, CS8604, CS8625
|
||||
|
||||
namespace OI.Metrology.Server.Services;
|
||||
|
||||
public class InboundDataService : IInboundDataService
|
||||
{
|
||||
private readonly IMetrologyRepository _MetrologyRepository;
|
||||
|
||||
public InboundDataService(IMetrologyRepository metrologyRepository) => _MetrologyRepository = metrologyRepository;
|
||||
|
||||
public long DoSQLInsert(JToken jsonbody, ToolType toolType, List<ToolTypeMetadata> metaData)
|
||||
{
|
||||
JArray detailsArray = null;
|
||||
string uniqueId = "";
|
||||
|
||||
foreach (JToken jt in jsonbody.Children())
|
||||
{
|
||||
if (jt is JProperty jp)
|
||||
{
|
||||
if (string.Equals(jp.Name, "Details", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (jp.First is JArray array)
|
||||
detailsArray = array;
|
||||
}
|
||||
else if (string.Equals(jp.Name, "UniqueId", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
uniqueId = Convert.ToString(((JValue)jp.Value).Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
long headerId = 0;
|
||||
|
||||
using (System.Transactions.TransactionScope transScope = _MetrologyRepository.StartTransaction())
|
||||
{
|
||||
try
|
||||
{
|
||||
_MetrologyRepository.PurgeExistingData(toolType.ID, uniqueId);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception("Failed to purge existing data: " + ex.Message, ex);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
headerId = _MetrologyRepository.InsertToolDataJSON(jsonbody, -1, metaData, toolType.HeaderTableName);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception("Insert failed for header: " + ex.Message, ex);
|
||||
}
|
||||
|
||||
int detailrow = 1;
|
||||
try
|
||||
{
|
||||
if (detailsArray is not null)
|
||||
{
|
||||
foreach (JToken detail in detailsArray)
|
||||
{
|
||||
_ = _MetrologyRepository.InsertToolDataJSON(detail, headerId, metaData, toolType.DataTableName);
|
||||
detailrow += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception("Insert failed for detail row " + detailrow.ToString() + ": " + ex.Message, ex);
|
||||
}
|
||||
|
||||
transScope.Complete();
|
||||
}
|
||||
|
||||
return headerId;
|
||||
}
|
||||
|
||||
// this method is for validating the json contents of the inbound request, it will make sure all required fields are included
|
||||
// errors are generated for missing fields, and warnings are generated for additional fields not in the metadata
|
||||
// this is recursive, detailIndex = 0 is for the header, then it calls itself for each of the details rows (in any)
|
||||
public void ValidateJSONFields(JToken jsonbody, int detailIndex, List<ToolTypeMetadata> metaData, List<string> errors, List<string> warnings)
|
||||
{
|
||||
bool isHeader = detailIndex == 0;
|
||||
string rowDesc = isHeader ? "header" : "detail index " + detailIndex.ToString(); // human readable description for error messages
|
||||
|
||||
// filter the metadata list by header/detail
|
||||
List<ToolTypeMetadata> fields = metaData.Where(md => md.Header == isHeader).ToList();
|
||||
|
||||
// get list of ApiFields from the metadata, exclude blank ApiName
|
||||
List<string> apiFields = fields.Where(f => string.IsNullOrWhiteSpace(f.ApiName) == false).Select(f => f.ApiName.Trim().ToUpper()).ToList();
|
||||
|
||||
// get list of ApiFields from the metadata with blank ColumnName - we ignore these fields in the jsonbody
|
||||
List<string> ignoreFields = fields.Where(f => (string.IsNullOrWhiteSpace(f.ApiName) == false) && string.IsNullOrWhiteSpace(f.ColumnName)).Select(f => f.ApiName.Trim().ToUpper()).ToList();
|
||||
|
||||
// keep a list of valid fields found in the jsonbody so we can check for duplicates
|
||||
List<string> validFields = new();
|
||||
|
||||
// get list of container fields in the ApiFields, ex: Points\Thickness will add Points to the list
|
||||
List<string> containerFields = apiFields.Where(f => f.Contains('\\')).Select(f => f.Split('\\')[0].Trim().ToUpper()).Distinct().ToList();
|
||||
|
||||
// pointer to the Details array from the jsonbody, this is hard-coded as the subfield for the common Header/Detail structure
|
||||
JArray detailsArray = null;
|
||||
|
||||
// process fields in the json body
|
||||
foreach (JToken jt in jsonbody.Children())
|
||||
{
|
||||
if (jt is JProperty jp)
|
||||
{
|
||||
string jpName = jp.Name.Trim().ToUpper();
|
||||
|
||||
if (apiFields.Contains(jpName))
|
||||
{
|
||||
// Normal field detected, remove it from the list so we know which fields are missing
|
||||
_ = apiFields.Remove(jpName);
|
||||
|
||||
// Check for duplicates
|
||||
if (validFields.Contains(jpName))
|
||||
errors.Add("Duplicated field on " + rowDesc + ": " + jp.Name);
|
||||
else
|
||||
validFields.Add(jpName);
|
||||
}
|
||||
else if (string.Equals(jp.Name, "Details", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// Details container field found
|
||||
|
||||
if (!isHeader)
|
||||
errors.Add("Details field not expected on " + rowDesc);
|
||||
|
||||
if (jp.First is JArray array)
|
||||
detailsArray = array;
|
||||
else if ((jp.First is JValue value) && (value.Value is null))
|
||||
detailsArray = null;
|
||||
else
|
||||
errors.Add("Invalid details field");
|
||||
}
|
||||
else if (ignoreFields.Contains(jpName))
|
||||
{
|
||||
// ignore these fields
|
||||
}
|
||||
else if (containerFields.Contains(jpName))
|
||||
{
|
||||
// ignore fields that are container fields
|
||||
}
|
||||
else
|
||||
{
|
||||
// output warnings if extra fields are found
|
||||
warnings.Add("Extra field on " + rowDesc + ": " + jp.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// process container fields, ex: Points
|
||||
ValidateJSONContainerFields(jsonbody, rowDesc, apiFields, containerFields, errors, warnings);
|
||||
|
||||
if (containerFields.Count > 1)
|
||||
errors.Add("Only one container field is supported");
|
||||
if (isHeader && (containerFields.Count > 0))
|
||||
errors.Add("Container field is only allowed on detail");
|
||||
|
||||
// output errors for fields that were not found in the json
|
||||
foreach (string f in apiFields)
|
||||
{
|
||||
errors.Add("Missing field on " + rowDesc + ": " + f);
|
||||
}
|
||||
|
||||
// if a Details container if found, process it by recursion
|
||||
if (detailsArray is not null)
|
||||
{
|
||||
int i = 1;
|
||||
foreach (JToken detail in detailsArray)
|
||||
{
|
||||
ValidateJSONFields(detail, i, metaData, errors, warnings);
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// this method is for validating the special container fields (only used for stratus biorad)
|
||||
// the container fields are used to collapse a 3 tier structure into the 2 tier used in sharepoint
|
||||
protected void ValidateJSONContainerFields(JToken jsonbody, string rowDesc, List<string> apiFields, List<string> containerFields, List<string> errors, List<string> warnings)
|
||||
{
|
||||
// process container fields, ex: Points
|
||||
foreach (string containerField in containerFields)
|
||||
{
|
||||
// get the json data for this container field, ex: Points
|
||||
JProperty contJP = jsonbody.Children<JProperty>().Where(jp => string.Equals(jp.Name, containerField, StringComparison.OrdinalIgnoreCase)).SingleOrDefault();
|
||||
if ((contJP is not null) && (contJP.Value is JArray array))
|
||||
{
|
||||
JArray contJPArray = array;
|
||||
|
||||
// Get a list of properties in the container field from the json body, but pre-pend the container field name, ex: Points\Position
|
||||
List<string> contFieldProperties = new();
|
||||
foreach (JToken sfJT in contJPArray.Values<JToken>()) // for each row in the json container
|
||||
{
|
||||
foreach (JProperty subJTJP in sfJT.Children<JProperty>()) // for each property for the row
|
||||
{
|
||||
string propname = (containerField + '\\' + subJTJP.Name).ToUpper();
|
||||
if (!contFieldProperties.Contains(propname))
|
||||
contFieldProperties.Add(propname);
|
||||
}
|
||||
}
|
||||
|
||||
// Get list of field bindings for this container field, ex: Points\Position, Points\Thickness
|
||||
List<string> contFieldBindings = apiFields.Where(f => f.StartsWith(containerField, StringComparison.OrdinalIgnoreCase)).Select(f => f.ToUpper()).ToList();
|
||||
foreach (string contFieldBinding in contFieldBindings)
|
||||
{
|
||||
// check if the jsonbody has this property in the container field
|
||||
if (contFieldProperties.Contains(contFieldBinding))
|
||||
{
|
||||
_ = contFieldProperties.Remove(contFieldBinding); // remove from the list of properties so we know it was found
|
||||
_ = apiFields.Remove(contFieldBinding); // remove from the list of missing fields
|
||||
}
|
||||
else
|
||||
{
|
||||
errors.Add("Missing field on " + rowDesc + ": " + contFieldBinding);
|
||||
}
|
||||
}
|
||||
|
||||
// Output warnings for extra properties in the container field
|
||||
foreach (string contFieldProp in contFieldProperties)
|
||||
{
|
||||
warnings.Add("Extra field on " + rowDesc + ": " + contFieldProperties);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
errors.Add("Missing container field on " + rowDesc + ": " + containerField);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
30
Server/Services/SQLDbConnectionFactory.cs
Normal file
30
Server/Services/SQLDbConnectionFactory.cs
Normal file
@ -0,0 +1,30 @@
|
||||
using OI.Metrology.Server.Models;
|
||||
using OI.Metrology.Shared.Repositories;
|
||||
using System.Data.Common;
|
||||
using System.Data.SqlClient;
|
||||
|
||||
#pragma warning disable CS8600, CS8602, CS8603, CS8604, CS8625
|
||||
|
||||
namespace OI.Metrology.Server.Services;
|
||||
|
||||
public class SQLDbConnectionFactory : IDbConnectionFactory
|
||||
{
|
||||
private readonly AppSettings _AppSettings;
|
||||
|
||||
public SQLDbConnectionFactory(AppSettings appSettings) => _AppSettings = appSettings;
|
||||
|
||||
public DbConnection GetDbConnection()
|
||||
{
|
||||
DbProviderFactories.RegisterFactory(
|
||||
typeof(SqlConnection).Namespace,
|
||||
SqlClientFactory.Instance);
|
||||
|
||||
if (string.IsNullOrEmpty(_AppSettings.ConnectionString))
|
||||
throw new Exception("Connection string is missing");
|
||||
|
||||
DbConnection c = SqlClientFactory.Instance.CreateConnection();
|
||||
c.ConnectionString = _AppSettings.ConnectionString;
|
||||
c.Open();
|
||||
return c;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user