From 1ff9d3d78b04743c5bf74f621a85d41adf66b966 Mon Sep 17 00:00:00 2001 From: Peter Foster Date: Tue, 14 Apr 2026 11:45:15 +0100 Subject: [PATCH] Add eBay credentials, edit listings feature, fix category service token --- .../Services/EbayCategoryService.cs | 20 +- .../Services/SavedListingsService.cs | 50 +++ EbayListingTool/Views/SavedListingsView.xaml | 72 +++++ .../Views/SavedListingsView.xaml.cs | 296 ++++++++++++++++++ EbayListingTool/appsettings.json | 6 +- 5 files changed, 434 insertions(+), 10 deletions(-) diff --git a/EbayListingTool/Services/EbayCategoryService.cs b/EbayListingTool/Services/EbayCategoryService.cs index 18ebcdb..80114a1 100644 --- a/EbayListingTool/Services/EbayCategoryService.cs +++ b/EbayListingTool/Services/EbayCategoryService.cs @@ -14,6 +14,9 @@ public class EbayCategoryService { private readonly EbayAuthService _auth; + // Static client — avoids socket exhaustion from per-call `new HttpClient()` + private static readonly HttpClient _http = new(); + public EbayCategoryService(EbayAuthService auth) { _auth = auth; @@ -26,15 +29,18 @@ public class EbayCategoryService try { - var token = await _auth.GetValidAccessTokenAsync(); - using var http = new HttpClient(); - http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); - http.DefaultRequestHeaders.Add("X-EBAY-C-MARKETPLACE-ID", "EBAY_GB"); + // Taxonomy API supports app-level tokens — no user login required + var token = await _auth.GetAppTokenAsync(); + using var request = new HttpRequestMessage(HttpMethod.Get, + $"{_auth.BaseUrl}/commerce/taxonomy/v1/category_tree/3/get_category_suggestions" + + $"?q={Uri.EscapeDataString(query)}"); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); + request.Headers.Add("X-EBAY-C-MARKETPLACE-ID", "EBAY_GB"); - var url = $"{_auth.BaseUrl}/commerce/taxonomy/v1/category_tree/3/get_category_suggestions" + - $"?q={Uri.EscapeDataString(query)}"; + var response = await _http.SendAsync(request); + var json = await response.Content.ReadAsStringAsync(); - var json = await http.GetStringAsync(url); + if (!response.IsSuccessStatusCode) return new List(); var obj = JObject.Parse(json); var results = new List(); diff --git a/EbayListingTool/Services/SavedListingsService.cs b/EbayListingTool/Services/SavedListingsService.cs index ff3605c..dedb37f 100644 --- a/EbayListingTool/Services/SavedListingsService.cs +++ b/EbayListingTool/Services/SavedListingsService.cs @@ -91,6 +91,56 @@ public class SavedListingsService catch { /* ignore — user may have already deleted it */ } } + /// + /// Updates an existing listing's metadata and regenerates its text export file. + /// The listing must be the same object reference held in Listings. + /// + public void Update(SavedListing listing) + { + if (Directory.Exists(listing.ExportFolder)) + { + // Replace the text export — remove old .txt files first + foreach (var old in Directory.GetFiles(listing.ExportFolder, "*.txt")) + { + try { File.Delete(old); } catch { } + } + var safeName = MakeSafeFilename(listing.Title); + var textFile = Path.Combine(listing.ExportFolder, $"{safeName}.txt"); + File.WriteAllText(textFile, BuildTextExport( + listing.Title, listing.Description, listing.Price, + listing.Category, listing.ConditionNotes)); + } + Persist(); // E1: propagates on failure so caller can show error + } + + /// + /// Copies a source photo into the listing's export folder and returns the destination path. + /// currentCount is the number of photos already in the edit session (used for naming). + /// + public string CopyPhotoToExportFolder(SavedListing listing, string sourcePath, int currentCount) + { + if (!Directory.Exists(listing.ExportFolder)) + throw new DirectoryNotFoundException($"Export folder not found: {listing.ExportFolder}"); + + var safeName = MakeSafeFilename(listing.Title); + var ext = Path.GetExtension(sourcePath); + + var dest = currentCount == 0 + ? Path.Combine(listing.ExportFolder, $"{safeName}{ext}") + : Path.Combine(listing.ExportFolder, $"{safeName}_{currentCount + 1}{ext}"); + + // Ensure unique filename if a file with that name already exists + int attempt = 2; + while (File.Exists(dest)) + { + dest = Path.Combine(listing.ExportFolder, $"{safeName}_{currentCount + attempt}{ext}"); + attempt++; + } + + File.Copy(sourcePath, dest); + return dest; + } + // S3: use ProcessStartInfo with FileName so spaces/special chars are handled correctly public void OpenExportFolder(SavedListing listing) { diff --git a/EbayListingTool/Views/SavedListingsView.xaml b/EbayListingTool/Views/SavedListingsView.xaml index a751a15..4827352 100644 --- a/EbayListingTool/Views/SavedListingsView.xaml +++ b/EbayListingTool/Views/SavedListingsView.xaml @@ -262,6 +262,15 @@ +