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 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 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 = _Repo.GetToolTypeByName(tooltype); if (toolType == null) { r.Errors.Add("Invalid tool type: " + tooltype); return BadRequest(r); } // get metadata List 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; } }