diff --git a/src/isnd/ApiConfig.cs b/src/isnd/ApiConfig.cs index e358ba3..dc4a0e6 100644 --- a/src/isnd/ApiConfig.cs +++ b/src/isnd/ApiConfig.cs @@ -2,13 +2,18 @@ namespace isnd { public static class ApiConfig { - public const string BaseApiPath = "/"; + public const string Publish = "put"; public const string Base = "index.json"; public const string Catalog = "catalog"; + public const string CatalogPage = "catalog-page"; public const string Get = "get"; public const string Search = "search"; public const string AutoComplete = "autocomplete"; + public const string CatalogLeaf = "catalog-leaf"; + public const string CatalogPackageDetail = "package-detail"; + + public const string Delete = "delete"; } } \ No newline at end of file diff --git a/src/isnd/Controllers/NewUpdateController.cs b/src/isnd/Controllers/NewUpdateController.cs index b5c5c44..f223178 100644 --- a/src/isnd/Controllers/NewUpdateController.cs +++ b/src/isnd/Controllers/NewUpdateController.cs @@ -6,11 +6,13 @@ using isnd.Data; namespace isnd.Controllers { + // TODO Web hook CI public class NewUpdateController : Controller { [Authorize(Policy = Constants.RequireAdminPolicyName)] public IActionResult NewRelease(NewReleaseInfo version) { + throw new NotImplementedException("web hook"); return View(version); } } diff --git a/src/isnd/Controllers/PackageVersionController.cs b/src/isnd/Controllers/PackageVersionController.cs index 1ff06e0..cf06584 100644 --- a/src/isnd/Controllers/PackageVersionController.cs +++ b/src/isnd/Controllers/PackageVersionController.cs @@ -6,7 +6,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using isnd.Data; using isnd.ViewModels; - +using isnd.Helpers; namespace isnd { [AllowAnonymous] @@ -78,16 +78,11 @@ namespace isnd return NotFound(); } - if (!IsOwner(packageVersion)) return Unauthorized(); + if (!User.IsOwner(packageVersion)) return Unauthorized(); return View(packageVersion); } - bool IsOwner(PackageVersion v) - { - var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); - return v.Package.OwnerId == userId; - } // POST: PackageVersion/Delete/5 [HttpPost, ActionName("Delete")] @@ -97,7 +92,7 @@ namespace isnd var packageVersion = await _context.PackageVersions.Include(p => p.Package) .FirstOrDefaultAsync(m => m.PackageId == PackageId && m.FullString == FullString); if (packageVersion == null) return NotFound(); - if (!IsOwner(packageVersion)) return Unauthorized(); + if (!User.IsOwner(packageVersion)) return Unauthorized(); _context.PackageVersions.Remove(packageVersion); await _context.SaveChangesAsync(); diff --git a/src/isnd/Controllers/PackagesController.Catalog.cs b/src/isnd/Controllers/PackagesController.Catalog.cs new file mode 100644 index 0000000..45d1a09 --- /dev/null +++ b/src/isnd/Controllers/PackagesController.Catalog.cs @@ -0,0 +1,62 @@ +using System.Linq; +using System.Threading.Tasks; +using isnd.Data.Catalog; +using isnd.Helpers; +using isnd.Services; +using isnd.ViewModels; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +namespace isnd.Controllers +{ + + public partial class PackagesController + { + + // https://docs.microsoft.com/en-us/nuget/api/catalog-resource#versioning + [HttpGet(_pkgRootPrefix + ApiConfig.Catalog)] + public IActionResult CatalogIndex() + { + return Ok(PackageManager.CurrentCatalogIndex); + } + + [HttpGet(_pkgRootPrefix + ApiConfig.CatalogPage + "-{id}")] + public IActionResult Index(string id) + { + // https://docs.microsoft.com/en-us/nuget/api/catalog-resource#versioning + return Ok(PackageManager.CurrentCatalogPages[int.Parse(id)]); + } + + [HttpGet(_pkgRootPrefix + ApiConfig.CatalogLeaf + "/{id}/{*lower}")] + public async Task CatalogLeafAsync(string id, string lower) + { + string pkgType = ParamHelpers.Optional(ref lower); + var pkgVersion = await _dbContext.PackageVersions + .Include(v => v.LatestCommit) + .Include(v => v.Package) + .SingleOrDefaultAsync( + v => v.PackageId == id && + v.FullString == lower && + v.Type == pkgType + ); + if (pkgVersion == null) return NotFound(); + + var pub = await _dbContext.Commits + .Include(c => c.Versions) + .OrderBy(c => c.CommitTimeStamp) + .SingleOrDefaultAsync + ( + c => c.Action == PackageAction.PublishPackage + && c.Versions.Contains(pkgVersion) + ); + return Ok(new CatalogLeaf + { + CommitId = id, + Id = pkgVersion.PackageId, + CommitTimeStamp = pkgVersion.LatestCommit.CommitTimeStamp + + }); + } + + } +} \ No newline at end of file diff --git a/src/isnd/Controllers/PackagesController.Delete.cs b/src/isnd/Controllers/PackagesController.Delete.cs new file mode 100644 index 0000000..35f425f --- /dev/null +++ b/src/isnd/Controllers/PackagesController.Delete.cs @@ -0,0 +1,36 @@ + +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.DataProtection; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using NuGet.Packaging.Core; +using NuGet.Versioning; +using isnd.Data; +using isnd.Helpers; +using Microsoft.AspNetCore.Http; +using isnd.Data.Catalog; +using System.ComponentModel.DataAnnotations; + +namespace isnd.Controllers +{ + + public partial class PackagesController + { + + [HttpDelete(_pkgRootPrefix + ApiConfig.Delete + "/{id}/{*lower}")] + public async Task Delete( + [FromRoute][SafeName][Required] string id, + [FromRoute][SafeName][Required] string lower) + { + string pkgtype = ParamHelpers.Optional(ref lower); + var report = await _packageManager.DeletePackageAsync(id, lower, pkgtype); + return Ok(report); + } + } +} diff --git a/src/isnd/Controllers/PackagesController.Files.cs b/src/isnd/Controllers/PackagesController.Files.cs new file mode 100644 index 0000000..f435b35 --- /dev/null +++ b/src/isnd/Controllers/PackagesController.Files.cs @@ -0,0 +1,58 @@ +using System.ComponentModel.DataAnnotations; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using isnd.ViewModels; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +namespace isnd.Controllers +{ + + public partial class PackagesController + { + // 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 + [HttpGet(_pkgRootPrefix + ApiConfig.Get + "/{id}/{lower}/{idf}-{lowerf}.nupkg")] + public IActionResult GetPackage( + [FromRoute][SafeName][Required] string id, + [FromRoute][SafeName][Required] string lower, + [FromRoute] string idf, [FromRoute] string lowerf) + { + var pkgpath = Path.Combine(_isndSettings.PackagesRootDir, + id, lower, $"{id}-{lower}.nupkg" + ); + + FileInfo pkgfi = new FileInfo(pkgpath); + + if (!pkgfi.Exists) + { + return BadRequest("!pkgfi.Exists"); + } + return File(pkgfi.OpenRead(), "application/zip; charset=binary"); + } + + // TODO GET {@id}/{LOWER_ID}/{LOWER_VERSION}/{LOWER_ID}.nuspec + // response 200 : the nuspec + [HttpGet(_pkgRootPrefix + ApiConfig.Get + "/{id}/{lower}/{idf}-{lowerf}.nuspec")] + public IActionResult GetNuspec( + [FromRoute][SafeName][Required] string id, + [FromRoute][SafeName][Required] string lower, + [FromRoute][SafeName][Required] string idf, + [FromRoute][SafeName][Required] string lowerf) + { + var pkgpath = Path.Combine(_isndSettings.PackagesRootDir, + id, lower, $"{id}.nuspec"); + + FileInfo pkgfi = new FileInfo(pkgpath); + if (!pkgfi.Exists) + { + return BadRequest("!pkgfi.Exists"); + } + + return File(pkgfi.OpenRead(), "text/xml; charset=utf-8"); + } + } +} \ No newline at end of file diff --git a/src/isnd/Controllers/PackagesController.Put.cs b/src/isnd/Controllers/PackagesController.Put.cs index a41b409..17ca186 100644 --- a/src/isnd/Controllers/PackagesController.Put.cs +++ b/src/isnd/Controllers/PackagesController.Put.cs @@ -142,6 +142,7 @@ namespace isnd.Controllers foreach (var v in pkgvers.ToArray()) _dbContext.PackageVersions.Remove(v); } + // FIXME default type or null if (types==null || types.Count==0) _dbContext.PackageVersions.Add (new PackageVersion{ @@ -152,7 +153,7 @@ namespace isnd.Controllers Patch = version.Patch, IsPrerelease = version.IsPrerelease, FullString = version.ToFullString(), - Type = "", + Type = null, LatestCommit = commit }); else diff --git a/src/isnd/Controllers/PackagesController.Views.cs b/src/isnd/Controllers/PackagesController.Views.cs new file mode 100644 index 0000000..8da26e2 --- /dev/null +++ b/src/isnd/Controllers/PackagesController.Views.cs @@ -0,0 +1,54 @@ +using System.Linq; +using System.Threading.Tasks; +using isnd.ViewModels; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +namespace isnd.Controllers +{ + + public partial class PackagesController + { + + // GET: PackageVersion + public async Task Index(PackageIndexViewModel model) + { + var applicationDbContext = _dbContext.Packages.Include(p => p.Versions).Where( + p => ( model.Prerelease || p.Versions.Any(v => !v.IsPrerelease)) + && ((model.query == null) || p.Id.StartsWith(model.query))); + model.data = await applicationDbContext.ToArrayAsync(); + return View(model); + } + + public async Task Details(string pkgid) + { + if (pkgid == null) + { + return NotFound(); + } + + var packageVersion = _dbContext.PackageVersions + .Include(p => p.Package) + .Where(m => m.PackageId == pkgid) + .OrderByDescending(p => p) + ; + + if (packageVersion == null) + { + return NotFound(); + } + bool results = await packageVersion.AnyAsync(); + var latest = await packageVersion.FirstAsync(); + + return View("Details", new PackageDetailViewModel + { + latest = latest, + pkgid = pkgid, + totalHits = packageVersion.Count(), + data = packageVersion.Take(10).ToArray() + }); + + } + } + +} \ No newline at end of file diff --git a/src/isnd/Controllers/PackagesController.cs b/src/isnd/Controllers/PackagesController.cs index f3d4f8f..a3d1560 100644 --- a/src/isnd/Controllers/PackagesController.cs +++ b/src/isnd/Controllers/PackagesController.cs @@ -18,7 +18,6 @@ using isnd.Interfaces; namespace isnd.Controllers { - [AllowAnonymous] public partial class PackagesController : Controller { @@ -33,6 +32,7 @@ namespace isnd.Controllers readonly ApplicationDbContext _dbContext; private readonly IPackageManager _packageManager; private readonly IUnleash _unleashĈlient; + const string _pkgRootPrefix = "~/"; public PackagesController( ILoggerFactory loggerFactory, @@ -51,72 +51,12 @@ namespace isnd.Controllers _ressources = _packageManager.GetResources(_unleashĈlient).ToArray(); } - // dotnet add . package -s http://localhost:5000/packages isn - // packages/FindPackagesById()?id='isn'&semVerLevel=2.0.0 - - // Search - // GET {@id}?q={QUERY}&skip={SKIP}&take={TAKE}&prerelease={PRERELEASE}&semVerLevel={SEMVERLEVEL}&packageType={PACKAGETYPE} - - - // GET: PackageVersion - public async Task Index(PackageIndexViewModel model) - { - var applicationDbContext = _dbContext.Packages.Include(p => p.Versions).Where( - p => ( model.Prerelease || p.Versions.Any(v => !v.IsPrerelease)) - && ((model.query == null) || p.Id.StartsWith(model.query))); - model.data = await applicationDbContext.ToArrayAsync(); - return View(model); - } - - // GET: PackageVersion/Details/5 - public async Task Details(string pkgid) - { - if (pkgid == null) - { - return NotFound(); - } - - var packageVersion = _dbContext.PackageVersions - .Include(p => p.Package) - .Where(m => m.PackageId == pkgid) - .OrderByDescending(p => p) - ; - - if (packageVersion == null) - { - return NotFound(); - } - bool results = await packageVersion.AnyAsync(); - var latest = await packageVersion.FirstAsync(); - - return View("Details", new PackageDetailViewModel { latest = latest, pkgid = pkgid, totalHits = packageVersion.Count(), data = packageVersion.Take(10).ToArray() } ); - - } - - const string _pkgRootPrefix = "~/" + ApiConfig.BaseApiPath ; - - [HttpGet(_pkgRootPrefix + ApiConfig.Base)] public IActionResult ApiIndex() { return Ok(_ressources); } -// - [HttpGet(_pkgRootPrefix + ApiConfig.Catalog)] - public IActionResult CatalogIndex() - { - // https://docs.microsoft.com/en-us/nuget/api/catalog-resource#versioning - return Ok(PackageManager.CurrentCatalogIndex); - } - - [HttpGet(_pkgRootPrefix + ApiConfig.Catalog + "-{id}")] - public IActionResult Index(string id) - { - // https://docs.microsoft.com/en-us/nuget/api/catalog-resource#versioning - return Ok(PackageManager.CurrentCatalogPages[int.Parse(id)]); - } - // GET /autocomplete?id=isn.protocol&prerelease=true [HttpGet(_pkgRootPrefix + ApiConfig.AutoComplete)] public IActionResult AutoComplete( @@ -170,48 +110,5 @@ namespace isnd.Controllers id, parsedVersion, prerelease, packageType, skip, take) }); } - - // 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 - [HttpGet(_pkgRootPrefix + ApiConfig.Get + "/{id}/{lower}/{idf}-{lowerf}.nupkg")] - public IActionResult GetPackage( - [FromRoute] string id, [FromRoute] string lower, - [FromRoute] string idf, [FromRoute] string lowerf) - { - var pkgpath = Path.Combine(_isndSettings.PackagesRootDir, - id, lower, $"{id}-{lower}.nupkg" - ); - - FileInfo pkgfi = new FileInfo(pkgpath); - - if (!pkgfi.Exists) - { - return BadRequest("!pkgfi.Exists"); - } - return File(pkgfi.OpenRead(), "application/zip; charset=binary"); - } - - // TODO GET {@id}/{LOWER_ID}/{LOWER_VERSION}/{LOWER_ID}.nuspec - // response 200 : the nuspec - [HttpGet(_pkgRootPrefix + ApiConfig.Get + "/{id}/{lower}/{idf}-{lowerf}.nuspec")] - public IActionResult GetNuspec( - [FromRoute][SafeName][Required] string id, - [FromRoute][SafeName][Required] string lower, - [FromRoute][SafeName][Required] string idf, - [FromRoute][SafeName][Required] string lowerf) - { - var pkgpath = Path.Combine(_isndSettings.PackagesRootDir, - id, lower, $"{id}.nuspec"); - - FileInfo pkgfi = new FileInfo(pkgpath); - if (!pkgfi.Exists) - { - return BadRequest("!pkgfi.Exists"); - } - - return File(pkgfi.OpenRead(), "text/xml; charset=utf-8"); - } } } \ No newline at end of file diff --git a/src/isnd/Data/ApplicationDbContext.cs b/src/isnd/Data/ApplicationDbContext.cs index b5ef97a..9780d31 100644 --- a/src/isnd/Data/ApplicationDbContext.cs +++ b/src/isnd/Data/ApplicationDbContext.cs @@ -7,6 +7,7 @@ using Microsoft.EntityFrameworkCore; using isnd.Data; using isnd.Data.ApiKeys; using isnd.Data.Catalog; +using isnd.Data.Historic; namespace isnd.Data { @@ -25,10 +26,15 @@ namespace isnd.Data v.FullString, v.Type }); + modelBuilder.Entity().HasKey + (c => new { c.CommitId, c.PackageId, c.FullString, c.PackageType }); + } public DbSet ApiKeys { get; set; } public DbSet Packages { get; set; } public DbSet PackageVersions { get; set; } public DbSet Commits { get; set; } + + public DbSet PackageVersionCommmit { get; set; } } } diff --git a/src/isnd/Data/Catalog/CatalogLeaf.cs b/src/isnd/Data/Catalog/CatalogLeaf.cs new file mode 100644 index 0000000..7b312a2 --- /dev/null +++ b/src/isnd/Data/Catalog/CatalogLeaf.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace isnd.Data.Catalog +{ + public class CatalogLeaf : IObject + { + + [JsonProperty("@type")] + public List RefType { get; set; } + + [JsonProperty("commitId")] + public string CommitId { get; set; } + + [JsonProperty("commitTimeStamp")] + public DateTime CommitTimeStamp { get; set; } + + [JsonProperty("published")] + public DateTime Published { get; set; } + + [JsonProperty("id")] + public string Id { get; set; } + + [JsonProperty("version")] + public string Version { get; set; } + + } +} \ No newline at end of file diff --git a/src/isnd/Data/Catalog/PackageDetail.cs b/src/isnd/Data/Catalog/PackageDetail.cs new file mode 100644 index 0000000..0ccadee --- /dev/null +++ b/src/isnd/Data/Catalog/PackageDetail.cs @@ -0,0 +1,7 @@ +namespace isnd.Data.Catalog +{ + public class PackageDetail : CatalogLeaf + { + + } +} \ No newline at end of file diff --git a/src/isnd/Data/Historic/PackageVersionCommit.cs b/src/isnd/Data/Historic/PackageVersionCommit.cs new file mode 100644 index 0000000..0ebb55b --- /dev/null +++ b/src/isnd/Data/Historic/PackageVersionCommit.cs @@ -0,0 +1,30 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using isnd.Data.Catalog; + +namespace isnd.Data.Historic +{ + public class PackageVersionCommit + { + public long CommitId { get; set; } + + [StringLength(1024)] + public string PackageId { get; set; } + + [StringLength(256)] + public string FullString { get; set; } + + [StringLength(256)] + + public string PackageType { get; set; } + + [ForeignKey("CommitId")] + public virtual Commit Commit { get; set; } + + [ForeignKey("PackageId")] + public virtual Package Package { get; set; } + + [ForeignKey("PackageId,FullString,PackageType")] + public virtual PackageVersion PackageVersion { get; set; } + } +} \ No newline at end of file diff --git a/src/isnd/Data/Package.cs b/src/isnd/Data/Package.cs index 21d05ba..370c895 100644 --- a/src/isnd/Data/Package.cs +++ b/src/isnd/Data/Package.cs @@ -10,6 +10,7 @@ namespace isnd.Data public class Package : IObject { [Key][Required] + [StringLength(1024)] public string Id { get; set; } [Required] diff --git a/src/isnd/Data/PackageVersion.cs b/src/isnd/Data/PackageVersion.cs index 04f9108..0bace47 100644 --- a/src/isnd/Data/PackageVersion.cs +++ b/src/isnd/Data/PackageVersion.cs @@ -9,6 +9,7 @@ namespace isnd.Data { [Required] [ForeignKey("Package")] + [StringLength(1024)] public string PackageId { get; set; } [Required] diff --git a/src/isnd/Entities/IsndSettings.cs b/src/isnd/Entities/IsndSettings.cs index 4bb7f74..83b8073 100644 --- a/src/isnd/Entities/IsndSettings.cs +++ b/src/isnd/Entities/IsndSettings.cs @@ -12,7 +12,5 @@ namespace isnd.Entities public int MaxUserKeyCount {get; set;} public int CatalogPageLen {get; set;} public TimeSpan DisplayDeletionLen {get; set;} - - } } \ No newline at end of file diff --git a/src/isnd/Helpers/PackageIdHelpers.cs b/src/isnd/Helpers/PackageIdHelpers.cs new file mode 100644 index 0000000..e5cd398 --- /dev/null +++ b/src/isnd/Helpers/PackageIdHelpers.cs @@ -0,0 +1,33 @@ +namespace isnd.Helpers +{ + public static class PackageIdHelpers + { + + internal static bool SeparatedByMinusMatch(string id, string q) + { + foreach (var part in id.Split('-')) + { + if (part.StartsWith(q, System.StringComparison.OrdinalIgnoreCase)) return true; + } + return false; + } + + + internal static bool CamelCaseMatch(string id, string query) + { + // Assert.False (q==null); + if (string.IsNullOrEmpty(query)) return true; + + while (id.Length > 0) + { + int i = 0; + while (id.Length > i && char.IsLower(id[i])) i++; + if (i == 0) break; + id = id.Substring(i); + if (id.StartsWith(query, System.StringComparison.OrdinalIgnoreCase)) return true; + } + return false; + } + + } +} \ No newline at end of file diff --git a/src/isnd/Helpers/PackageVersionHelpers.cs b/src/isnd/Helpers/PackageVersionHelpers.cs new file mode 100644 index 0000000..b8ad75a --- /dev/null +++ b/src/isnd/Helpers/PackageVersionHelpers.cs @@ -0,0 +1,15 @@ +using System.Security.Claims; +using isnd.Data; + +namespace isnd.Helpers +{ + public static class PackageVersionHelpers + { + + public static bool IsOwner(this ClaimsPrincipal user, PackageVersion v) + { + var userId = user.FindFirstValue(ClaimTypes.NameIdentifier); + return v.Package.OwnerId == userId; + } + } +} \ No newline at end of file diff --git a/src/isnd/Helpers/ParamHelpers.cs b/src/isnd/Helpers/ParamHelpers.cs new file mode 100644 index 0000000..fa4f186 --- /dev/null +++ b/src/isnd/Helpers/ParamHelpers.cs @@ -0,0 +1,16 @@ +namespace isnd.Helpers +{ + public static class ParamHelpers + { + public static string Optional(ref string prm) + { + int sopt = prm.IndexOf('/'); + if (sopt>0) + { + prm = prm.Substring(0,sopt); + return prm.Substring(sopt + 1); + } + return null; + } + } +} \ No newline at end of file diff --git a/src/isnd/Interfaces/IPackageManager.cs b/src/isnd/Interfaces/IPackageManager.cs index 125f151..c8994bb 100644 --- a/src/isnd/Interfaces/IPackageManager.cs +++ b/src/isnd/Interfaces/IPackageManager.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Threading.Tasks; using isnd.Controllers; using isnd.Data.Catalog; using isnd.Services; @@ -17,6 +18,7 @@ namespace isnd.Interfaces PackageIndexViewModel SearchByName(string query, int skip, int take, bool prerelease = false, string packageType = null); IEnumerable GetResources(IUnleash unleashĈlient); void ÛpdateCatalogFor(Commit commit); + Task DeletePackageAsync(string pkgId, string fullVersion, string pkgType); } } \ No newline at end of file diff --git a/src/isnd/Migrations/20210905144343_pkgVerCom.Designer.cs b/src/isnd/Migrations/20210905144343_pkgVerCom.Designer.cs new file mode 100644 index 0000000..270e1c3 --- /dev/null +++ b/src/isnd/Migrations/20210905144343_pkgVerCom.Designer.cs @@ -0,0 +1,392 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using isnd.Data; + +namespace isndhost.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20210905144343_pkgVerCom")] + partial class pkgVerCom + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn) + .HasAnnotation("ProductVersion", "2.2.6-servicing-10079") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Name") + .HasMaxLength(256); + + b.Property("NormalizedName") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasName("RoleNameIndex"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("RoleId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider"); + + b.Property("ProviderKey"); + + b.Property("ProviderDisplayName"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId"); + + b.Property("RoleId"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId"); + + b.Property("LoginProvider"); + + b.Property("Name"); + + b.Property("Value"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("isnd.Data.ApiKeys.ApiKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("CreationDate"); + + b.Property("Name"); + + b.Property("UserId") + .IsRequired(); + + b.Property("ValidityPeriodInDays"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("ApiKeys"); + }); + + modelBuilder.Entity("isnd.Data.ApplicationUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessFailedCount"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Email") + .HasMaxLength(256); + + b.Property("EmailConfirmed"); + + b.Property("FullName"); + + b.Property("LockoutEnabled"); + + b.Property("LockoutEnd"); + + b.Property("NormalizedEmail") + .HasMaxLength(256); + + b.Property("NormalizedUserName") + .HasMaxLength(256); + + b.Property("PasswordHash"); + + b.Property("PhoneNumber"); + + b.Property("PhoneNumberConfirmed"); + + b.Property("SecurityStamp"); + + b.Property("TwoFactorEnabled"); + + b.Property("UserName") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasName("UserNameIndex"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("isnd.Data.Catalog.Commit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Action"); + + b.Property("TimeStamp"); + + b.HasKey("Id"); + + b.ToTable("Commits"); + }); + + modelBuilder.Entity("isnd.Data.Historic.PackageVersionCommit", b => + { + b.Property("CommitId"); + + b.Property("PackageId") + .HasMaxLength(1024); + + b.Property("FullString") + .HasMaxLength(256); + + b.Property("PackageType") + .HasMaxLength(256); + + b.HasKey("CommitId", "PackageId", "FullString", "PackageType"); + + b.HasIndex("PackageId", "FullString", "PackageType"); + + b.ToTable("PackageVersionCommmit"); + }); + + modelBuilder.Entity("isnd.Data.Package", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasMaxLength(1024); + + b.Property("CommitNId"); + + b.Property("CommitTimeStamp"); + + b.Property("Description") + .HasMaxLength(1024); + + b.Property("OwnerId") + .IsRequired(); + + b.Property("Public"); + + b.HasKey("Id"); + + b.HasIndex("CommitNId"); + + b.HasIndex("OwnerId"); + + b.ToTable("Packages"); + }); + + modelBuilder.Entity("isnd.Data.PackageVersion", b => + { + b.Property("PackageId") + .HasMaxLength(1024); + + b.Property("FullString") + .HasMaxLength(256); + + b.Property("Type") + .HasMaxLength(256); + + b.Property("CommitNId"); + + b.Property("IsPrerelease"); + + b.Property("Major"); + + b.Property("Minor"); + + b.Property("Patch"); + + b.HasKey("PackageId", "FullString", "Type"); + + b.HasIndex("CommitNId"); + + b.ToTable("PackageVersions"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("isnd.Data.ApplicationUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("isnd.Data.ApplicationUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("isnd.Data.ApplicationUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("isnd.Data.ApplicationUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("isnd.Data.ApiKeys.ApiKey", b => + { + b.HasOne("isnd.Data.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("isnd.Data.Historic.PackageVersionCommit", b => + { + b.HasOne("isnd.Data.Catalog.Commit", "Commit") + .WithMany() + .HasForeignKey("CommitId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("isnd.Data.Package", "Package") + .WithMany() + .HasForeignKey("PackageId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("isnd.Data.PackageVersion", "PackageVersion") + .WithMany() + .HasForeignKey("PackageId", "FullString", "PackageType") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("isnd.Data.Package", b => + { + b.HasOne("isnd.Data.Catalog.Commit", "LatestVersion") + .WithMany() + .HasForeignKey("CommitNId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("isnd.Data.ApplicationUser", "Owner") + .WithMany() + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("isnd.Data.PackageVersion", b => + { + b.HasOne("isnd.Data.Catalog.Commit", "LatestCommit") + .WithMany("Versions") + .HasForeignKey("CommitNId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("isnd.Data.Package", "Package") + .WithMany("Versions") + .HasForeignKey("PackageId") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/isnd/Migrations/20210905144343_pkgVerCom.cs b/src/isnd/Migrations/20210905144343_pkgVerCom.cs new file mode 100644 index 0000000..96045b6 --- /dev/null +++ b/src/isnd/Migrations/20210905144343_pkgVerCom.cs @@ -0,0 +1,81 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace isndhost.Migrations +{ + public partial class pkgVerCom : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "PackageId", + table: "PackageVersions", + maxLength: 1024, + nullable: false, + oldClrType: typeof(string)); + + migrationBuilder.AlterColumn( + name: "Id", + table: "Packages", + maxLength: 1024, + nullable: false, + oldClrType: typeof(string)); + + migrationBuilder.CreateTable( + name: "PackageVersionCommmit", + columns: table => new + { + CommitId = table.Column(nullable: false), + PackageId = table.Column(maxLength: 1024, nullable: false), + FullString = table.Column(maxLength: 256, nullable: false), + PackageType = table.Column(maxLength: 256, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_PackageVersionCommmit", x => new { x.CommitId, x.PackageId, x.FullString, x.PackageType }); + table.ForeignKey( + name: "FK_PackageVersionCommmit_Commits_CommitId", + column: x => x.CommitId, + principalTable: "Commits", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_PackageVersionCommmit_Packages_PackageId", + column: x => x.PackageId, + principalTable: "Packages", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_PackageVersionCommmit_PackageVersions_PackageId_FullString_~", + columns: x => new { x.PackageId, x.FullString, x.PackageType }, + principalTable: "PackageVersions", + principalColumns: new[] { "PackageId", "FullString", "Type" }, + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_PackageVersionCommmit_PackageId_FullString_PackageType", + table: "PackageVersionCommmit", + columns: new[] { "PackageId", "FullString", "PackageType" }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "PackageVersionCommmit"); + + migrationBuilder.AlterColumn( + name: "PackageId", + table: "PackageVersions", + nullable: false, + oldClrType: typeof(string), + oldMaxLength: 1024); + + migrationBuilder.AlterColumn( + name: "Id", + table: "Packages", + nullable: false, + oldClrType: typeof(string), + oldMaxLength: 1024); + } + } +} diff --git a/src/isnd/Migrations/ApplicationDbContextModelSnapshot.cs b/src/isnd/Migrations/ApplicationDbContextModelSnapshot.cs index f7bebf6..9f9a98e 100644 --- a/src/isnd/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/src/isnd/Migrations/ApplicationDbContextModelSnapshot.cs @@ -213,10 +213,31 @@ namespace isndhost.Migrations b.ToTable("Commits"); }); + modelBuilder.Entity("isnd.Data.Historic.PackageVersionCommit", b => + { + b.Property("CommitId"); + + b.Property("PackageId") + .HasMaxLength(1024); + + b.Property("FullString") + .HasMaxLength(256); + + b.Property("PackageType") + .HasMaxLength(256); + + b.HasKey("CommitId", "PackageId", "FullString", "PackageType"); + + b.HasIndex("PackageId", "FullString", "PackageType"); + + b.ToTable("PackageVersionCommmit"); + }); + modelBuilder.Entity("isnd.Data.Package", b => { b.Property("Id") - .ValueGeneratedOnAdd(); + .ValueGeneratedOnAdd() + .HasMaxLength(1024); b.Property("CommitNId"); @@ -241,7 +262,8 @@ namespace isndhost.Migrations modelBuilder.Entity("isnd.Data.PackageVersion", b => { - b.Property("PackageId"); + b.Property("PackageId") + .HasMaxLength(1024); b.Property("FullString") .HasMaxLength(256); @@ -319,6 +341,24 @@ namespace isndhost.Migrations .OnDelete(DeleteBehavior.Cascade); }); + modelBuilder.Entity("isnd.Data.Historic.PackageVersionCommit", b => + { + b.HasOne("isnd.Data.Catalog.Commit", "Commit") + .WithMany() + .HasForeignKey("CommitId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("isnd.Data.Package", "Package") + .WithMany() + .HasForeignKey("PackageId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("isnd.Data.PackageVersion", "PackageVersion") + .WithMany() + .HasForeignKey("PackageId", "FullString", "PackageType") + .OnDelete(DeleteBehavior.Cascade); + }); + modelBuilder.Entity("isnd.Data.Package", b => { b.HasOne("isnd.Data.Catalog.Commit", "LatestVersion") diff --git a/src/isnd/Services/PackageManager.cs b/src/isnd/Services/PackageManager.cs index 5641f93..d886008 100644 --- a/src/isnd/Services/PackageManager.cs +++ b/src/isnd/Services/PackageManager.cs @@ -1,9 +1,13 @@ +using System; using System.Collections.Generic; using System.Linq; +using System.Text; +using System.Threading.Tasks; using isnd.Controllers; using isnd.Data; using isnd.Data.Catalog; using isnd.Entities; +using isnd.Helpers; using isnd.Interfaces; using isnd.ViewModels; using Microsoft.EntityFrameworkCore; @@ -68,6 +72,15 @@ namespace isnd.Services type = "SearchQueryService/3.5.0", comment = "Search Query service" }); + + if (defaultActivation || unleashClient.IsEnabled("pkg-catalog", false)) + res.Add( + new Resource + { + id = extUrl + ApiConfig.Catalog, + type = "Catalog/3.0.0", + comment = "Package Catalog Index" + }); return res; } @@ -80,7 +93,7 @@ namespace isnd.Services var scope = dbContext.Packages .Include(p => p.Versions) .Where( - p => (CamelCaseMatch(p.Id, query) || SeparatedByMinusMatch(p.Id, query)) + p => (PackageIdHelpers.CamelCaseMatch(p.Id, query) || PackageIdHelpers.SeparatedByMinusMatch(p.Id, query)) && (prerelease || p.Versions.Any(v => !v.IsPrerelease)) && (packageType == null || p.Versions.Any(v => v.Type == packageType)) ); @@ -146,15 +159,17 @@ namespace isnd.Services } return CurrentCatalogIndex; } - public void ÛpdateCatalogFor(Commit last = null) + public void ÛpdateCatalogFor(Commit reason = null) { int i = 0; int p = 0; var oldIndex = CurrentCatalogIndex; var oldPages = CurrentCatalogPages; + string baseid= extUrl + ApiConfig.Catalog; + string basepageid= extUrl + ApiConfig.CatalogPage; CurrentCatalogIndex = new CatalogIndex { - Id = extUrl, + Id = baseid, Items = new List() }; CurrentCatalogPages = new List(); @@ -170,16 +185,18 @@ namespace isnd.Services { page = new Page { - Parent = isndSettings.ExternalUrl + "/package", + Id = basepageid + "-" + p++, + Parent = baseid, CommitId = commit.CommitId, CommitTimeStamp = commit.CommitTimeStamp, - Id = this.isndSettings.ExternalUrl + "/package/index-" + p++, Items = new List() }; CurrentCatalogPages.Add(page); pageRef = new PageRef { - Id = page.Id + Id = page.Id, + CommitId = commit.CommitId, + CommitTimeStamp = commit.CommitTimeStamp }; CurrentCatalogIndex.Items.Add(pageRef); i = 0; @@ -197,54 +214,61 @@ namespace isnd.Services Where (cv => cv.CommitId == commit.CommitId) .OrderByDescending(vc => vc.CommitNId).First(); + StringBuilder refid = new StringBuilder(extUrl); + refid.AppendFormat("{0}/{1}/{2}",ApiConfig.CatalogLeaf, v.PackageId + , v.FullString); + if (v.Type!=null) + refid.AppendFormat("/{0}",v.Type); + var pkgref = new PackageRef { Version = v.FullString, LastCommit = v.LatestCommit, CommitId = v.LatestCommit.CommitId, CommitTimeStamp = v.LatestCommit.CommitTimeStamp, - RefId = isndSettings.ExternalUrl + v.NugetLink, - Id = v.PackageId + RefId = refid.ToString(), + Id = v.PackageId, + RefType = v.LatestCommit.Action == PackageAction.PublishPackage + ? "nuget:PackageDetails" : + "nuget:PackageDelete" }; page.Items.Add(pkgref); } - last = commit; + reason = commit; i++; } - if (last != null) + if (reason != null) { - CurrentCatalogIndex.CommitId = last.CommitId; + CurrentCatalogIndex.CommitId = reason.CommitId; } - else + else { + // From a fresh db CurrentCatalogIndex.CommitId = "none"; } } - protected static bool CamelCaseMatch(string id, string query) + public async Task DeletePackageAsync(string pkgId, string fullVersion, string pkgType) { - // Assert.False (q==null); - if (string.IsNullOrEmpty(query)) return true; - - while (id.Length > 0) - { - int i = 0; - while (id.Length > i && char.IsLower(id[i])) i++; - if (i == 0) break; - id = id.Substring(i); - if (id.StartsWith(query, System.StringComparison.OrdinalIgnoreCase)) return true; - } - return false; - } - protected static bool SeparatedByMinusMatch(string id, string q) - { - foreach (var part in id.Split('-')) + // TODO package deletion on disk + var commit = new Commit{ + Action = PackageAction.DeletePackage, + TimeStamp = DateTime.Now + }; + var pkg = await dbContext.PackageVersions.SingleOrDefaultAsync( + v => v.PackageId == pkgId && + v.FullString == fullVersion && + v.Type == pkgType + ); + if (pkg == null) { - if (part.StartsWith(q, System.StringComparison.OrdinalIgnoreCase)) return true; + return new PackageDeletionReport{ Deleted = false }; } - return false; + dbContext.PackageVersions.Remove(pkg); + await dbContext.SaveChangesAsync(); + ÛpdateCatalogFor(commit); + return new PackageDeletionReport{ Deleted = true }; } - } } \ No newline at end of file diff --git a/src/isnd/ViewModels/DeletePackageReport.cs b/src/isnd/ViewModels/DeletePackageReport.cs new file mode 100644 index 0000000..3b4c5ab --- /dev/null +++ b/src/isnd/ViewModels/DeletePackageReport.cs @@ -0,0 +1,7 @@ +namespace isnd.ViewModels +{ + public class PackageDeletionReport + { + public bool Deleted { get; set; } + } +} \ No newline at end of file diff --git a/src/isnd/Services/SearchResult.cs b/src/isnd/ViewModels/SearchResult.cs similarity index 100% rename from src/isnd/Services/SearchResult.cs rename to src/isnd/ViewModels/SearchResult.cs diff --git a/src/isnd/Views/Shared/_Layout.cshtml b/src/isnd/Views/Shared/_Layout.cshtml index a3a3e0d..a0389c1 100644 --- a/src/isnd/Views/Shared/_Layout.cshtml +++ b/src/isnd/Views/Shared/_Layout.cshtml @@ -38,7 +38,7 @@ diff --git a/src/isnd/isnd.csproj b/src/isnd/isnd.csproj index 2e0227c..8c556fe 100644 --- a/src/isnd/isnd.csproj +++ b/src/isnd/isnd.csproj @@ -1,5 +1,4 @@ - - + netcoreapp2.1 @@ -17,24 +16,22 @@ - + - + - - - + - +