Starting a Package detail

Paul Schneider 1 year ago
parent 654065d7fe
commit 948c39d12b
17 changed files with 70 additions and 255 deletions

@ -0,0 +1,4 @@
all:
dotnet build

@ -4,18 +4,13 @@ namespace isnd.Entities
{
public static class ApiConfig
{
public const string Publish = "put";
public const string Index = "index";
public const string IndexDotJson = Index + ".json";
public const string Catalog = "catalog";
public const string CatalogPage = "catalog-page";
public const string GetPackage = Constants.PaquetFileEstension;
public const string GetVersion = "version";
public const string Search = "search";
public const string AutoComplete = "autocomplete";
public const string CatalogLeaf = "catalog-leaf";
public const string Delete = "delete";
public const string Registration = "registration";
internal const string GetNuspec = Constants.SpecFileEstension;
public const string Catalog = "/catalog";
public const string Package = "/package";
public const string Search = "/search";
public const string AutoComplete = "/autocomplete";
public const string Registration = "/registration";
public const string Nuspec = "/nuspec";
public const string Nuget = "/nuget";
}
}

@ -4,7 +4,6 @@ namespace isn.abst
{
public const string PaquetFileEstension = "nupkg";
public const string SpecFileEstension = "nuspec";
public const string JsonFileEstension = "json";
public const string PackageRootServerPrefix = "~/pkgs/";
public const string ApiVersion = "/v3";
}
}

@ -10,6 +10,5 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="11.0.1" />
<Reference Include="System.ComponentModel.DataAnnotations" Version="4.0.0.0"/>
</ItemGroup>
</Project>

@ -1,195 +0,0 @@
using System;
using System.IO;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace isn
{
public class UploadFilesToServerUsingWebRequest
{
public void UploadFilesToServer(PushReport report, Uri uri, FileInfo fi,
string apikey)
{
// string formdataTemplate = "Content-Disposition: form-data; name=\"{0}\"\r\n\r\n{1}";
const int TXLEN = 0x1000;
/// the form-data file upload, properly formatted
string fileheaderTemplate = "Content-Disposition: form-data; name=\"{0}\"; filename=\"{1}\";\r\nContent-Type: {2}\r\n\r\n";
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
// "X-NuGet-ApiKey
string boundary = "----------" + DateTime.Now.Ticks.ToString("x");
string fileheader = string.Format(fileheaderTemplate, "file", fi.Name, "application/octet-stream");
byte[] fileheaderbytes = Encoding.ASCII.GetBytes(fileheader);
var boundarybytes = System.Text.Encoding.ASCII.GetBytes("\r\n--" + boundary + "\r\n");
var endBoundaryBytes = System.Text.Encoding.ASCII.GetBytes("\r\n--" + boundary + "--");
HttpWebRequest httpWebRequest = (HttpWebRequest) WebRequest.Create(uri);
httpWebRequest.Method = "PUT";
httpWebRequest.ContentType = "multipart/form-data; boundary=" + boundary;
httpWebRequest.AllowAutoRedirect = false;
httpWebRequest.Headers.Add("X-NuGet-Client-Version", Constants.ClientVersion);
httpWebRequest.Headers.Add("X-NuGet-ApiKey", apikey);
httpWebRequest.ContentLength = boundarybytes.Length +
fileheaderbytes.Length + fi.Length + endBoundaryBytes.Length;
httpWebRequest.BeginGetRequestStream(async (result) =>
{
try
{
HttpWebRequest request = (HttpWebRequest)result.AsyncState;
using (Stream requestStream = request.EndGetRequestStream(result))
{
await WriteToStream(requestStream, boundarybytes, boundarybytes.Length);
await WriteToStream(requestStream, fileheaderbytes, fileheaderbytes.Length);
using (var fss = fi.OpenRead())
{
byte[] buffer = new byte[TXLEN];
var form_bytes_read = fss.Read(buffer, 0, TXLEN);
while (form_bytes_read > 0)
{
await WriteToStream(requestStream, buffer, form_bytes_read);
form_bytes_read = fss.Read(buffer, 0, TXLEN);
}
}
requestStream.Write(endBoundaryBytes, 0, endBoundaryBytes.Length);
requestStream.Close();
}
}
catch (Exception rex)
{
report.Executed = false;
report.StackTrace = rex.StackTrace;
report.Message = rex.Message;
Console.Error.WriteLine("Stack trace:");
Console.Error.WriteLine(rex.StackTrace);
WebResponse eresp = httpWebRequest.GetResponse();
if (!CheckResponse(eresp, report))
throw new Exception("Invalid server response type");
}
}, httpWebRequest);
WebResponse resp = httpWebRequest.GetResponse();
if (!CheckResponse(resp, report)) throw new Exception("Invalid server response type");
if (Program.Settings.AutoUpdateApiKey)
Program.EnsureKeyStored();
}
static bool CheckResponse(WebResponse resp, PushReport report)
{
Stream stream = resp.GetResponseStream();
StreamReader re = new StreamReader(stream);
if (resp is HttpWebResponse)
{
if (resp.ContentType == "text/json")
{
String json = re.ReadToEnd();
report.Message = json;
var hrep = resp as HttpWebResponse;
report.StatusCode = hrep.StatusCode.ToString();
report.OK = hrep.StatusCode == HttpStatusCode.Accepted
|| hrep.StatusCode == HttpStatusCode.OK;
return true;
}
}
return false;
}
/// <summary>
/// Creates HTTP POST request &amp; uploads database to server. Author : Farhan Ghumra
/// </summary>
internal async Task UploadFilesToServerAsync(PushReport report, Uri uri, FileInfo fi,
string apikey)
{
// string formdataTemplate = "Content-Disposition: form-data; name=\"{0}\"\r\n\r\n{1}";
const int TXLEN = 0x1000;
/// the form-data file upload, properly formatted
string fileheaderTemplate = "Content-Disposition: form-data; name=\"{0}\"; filename=\"{1}\";\r\nContent-Type: {2}\r\n\r\n";
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
// "X-NuGet-ApiKey
string boundary = "----------" + DateTime.Now.Ticks.ToString("x");
string fileheader = string.Format(fileheaderTemplate, "file", fi.Name, "application/octet-stream");
byte[] fileheaderbytes = Encoding.ASCII.GetBytes(fileheader);
var boundarybytes = System.Text.Encoding.ASCII.GetBytes("\r\n--" + boundary + "\r\n");
var endBoundaryBytes = System.Text.Encoding.ASCII.GetBytes("\r\n--" + boundary + "--");
HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create(uri);
httpWebRequest.Method = "PUT";
httpWebRequest.ContentType = "multipart/form-data; boundary=" + boundary;
httpWebRequest.AllowAutoRedirect = false;
httpWebRequest.Headers.Add("X-NuGet-Client-Version", Constants.ClientVersion);
httpWebRequest.Headers.Add("X-NuGet-ApiKey", apikey);
httpWebRequest.ContentLength = boundarybytes.Length +
fileheaderbytes.Length + fi.Length + endBoundaryBytes.Length;
httpWebRequest.BeginGetRequestStream(async (result) =>
{
try
{
HttpWebRequest request = (HttpWebRequest)result.AsyncState;
using (Stream requestStream = request.EndGetRequestStream(result))
{
await WriteToStream(requestStream, boundarybytes, boundarybytes.Length);
await WriteToStream(requestStream, fileheaderbytes, fileheaderbytes.Length);
using (var fss = fi.OpenRead())
{
byte[] buffer = new byte[TXLEN];
var form_bytes_read = fss.Read(buffer, 0, TXLEN);
while (form_bytes_read > 0)
{
await WriteToStream(requestStream, buffer, form_bytes_read);
form_bytes_read = fss.Read(buffer, 0, TXLEN);
}
}
requestStream.Write(endBoundaryBytes, 0, endBoundaryBytes.Length);
requestStream.Close();
}
}
catch (Exception rex)
{
report.Message = rex.Message;
Console.Error.WriteLine(rex.Message);
Console.Error.WriteLine("Stack trace:");
Console.Error.WriteLine(rex.StackTrace);
throw;
}
}, httpWebRequest);
WebResponse resp = await httpWebRequest.GetResponseAsync();
Stream stream = resp.GetResponseStream();
StreamReader re = new StreamReader(stream);
if (resp is HttpWebResponse)
{
String json = await re.ReadToEndAsync();
report.Message = json;
var hrep = resp as HttpWebResponse;
report.StatusCode = hrep.StatusCode.ToString();
report.OK = hrep.StatusCode == HttpStatusCode.Accepted
|| hrep.StatusCode == HttpStatusCode.OK;
}
else throw new Exception("Invalid server response type");
}
/// <summary>
/// Writes byte array to stream. Author : Farhan Ghumra
/// </summary>
private static async Task WriteToStream(Stream s, byte[] bytes, int len)
{
await s.WriteAsync(bytes, 0, len);
}
}
}

@ -18,7 +18,6 @@
<PackageReference Include="Mono.Options" Version="5.3.0" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
<PackageReference Include="unleash.client" Version="1.6.1" />
<Reference Include="System.Net.Http" Version="4.0.0" />
<PackageReference Include="GitVersion.MsBuild" Version="5.6.10*" />
<ProjectReference Include="../isn.abst/isn.abst.csproj" />
</ItemGroup>

@ -33,10 +33,10 @@ namespace isnd.Controllers
/// API index
/// </summary>
/// <returns></returns>
[HttpGet(Constants.PackageRootServerPrefix + ApiConfig.IndexDotJson)]
[HttpGet("~" + Constants.ApiVersion + "/index.json")]
public IActionResult ApiIndex()
{
return Ok(new ApiIndexViewModel(packageManager.CatalogBaseUrl + ApiConfig.IndexDotJson){ Version = PackageManager.BASE_API_LEVEL, Resources = resources });
return Ok(new ApiIndexViewModel(packageManager.CatalogBaseUrl){ Version = PackageManager.BASE_API_LEVEL, Resources = resources });
}
}

@ -9,7 +9,7 @@ namespace isnd.Controllers
{
// GET /autocomplete?id=isn.protocol&prerelease=true
[HttpGet(Constants.PackageRootServerPrefix + ApiConfig.AutoComplete)]
[HttpGet("~" + Constants.ApiVersion + ApiConfig.AutoComplete)]
public IActionResult AutoComplete(
string id,
string semVerLevel,

@ -16,17 +16,17 @@ namespace isnd.Controllers
{
// https://docs.microsoft.com/en-us/nuget/api/catalog-resource#versioning
[HttpGet(Constants.PackageRootServerPrefix + ApiConfig.Catalog)]
[HttpGet("~" + Constants.ApiVersion + ApiConfig.Catalog)]
public async Task<IActionResult> CatalogIndex()
{
return Ok(await packageManager.GetCatalogIndexAsync());
}
[HttpGet(Constants.PackageRootServerPrefix + "{apiVersion}/" + ApiConfig.Registration + "/{id}/{lower}.json")]
public async Task<IActionResult> CatalogRegistration(string apiVersion, string id, string lower)
[HttpGet("~" + Constants.ApiVersion + "/{id}/{?version}")]
public async Task<IActionResult> CatalogRegistration(string id, string version)
{
if (lower.Equals("index", System.StringComparison.InvariantCultureIgnoreCase))
if (string.IsNullOrWhiteSpace(version))
{
var query = new Data.Catalog.RegistrationPageIndexQuery
{
@ -39,11 +39,19 @@ namespace isnd.Controllers
return Ok(index);
}
// return a Package
var leaf = await packageManager.GetCatalogEntryAsync(id, lower, null);
var leaf = await packageManager.GetCatalogEntryAsync(id, version, null);
if (null == leaf) return NotFound(new { id, lower });
if (null == leaf) return NotFound(new { id, version });
return Ok(leaf);
}
/*
"@id": "https://www.nuget.org/packages/{id}/{version}",
"@type": "PackageDetailsUriTemplate/5.1.0",
"comment": "URI template used by NuGet Client to construct details URL for packages"
*/
}
}

@ -11,7 +11,7 @@ namespace isnd.Controllers
{
public partial class PackagesController
{
[HttpDelete(Constants.PackageRootServerPrefix + ApiConfig.Delete + "/{id}/{lower?}/{type?}")]
[HttpDelete("~" + Constants.ApiVersion + ApiConfig.Package + "/{id}/{lower?}/{type?}")]
public async Task<IActionResult> ApiDelete(
[FromRoute][SafeName][Required] string id,
[FromRoute][SafeName][Required] string lower,

@ -11,7 +11,7 @@ namespace isnd.Controllers
public partial class PackagesController
{
// Web get the paquet
[HttpGet(Constants.PackageRootServerPrefix + ApiConfig.GetPackage + "/{id}/{lower}/{idf}-{lowerf}."
[HttpGet("~" + Constants.ApiVersion + ApiConfig.Package + "/{id}/{lower}/{idf}-{lowerf}."
+ Constants.PaquetFileEstension)]
public IActionResult GetPackage(
[FromRoute][SafeName][Required] string id,
@ -32,7 +32,7 @@ namespace isnd.Controllers
}
// Web get spec
[HttpGet(Constants.PackageRootServerPrefix + Constants.SpecFileEstension + "/{id}/{lower}/{idf}-{lowerf}."
[HttpGet("~" + Constants.ApiVersion + Constants.SpecFileEstension + "/{id}/{lower}/{idf}-{lowerf}."
+ Constants.SpecFileEstension)]
public IActionResult GetNuspec(
[FromRoute][SafeName][Required] string id,

@ -7,7 +7,7 @@ namespace isnd.Controllers
{
public partial class PackagesController
{
[HttpGet(Constants.PackageRootServerPrefix + ApiConfig.GetVersion + "/{id}/{lower}/" + ApiConfig.IndexDotJson)]
[HttpGet( ApiConfig.Package + "/{id}/{lower}")]
public IActionResult GetVersions(
string id,
string lower,

@ -25,7 +25,7 @@ namespace isnd.Controllers
public partial class PackagesController
{
// TODO [Authorize(Policy = IsndConstants.RequireValidApiKey)]
[HttpPut(Constants.PackageRootServerPrefix + ApiConfig.Publish)]
[HttpPut("~" + Constants.ApiVersion + ApiConfig.Package)]
public async Task<IActionResult> Put()
{
try

@ -11,7 +11,7 @@ namespace isnd.Controllers
public partial class PackagesController
{
// GET {@id}?q={QUERY}&skip={SKIP}&take={TAKE}&prerelease={PRERELEASE}&semVerLevel={SEMVERLEVEL}&packageType={PACKAGETYPE}
[HttpGet(Constants.PackageRootServerPrefix + ApiConfig.Search)]
[HttpGet("~" + Constants.ApiVersion + ApiConfig.Search)]
public IActionResult Search(
string q,
int skip = 0,

@ -1,15 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using isn.abst;
using isn.Abstract;
using isnd.Data;
using isnd.Data.Catalog;
using isnd.Data.Packages;
using isnd.Data.Packages.Catalog;
using isnd.Entities;
using isnd.Helpers;
using isnd.Interfaces;
using isnd.ViewModels;
using Microsoft.EntityFrameworkCore;
@ -22,15 +20,14 @@ namespace isnd.Services
public class PackageManager : IPackageManager
{
public const string BASE_API_LEVEL = "3.0.0";
ApplicationDbContext dbContext;
readonly ApplicationDbContext dbContext;
public PackageManager(ApplicationDbContext dbContext,
IOptions<IsndSettings> siteConfigOptionsOptions)
{
this.dbContext = dbContext;
isndSettings = siteConfigOptionsOptions.Value;
extUrl = isndSettings.ExternalUrl + "/";
apiBase = isndSettings.ExternalUrl + Constants.ApiVersion;
}
public IEnumerable<Resource> GetResources(IUnleash unleashClient)
@ -38,26 +35,38 @@ namespace isnd.Services
var res = new List<Resource>
{
new Resource(extUrl + ApiConfig.Publish, "PackagePublish/2.0.0")
new Resource(apiBase + ApiConfig.Package, "PackagePublish/2.0.0")
{
Comment = "Package Publish service"
},
// under dev, only leash in release mode
new Resource(extUrl + ApiConfig.GetPackage, "PackageBaseAddress/3.0.0")
new Resource(apiBase + ApiConfig.Package + "/{id}/{version}", "PackageDetailsUriTemplate/5.1.0")
{
Comment = @"Package Base Address service - Base URL of where NuGet packages are stored, in the format https://<host>/nupkg/{id-lower}/{version-lower}/{id-lower}.{version-lower}.nupkg"
},
/*
"@id": "https://www.nuget.org/packages/{id}/{version}",
"@type": "PackageDetailsUriTemplate/5.1.0",
"comment": "URI template used by NuGet Client to construct details URL for packages"
*/
new Resource(apiBase + ApiConfig.Nuget, "PackageBaseAddress/3.0.0")
{
Comment = @"Package Base Address service - Base URL of where NuGet packages are stored, in the format https://<host>/nupkg/{id-lower}/{version-lower}/{id-lower}.{version-lower}.nupkg"
},
new Resource(extUrl + ApiConfig.AutoComplete, "SearchAutocompleteService/" + BASE_API_LEVEL)
new Resource(apiBase + ApiConfig.AutoComplete, "SearchAutocompleteService/" + BASE_API_LEVEL)
{
Comment = "Auto complete service"
},
new Resource(extUrl + ApiConfig.Search,"SearchQueryService/" + BASE_API_LEVEL)
new Resource(apiBase + ApiConfig.Search,"SearchQueryService/" + BASE_API_LEVEL)
{
Comment = "Search Query service"
},
new Resource(extUrl + "versioned/" + ApiConfig.Registration, "RegistrationsBaseUrl/Versioned")
new Resource(apiBase + "versioned/" + ApiConfig.Registration, "RegistrationsBaseUrl/Versioned")
{
Comment = "Base URL of storage where isn package registration info is stored. This base URL includes SemVer 2.0.0 packages."
}
@ -113,8 +122,6 @@ namespace isnd.Services
};
}
// TODO stocker MetaData plutôt que FullString en base,
// et en profiter pour corriger ce listing
public string[] GetVersions(
string id,
NuGetVersion parsedVersion,
@ -136,10 +143,10 @@ namespace isnd.Services
public string CatalogBaseUrl => extUrl;
public string CatalogBaseUrl => apiBase;
private IsndSettings isndSettings;
private string extUrl;
private string apiBase;
public virtual async Task<RegistrationPageIndex> GetCatalogIndexAsync()
{
@ -150,8 +157,8 @@ namespace isnd.Services
{
int i = 0;
string baseid = extUrl + ApiConfig.Catalog;
string bidreg = $"{extUrl}v3.4.0/{ApiConfig.Registration}";
string baseid = apiBase + ApiConfig.Catalog;
string bidreg = $"{apiBase}v3.4.0/{ApiConfig.Registration}";
RegistrationPageIndex index = new RegistrationPageIndex(baseid);
var scope = await dbContext.Commits.OrderBy(c => c.TimeStamp).ToArrayAsync();
@ -175,7 +182,7 @@ namespace isnd.Services
(p => p.GetPackageId() == pkgid.Key);
if (page == null)
{
page = new RegistrationPage(bidreg, pkgid.Key, extUrl);
page = new RegistrationPage(bidreg, pkgid.Key, apiBase);
index.Items.Add(page);
}
@ -242,7 +249,7 @@ namespace isnd.Services
.Where(v => v.PackageId == pkgId
&& semver == v.SementicVersionString
&& (pkgType == null || pkgType == v.Type)).SingleOrDefaultAsync()).ToLeave(BID,
extUrl);
apiBase);
}
public async Task<PackageDeletionReport> UserAskForPackageDeletionAsync(string uid, string id, string lower, string type)
@ -256,7 +263,7 @@ namespace isnd.Services
return new PackageDeletionReport { Deleted = true, DeletedVersion = packageVersion };
}
public string BID { get => $"{extUrl}v3.4.0/{ApiConfig.Registration}"; }
public string BID { get => $"{apiBase}v3.4.0/{ApiConfig.Registration}"; }
public IEnumerable<CatalogEntry> SearchCatalogEntriesById(string pkgId, string semver, string pkgType)
{
@ -268,7 +275,7 @@ namespace isnd.Services
.Where(v => v.PackageId == pkgId && semver == v.FullString
&& (pkgType == null || pkgType == v.Type))
.OrderByDescending(p => p.CommitNId)
.Select(p => p.ToLeave(BID, extUrl))
.Select(p => p.ToLeave(BID, apiBase))
;
}
public PackageVersion GetPackage(string pkgId, string semver, string pkgType)
@ -288,13 +295,13 @@ namespace isnd.Services
var scope = await dbContext.Packages.Include(p => p.Versions).Include(p => p.Owner)
.Where(p => p.Id.ToLower() == query.Query).Skip(query.Skip).Take(query.Take).ToListAsync();
string bid = $"{extUrl}v3.4.0/{ApiConfig.Registration}";
string bid = $"{apiBase}v3.4.0/{ApiConfig.Registration}";
return
new RegistrationPageIndex(bid, query.Query, extUrl, scope);
new RegistrationPageIndex(bid, query.Query, apiBase, scope);
}
public async Task<RegistrationPageIndex> SearchPackageAsync(RegistrationPageIndexQuery query)
{
string bid = $"{extUrl}v3.4.0/{ApiConfig.Registration}";
string bid = $"{apiBase}v3.4.0/{ApiConfig.Registration}";
if (query.Query == null) query.Query = "";
var scope = (await dbContext.Packages.Include(p => p.Versions).Include(p => p.Owner)
@ -305,7 +312,7 @@ namespace isnd.Services
var pkgs = scope.Skip(query.Skip).Take(query.Take);
return
new RegistrationPageIndex(bid, query.Query, extUrl, pkgs);
new RegistrationPageIndex(bid, query.Query, apiBase, pkgs);
}
private static bool MatchingExact(Package p, RegistrationPageIndexQuery query)

@ -33,7 +33,7 @@ namespace isn.tests
[Fact]
public async Task TestHttpClient()
{
string url = "https://isn.pschneider.fr/" + ApiConfig.IndexDotJson;
string url = "https://isn.pschneider.fr/v3/index.json" ;
HttpClient client = new HttpClient();
// var json = await client.GetStringAsync(new System.Uri(url));
var response = await client.GetAsync(url);

@ -18,7 +18,6 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\isn\isn.csproj" />
<ProjectReference Include="..\..\src\isn.abstract\isn.abstract.csproj" />
<Reference Include="System.Net.Http" />
<ProjectReference Include="..\..\src\isn.abst\isn.abst.csproj" />
</ItemGroup>
</Project>
Loading…