using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using Newtonsoft.Json.Linq; using OI.Metrology.Archive.Models; using OI.Metrology.Shared.DataModels; using OI.Metrology.Shared.Models.Stateless; using OI.Metrology.Shared.Services; using System; using System.Collections.Generic; using System.Linq; namespace OI.Metrology.Archive.ApiControllers; [ApiController] public class InboundController : ControllerBase { private readonly ILogger _Logger; private readonly AppSettings _AppSettings; private readonly IAttachmentsService _AttachmentService; private readonly IInboundDataService _InboundDataService; private readonly IMetrologyRepository _MetrologyRepository; public InboundController(AppSettings appSettings, ILogger logger, IMetrologyRepository metrologyRepository, IInboundDataService inboundDataService, IAttachmentsService attachmentService) { _Logger = logger; _AppSettings = appSettings; _AttachmentService = attachmentService; _InboundDataService = inboundDataService; _MetrologyRepository = metrologyRepository; } // 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 Errors { get; set; } public List 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(), Warnings = new List() }; 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 = _MetrologyRepository.GetToolTypeByName(tooltype); if (toolType is null) { r.Errors.Add("Invalid tool type: " + tooltype); return BadRequest(r); } // get metadata List metaData = _MetrologyRepository.GetToolTypeMetadataByToolTypeID(toolType.ID).ToList(); if (metaData is null) { r.Errors.Add("Invalid metadata for tool type: " + tooltype); return BadRequest(r); } // validate fields if (jsonbody is not 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 = _MetrologyRepository.GetToolTypeByName(tooltype); if (toolType is null) return BadRequest($"Invalid tool type: {tooltype}"); if (Request.Form is 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() { if (string.IsNullOrWhiteSpace(_AppSettings.InboundApiAllowedIPList)) return true; System.Net.IPAddress remoteIP = HttpContext.Connection.RemoteIpAddress; byte[] remoteIPBytes = remoteIP.GetAddressBytes(); string[] allowedIPs = _AppSettings.InboundApiAllowedIPList.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; } }