main
Paul Schneider 6 months ago
parent 89e1b5a235
commit d6180aa154
24 changed files with 255 additions and 78 deletions

7
.gitignore vendored

@ -6,14 +6,8 @@
/src/isn/.vscode/ /src/isn/.vscode/
/test/isnd.tests/obj/ /test/isnd.tests/obj/
/test/isnd.tests/bin/ /test/isnd.tests/bin/
/packages/
bower_components/
/test/isn.tests/bin /test/isn.tests/bin
/test/isn.tests/obj/ /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/bin
/src/isn.abst/obj /src/isn.abst/obj
/src/isnd/packages/ /src/isnd/packages/
@ -23,4 +17,5 @@ appsettings.Development.json
/artifacts/ /artifacts/
/.vs/ /.vs/
/.vscode/ /.vscode/
appsettings.Development.json

11
.vscode/tasks.json vendored

@ -90,20 +90,13 @@
], ],
"problemMatcher": "$msCompile" "problemMatcher": "$msCompile"
}, },
{
"label": "copyTestConfig",
"command": "dotnet",
"type": "process",
"args": [ "build", "/t:CopyTestConfig" ],
"group": "test"
},
{ {
"label": "test", "label": "test",
"command": "dotnet", "command": "dotnet",
"type": "process", "type": "process",
"options": { "options": {
"env": { "env": {
"ASPNETCORE_ENVIRONMENT": "Testing" "ASPNETCORE_ENVIRONMENT": "Development"
} }
}, },
"args": [ "args": [
@ -112,7 +105,7 @@
"--logger:xunit" "--logger:xunit"
], ],
"problemMatcher": "$msCompile", "problemMatcher": "$msCompile",
"dependsOn": [ "build", "copyTestConfig"], "dependsOn": [ "build"],
"group": "test" "group": "test"
}, },
{ {

@ -1,9 +1,9 @@
{ {
"dotnet": { "dotnet": {
"enabled": false "enabled": true
}, },
"msbuild": { "msbuild": {
"enabled": true "enabled": false
}, },
"Dnx": { "Dnx": {
"enabled": false "enabled": false

@ -22,7 +22,7 @@ namespace isnd.Data.Catalog
[JsonProperty("@id")] [JsonProperty("@id")]
public string Id { get => GetId(); } public string Id { get => id; }
private readonly string id; private readonly string id;
public string GetId() { return id; } public string GetId() { return id; }

@ -106,7 +106,7 @@ namespace isnd.Controllers
ApiKey key = await dbContext.ApiKeys.FirstOrDefaultAsync(k => k.Id == id && k.UserId == userid); ApiKey key = await dbContext.ApiKeys.FirstOrDefaultAsync(k => k.Id == id && k.UserId == userid);
if (key == null) if (key == null)
{ {
ModelState.AddModelError(null, "Key not found"); ModelState.AddModelError("id", "Key not found");
return View(); return View();
} }
return View("Details", new DetailModel { ApiKey = key, Name = key.Name, ProtectedValue = protector.Protect(key.Id)}); return View("Details", new DetailModel { ApiKey = key, Name = key.Name, ProtectedValue = protector.Protect(key.Id)});

@ -7,6 +7,7 @@ using isn.abst;
namespace isnd.Controllers namespace isnd.Controllers
{ {
// TODO /search GET {@id}?q={QUERY}&skip={SKIP}&take={TAKE}&prerelease={PRERELEASE}&semVerLevel={SEMVERLEVEL}&packageType={PACKAGETYPE}
public partial class PackagesController public partial class PackagesController
{ {

@ -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<IActionResult> 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);
}
}
}

@ -23,7 +23,7 @@ namespace isnd.Controllers
return View(new RegistrationPageIndexQueryAndResult return View(new RegistrationPageIndexQueryAndResult
{ {
Query = model, Query = model,
Result = pkgs.ToArray() Result = pkgs.GetResults().Select(p => new PackageRegistration(apiBase, p)).ToArray()
}); });
} }

@ -8,6 +8,7 @@ using isnd.Data;
using isnd.Entities; using isnd.Entities;
using isnd.Interfaces; using isnd.Interfaces;
using isn.Abstract; using isn.Abstract;
using isn.abst;
namespace isnd.Controllers namespace isnd.Controllers
{ {
@ -16,6 +17,7 @@ namespace isnd.Controllers
{ {
const int maxTake = 100; const int maxTake = 100;
private readonly Resource[] resources; private readonly Resource[] resources;
private readonly string apiBase;
private readonly ILogger<PackagesController> logger; private readonly ILogger<PackagesController> logger;
private readonly IDataProtector protector; private readonly IDataProtector protector;
@ -36,6 +38,9 @@ namespace isnd.Controllers
this.dbContext = dbContext; this.dbContext = dbContext;
packageManager = pm; packageManager = pm;
resources = packageManager.GetResources().ToArray(); resources = packageManager.GetResources().ToArray();
this.apiBase = isndSettings.ExternalUrl + Constants.ApiVersionPrefix;
} }
} }
} }

@ -23,7 +23,7 @@ namespace isnd.Data.Catalog
this.apiBase = apiBase; this.apiBase = apiBase;
} }
public CatalogPage(string bid, string pkgid, string apiBase, List<PackageVersion> versions) : this(pkgid, apiBase) public CatalogPage(string pkgid, string apiBase, List<PackageVersion> versions) : this(pkgid, apiBase)
{ {
AddVersionRange(versions); AddVersionRange(versions);
} }

@ -1,4 +1,5 @@
using isnd.Data.Packages; using isnd.Data.Packages;
using isnd.Entities;
using Newtonsoft.Json; using Newtonsoft.Json;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -14,11 +15,11 @@ namespace isnd.Data.Catalog
Items = new List<CatalogPage>(); Items = new List<CatalogPage>();
} }
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<CatalogPage> Items = new List<CatalogPage>
{ {
new CatalogPage(bid, pkg.Id, apiBase, pkg.Versions) new CatalogPage(pkg.Id, apiBase, pkg.Versions)
}; };
} }

@ -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<PackageVersion> Versions { get; set; }
long CommitNId { get; set; }
string CommitId { get; }
Commit LatestCommit { get; set; }
DateTimeOffset CommitTimeStamp { get; set; }
}
}

@ -2,24 +2,12 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using isnd.Interfaces; using isnd.Interfaces;
using Newtonsoft.Json; using Newtonsoft.Json;
namespace isnd.Data.Packages 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<PackageVersion> Versions { get; set; }
long CommitNId { get; set; }
string CommitId { get; }
Commit LatestCommit { get; set; }
DateTimeOffset CommitTimeStamp { get; set; }
}
public class Package public class Package
{ {
@ -60,6 +48,9 @@ namespace isnd.Data.Packages
public virtual Commit LatestCommit { get; set; } public virtual Commit LatestCommit { get; set; }
internal string GetLatestVersion()
{
return Versions.Max(v => v.NugetVersion).ToFullString();
}
} }
} }

@ -27,7 +27,7 @@ namespace isnd.Interfaces
Task<PackageRegistration> GetCatalogIndexAsync(); Task<PackageRegistration> GetCatalogIndexAsync();
Task<PackageRegistration> GetPackageRegistrationIndexAsync(PackageRegistrationQuery query); Task<PackageRegistration> GetPackageRegistrationIndexAsync(PackageRegistrationQuery query);
Task<IEnumerable<PackageRegistration>> SearchPackageAsync(PackageRegistrationQuery query); Task<PackageSearchResult> SearchPackageAsync(PackageRegistrationQuery query);
} }
} }

@ -62,12 +62,11 @@ namespace isnd.Services
}, },
new Resource(apiBase + ApiConfig.Search, 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, new Resource(apiBase + ApiConfig.Registration,
"RegistrationsBaseUrl/Versioned") "RegistrationsBaseUrl/Versioned")
{ {
@ -75,9 +74,6 @@ namespace isnd.Services
"This base URL includes SemVer 2.0.0 packages." "This base URL includes SemVer 2.0.0 packages."
} }
}; };
// TODO ? RegistrationsBaseUrl RegistrationsBaseUrl/v* Catalog
return res; return res;
} }
@ -275,31 +271,24 @@ namespace isnd.Services
foreach (var version in scope.Versions) foreach (var version in scope.Versions)
version.LatestCommit = dbContext.Commits.Single(c=>c.Id == version.CommitNId); version.LatestCommit = dbContext.Commits.Single(c=>c.Id == version.CommitNId);
return return
new PackageRegistration(bid, apiBase, scope); new PackageRegistration(apiBase, scope);
} }
public async Task<IEnumerable<PackageRegistration>> SearchPackageAsync(PackageRegistrationQuery query)
public async Task<PackageSearchResult> SearchPackageAsync(PackageRegistrationQuery query)
{ {
string bid = $"{apiBase}{ApiConfig.Registration}"; string bid = $"{apiBase}{ApiConfig.Registration}";
if (string.IsNullOrWhiteSpace(query.Query))
if (query.Query == null) query.Query = ""; query.Query="";
var scope = (await dbContext.Packages var scope = dbContext.Packages
.Include(p => p.Owner) .Include(p => p.Owner)
.Include(p => p.Versions) .Include(p => p.Versions)
.Include(p => p.LatestCommit) .Include(p => p.LatestCommit)
.Where(p => p.Id.StartsWith(query.Query) .Where(p => p.Id.StartsWith(query.Query)
&& (query.Prerelease || p.Versions.Any(p => !p.IsPrerelease))) && (query.Prerelease || p.Versions.Any(p => !p.IsPrerelease)))
.OrderBy(p => p.CommitNId) .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))
);
return scope.Select(p => new PackageRegistration(bid, apiBase, p)); return new PackageSearchResult(await scope.Skip(query.Skip).Take(query.Take)
.ToListAsync(), apiBase, scope.Count());
} }
} }
} }

@ -16,7 +16,6 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens; using Microsoft.IdentityModel.Tokens;
using System; using System;
using Microsoft.OpenApi.Models;
using System.IO; using System.IO;
using Microsoft.AspNetCore.HttpOverrides; using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.DataProtection;
@ -96,6 +95,7 @@ namespace isnd
s.SerializerSettings.ReferenceResolverProvider = () => new NSJWebApiReferenceResolver(); s.SerializerSettings.ReferenceResolverProvider = () => new NSJWebApiReferenceResolver();
}) })
.AddXmlSerializerFormatters(); .AddXmlSerializerFormatters();
#if SWAGGER
services.AddSwaggerGen(options => services.AddSwaggerGen(options =>
{ {
options.SwaggerDoc("v1", new OpenApiInfo options.SwaggerDoc("v1", new OpenApiInfo
@ -118,6 +118,8 @@ namespace isnd
var xmlFilename = $"{typeof(Startup).Assembly.GetName().Name}.xml"; var xmlFilename = $"{typeof(Startup).Assembly.GetName().Name}.xml";
options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, xmlFilename)); options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, xmlFilename));
}); });
#endif
} }
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. // 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.UseDeveloperExceptionPage();
app.UseMigrationsEndPoint(); app.UseMigrationsEndPoint();
#if SWAGGER
app.UseSwagger(); app.UseSwagger();
app.UseSwaggerUI(); app.UseSwaggerUI();
#endif
} }
else else
{ {

@ -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")
{
}
/// <summary>
/// The full SemVer 2.0.0 version string of the package (could contain build metadata)
/// </summary>
/// <value></value>
[Required]
public string version { get; set; }
/// <summary>
/// All of the versions of the package matching the prerelease parameter
/// </summary>
/// <value></value>
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; }
/// <summary>
/// The absolute URL to the associated registration index
/// </summary>
/// <value></value>
public string registration { get; set; }
public string summary { get; set; }
public string[] tags { get; set; }
public string title { get; set; }
/// <summary>
/// This value can be inferred by the sum of downloads in the versions array
/// </summary>
/// <value></value>
public int totalDownloads { get; set; }
/// <summary>
/// boolean indicating whether the package is verified
/// </summary>
/// <value></value>
public bool verified { get; set; }
/// <summary>
/// The package types defined by the package author (added in SearchQueryService/3.5.0)
/// </summary>
/// <value></value>
[Required]
public PackageType[] packageTypes { get; set; }
}
}

@ -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<Package> GetResults()
{
return result;
}
private IEnumerable<Package> result;
[JsonIgnore]
public string ApiBase{get; private set;}
public PackageSearchResult(IEnumerable<Package> 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; }
}
}

@ -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; }
}
}

@ -37,7 +37,6 @@
</PackageReference> </PackageReference>
<PackageReference Include="System.Security.Cryptography.Pkcs" Version="8.0.0" /> <PackageReference Include="System.Security.Cryptography.Pkcs" Version="8.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Antiforgery" Version="2.2.0" /> <PackageReference Include="Microsoft.AspNetCore.Antiforgery" Version="2.2.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="7.0.15" /> <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="7.0.15" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

@ -30,14 +30,13 @@ namespace isn.tests
public void TestPush() public void TestPush()
{ {
Program.LoadConfig(); Program.LoadConfig();
var report = Program.PushPkg(new string[] { "./src/isn.abst/bin/Debug/isn.abst.1.0.1.nupkg" var report = Program.PushPkg(new string[] { "./src/isn.abst/bin/Debug/isn.abst.1.0.1.nupkg" });
+ Constants.PacketFileExtension });
} }
[Fact] [Fact]
public void GetServerResourcesUsingHttpClientAsyncTest() 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)); Console.WriteLine(JsonConvert.SerializeObject(model));
Assert.NotNull(model.Resources); Assert.NotNull(model.Resources);
var pub = model.Resources.FirstOrDefault((r) => r.Type.StartsWith("PackagePublish/")); var pub = model.Resources.FirstOrDefault((r) => r.Type.StartsWith("PackagePublish/"));

@ -47,11 +47,11 @@ namespace isnd.host.tests
{ {
var services = serviceScope.ServiceProvider; var services = serviceScope.ServiceProvider;
var dbContext = services.GetRequiredService<ApplicationDbContext>(); var dbContext = services.GetRequiredService<ApplicationDbContext>();
var paul = dbContext.Users.FirstOrDefaultAsync var tester = dbContext.Users.FirstOrDefaultAsync
(u => u.Email == "paul@pschneider.fr").Result; (u => u.Id == server.TestingUser.Id).Result;
if (paul != null) if (tester != null)
{ {
dbContext.Users.Remove(paul); dbContext.Users.Remove(tester);
dbContext.SaveChanges(); dbContext.SaveChanges();
} }
} }
@ -64,10 +64,10 @@ namespace isnd.host.tests
using (var serviceScope = server.Host.Services.CreateScope()) using (var serviceScope = server.Host.Services.CreateScope())
{ {
var isnSettings = serviceScope.ServiceProvider.GetService<IOptions<isnd.Entities.IsndSettings>>().Value; var isnSettings = serviceScope.ServiceProvider.GetService<IOptions<isnd.Entities.IsndSettings>>().Value;
string pkgSourceUrl = isnSettings.ExternalUrl + apiindex; string pkgSourceUrl = isnSettings.ExternalUrl + apiindex + ".json";
ProcessStartInfo psi = new ProcessStartInfo("nuget"); ProcessStartInfo psi = new ProcessStartInfo("nuget");
psi.ArgumentList.Add("install"); psi.ArgumentList.Add("install");
psi.ArgumentList.Add("gitversion"); psi.ArgumentList.Add("isnd");
psi.ArgumentList.Add("-PreRelease"); psi.ArgumentList.Add("-PreRelease");
psi.ArgumentList.Add("-Source"); psi.ArgumentList.Add("-Source");
psi.ArgumentList.Add(pkgSourceUrl); psi.ArgumentList.Add(pkgSourceUrl);
@ -114,7 +114,7 @@ namespace isnd.host.tests
} }
public string SPIIndexURI public string SPIIndexURI
{ {
get => server.Addresses.First() + "/v3/index.json"; get => server.Addresses.First(a => a.StartsWith("https:")) + "/v3/index.json";
} }
[Fact] [Fact]
@ -128,7 +128,7 @@ namespace isnd.host.tests
PackageMetadataResource resource = await repository.GetResourceAsync<PackageMetadataResource>(); PackageMetadataResource resource = await repository.GetResourceAsync<PackageMetadataResource>();
IEnumerable<IPackageSearchMetadata> packages = await resource.GetMetadataAsync( IEnumerable<IPackageSearchMetadata> packages = await resource.GetMetadataAsync(
"isn.abst", "isnd",
includePrerelease: true, includePrerelease: true,
includeUnlisted: true, includeUnlisted: true,
cache, cache,
@ -149,10 +149,12 @@ namespace isnd.host.tests
[Fact] [Fact]
public async Task TestFindPackageAsync() public async Task TestFindPackageAsync()
{ {
ILogger logger = NullLogger.Instance; ILogger logger = new TestLogger();
CancellationToken cancellationToken = CancellationToken.None; CancellationToken cancellationToken = CancellationToken.None;
SourceRepository repository = Repository.Factory.GetCoreV3(SPIIndexURI); SourceRepository repository = Repository.Factory.GetCoreV3(SPIIndexURI);
repository.PackageSource.AllowInsecureConnections=true;
PackageSearchResource resource = await repository.GetResourceAsync<PackageSearchResource>(); PackageSearchResource resource = await repository.GetResourceAsync<PackageSearchResource>();
SearchFilter searchFilter = new SearchFilter(includePrerelease: true); SearchFilter searchFilter = new SearchFilter(includePrerelease: true);
@ -198,12 +200,23 @@ namespace isnd.host.tests
{ {
public override void Log(ILogMessage message) 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));
} }
} }
} }

@ -14,7 +14,10 @@
"Kestrel": { "Kestrel": {
"Endpoints": { "Endpoints": {
"Http": { "Http": {
"Url": "http://localhost:5005" "Url": "http://127.0.0.1:5002"
},
"Https": {
"Url": "https://127.0.0.1:5003"
} }
} }
} }

Loading…