diff --git a/src/isnd/Controllers/PackagesController.cs b/src/isnd/Controllers/PackagesController.cs index f680b58..2463d41 100644 --- a/src/isnd/Controllers/PackagesController.cs +++ b/src/isnd/Controllers/PackagesController.cs @@ -10,15 +10,11 @@ using Microsoft.Extensions.Options; using NuGet.Versioning; using isnd.Data; using isnd.Entities; -using Unleash.ClientFactory; using Unleash; -using System.Collections.Generic; using isnd.Services; -using isnd.Entities; -using Microsoft.AspNetCore.Hosting; -using isnd.Helpers; using isnd.ViewModels; using System.Threading.Tasks; +using isnd.Interfaces; namespace isnd.Controllers { @@ -36,7 +32,7 @@ namespace isnd.Controllers private readonly IsndSettings _isndSettings; readonly ApplicationDbContext _dbContext; - private readonly PackageManager _packageManager; + private readonly IPackageManager _packageManager; private readonly IUnleash _unleashĈlient; public PackagesController( @@ -44,13 +40,14 @@ namespace isnd.Controllers IDataProtectionProvider provider, IOptions isndOptions, IUnleash unleashĈlient, - ApplicationDbContext dbContext) + ApplicationDbContext dbContext, + IPackageManager pm) { _logger = loggerFactory.CreateLogger(); _isndSettings = isndOptions.Value; _protector = provider.CreateProtector(_isndSettings.ProtectionTitle); _dbContext = dbContext; - _packageManager = new PackageManager(dbContext); + _packageManager = pm; _unleashĈlient = unleashĈlient; _ressources = _packageManager.GetResources(_unleashĈlient).ToArray(); } @@ -104,33 +101,12 @@ namespace isnd.Controllers return Ok(_ressources); } - - [HttpGet(_pkgRootPrefix + "/index.json")] - public IActionResult Index( - string q, - string semVerLevel, - bool prerelease = false, - string packageType = null, - int skip = 0, - int take = 25) +// + [HttpGet(_pkgRootPrefix)] + public IActionResult Index() { - if (string.IsNullOrEmpty(q)) - { - ModelState.AddModelError("q", "no value"); - } - if (take > maxTake) - { - ModelState.AddModelError("take", "Maximum exceeded"); - } - if (semVerLevel != defaultSemVer) - { - _logger.LogWarning("Unexpected sementic version : "+semVerLevel); - } - if (ModelState.IsValid) - { - return Ok(_packageManager.SearchByName(q,skip,take,prerelease,packageType)); - } - return BadRequest(new { error = ModelState }); + // https://docs.microsoft.com/en-us/nuget/api/catalog-resource#versioning + return Ok(PackageManager.CurrentCatalogIndex); } // GET /autocomplete?id=isn.protocol&prerelease=true diff --git a/src/isnd/Controllers/Resource.cs b/src/isnd/Controllers/Resource.cs index 437bb5d..7157308 100644 --- a/src/isnd/Controllers/Resource.cs +++ b/src/isnd/Controllers/Resource.cs @@ -1,6 +1,6 @@ namespace isnd.Controllers { - internal class Resource + public class Resource { public string id {get; set; } public string type {get; set; } diff --git a/src/isnd/Data/ApplicationDbContext.cs b/src/isnd/Data/ApplicationDbContext.cs index e52429b..b5ef97a 100644 --- a/src/isnd/Data/ApplicationDbContext.cs +++ b/src/isnd/Data/ApplicationDbContext.cs @@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; using isnd.Data; using isnd.Data.ApiKeys; +using isnd.Data.Catalog; namespace isnd.Data { @@ -28,5 +29,6 @@ namespace isnd.Data public DbSet ApiKeys { get; set; } public DbSet Packages { get; set; } public DbSet PackageVersions { get; set; } + public DbSet Commits { get; set; } } } diff --git a/src/isnd/Data/Catalog/CatalogIndex.cs b/src/isnd/Data/Catalog/CatalogIndex.cs new file mode 100644 index 0000000..f68f3fb --- /dev/null +++ b/src/isnd/Data/Catalog/CatalogIndex.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace isnd.Data.Catalog +{ + public class CatalogIndex : IObject + { + [JsonProperty("@id")] + public string Id { get; set ; } + + [JsonProperty("items")] + public List Items { get; set; } + + + [JsonProperty("count")] + public int Count { get => Items.Count; } + public string CommitId { get; set; } + public DateTime CommitTimeStamp { get; set; } + + } + + +} \ No newline at end of file diff --git a/src/isnd/Data/Catalog/Commit.cs b/src/isnd/Data/Catalog/Commit.cs new file mode 100644 index 0000000..c0c23fb --- /dev/null +++ b/src/isnd/Data/Catalog/Commit.cs @@ -0,0 +1,32 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace isnd.Data.Catalog +{ + public enum PackageAction + { + DeletePackage, + PublishPackage + } + + public class Commit : IObject + { + [Key][Required,DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public long Id { get; set; } + + [Required] + public DateTime TimeStamp{ get; set; } + + [Required] + public PackageAction Action { get; set; } + + [Required] + public string PackageId { get; set; } + [ForeignKey("PackageId")] + + public virtual Package Package{ get; set; } + public string CommitId { get => Id.ToString(); } + public DateTime CommitTimeStamp { get => TimeStamp; } + } +} \ No newline at end of file diff --git a/src/isnd/Data/Catalog/IObject.cs b/src/isnd/Data/Catalog/IObject.cs new file mode 100644 index 0000000..0ec1c22 --- /dev/null +++ b/src/isnd/Data/Catalog/IObject.cs @@ -0,0 +1,14 @@ +using System; +using Newtonsoft.Json; + +namespace isnd.Data.Catalog +{ + public interface IObject + { + [JsonProperty("commitId")] + string CommitId { get; } + + [JsonProperty("commitTimeStamp")] + DateTime CommitTimeStamp { get; } + } +} \ No newline at end of file diff --git a/src/isnd/Data/Catalog/PackageRef.cs b/src/isnd/Data/Catalog/PackageRef.cs new file mode 100644 index 0000000..8b86bfd --- /dev/null +++ b/src/isnd/Data/Catalog/PackageRef.cs @@ -0,0 +1,34 @@ +using System; +using Newtonsoft.Json; + +namespace isnd.Data.Catalog +{ + /// + /// An presence of package in a catalog, + /// for availability, or deletion, + /// + /// + public class PackageRef : IObject + { + + + [JsonProperty("@id")] + public string RefId { get; set; } + + /// + /// Reference type : + /// nuget:PackageDetails vs nuget:PackageDelete + /// + /// + [JsonProperty("@type")] + public string RefType { get; set; } + + [JsonProperty("nuget:id")] + public string Id { get; set; } + + [JsonProperty("nuget:version")] + public string Version { get; set; } + public string CommitId { get; set; } + public DateTime CommitTimeStamp { get; set; } + } +} \ No newline at end of file diff --git a/src/isnd/Data/Catalog/Page.cs b/src/isnd/Data/Catalog/Page.cs new file mode 100644 index 0000000..c1781c8 --- /dev/null +++ b/src/isnd/Data/Catalog/Page.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace isnd.Data.Catalog +{ + public class Page : IObject + { + [JsonProperty("@id")] + public string Id { get; set; } + + [JsonProperty("parent")] + public string Parent { get; set; } + + [JsonProperty("items")] + public virtual List Items { get; set; } + public string CommitId { get; set; } + public DateTime CommitTimeStamp { get; set; } + } +} \ No newline at end of file diff --git a/src/isnd/Data/Catalog/PageRef.cs b/src/isnd/Data/Catalog/PageRef.cs new file mode 100644 index 0000000..db5032d --- /dev/null +++ b/src/isnd/Data/Catalog/PageRef.cs @@ -0,0 +1,26 @@ +using System; +using Newtonsoft.Json; + +namespace isnd.Data.Catalog +{ + public class PageRef : IObject + { + /// + /// Page Url + /// + /// + [JsonProperty("@id")] + public string Id { get; set; } + + /// + /// Page entry count + /// + /// + [JsonProperty("count")] + public int Count { get; set; } + public string CommitId { get; set; } + public DateTime CommitTimeStamp { get; set; } + } + + +} \ No newline at end of file diff --git a/src/isnd/Data/Package.cs b/src/isnd/Data/Package.cs index bd0a733..0470821 100644 --- a/src/isnd/Data/Package.cs +++ b/src/isnd/Data/Package.cs @@ -1,11 +1,13 @@ +using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using isnd.Data.Catalog; using Newtonsoft.Json; namespace isnd.Data { - public class Package + public class Package : IObject { [Key][Required] public string Id { get; set; } @@ -17,11 +19,15 @@ namespace isnd.Data [StringLength(1024)] public string Description { get; set; } + public bool Public { get ; set;} + [JsonIgnore] virtual public ApplicationUser Owner { get; set; } [JsonIgnore] public virtual List Versions { get; set; } + public string CommitId { get; set; } + public DateTime CommitTimeStamp { get; set; } } } \ No newline at end of file diff --git a/src/isnd/Entities/NugetSettings.cs b/src/isnd/Entities/IsndSettings.cs similarity index 68% rename from src/isnd/Entities/NugetSettings.cs rename to src/isnd/Entities/IsndSettings.cs index 8072753..2680eee 100644 --- a/src/isnd/Entities/NugetSettings.cs +++ b/src/isnd/Entities/IsndSettings.cs @@ -2,8 +2,11 @@ namespace isnd.Entities { public class IsndSettings { + public string ExternalUrl { get; set; } + public string ProtectionTitle {get; set;} public string PackagesRootDir {get; set;} + public string CatalogDir {get; set;} public int MaxUserKeyCount {get; set;} } diff --git a/src/isnd/Interfaces/IPackageManager.cs b/src/isnd/Interfaces/IPackageManager.cs new file mode 100644 index 0000000..b807b4b --- /dev/null +++ b/src/isnd/Interfaces/IPackageManager.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using isnd.Controllers; +using isnd.Data.Catalog; +using isnd.Services; +using isnd.ViewModels; +using NuGet.Versioning; +using Unleash; + +namespace isnd.Interfaces +{ + public interface IPackageManager + { + AutoCompleteResult AutoComplete(string id, int skip, int take, bool prerelease = false, string packageType = null); + Page CatalogPage(); + CatalogIndex GenerateCatalogIndex(string commitId); + CatalogIndex GetCatalogIndex(); + string[] GetVersions(string id, NuGetVersion parsedVersion, bool prerelease = false, string packageType = null, int skip = 0, int take = 25); + void PublishCatalog(); + PackageIndexViewModel SearchByName(string query, int skip, int take, bool prerelease = false, string packageType = null); + IEnumerable GetResources(IUnleash unleashĈlient); + } + +} \ No newline at end of file diff --git a/src/isnd/Services/PackageManager.cs b/src/isnd/Services/PackageManager.cs index 9b383d5..1a261ae 100644 --- a/src/isnd/Services/PackageManager.cs +++ b/src/isnd/Services/PackageManager.cs @@ -1,23 +1,34 @@ +using System; using System.Collections.Generic; using System.Linq; using isnd.Controllers; using isnd.Data; +using isnd.Data.Catalog; +using isnd.Entities; +using isnd.Interfaces; using isnd.ViewModels; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Options; using NuGet.Versioning; using Unleash; namespace isnd.Services { - public class PackageManager + + + public class PackageManager : IPackageManager { ApplicationDbContext dbContext; - public PackageManager(ApplicationDbContext dbContext) + public PackageManager(ApplicationDbContext dbContext, IOptions pmConfigOptions) { this.dbContext = dbContext; + CurrentCatalogIndex = GetCatalogIndex(); + this.pmConfigOptions = pmConfigOptions.Value; } - public PackageIndexViewModel SearchByName(string query, - int skip, int take,bool prerelease = false, + + + public PackageIndexViewModel SearchByName(string query, + int skip, int take, bool prerelease = false, string packageType = null) { @@ -32,14 +43,63 @@ namespace isnd.Services var pkgs = scope.Skip(skip).Take(take).ToArray(); return new PackageIndexViewModel + { + query = query, + totalHits = total, + data = pkgs + }; + } + const int maxPageLen = 512; + + public CatalogIndex GenerateCatalogIndex(string commitId) + { + + var root = "/index.json"; + + + var catalog = new CatalogIndex + { + CommitId = commitId, + CommitTimeStamp = DateTime.Now + }; + var scope = dbContext.Packages.Where(p => p.Public) + .OrderBy(p => p.CommitTimeStamp); + catalog.Items = new List(); + + int pagecount = (int)(scope.Count() / maxPageLen); + + for (int pagelen = 0, pagenum = 0; pagelen < pagecount; pagenum++) + { + Page p = new Page { - query = query, - totalHits = total, - data = pkgs + }; + + } + dbContext.Packages.Where(p => p.Public) + .OrderBy(p => p.CommitTimeStamp); + return catalog; + } - public AutoCompleteResult AutoComplete (string id, - int skip, int take, bool prerelease = false, + public Page CatalogPage() + { + var scope = dbContext.Packages + .Where(P => P.Versions.Count > 0) + .Select( + p => new PackageRef { Id = p.Id, Version = p.Versions.Max().FullString }); + + return new Page + { + Items = scope.ToList() + }; + } + public void PublishCatalog() + { + + } + + public AutoCompleteResult AutoComplete(string id, + int skip, int take, bool prerelease = false, string packageType = null) { var scope = dbContext.PackageVersions.Where( @@ -49,11 +109,11 @@ namespace isnd.Services ) .OrderBy(v => v.FullString); return new AutoCompleteResult - { - totalHits = scope.Count(), - data = scope.Select(v => v.FullString) + { + totalHits = scope.Count(), + data = scope.Select(v => v.FullString) .Skip(skip).Take(take).ToArray() - }; + }; } // TODO stocker MetaData plutôt que FullString en base, @@ -77,11 +137,28 @@ namespace isnd.Services .Skip(skip).Take(take).ToArray(); } - protected static bool CamelCaseMatch(string id, string q) + public static CatalogIndex CurrentCatalogIndex { get; protected set; } + + private IsndSettings pmConfigOptions; + + public virtual CatalogIndex GetCatalogIndex() + { + if (CurrentCatalogIndex == null) + { + LoadCatalogFromDb(); + } + return CurrentCatalogIndex; + } + void LoadCatalogFromDb() + { + dbContext.Commits.OrderBy(c => c.TimeStamp); + throw new NotImplementedException(); + } + + protected static bool CamelCaseMatch(string id, string query) { // Assert.False (q==null); - string query = q; - if (query.Length == 0) return false; + if (string.IsNullOrEmpty(query)) return true; while (id.Length > 0) { @@ -89,7 +166,7 @@ namespace isnd.Services while (id.Length > i && char.IsLower(id[i])) i++; if (i == 0) break; id = id.Substring(i); - if (id.StartsWith(q, System.StringComparison.OrdinalIgnoreCase)) return true; + if (id.StartsWith(query, System.StringComparison.OrdinalIgnoreCase)) return true; } return false; } @@ -102,7 +179,7 @@ namespace isnd.Services return false; } - internal List GetResources(IUnleash unleashClient) + public IEnumerable GetResources(IUnleash unleashClient) { var res = new List(); if (unleashClient.IsEnabled("pkg-push")) @@ -125,7 +202,7 @@ namespace isnd.Services res.Add( new Resource { - id = "package/index.json", + id = "package", type = "SearchAutocompleteService/3.5.0", comment = "Auto complete service" }); @@ -133,7 +210,7 @@ namespace isnd.Services res.Add( new Resource { - id = "package/index.json", + id = "package", type = "SearchQueryService/3.5.0", comment = "Search Query service" }); diff --git a/src/isnd/Startup.cs b/src/isnd/Startup.cs index 13a108a..5068155 100644 --- a/src/isnd/Startup.cs +++ b/src/isnd/Startup.cs @@ -71,10 +71,12 @@ namespace isnd var config = s.GetRequiredService>(); return s.GetRequiredService().CreateUnleahClient(config.Value); }); + services.AddSingleton(); + // _unleashĈlient = env.CreateUnleahClient(unleashClientSettings.Value); var smtpSettingsconf = Configuration.GetSection("Smtp"); services.Configure(smtpSettingsconf); - var isndSettingsconf = Configuration.GetSection("Nuget"); + var isndSettingsconf = Configuration.GetSection("Isn"); services.Configure(isndSettingsconf); var adminStartupListConf = Configuration.GetSection("AdminList"); services.Configure(adminStartupListConf); @@ -85,6 +87,7 @@ namespace isnd public static IUnleash UnleashĈlient { get; private set; } + public static string ExternalAddress { get; internal set; } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. diff --git a/src/isnd/appsettings.Development.json b/src/isnd/appsettings.Development.json index 62e3baa..f0eee3f 100644 --- a/src/isnd/appsettings.Development.json +++ b/src/isnd/appsettings.Development.json @@ -4,7 +4,8 @@ "paul@pschneider.fr" ] }, - "Nuget": { + "Isn": { + "ExternalUrl": "http://localhost:5000", "PackagesRootDir" : "packages", "ProtectionTitle": "protected-data-v1", "MaxUserKeyCount": 5 diff --git a/src/isnd/appsettings.json b/src/isnd/appsettings.json index 5b3a64e..325cb20 100644 --- a/src/isnd/appsettings.json +++ b/src/isnd/appsettings.json @@ -4,7 +4,8 @@ "happy-new-root" ] }, - "Nuget": { + "Isn": { + "ExternalUrl": "", "PackagesRootDir" : "", "ProtectionTitle": "protected-data-v1", "MaxUserKeyCount": 1