isn/src/nuget-host/Controllers/PackagesController.cs

187 lines
6.7 KiB
C#
Raw Normal View History

2021-04-08 02:03:17 +01:00
using System.IO;
using System.Linq;
2021-04-08 03:08:20 +01:00
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.DataProtection;
2021-04-08 02:03:17 +01:00
using Microsoft.AspNetCore.Mvc;
2021-06-21 23:38:05 +01:00
using Microsoft.EntityFrameworkCore;
2021-04-08 02:03:17 +01:00
using Microsoft.Extensions.Logging;
2021-05-02 15:22:46 +01:00
using Microsoft.Extensions.Options;
2021-06-22 01:25:28 +01:00
using NuGet.Versioning;
2021-05-09 03:06:23 +01:00
using nuget_host.Data;
2021-05-02 15:22:46 +01:00
using nuget_host.Entities;
2021-04-08 02:03:17 +01:00
namespace nuget_host.Controllers
{
2021-05-09 03:06:23 +01:00
[AllowAnonymous]
2021-06-21 23:38:05 +01:00
public partial class PackagesController : Controller
2021-04-08 02:03:17 +01:00
{
2021-04-25 12:12:50 +01:00
private readonly ILogger<PackagesController> logger;
private readonly IDataProtector protector;
2021-04-08 02:03:17 +01:00
2021-05-02 15:22:46 +01:00
private readonly NugetSettings nugetSettings;
2021-05-09 03:06:23 +01:00
ApplicationDbContext dbContext;
2021-05-02 15:22:46 +01:00
public PackagesController(
ILoggerFactory loggerFactory,
IDataProtectionProvider provider,
2021-05-09 03:06:23 +01:00
IOptions<NugetSettings> nugetOptions,
ApplicationDbContext dbContext)
2021-04-08 02:03:17 +01:00
{
logger = loggerFactory.CreateLogger<PackagesController>();
2021-05-02 15:22:46 +01:00
nugetSettings = nugetOptions.Value;
protector = provider.CreateProtector(nugetSettings.ProtectionTitle);
2021-05-09 03:06:23 +01:00
this.dbContext = dbContext;
2021-04-08 02:03:17 +01:00
}
2021-06-22 00:10:01 +01:00
const string defaultSemVer = "2.0.0";
2021-05-23 20:21:46 +01:00
// dotnet add . package -s http://localhost:5000/packages nuget-cli
// packages/FindPackagesById()?id='nuget-cli'&semVerLevel=2.0.0
2021-06-21 23:38:05 +01:00
// Search
// GET {@id}?q={QUERY}&skip={SKIP}&take={TAKE}&prerelease={PRERELEASE}&semVerLevel={SEMVERLEVEL}&packageType={PACKAGETYPE}
2021-06-22 01:25:28 +01:00
const string _apiPrefix = "~/package";
[HttpGet(_apiPrefix + "/index.json")]
2021-06-21 23:38:05 +01:00
public IActionResult Index(
string q,
2021-06-22 00:10:01 +01:00
string semVerLevel = defaultSemVer,
2021-06-21 23:38:05 +01:00
bool prerelease = false,
string packageType = null,
int skip = 0,
2021-06-22 00:10:01 +01:00
int take = 25)
2021-04-08 02:03:17 +01:00
{
2021-06-21 23:38:05 +01:00
if (string.IsNullOrEmpty(q))
2021-04-08 02:03:17 +01:00
{
2021-06-21 23:38:05 +01:00
ModelState.AddModelError("q", "no value");
2021-04-08 02:03:17 +01:00
}
2021-06-22 01:25:28 +01:00
if (take > maxTake)
2021-04-08 02:03:17 +01:00
{
2021-06-22 01:25:28 +01:00
ModelState.AddModelError("take", "Maximum exceeded");
}
if (ModelState.IsValid)
{
2021-06-22 00:10:01 +01:00
var scope = dbContext.Packages
2021-06-21 23:38:05 +01:00
.Include(p => p.Versions)
.Where(
p => (CamelCaseMatch(p.Id, q) || SeparatedByMinusMatch(p.Id, q))
&& (prerelease || p.Versions.Any(v => !v.IsPrerelease))
&& (packageType == null || p.Versions.Any(v => v.Type == packageType))
);
2021-06-22 00:10:01 +01:00
var result = new
{
2021-06-21 23:38:05 +01:00
totalHits = scope.Count(),
2021-06-22 01:25:28 +01:00
data = scope.OrderBy(p => p.Id)
.Skip(skip).Take(take).ToArray()
2021-06-21 23:38:05 +01:00
};
return Ok(result);
2021-06-22 01:25:28 +01:00
2021-04-08 02:03:17 +01:00
}
2021-06-22 01:25:28 +01:00
return BadRequest(new { error = ModelState });
2021-04-08 02:03:17 +01:00
}
2021-04-08 03:08:20 +01:00
2021-06-21 23:38:05 +01:00
protected static bool CamelCaseMatch(string id, string q)
{
// Assert.False (q==null);
string query = q;
2021-06-22 00:10:01 +01:00
if (query.Length == 0) return false;
2021-05-02 15:22:46 +01:00
2021-06-21 23:38:05 +01:00
while (id.Length > 0)
{
int i = 0;
while (id.Length > i && char.IsLower(id[i])) i++;
2021-06-22 00:10:01 +01:00
if (i == 0) break;
2021-06-21 23:38:05 +01:00
id = id.Substring(i);
if (id.StartsWith(q, System.StringComparison.OrdinalIgnoreCase)) return true;
}
return false;
}
protected static bool SeparatedByMinusMatch(string id, string q)
2021-04-08 03:08:20 +01:00
{
2021-06-21 23:38:05 +01:00
foreach (var part in id.Split('-'))
{
if (part.StartsWith(q, System.StringComparison.OrdinalIgnoreCase)) return true;
}
return false;
2021-04-08 03:08:20 +01:00
}
2021-06-22 01:25:28 +01:00
const int maxTake = 100;
2021-06-22 00:10:01 +01:00
// GET /autocomplete?id=nuget.protocol&prerelease=true
2021-06-22 01:25:28 +01:00
[HttpGet(_apiPrefix + "/autocomplete")]
2021-06-22 00:10:01 +01:00
public IActionResult AutoComplete(
string id,
string semVerLevel = defaultSemVer,
bool prerelease = false,
string packageType = null,
int skip = 0,
int take = 25)
{
2021-06-22 01:25:28 +01:00
if (take > maxTake)
{
ModelState.AddModelError("take", "Maximum exceeded");
return BadRequest(ModelState);
}
var scope = dbContext.PackageVersions.Where(
v => v.PackageId == id
&& (prerelease || !v.IsPrerelease)
&& (packageType == null || v.Type == packageType)
)
.OrderBy(v => v.FullString);
return Ok(new
{
data =scope.Select(v => v.FullString)
.Skip(skip).Take(take).ToArray(),
totalHits = scope.Count()
});
}
// TODO GET {@id}/{LOWER_ID}/index.json
// LOWER_ID URL string yes The package ID, lowercased
// response : versions array of strings yes The versions available
[HttpGet(_apiPrefix + "/{id}/{lower}/index.json")]
public IActionResult GetVersions(
string id,
string lower,
bool prerelease = false,
string packageType = null,
int skip = 0,
int take = 25)
{
if (take > maxTake)
{
ModelState.AddModelError("take", "Maximum exceeded");
}
// NugetVersion
if (!NuGetVersion.TryParse(lower, out NuGetVersion parsedVersion))
{
ModelState.AddModelError("lower", "invalid version string");
}
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
2021-06-22 00:10:01 +01:00
return Ok(new
{
2021-06-22 01:25:28 +01:00
// TODO stocker MetaData plutôt que FullString en base,
// et en profiter pour corriger ce listing
versions =
2021-06-22 00:10:01 +01:00
dbContext.PackageVersions.Where(
v => v.PackageId == id
&& (prerelease || !v.IsPrerelease)
&& (packageType == null || v.Type == packageType)
2021-06-22 01:25:28 +01:00
&& (parsedVersion.CompareTo(new SemanticVersion(v.Major, v.Minor, v.Patch)) < 0)
)
.OrderBy(v => v.FullString)
.Select(v => v.FullString)
2021-06-22 00:10:01 +01:00
.Skip(skip).Take(take).ToArray()
});
}
2021-06-22 01:25:28 +01:00
// TODO GET GET {@id}/{LOWER_ID}/{LOWER_VERSION}/{LOWER_ID}.{LOWER_VERSION}.nupkg
// LOWER_ID URL string yes The package ID, lowercase
// LOWER_VERSION URL string yes The package version, normalized and lowercased
// response 200 : the package
// TODO GET {@id}/{LOWER_ID}/{LOWER_VERSION}/{LOWER_ID}.nuspec
// response 200 : the nuspec
2021-06-22 00:10:01 +01:00
2021-04-08 02:03:17 +01:00
}
}