diff --git a/.gitignore b/.gitignore index 68dafe9..895ed0c 100644 --- a/.gitignore +++ b/.gitignore @@ -6,14 +6,8 @@ /src/isn/.vscode/ /test/isnd.tests/obj/ /test/isnd.tests/bin/ -/packages/ -bower_components/ /test/isn.tests/bin /test/isn.tests/obj/ -appsettings.Testing.json -appsettings.Development.json -/wwwroot/.sass-cache/ -/src/isnd/wwwroot/.sass-cache/ /src/isn.abst/bin /src/isn.abst/obj /src/isnd/packages/ @@ -23,4 +17,5 @@ appsettings.Development.json /artifacts/ /.vs/ /.vscode/ +appsettings.Development.json diff --git a/.vscode/tasks.json b/.vscode/tasks.json index cc5e654..eb08d94 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -90,20 +90,13 @@ ], "problemMatcher": "$msCompile" }, - { - "label": "copyTestConfig", - "command": "dotnet", - "type": "process", - "args": [ "build", "/t:CopyTestConfig" ], - "group": "test" - }, { "label": "test", "command": "dotnet", "type": "process", "options": { "env": { - "ASPNETCORE_ENVIRONMENT": "Testing" + "ASPNETCORE_ENVIRONMENT": "Development" } }, "args": [ @@ -112,7 +105,7 @@ "--logger:xunit" ], "problemMatcher": "$msCompile", - "dependsOn": [ "build", "copyTestConfig"], + "dependsOn": [ "build"], "group": "test" }, { diff --git a/omnisharp.json b/omnisharp.json index 5df81ef..2c3209f 100644 --- a/omnisharp.json +++ b/omnisharp.json @@ -1,9 +1,9 @@ { "dotnet": { - "enabled": false + "enabled": true }, "msbuild": { - "enabled": true + "enabled": false }, "Dnx": { "enabled": false diff --git a/src/isn.abst/HappyIdOwner.cs b/src/isn.abst/Permalink.cs similarity index 95% rename from src/isn.abst/HappyIdOwner.cs rename to src/isn.abst/Permalink.cs index 1a1ed00..56a6ee8 100644 --- a/src/isn.abst/HappyIdOwner.cs +++ b/src/isn.abst/Permalink.cs @@ -22,7 +22,7 @@ namespace isnd.Data.Catalog [JsonProperty("@id")] - public string Id { get => GetId(); } + public string Id { get => id; } private readonly string id; public string GetId() { return id; } diff --git a/src/isnd/Controllers/ApiKeysController.cs b/src/isnd/Controllers/ApiKeysController.cs index d2d8ea9..ce55a2f 100644 --- a/src/isnd/Controllers/ApiKeysController.cs +++ b/src/isnd/Controllers/ApiKeysController.cs @@ -106,7 +106,7 @@ namespace isnd.Controllers ApiKey key = await dbContext.ApiKeys.FirstOrDefaultAsync(k => k.Id == id && k.UserId == userid); if (key == null) { - ModelState.AddModelError(null, "Key not found"); + ModelState.AddModelError("id", "Key not found"); return View(); } return View("Details", new DetailModel { ApiKey = key, Name = key.Name, ProtectedValue = protector.Protect(key.Id)}); diff --git a/src/isnd/Controllers/Packages/PackagesController.GetPackage.cs b/src/isnd/Controllers/Packages/PackagesController.GetPackage.cs index 2c67ceb..e929239 100644 --- a/src/isnd/Controllers/Packages/PackagesController.GetPackage.cs +++ b/src/isnd/Controllers/Packages/PackagesController.GetPackage.cs @@ -7,6 +7,7 @@ using isn.abst; namespace isnd.Controllers { + // TODO /search GET {@id}?q={QUERY}&skip={SKIP}&take={TAKE}&prerelease={PRERELEASE}&semVerLevel={SEMVERLEVEL}&packageType={PACKAGETYPE} public partial class PackagesController { diff --git a/src/isnd/Controllers/Packages/PackagesController.Search.cs b/src/isnd/Controllers/Packages/PackagesController.Search.cs new file mode 100644 index 0000000..30d275c --- /dev/null +++ b/src/isnd/Controllers/Packages/PackagesController.Search.cs @@ -0,0 +1,36 @@ +using Microsoft.AspNetCore.Mvc; +using isnd.Entities; +using isn.abst; +using System.Threading.Tasks; +using isnd.Data.Catalog; + +namespace isnd.Controllers +{ + // TODO /search GET {@id}?q={QUERY}&skip={SKIP}&take={TAKE}&prerelease={PRERELEASE}&semVerLevel={SEMVERLEVEL}&packageType={PACKAGETYPE} + + public partial class PackagesController + { + // Web get the paquet + [HttpGet("~" + Constants.ApiVersionPrefix + ApiConfig.Search)] + [HttpHead("~" + Constants.ApiVersionPrefix + ApiConfig.Search)] + public async Task Search( + string q=null, + int skip=0, + int take=25, + bool prerelease=false, + string semVerLevel = "2.0.0", + string packageType = null) + { + PackageRegistrationQuery query = new PackageRegistrationQuery + { + Prerelease= prerelease, + Query = q, + Skip = skip, + Take = take + }; + var result = await packageManager.SearchPackageAsync(query); + + return Ok(result); + } + } +} \ No newline at end of file diff --git a/src/isnd/Controllers/Packages/PackagesController.WebViews.cs b/src/isnd/Controllers/Packages/PackagesController.WebViews.cs index bd77a9e..2a658f7 100644 --- a/src/isnd/Controllers/Packages/PackagesController.WebViews.cs +++ b/src/isnd/Controllers/Packages/PackagesController.WebViews.cs @@ -23,7 +23,7 @@ namespace isnd.Controllers return View(new RegistrationPageIndexQueryAndResult { Query = model, - Result = pkgs.ToArray() + Result = pkgs.GetResults().Select(p => new PackageRegistration(apiBase, p)).ToArray() }); } diff --git a/src/isnd/Controllers/Packages/PackagesController.cs b/src/isnd/Controllers/Packages/PackagesController.cs index cbf2e5d..6910077 100644 --- a/src/isnd/Controllers/Packages/PackagesController.cs +++ b/src/isnd/Controllers/Packages/PackagesController.cs @@ -8,6 +8,7 @@ using isnd.Data; using isnd.Entities; using isnd.Interfaces; using isn.Abstract; +using isn.abst; namespace isnd.Controllers { @@ -16,6 +17,7 @@ namespace isnd.Controllers { const int maxTake = 100; private readonly Resource[] resources; + private readonly string apiBase; private readonly ILogger logger; private readonly IDataProtector protector; @@ -36,6 +38,9 @@ namespace isnd.Controllers this.dbContext = dbContext; packageManager = pm; resources = packageManager.GetResources().ToArray(); + this.apiBase = isndSettings.ExternalUrl + Constants.ApiVersionPrefix; } + + } } diff --git a/src/isnd/Data/Catalog/CatalogPage.cs b/src/isnd/Data/Catalog/CatalogPage.cs index b8f7e4c..51a09b7 100644 --- a/src/isnd/Data/Catalog/CatalogPage.cs +++ b/src/isnd/Data/Catalog/CatalogPage.cs @@ -23,7 +23,7 @@ namespace isnd.Data.Catalog this.apiBase = apiBase; } - public CatalogPage(string bid, string pkgid, string apiBase, List versions) : this(pkgid, apiBase) + public CatalogPage(string pkgid, string apiBase, List versions) : this(pkgid, apiBase) { AddVersionRange(versions); } diff --git a/src/isnd/Data/Catalog/PackageRegistration.cs b/src/isnd/Data/Catalog/PackageRegistration.cs index d9f152b..e5f0afa 100644 --- a/src/isnd/Data/Catalog/PackageRegistration.cs +++ b/src/isnd/Data/Catalog/PackageRegistration.cs @@ -1,4 +1,5 @@ using isnd.Data.Packages; +using isnd.Entities; using Newtonsoft.Json; using System; using System.Collections.Generic; @@ -14,11 +15,11 @@ namespace isnd.Data.Catalog Items = new List(); } - public PackageRegistration(string bid, string apiBase, Packages.Package pkg) : base(bid + $"/{pkg.Id}/index.json") + public PackageRegistration(string apiBase, Packages.Package pkg) : base($"{apiBase}{ApiConfig.Registration}/{pkg.Id}/index.json") { Items = new List { - new CatalogPage(bid, pkg.Id, apiBase, pkg.Versions) + new CatalogPage(pkg.Id, apiBase, pkg.Versions) }; } diff --git a/src/isnd/Data/Catalog/RegistrationPageIndexQuery.cs b/src/isnd/Data/Catalog/PackageRegistrationQuery.cs similarity index 100% rename from src/isnd/Data/Catalog/RegistrationPageIndexQuery.cs rename to src/isnd/Data/Catalog/PackageRegistrationQuery.cs diff --git a/src/isnd/Data/Packages/IPackage.cs b/src/isnd/Data/Packages/IPackage.cs new file mode 100644 index 0000000..76276f7 --- /dev/null +++ b/src/isnd/Data/Packages/IPackage.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; + +namespace isnd.Data.Packages +{ + public interface IPackage + { + string Id { get; set; } + string OwnerId { get; set; } + string Description { get; set; } + bool Public { get; set; } + ApplicationUser Owner { get; set; } + List Versions { get; set; } + long CommitNId { get; set; } + string CommitId { get; } + Commit LatestCommit { get; set; } + DateTimeOffset CommitTimeStamp { get; set; } + } +} \ No newline at end of file diff --git a/src/isnd/Data/Packages/Package.cs b/src/isnd/Data/Packages/Package.cs index 00c93ac..2c41a3b 100644 --- a/src/isnd/Data/Packages/Package.cs +++ b/src/isnd/Data/Packages/Package.cs @@ -2,24 +2,12 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; using isnd.Interfaces; using Newtonsoft.Json; namespace isnd.Data.Packages { - public interface IPackage - { - string Id { get; set; } - string OwnerId { get; set; } - string Description { get; set; } - bool Public { get; set; } - ApplicationUser Owner { get; set; } - List Versions { get; set; } - long CommitNId { get; set; } - string CommitId { get; } - Commit LatestCommit { get; set; } - DateTimeOffset CommitTimeStamp { get; set; } - } public class Package { @@ -60,6 +48,9 @@ namespace isnd.Data.Packages public virtual Commit LatestCommit { get; set; } - + internal string GetLatestVersion() + { + return Versions.Max(v => v.NugetVersion).ToFullString(); + } } } \ No newline at end of file diff --git a/src/isnd/Interfaces/IPackageManager.cs b/src/isnd/Interfaces/IPackageManager.cs index cdb6fae..9c92f21 100644 --- a/src/isnd/Interfaces/IPackageManager.cs +++ b/src/isnd/Interfaces/IPackageManager.cs @@ -27,7 +27,7 @@ namespace isnd.Interfaces Task GetCatalogIndexAsync(); Task GetPackageRegistrationIndexAsync(PackageRegistrationQuery query); - Task> SearchPackageAsync(PackageRegistrationQuery query); + Task SearchPackageAsync(PackageRegistrationQuery query); } } \ No newline at end of file diff --git a/src/isnd/Services/PackageManager.cs b/src/isnd/Services/PackageManager.cs index 3a47968..00a3330 100644 --- a/src/isnd/Services/PackageManager.cs +++ b/src/isnd/Services/PackageManager.cs @@ -62,12 +62,11 @@ namespace isnd.Services }, new Resource(apiBase + ApiConfig.Search, - "SearchQueryService/" + BASE_API_LEVEL) + "SearchQueryService/3.0.0-rc") { - Comment = "Search Query service" + Comment = "Query endpoint of NuGet Search service (primary) used by RC clients" }, - new Resource(apiBase + ApiConfig.Registration, "RegistrationsBaseUrl/Versioned") { @@ -75,9 +74,6 @@ namespace isnd.Services "This base URL includes SemVer 2.0.0 packages." } }; - - // TODO ? RegistrationsBaseUrl RegistrationsBaseUrl/v* Catalog - return res; } @@ -275,31 +271,24 @@ namespace isnd.Services foreach (var version in scope.Versions) version.LatestCommit = dbContext.Commits.Single(c=>c.Id == version.CommitNId); return - new PackageRegistration(bid, apiBase, scope); + new PackageRegistration(apiBase, scope); } - public async Task> SearchPackageAsync(PackageRegistrationQuery query) + + public async Task SearchPackageAsync(PackageRegistrationQuery query) { string bid = $"{apiBase}{ApiConfig.Registration}"; - - if (query.Query == null) query.Query = ""; - var scope = (await dbContext.Packages + if (string.IsNullOrWhiteSpace(query.Query)) + query.Query=""; + var scope = dbContext.Packages .Include(p => p.Owner) .Include(p => p.Versions) .Include(p => p.LatestCommit) .Where(p => p.Id.StartsWith(query.Query) && (query.Prerelease || p.Versions.Any(p => !p.IsPrerelease))) - .OrderBy(p => p.CommitNId) - .Skip(query.Skip).Take(query.Take) - .ToListAsync() - ); - scope.ForEach(pkg => - pkg.Versions.ForEach (version => - version.LatestCommit = dbContext.Commits.Single(c => c.Id == version.CommitNId)) - ); + .OrderBy(p => p.CommitNId); - return scope.Select(p => new PackageRegistration(bid, apiBase, p)); + return new PackageSearchResult(await scope.Skip(query.Skip).Take(query.Take) + .ToListAsync(), apiBase, scope.Count()); } - - } } \ No newline at end of file diff --git a/src/isnd/Startup.cs b/src/isnd/Startup.cs index 20cdfb4..9888036 100644 --- a/src/isnd/Startup.cs +++ b/src/isnd/Startup.cs @@ -16,7 +16,6 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; using System; -using Microsoft.OpenApi.Models; using System.IO; using Microsoft.AspNetCore.HttpOverrides; using Microsoft.AspNetCore.DataProtection; @@ -96,6 +95,7 @@ namespace isnd s.SerializerSettings.ReferenceResolverProvider = () => new NSJWebApiReferenceResolver(); }) .AddXmlSerializerFormatters(); + #if SWAGGER services.AddSwaggerGen(options => { options.SwaggerDoc("v1", new OpenApiInfo @@ -118,6 +118,8 @@ namespace isnd var xmlFilename = $"{typeof(Startup).Assembly.GetName().Name}.xml"; options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, xmlFilename)); }); + #endif + } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. @@ -131,8 +133,10 @@ namespace isnd { app.UseDeveloperExceptionPage(); app.UseMigrationsEndPoint(); + #if SWAGGER app.UseSwagger(); app.UseSwaggerUI(); + #endif } else { diff --git a/src/isnd/ViewModels/PackageHit.cs b/src/isnd/ViewModels/PackageHit.cs new file mode 100644 index 0000000..5919599 --- /dev/null +++ b/src/isnd/ViewModels/PackageHit.cs @@ -0,0 +1,60 @@ +using System.ComponentModel.DataAnnotations; +using isnd.Data.Catalog; +using Newtonsoft.Json; +using NuGet.Packaging.Core; +using NuGet.Protocol.Core.Types; + +namespace isnd.ViewModels +{ + public class PackageHit : Permalink + { + public PackageHit(string id) : base(id, "Package") + { + } + + /// + /// The full SemVer 2.0.0 version string of the package (could contain build metadata) + /// + /// + [Required] + public string version { get; set; } + + /// + /// All of the versions of the package matching the prerelease parameter + /// + /// + public SearchVersionInfo[] versions { get; set; } + + public string description { get; set; } + + public string[] authors { get; set; } + public string iconUrl { get; set; } + public string licenseUrl { get; set; } + public string projectUrl { get; set; } + /// + /// The absolute URL to the associated registration index + /// + /// + public string registration { get; set; } + public string summary { get; set; } + public string[] tags { get; set; } + public string title { get; set; } + /// + /// This value can be inferred by the sum of downloads in the versions array + /// + /// + public int totalDownloads { get; set; } + /// + /// boolean indicating whether the package is verified + /// + /// + public bool verified { get; set; } + + /// + /// The package types defined by the package author (added in SearchQueryService/3.5.0) + /// + /// + [Required] + public PackageType[] packageTypes { get; set; } + } +} \ No newline at end of file diff --git a/src/isnd/ViewModels/PackageSearchResult.cs b/src/isnd/ViewModels/PackageSearchResult.cs new file mode 100644 index 0000000..464e347 --- /dev/null +++ b/src/isnd/ViewModels/PackageSearchResult.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Reflection.Metadata.Ecma335; +using System.Text.Json.Serialization; +using isnd.Data.Packages; +using isnd.Entities; + +namespace isnd.ViewModels +{ + public class PackageSearchResult + { + + public IEnumerable GetResults() + { + return result; + } + + private IEnumerable result; + + [JsonIgnore] + public string ApiBase{get; private set;} + + public PackageSearchResult(IEnumerable result, string apiBase, int totalHit) + { + this.result = result; + this.ApiBase = apiBase; + data=result.Select(r => NewPackageHit(apiBase, r)).ToArray(); + this.totalHits = totalHit; + } + + private static PackageHit NewPackageHit(string apiBase, Package package) + { + string regId = $"{apiBase}{ApiConfig.Registration}/{package.Id}/index.json"; + return new PackageHit(regId) + { + version = package.GetLatestVersion(), + description = package.Description, + title = package.Id, + versions = package.Versions.Select(v => new SearchVersionInfo(regId, v)).ToArray() + }; + } + + public PackageHit[] data { get; protected set; } + + public int totalHits { get; set; } + } +} \ No newline at end of file diff --git a/src/isnd/ViewModels/SearchVersionInfo.cs b/src/isnd/ViewModels/SearchVersionInfo.cs new file mode 100644 index 0000000..57aa01d --- /dev/null +++ b/src/isnd/ViewModels/SearchVersionInfo.cs @@ -0,0 +1,21 @@ +using isnd.Data.Catalog; +using Newtonsoft.Json; + +namespace isnd.ViewModels +{ + public class SearchVersionInfo: Permalink + { + public SearchVersionInfo(string id, Data.PackageVersion v) : base(id, "VersionInfo") + { + Version = v.FullString; + } + + [JsonProperty("version")] + public string Version { get; set; } + + + [JsonProperty("downloads")] + public long Downloads { get; set; } + + } +} \ No newline at end of file diff --git a/src/isnd/isnd.csproj b/src/isnd/isnd.csproj index 2906be1..9f90aa9 100644 --- a/src/isnd/isnd.csproj +++ b/src/isnd/isnd.csproj @@ -37,7 +37,6 @@ - diff --git a/test/isn.tests/PushTest.cs b/test/isn.tests/PushTest.cs index 11e0826..0d2838a 100644 --- a/test/isn.tests/PushTest.cs +++ b/test/isn.tests/PushTest.cs @@ -30,14 +30,13 @@ namespace isn.tests public void TestPush() { Program.LoadConfig(); - var report = Program.PushPkg(new string[] { "./src/isn.abst/bin/Debug/isn.abst.1.0.1.nupkg" - + Constants.PacketFileExtension }); + var report = Program.PushPkg(new string[] { "./src/isn.abst/bin/Debug/isn.abst.1.0.1.nupkg" }); } [Fact] public void GetServerResourcesUsingHttpClientAsyncTest() { - var model = SourceHelpers.GetServerResources("Https://isn.pschneider.fr/v3/index"); + var model = SourceHelpers.GetServerResources("Https://isn.pschneider.fr/v3/index.json"); Console.WriteLine(JsonConvert.SerializeObject(model)); Assert.NotNull(model.Resources); var pub = model.Resources.FirstOrDefault((r) => r.Type.StartsWith("PackagePublish/")); diff --git a/test/isnd.tests/UnitTestWebHost.cs b/test/isnd.tests/UnitTestWebHost.cs index d02baf8..2374c03 100644 --- a/test/isnd.tests/UnitTestWebHost.cs +++ b/test/isnd.tests/UnitTestWebHost.cs @@ -47,11 +47,11 @@ namespace isnd.host.tests { var services = serviceScope.ServiceProvider; var dbContext = services.GetRequiredService(); - var paul = dbContext.Users.FirstOrDefaultAsync - (u => u.Email == "paul@pschneider.fr").Result; - if (paul != null) + var tester = dbContext.Users.FirstOrDefaultAsync + (u => u.Id == server.TestingUser.Id).Result; + if (tester != null) { - dbContext.Users.Remove(paul); + dbContext.Users.Remove(tester); dbContext.SaveChanges(); } } @@ -64,10 +64,10 @@ namespace isnd.host.tests using (var serviceScope = server.Host.Services.CreateScope()) { var isnSettings = serviceScope.ServiceProvider.GetService>().Value; - string pkgSourceUrl = isnSettings.ExternalUrl + apiindex; + string pkgSourceUrl = isnSettings.ExternalUrl + apiindex + ".json"; ProcessStartInfo psi = new ProcessStartInfo("nuget"); psi.ArgumentList.Add("install"); - psi.ArgumentList.Add("gitversion"); + psi.ArgumentList.Add("isnd"); psi.ArgumentList.Add("-PreRelease"); psi.ArgumentList.Add("-Source"); psi.ArgumentList.Add(pkgSourceUrl); @@ -114,7 +114,7 @@ namespace isnd.host.tests } public string SPIIndexURI { - get => server.Addresses.First() + "/v3/index.json"; + get => server.Addresses.First(a => a.StartsWith("https:")) + "/v3/index.json"; } [Fact] @@ -128,7 +128,7 @@ namespace isnd.host.tests PackageMetadataResource resource = await repository.GetResourceAsync(); IEnumerable packages = await resource.GetMetadataAsync( - "isn.abst", + "isnd", includePrerelease: true, includeUnlisted: true, cache, @@ -149,10 +149,12 @@ namespace isnd.host.tests [Fact] public async Task TestFindPackageAsync() { - ILogger logger = NullLogger.Instance; + ILogger logger = new TestLogger(); CancellationToken cancellationToken = CancellationToken.None; SourceRepository repository = Repository.Factory.GetCoreV3(SPIIndexURI); + repository.PackageSource.AllowInsecureConnections=true; + PackageSearchResource resource = await repository.GetResourceAsync(); SearchFilter searchFilter = new SearchFilter(includePrerelease: true); @@ -198,12 +200,23 @@ namespace isnd.host.tests { public override void Log(ILogMessage message) { - Console.WriteLine(message.Message); + string msg = $"{message.Level}: {message.Message}"; + switch (message.Level) + { + case LogLevel.Debug: + case LogLevel.Information: + case LogLevel.Verbose: + case LogLevel.Minimal: + case LogLevel.Warning: + case LogLevel.Error: + Debug.WriteLine(msg); + break; + } } - public async override Task LogAsync(ILogMessage message) + public override Task LogAsync(ILogMessage message) { - Log(message); + return Task.Run(()=>Log(message)); } } } diff --git a/test/isnd.tests/appsettings.json b/test/isnd.tests/appsettings.json index b3280bc..26d0818 100644 --- a/test/isnd.tests/appsettings.json +++ b/test/isnd.tests/appsettings.json @@ -14,7 +14,10 @@ "Kestrel": { "Endpoints": { "Http": { - "Url": "http://localhost:5005" + "Url": "http://127.0.0.1:5002" + }, + "Https": { + "Url": "https://127.0.0.1:5003" } } }