diff --git a/CHANGELOG.md b/CHANGELOG.md index 595ddaf..0ef084d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +### 1.1.0 - Unreleased + + - Initial support for proxied search query \#9 + ### 1.0.1 (2019-Feb-13) - fix returning metadata of packages with no dependencies \#8 diff --git a/README.md b/README.md index 52dc50f..0a6616d 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,6 @@ Not implemented: * Does not work with older paket versions * Cache package metadata and invalidates when upstream changes are detected using [NuGet.CatalogReader](https://github.com/emgarten/NuGet.CatalogReader). *This will be ported from older liget < 1.0.0.* * V2 search, filter and alike queries. *This is not planned.* - * search and autocomplete endpoints for cached packages. Basically, you need to query nuget.org to *search* for public packages. # Usage diff --git a/src/LiGet/Cache/CacheService.cs b/src/LiGet/Cache/CacheService.cs index dd30975..3733118 100644 --- a/src/LiGet/Cache/CacheService.cs +++ b/src/LiGet/Cache/CacheService.cs @@ -148,5 +148,10 @@ public Task FindAsync(PackageIdentity identity) //TODO: possibly cache and stream from in-memory cache return _sourceRepository.GetMetadataAsync(identity, CancellationToken.None); } + + public Task> SearchAsync(string searchTerm, SearchFilter filter, int skip, int take, CancellationToken ct) + { + return _sourceRepository.SearchAsync(searchTerm, filter, skip, take, ct); + } } } diff --git a/src/LiGet/Cache/FakeCacheService.cs b/src/LiGet/Cache/FakeCacheService.cs index 17a950a..dfb49f0 100644 --- a/src/LiGet/Cache/FakeCacheService.cs +++ b/src/LiGet/Cache/FakeCacheService.cs @@ -57,5 +57,10 @@ public Task CacheAsync( { return Task.CompletedTask; } + + public Task> SearchAsync(string searchTerm, SearchFilter filter, int skip, int take, CancellationToken ct) + { + throw new System.NotImplementedException(); + } } } diff --git a/src/LiGet/Cache/ICacheService.cs b/src/LiGet/Cache/ICacheService.cs index 71d8602..fcf713f 100644 --- a/src/LiGet/Cache/ICacheService.cs +++ b/src/LiGet/Cache/ICacheService.cs @@ -30,5 +30,7 @@ public interface ICacheService Task GetNuspecStreamAsync(PackageIdentity identity); Task GetReadmeStreamAsync(PackageIdentity identity); Task FindAsync(PackageIdentity identity); + + Task> SearchAsync(string searchTerm, SearchFilter filter, int skip, int take, CancellationToken ct); } } diff --git a/src/LiGet/CarterModules/CacheIndexModule.cs b/src/LiGet/CarterModules/CacheIndexModule.cs index a4b4de0..0aeb3c8 100644 --- a/src/LiGet/CarterModules/CacheIndexModule.cs +++ b/src/LiGet/CarterModules/CacheIndexModule.cs @@ -39,6 +39,7 @@ await res.AsJson(new Version = "3.0.0", Resources = ServiceWithAliases("PackagePublish", req.PackagePublish(prefix), "2.0.0") // api.nuget.org returns this too. + .Concat(ServiceWithAliases("SearchQueryService", req.PackageSearch(prefix), "", "3.0.0-beta", "3.0.0-rc")) // each version is an alias of others .Concat(ServiceWithAliases("RegistrationsBaseUrl", req.RegistrationsBase(prefix), "", "3.0.0-rc", "3.0.0-beta")) .Concat(ServiceWithAliases("PackageBaseAddress", req.PackageBase(prefix), "3.0.0")) .ToList() diff --git a/src/LiGet/CarterModules/CacheSearchModule.cs b/src/LiGet/CarterModules/CacheSearchModule.cs new file mode 100644 index 0000000..5172b03 --- /dev/null +++ b/src/LiGet/CarterModules/CacheSearchModule.cs @@ -0,0 +1,36 @@ +using System.Linq; +using System.Threading; +using Carter; +using Carter.ModelBinding; +using Carter.Request; +using Carter.Response; +using LiGet.Cache; +using LiGet.Configuration; +using LiGet.WebModels; +using NuGet.Protocol.Core.Types; + +namespace LiGet.CarterModules +{ + public class CacheSearchModule : CarterModule + { + const string prefix = "api/cache"; + + public CacheSearchModule(ICacheService cacheService) { + this.Get("/api/cache/v3/search", async (req, res, routeData) => { + var query = req.Query.As("q"); + int? skip = req.Query.As("skip"); + int? take = req.Query.As("take"); + bool? prerelease = req.Query.As("prerelease"); + string semVerLevel = req.Query.As("semVerLevel"); + query = query ?? string.Empty; + + var results = await cacheService.SearchAsync(query, new SearchFilter(prerelease ?? false), skip ?? 0, take ?? 0, CancellationToken.None); + await res.AsJson(new + { + TotalHits = results.Count(), + Data = results.Select(r => new SearchResultModel(r, r.GetVersionsAsync().Result, req, prefix)) + }); + }); + } + } +} \ No newline at end of file diff --git a/src/LiGet/ISourceRepository.cs b/src/LiGet/ISourceRepository.cs index b7a4dcb..fe925a7 100644 --- a/src/LiGet/ISourceRepository.cs +++ b/src/LiGet/ISourceRepository.cs @@ -13,6 +13,7 @@ public interface ISourceRepository Task> GetMetadataAsync(string packageId, bool includePrerelease, bool includeUnlisted, CancellationToken ct); Task> GetAllVersionsAsync(string id, CancellationToken ct); Task GetPackageUriAsync(string id, string version, CancellationToken cancellationToken); - Task GetMetadataAsync(PackageIdentity identity, CancellationToken none); + Task GetMetadataAsync(PackageIdentity identity, CancellationToken ct); + Task> SearchAsync(string searchTerm, SearchFilter filter, int skip, int take, CancellationToken ct); } } \ No newline at end of file diff --git a/src/LiGet/NuGetClient.cs b/src/LiGet/NuGetClient.cs index c5c524f..00eacde 100644 --- a/src/LiGet/NuGetClient.cs +++ b/src/LiGet/NuGetClient.cs @@ -40,6 +40,7 @@ private class NuRepository : ISourceRepository private RegistrationResourceV3 _regResource; private PackageMetadataResourceV3 _metadataSearch; private RemoteV3FindPackageByIdResource _versionSearch; + private PackageSearchResourceV3 _search; private NuGetLoggerAdapter _loggerAdapter; public NuRepository(List> providers, Uri repositoryUrl, Microsoft.Extensions.Logging.ILogger logger) @@ -55,6 +56,8 @@ public NuRepository(List> providers, Uri repository ReportAbuseResourceV3 reportAbuseResource = _sourceRepository.GetResource(); _metadataSearch = new PackageMetadataResourceV3(httpSource.HttpSource, _regResource, reportAbuseResource); _versionSearch = new RemoteV3FindPackageByIdResource(_sourceRepository, httpSource.HttpSource); + RawSearchResourceV3 rawSearchResource = _sourceRepository.GetResource(); + _search = new PackageSearchResourceV3(rawSearchResource, _metadataSearch); this._loggerAdapter = new NuGetLoggerAdapter(logger); } @@ -100,6 +103,11 @@ public Task GetMetadataAsync(PackageIdentity identity, C { return _metadataSearch.GetMetadataAsync(identity, _cacheContext, _loggerAdapter, ct); } + + public Task> SearchAsync(string searchTerm, SearchFilter filter, int skip, int take, CancellationToken ct) + { + return _search.SearchAsync(searchTerm, filter, skip, take, _loggerAdapter, ct); + } } } } \ No newline at end of file diff --git a/src/LiGet/Services/ISearchService.cs b/src/LiGet/Services/ISearchService.cs index 2f2df81..3e093df 100644 --- a/src/LiGet/Services/ISearchService.cs +++ b/src/LiGet/Services/ISearchService.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using LiGet.Entities; +using NuGet.Protocol.Core.Types; using NuGet.Versioning; namespace LiGet.Services @@ -17,6 +18,31 @@ public interface ISearchService public class SearchResult { + public SearchResult() + { + } + + public SearchResult(IPackageSearchMetadata result, IEnumerable versions) + { + this.Id = result.Identity.Id; + this.Version = result.Identity.Version; + this.Description = result.Description; + this.Authors = result.Authors; + this.IconUrl = result.IconUrl?.AbsoluteUri; + this.LicenseUrl = result.LicenseUrl?.AbsoluteUri; + this.ProjectUrl = result.ProjectUrl?.AbsoluteUri; + this.Summary = result.Summary; + if(result.Tags != null) + this.Tags = result.Tags.Split(','); + this.Title = result.Title; + this.TotalDownloads = result.DownloadCount ?? 0; + var list = new List(); + foreach(var v in versions) { + list.Add(new SearchResultVersion(v)); + } + this.Versions = list; + } + public string Id { get; set; } public NuGetVersion Version { get; set; } @@ -36,6 +62,13 @@ public class SearchResult public class SearchResultVersion { + + public SearchResultVersion(VersionInfo v) + { + this.Version = v.Version; + this.Downloads = v.DownloadCount ?? 0; + } + public SearchResultVersion(NuGetVersion version, long downloads) { Version = version ?? throw new ArgumentNullException(nameof(version)); diff --git a/src/LiGet/WebModels/SearchResultModel.cs b/src/LiGet/WebModels/SearchResultModel.cs index e661cb3..dc7c395 100644 --- a/src/LiGet/WebModels/SearchResultModel.cs +++ b/src/LiGet/WebModels/SearchResultModel.cs @@ -4,6 +4,7 @@ using LiGet.Services; using LiGet.Extensions; using Microsoft.AspNetCore.Http; +using NuGet.Protocol.Core.Types; namespace LiGet.WebModels { @@ -27,6 +28,18 @@ public SearchResultModel(SearchResult result, HttpRequest url, string prefix) Versions = versions.ToList().AsReadOnly(); } + public SearchResultModel(IPackageSearchMetadata result, IEnumerable versions, HttpRequest url, string prefix) + { + _result = new SearchResult(result, versions); + _url = url ?? throw new ArgumentNullException(nameof(url)); + this._prefix = prefix; + Versions = _result.Versions.Select( + v => new SearchResultVersionModel( + url.PackageRegistration(_result.Id, v.Version.ToNormalizedString()), + v.Version.ToNormalizedString(), + v.Downloads)).ToList(); + } + public string Id => _result.Id; public string Version => _result.Version.ToNormalizedString(); public string Description => _result.Description; diff --git a/tests/LiGet.Tests/NuGetClientIntegrationTest.cs b/tests/LiGet.Tests/NuGetClientIntegrationTest.cs index 5f2d0e4..9b00c08 100644 --- a/tests/LiGet.Tests/NuGetClientIntegrationTest.cs +++ b/tests/LiGet.Tests/NuGetClientIntegrationTest.cs @@ -141,7 +141,7 @@ public async Task V2IndexResourceReturnsODataServiceDocument(string indexEndpoin } [Theory] - [MemberData(nameof(V3CasesExcludingCache))] + [MemberData(nameof(V3Cases))] public async Task IndexIncludesAtLeastOneSearchQueryEntry(string indexEndpoint) { InitializeClient(indexEndpoint); @@ -335,6 +335,19 @@ await packageResource.Push(TestResources.GetNupkgBagetTest1(), Assert.Equal(new PackageIdentity("liget-test1", NuGetVersion.Parse("1.0.0")), one.Identity); } + [Fact] + [Trait("Category", "integration")] // because it queries nuget.org + public async Task CacheSearchPackage() + { + InitializeClient(CacheIndex); + var packageResource = await _sourceRepository.GetResourceAsync(); + PackageSearchResourceV3 search = GetSearch(); + var found = await search.SearchAsync("log4net", new SearchFilter(true), 0, 10, logger, CancellationToken.None); + Assert.NotEmpty(found); + var one = found.First(); + Assert.Equal("log4net", one.Identity.Id); + } + private PackageSearchResourceV3 GetSearch() { PackageMetadataResourceV3 packageMetadataRes = GetPackageMetadataResource();