From e9789a99f8cfb120c61c132d13c355c55841bf29 Mon Sep 17 00:00:00 2001 From: Matty Jorgensen Date: Wed, 9 Aug 2023 22:11:31 -0500 Subject: [PATCH] Update existing links if they exist in LinkAce --- .../Extensions/StringExtensions.cs | 7 ++++ .../Json/SnakeCaseContractResolver.cs | 13 ++++++ .../LinkAce/LinkAceBookmarkingService.cs | 40 ++++++++++++++++--- .../Bookmarking/LinkAce/LinkAceEntites.cs | 32 +++++++++++++++ 4 files changed, 87 insertions(+), 5 deletions(-) create mode 100644 src/BookmarkSync.Core/Extensions/StringExtensions.cs create mode 100644 src/BookmarkSync.Core/Json/SnakeCaseContractResolver.cs create mode 100644 src/BookmarkSync.Infrastructure/Services/Bookmarking/LinkAce/LinkAceEntites.cs diff --git a/src/BookmarkSync.Core/Extensions/StringExtensions.cs b/src/BookmarkSync.Core/Extensions/StringExtensions.cs new file mode 100644 index 0000000..bd42767 --- /dev/null +++ b/src/BookmarkSync.Core/Extensions/StringExtensions.cs @@ -0,0 +1,7 @@ +namespace BookmarkSync.Core.Extensions; + +public static class StringExtensions +{ + public static string ToSnakeCase(this string str) + => string.Concat(str.Select((x, i) => i > 0 && char.IsUpper(x) ? "_" + x : x.ToString())).ToLower(); +} diff --git a/src/BookmarkSync.Core/Json/SnakeCaseContractResolver.cs b/src/BookmarkSync.Core/Json/SnakeCaseContractResolver.cs new file mode 100644 index 0000000..8b1d00a --- /dev/null +++ b/src/BookmarkSync.Core/Json/SnakeCaseContractResolver.cs @@ -0,0 +1,13 @@ +using BookmarkSync.Core.Extensions; +using Newtonsoft.Json.Serialization; + +namespace BookmarkSync.Core.Json; + +public class SnakeCaseContractResolver : DefaultContractResolver +{ + public static readonly SnakeCaseContractResolver Instance = new(); + override protected string ResolvePropertyName(string propertyName) + { + return propertyName.ToSnakeCase(); + } +} diff --git a/src/BookmarkSync.Infrastructure/Services/Bookmarking/LinkAce/LinkAceBookmarkingService.cs b/src/BookmarkSync.Infrastructure/Services/Bookmarking/LinkAce/LinkAceBookmarkingService.cs index 17c01ae..b6830db 100644 --- a/src/BookmarkSync.Infrastructure/Services/Bookmarking/LinkAce/LinkAceBookmarkingService.cs +++ b/src/BookmarkSync.Infrastructure/Services/Bookmarking/LinkAce/LinkAceBookmarkingService.cs @@ -1,6 +1,8 @@ using System.Net.Http.Headers; using System.Net.Mime; using System.Text; +using System.Web; +using BookmarkSync.Core.Json; using Newtonsoft.Json; namespace BookmarkSync.Infrastructure.Services.Bookmarking.LinkAce; @@ -8,15 +10,16 @@ namespace BookmarkSync.Infrastructure.Services.Bookmarking.LinkAce; public class LinkAceBookmarkingService : BookmarkingService, IBookmarkingService { private static readonly ILogger _logger = Log.ForContext(); + private readonly string _linkAceUri; public LinkAceBookmarkingService(IConfigManager configManager) { ApiToken = configManager.App.Bookmarking.ApiToken ?? throw new InvalidOperationException("Missing API token"); - string linkAceUri = configManager.GetConfigValue("App:Bookmarking:LinkAceUri") ?? - throw new InvalidOperationException("Missing LinkAce Uri"); - ApiUri = $"{linkAceUri}/api/v1/links"; + _linkAceUri = configManager.GetConfigValue("App:Bookmarking:LinkAceUri") ?? + throw new InvalidOperationException("Missing LinkAce Uri"); + ApiUri = $"{_linkAceUri}/api/v1/links"; Client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", ApiToken); } - /// + /// public async Task Save(Bookmark bookmark) { // Prep payload @@ -40,7 +43,34 @@ public async Task Save(Bookmark bookmark) }; var stringContent = new StringContent(JsonConvert.SerializeObject(payload), Encoding.UTF8, MediaTypeNames.Application.Json); - var response = await Client.PostAsync(ApiUri, stringContent); + + // Check for existing bookmarks with the same URL. + var uriBuilder = new UriBuilder($"{_linkAceUri}/api/v1/search/links"); + var query = HttpUtility.ParseQueryString(string.Empty); + query["query"] = bookmark.Uri; + uriBuilder.Query = query.ToString(); + var linksResponse = await Client.GetAsync(uriBuilder.ToString()); + linksResponse.EnsureSuccessStatusCode(); + string responseContent = await linksResponse.Content.ReadAsStringAsync(); + var responseObj = JsonConvert.DeserializeObject(responseContent, + new JsonSerializerSettings + { + ContractResolver = SnakeCaseContractResolver.Instance + }); + + HttpResponseMessage? response; + var existingLink = responseObj?.Data?.Where(b => b.Url == bookmark.Uri).FirstOrDefault(); + if (existingLink != null) + { + // Bookmark exists in LinkAce, try to update. + _logger.Information("Bookmark {Uri} exists in LinkAce, updating...", bookmark.Uri); + response = await Client.PatchAsync($"{ApiUri}/{existingLink.Id}", stringContent); + response.EnsureSuccessStatusCode(); + _logger.Debug("Response status: {StatusCode}", response.StatusCode); + return response; + } + + response = await Client.PostAsync(ApiUri, stringContent); response.EnsureSuccessStatusCode(); _logger.Debug("Response status: {StatusCode}", response.StatusCode); return response; diff --git a/src/BookmarkSync.Infrastructure/Services/Bookmarking/LinkAce/LinkAceEntites.cs b/src/BookmarkSync.Infrastructure/Services/Bookmarking/LinkAce/LinkAceEntites.cs new file mode 100644 index 0000000..8b88a02 --- /dev/null +++ b/src/BookmarkSync.Infrastructure/Services/Bookmarking/LinkAce/LinkAceEntites.cs @@ -0,0 +1,32 @@ +namespace BookmarkSync.Infrastructure.Services.Bookmarking.LinkAce; + +public class LinkAceApiSearchResponse +{ + public int CurrentPage { get; set; } + public List? Data { get; set; } + public string? FirstPageUrl { get; set; } + public int? From { get; set; } + public int LastPage { get; set; } + public string? LastPageUrl { get; set; } + public string? NextPageUrl { get; set; } + public string Path { get; set; } + public string PerPage { get; set; } + public string? PreviousPageUrl { get; set; } + public int? To { get; set; } + public int Total { get; set; } +} +public class LinkAceBookmark +{ + public bool CheckDisabled { get; set; } + public DateTime CreatedAt { get; set; } + public DateTime? DeletedAt { get; set; } + public string? Description { get; set; } + public string Icon { get; set; } + public int Id { get; set; } + public bool IsPrivate { get; set; } + public int Status { get; set; } + public string Title { get; set; } + public DateTime UpdatedAt { get; set; } + public string Url { get; set; } + public int UserId { get; set; } +}