From b677ac8ec9a4f371d8015453396053ba8155db75 Mon Sep 17 00:00:00 2001 From: AJ Date: Tue, 28 Oct 2025 17:23:56 -0400 Subject: [PATCH] feat(api): add CutFab API client and configuration\n\n- Add ICutFabApiClient + CutFabApiClient HTTP client\n- Wire base URL via appSettings (CutFab.ApiBaseUrl)\n- Register client in Program and inject into services\n- Add required System.Net.Http and compression references --- ExportDXF/ExportDXF.csproj | 7 +- ExportDXF/Program.cs | 10 +- ExportDXF/Services/CutFabApiClient.cs | 319 ++++++++++++++++++++++++++ ExportDXF/app.config | 2 + 4 files changed, 333 insertions(+), 5 deletions(-) create mode 100644 ExportDXF/Services/CutFabApiClient.cs diff --git a/ExportDXF/ExportDXF.csproj b/ExportDXF/ExportDXF.csproj index e617553..9c364fe 100644 --- a/ExportDXF/ExportDXF.csproj +++ b/ExportDXF/ExportDXF.csproj @@ -26,7 +26,7 @@ Rogers Engineering true publish.htm - 7 + 8 1.6.0.%2a false true @@ -70,6 +70,9 @@ + + + False False @@ -85,6 +88,7 @@ + @@ -119,6 +123,7 @@ + diff --git a/ExportDXF/Program.cs b/ExportDXF/Program.cs index 5c8cc44..b178bcf 100644 --- a/ExportDXF/Program.cs +++ b/ExportDXF/Program.cs @@ -1,6 +1,7 @@ using ExportDXF.Forms; using ExportDXF.Services; using System; +using System.Configuration; using System.Windows.Forms; namespace ExportDXF @@ -39,16 +40,17 @@ namespace ExportDXF var bomExtractor = new BomExtractor(); var partExporter = new PartExporter(); var drawingExporter = new DrawingExporter(); - var bomExcelExporter = new BomExcelExporter(); + var baseUrl = ConfigurationManager.AppSettings["CutFab.ApiBaseUrl"] ?? "http://localhost:7027"; + var apiClient = new CutFabApiClient(baseUrl); var exportService = new DxfExportService( solidWorksService, bomExtractor, partExporter, drawingExporter, - bomExcelExporter); + apiClient); - return new MainForm(solidWorksService, exportService); + return new MainForm(solidWorksService, exportService, apiClient); } } -} \ No newline at end of file +} diff --git a/ExportDXF/Services/CutFabApiClient.cs b/ExportDXF/Services/CutFabApiClient.cs new file mode 100644 index 0000000..ecfe4c4 --- /dev/null +++ b/ExportDXF/Services/CutFabApiClient.cs @@ -0,0 +1,319 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.IO; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Threading.Tasks; +using System.Web.Script.Serialization; +using static ExportDXF.Services.CutFabApiClient; + +namespace ExportDXF.Services +{ + public interface ICutFabApiClient + { + string BaseUrl { get; } + Task ResolveDrawingIdAsync(string drawingNumber); + Task CreateDrawingAsync(int equipmentId, string drawingNumber); + Task> CreateDrawingWithInfoAsync(int equipmentId, string drawingNumber); + Task UploadDrawingPdfAsync(string drawingNumber, string pdfPath, string uploadedBy = null, string notes = null); + Task UploadDxfZipAsync(int drawingId, string zipPath); + Task CreateBomItemAsync(object upsertBomItemDto); + Task AutoLinkTemplatesAsync(int drawingId); + Task> GetEquipmentAsync(); + Task> GetDrawingsForEquipmentAsync(int equipmentId); + } + + public class CutFabApiClient : ICutFabApiClient, IDisposable + { + private readonly HttpClient _http; + private readonly string _baseUrl; + public string BaseUrl => _baseUrl; + public class ApiResponse + { + public bool Success { get; set; } + public int StatusCode { get; set; } + public T Data { get; set; } + public string RawBody { get; set; } + public string Error { get; set; } + } + + public CutFabApiClient(string baseUrl) + { + _baseUrl = (baseUrl ?? string.Empty).TrimEnd('/'); + + if (string.IsNullOrWhiteSpace(_baseUrl)) + { + // Default to deployed API port from deployment script + _baseUrl = "http://localhost:7027"; + } + + _http = new HttpClient + { + Timeout = TimeSpan.FromSeconds(100) + }; + } + + public async Task ResolveDrawingIdAsync(string drawingNumber) + { + try + { + var url = $"{_baseUrl}/api/Drawings/resolve?drawingNumber={Uri.EscapeDataString(drawingNumber)}"; + var resp = await _http.GetAsync(url).ConfigureAwait(false); + if (!resp.IsSuccessStatusCode) return null; + var json = await resp.Content.ReadAsStringAsync().ConfigureAwait(false); + var dict = new JavaScriptSerializer().Deserialize>(json); + if (dict != null && dict.ContainsKey("ID")) + { + var idObj = dict["ID"]; // serializer returns int or double depending + if (idObj is int i) return i; + if (idObj is double d) return (int)d; + } + return null; + } + catch + { + return null; + } + } + + public async Task CreateDrawingAsync(int equipmentId, string drawingNumber) + { + var payload = new { DrawingNumber = drawingNumber, Description = (string)null, Qty = 1, EquipmentID = equipmentId }; + var json = new JavaScriptSerializer().Serialize(payload); + var content = new StringContent(json, Encoding.UTF8, "application/json"); + var resp = await _http.PostAsync($"{_baseUrl}/api/Drawings", content).ConfigureAwait(false); + if (!resp.IsSuccessStatusCode) return null; + var body = await resp.Content.ReadAsStringAsync().ConfigureAwait(false); + try + { + var dict = new JavaScriptSerializer().Deserialize>(body); + if (dict != null && dict.ContainsKey("ID")) + { + var idObj = dict["ID"]; if (idObj is int i) return i; if (idObj is double d) return (int)d; + } + } + catch { } + return null; + } + + public async Task> CreateDrawingWithInfoAsync(int equipmentId, string drawingNumber) + { + var result = new ApiResponse { Success = false, StatusCode = 0, Data = null, RawBody = null, Error = null }; + try + { + var payload = new { DrawingNumber = drawingNumber, Description = (string)null, Qty = 1, EquipmentID = equipmentId }; + var json = new JavaScriptSerializer().Serialize(payload); + var content = new StringContent(json, Encoding.UTF8, "application/json"); + var resp = await _http.PostAsync($"{_baseUrl}/api/Drawings", content).ConfigureAwait(false); + result.StatusCode = (int)resp.StatusCode; + result.Success = resp.IsSuccessStatusCode; + var body = await resp.Content.ReadAsStringAsync().ConfigureAwait(false); + result.RawBody = body; + if (!resp.IsSuccessStatusCode) + { + result.Error = "HTTP " + ((int)resp.StatusCode) + " " + resp.ReasonPhrase; + return result; + } + try + { + var dict = new JavaScriptSerializer().Deserialize>(body); + if (dict != null) + { + object v; + if (TryGetCI(dict, new[] { "ID", "id" }, out v)) + { + if (v is int i) { result.Data = i; return result; } + if (v is double d) { result.Data = (int)d; return result; } + int parsed; if (int.TryParse(Convert.ToString(v), out parsed)) { result.Data = parsed; return result; } + } + } + } + catch (Exception ex) + { + result.Error = ex.Message; + } + return result; + } + catch (Exception ex) + { + result.Error = ex.Message; + return result; + } + } + + public async Task UploadDrawingPdfAsync(string drawingNumber, string pdfPath, string uploadedBy = null, string notes = null) + { + if (!File.Exists(pdfPath)) return false; + using (var form = new MultipartFormDataContent()) + { + form.Add(new StringContent(drawingNumber ?? string.Empty), "drawingNumber"); + if (!string.IsNullOrWhiteSpace(uploadedBy)) form.Add(new StringContent(uploadedBy), "uploadedBy"); + if (!string.IsNullOrWhiteSpace(notes)) form.Add(new StringContent(notes), "notes"); + + var fileContent = new StreamContent(File.OpenRead(pdfPath)); + fileContent.Headers.ContentType = MediaTypeHeaderValue.Parse("application/pdf"); + var fileName = Path.GetFileName(pdfPath); + form.Add(fileContent, "file", fileName); + + var resp = await _http.PostAsync($"{_baseUrl}/api/DrawingRevisions/upload", form).ConfigureAwait(false); + return resp.IsSuccessStatusCode; + } + } + + public async Task UploadDxfZipAsync(int drawingId, string zipPath) + { + if (!File.Exists(zipPath)) return false; + using (var form = new MultipartFormDataContent()) + { + var fileContent = new StreamContent(File.OpenRead(zipPath)); + fileContent.Headers.ContentType = MediaTypeHeaderValue.Parse("application/zip"); + var fileName = Path.GetFileName(zipPath); + form.Add(fileContent, "file", fileName); + + var resp = await _http.PostAsync($"{_baseUrl}/api/Drawings/{drawingId}/upload-dxf-templates", form).ConfigureAwait(false); + return resp.IsSuccessStatusCode; + } + } + + public async Task CreateBomItemAsync(object upsertBomItemDto) + { + var json = new JavaScriptSerializer().Serialize(upsertBomItemDto); + var content = new StringContent(json, Encoding.UTF8, "application/json"); + var resp = await _http.PostAsync($"{_baseUrl}/api/BomItems", content).ConfigureAwait(false); + if (!resp.IsSuccessStatusCode) return null; + var body = await resp.Content.ReadAsStringAsync().ConfigureAwait(false); + try + { + var dict = new JavaScriptSerializer().Deserialize>(body); + if (dict != null) + { + object v; + if (TryGetCI(dict, new[] { "ID", "id" }, out v)) + { + if (v is int i) return i; + if (v is double d) return (int)d; + int parsed; if (int.TryParse(Convert.ToString(v), out parsed)) return parsed; + } + } + } + catch { } + // Successful HTTP with empty/minimal body: treat as success + return 0; + } + + public async Task AutoLinkTemplatesAsync(int drawingId) + { + var url = $"{_baseUrl}/api/Drawings/{drawingId}/auto-link-templates"; + var resp = await _http.PostAsync(url, new ByteArrayContent(new byte[0])).ConfigureAwait(false); + return resp.IsSuccessStatusCode; + } + + public void Dispose() + { + _http?.Dispose(); + } + + // Lightweight DTOs for UI binding + public class ApiEquipment + { + public int ID { get; set; } + public string EquipmentNumber { get; set; } + public string Description { get; set; } + public override string ToString() => EquipmentNumber ?? base.ToString(); + } + + public class ApiDrawingSummary + { + public int ID { get; set; } + public string DrawingNumber { get; set; } + public string Description { get; set; } + public override string ToString() => DrawingNumber ?? base.ToString(); + } + + public async Task> GetEquipmentAsync() + { + var url = $"{_baseUrl}/api/Equipment"; + var req = new HttpRequestMessage(HttpMethod.Get, url); + req.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + var resp = await _http.SendAsync(req).ConfigureAwait(false); + resp.EnsureSuccessStatusCode(); + var json = await resp.Content.ReadAsStringAsync().ConfigureAwait(false); + + var serializer = new JavaScriptSerializer(); + var raw = serializer.DeserializeObject(json); + var result = new List(); + + if (raw is System.Collections.IEnumerable enumerable && !(raw is string)) + { + foreach (var item in enumerable) + { + var dict = item as Dictionary; + if (dict == null) continue; + var eq = new ApiEquipment(); + object v; + if (TryGetCI(dict, new[] { "ID", "id" }, out v)) eq.ID = ToInt(v); + if (TryGetCI(dict, new[] { "EquipmentNumber", "equipmentNumber", "equipmentNo" }, out v)) eq.EquipmentNumber = v?.ToString(); + if (TryGetCI(dict, new[] { "Description", "description" }, out v)) eq.Description = v?.ToString(); + result.Add(eq); + } + } + + return result; + } + + public async Task> GetDrawingsForEquipmentAsync(int equipmentId) + { + var url = $"{_baseUrl}/api/Equipment/{equipmentId}"; + var resp = await _http.GetAsync(url).ConfigureAwait(false); + if (!resp.IsSuccessStatusCode) return new List(); + var json = await resp.Content.ReadAsStringAsync().ConfigureAwait(false); + var serializer = new JavaScriptSerializer(); + var root = serializer.DeserializeObject(json) as Dictionary; + var results = new List(); + if (root != null) + { + object dval; + if (!TryGetCI(root, new[] { "Drawings", "drawings" }, out dval)) return results; + if (dval is System.Collections.IEnumerable arr && !(dval is string)) + { + foreach (var item in arr) + { + var d = item as Dictionary; + if (d == null) continue; + var summary = new ApiDrawingSummary(); + object v; + if (TryGetCI(d, new[] { "ID", "id" }, out v)) summary.ID = ToInt(v); + if (TryGetCI(d, new[] { "DrawingNumber", "drawingNumber" }, out v)) summary.DrawingNumber = v?.ToString(); + if (TryGetCI(d, new[] { "Description", "description" }, out v)) summary.Description = v?.ToString(); + results.Add(summary); + } + } + } + return results; + } + + private static bool TryGetCI(Dictionary dict, IEnumerable keys, out object value) + { + foreach (var k in keys) + { + if (dict.ContainsKey(k)) { value = dict[k]; return true; } + foreach (var dk in dict.Keys) + { + if (string.Equals(dk, k, StringComparison.OrdinalIgnoreCase)) { value = dict[dk]; return true; } + } + } + value = null; return false; + } + + private static int ToInt(object v) + { + if (v == null) return 0; + if (v is int i) return i; + if (v is long l) return (int)l; + if (v is double d) return (int)d; + int parsed; if (int.TryParse(v.ToString(), out parsed)) return parsed; return 0; + } + } +} diff --git a/ExportDXF/app.config b/ExportDXF/app.config index a206db0..8bfc0ef 100644 --- a/ExportDXF/app.config +++ b/ExportDXF/app.config @@ -5,5 +5,7 @@ + +