Skip to content

Commit c735cf7

Browse files
committed
Finished implementing Web UI. 🎉
1 parent 226248d commit c735cf7

34 files changed

Lines changed: 14270 additions & 38 deletions

src/SlimGet/Controllers/GalleryController.cs

Lines changed: 152 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,18 @@
1515
// limitations under the License.
1616

1717
using System;
18+
using System.Collections.Generic;
1819
using System.Linq;
1920
using System.Threading;
2021
using System.Threading.Tasks;
2122
using Microsoft.AspNetCore.Authorization;
2223
using Microsoft.AspNetCore.Mvc;
2324
using Microsoft.EntityFrameworkCore;
2425
using Microsoft.Extensions.Options;
26+
using NuGet.Frameworks;
27+
using NuGet.Versioning;
2528
using SlimGet.Data.Configuration;
29+
using SlimGet.Data.Database;
2630
using SlimGet.Models;
2731
using SlimGet.Services;
2832

@@ -49,17 +53,74 @@ public async Task<IActionResult> Index(CancellationToken cancellationToken)
4953
return this.View(new GalleryIndexModel(pkgCount, verCount));
5054
}
5155

52-
[HttpGet, SlimGetRoute(Routing.GalleryPackageIndexRouteName)]
53-
public IActionResult Packages()
54-
=> this.View();
56+
[HttpGet, SlimGetRoute(Routing.GalleryListRouteName)]
57+
public async Task<IActionResult> Packages([FromQuery] int skip, CancellationToken cancellationToken)
58+
{
59+
if (skip < 0)
60+
return this.BadRequest();
61+
62+
var dbpackages = this.Database.Packages
63+
.Include(x => x.Tags)
64+
.Include(x => x.Authors)
65+
.Include(x => x.Versions)
66+
.OrderByDescending(x => x.DownloadCount)
67+
.ThenBy(x => x.Id);
68+
69+
var count = await dbpackages.CountAsync(cancellationToken);
70+
var next = skip + 20 <= count ? skip + 20 : -1;
71+
72+
return this.View(new GalleryPackageListModel(count, this.PreparePackages(dbpackages.Skip(skip).Take(20)), next, skip - 20));
73+
}
74+
75+
[HttpGet, SlimGetRoute(Routing.GalleryPackageRouteName)]
76+
public async Task<IActionResult> Package(string id, string version, CancellationToken cancellationToken)
77+
{
78+
var dbpackage = await this.Database.Packages
79+
.Include(x => x.Tags)
80+
.Include(x => x.Versions)
81+
.ThenInclude(x => x.Binaries)
82+
.ThenInclude(x => x.PackageSymbols)
83+
.Include(x => x.Versions)
84+
.ThenInclude(x => x.Dependencies)
85+
.Include(x => x.Authors)
86+
.FirstOrDefaultAsync(x => x.Id == id, cancellationToken).ConfigureAwait(false);
87+
88+
if (dbpackage == null)
89+
return this.NotFound();
90+
91+
var dbversion = version != null
92+
? dbpackage.Versions.FirstOrDefault(x => x.Version == version)
93+
: dbpackage.Versions.OrderByDescending(x => x.NuGetVersion).First();
94+
95+
if (dbversion == null)
96+
return this.NotFound();
97+
98+
return this.View(this.PreparePackage(dbpackage, dbversion));
99+
}
100+
101+
[HttpGet, SlimGetRoute(Routing.GallerySearchRouteName)]
102+
public async Task<IActionResult> Search([FromQuery] GallerySearchModel search, CancellationToken cancellationToken)
103+
{
104+
var query = search.Query;
105+
var prerelease = search.Prerelease;
106+
var skip = search.Skip;
107+
var dbpackages = this.Database.Packages
108+
.Include(x => x.Versions)
109+
.Include(x => x.Tags)
110+
.Include(x => x.Authors)
111+
.Where(x => (EF.Functions.Similarity(x.Id, query) >= 0.35 ||
112+
EF.Functions.Similarity(x.Description, query) >= 0.2 ||
113+
EF.Functions.Similarity(x.Title, query) >= 0.2 ||
114+
x.Tags.Any(y => EF.Functions.Similarity(y.Tag, query) >= 0.35)) &&
115+
x.Versions.Any(y => !y.IsPrerelase || prerelease))
116+
.OrderByDescending(x => x.DownloadCount)
117+
.ThenBy(x => x.Id);
55118

56-
[HttpGet, SlimGetRoute(Routing.GalleryPackageDetailsRouteName)]
57-
public IActionResult Packages(string id)
58-
=> this.View();
119+
var count = await dbpackages.CountAsync(cancellationToken);
120+
var next = skip + 20 <= count ? skip + 20 : -1;
59121

60-
[HttpGet, SlimGetRoute(Routing.GalleryPackageVersionRouteName)]
61-
public IActionResult Packages(string id, string version)
62-
=> this.View();
122+
return this.View(new GallerySearchResultModel(count, this.PreparePackages(dbpackages.Skip(skip).Take(20), prerelease), next, skip - 20, query, prerelease));
123+
}
63124

64125
[HttpGet, SlimGetRoute(Routing.GalleryAboutRouteName)]
65126
public IActionResult About()
@@ -75,5 +136,87 @@ public IActionResult About()
75136
this.PackageStorageConfiguration.SymbolsEnabled,
76137
!this.PackageStorageConfiguration.ReadOnlyFeed));
77138
}
139+
140+
private IEnumerable<GalleryPackageListItemModel> PreparePackages(IEnumerable<Package> dbpackages, bool prerelease = true)
141+
{
142+
foreach (var dbpackage in dbpackages)
143+
{
144+
var version = dbpackage.Versions
145+
.Where(x => !x.IsPrerelase || prerelease)
146+
.OrderByDescending(x => x.NuGetVersion)
147+
.First();
148+
149+
yield return new GalleryPackageListItemModel
150+
{
151+
Id = dbpackage.Id,
152+
Title = dbpackage.Title,
153+
IconUrl = dbpackage.IconUrl,
154+
Authors = dbpackage.AuthorNames,
155+
Tags = dbpackage.TagNames,
156+
DownloadCount = dbpackage.DownloadCount,
157+
PublishedAt = dbpackage.PublishedAt.Value,
158+
LastUpdatedAt = version.PublishedAt.Value,
159+
LatestVersion = version.NuGetVersion,
160+
Description = dbpackage.Description
161+
};
162+
}
163+
}
164+
165+
private GalleryPackageInfoModel PreparePackage(Package dbpackage, PackageVersion dbversion)
166+
=> new GalleryPackageInfoModel
167+
{
168+
Id = dbpackage.Id,
169+
Title = dbpackage.Title,
170+
IconUrl = dbpackage.IconUrl,
171+
ProjectUrl = dbpackage.ProjectUrl,
172+
LicenseUrl = dbpackage.LicenseUrl,
173+
RepositoryUrl = dbpackage.RepositoryUrl,
174+
Authors = dbpackage.AuthorNames,
175+
Tags = dbpackage.TagNames,
176+
DownloadCount = dbpackage.DownloadCount,
177+
VersionDownloadCount = dbversion.DownloadCount,
178+
PublishedAt = dbversion.PublishedAt.Value,
179+
Version = dbversion.NuGetVersion,
180+
Description = dbpackage.Description,
181+
DownloadUrl = this.Url.AbsoluteUrl(Routing.DownloadPackageContentsRouteName, this.HttpContext, new
182+
{
183+
id = dbpackage.IdLowercase,
184+
version = dbversion.VersionLowercase,
185+
filename = $"{dbpackage.IdLowercase}.{dbversion.VersionLowercase}"
186+
}),
187+
ManifestUrl = this.Url.AbsoluteUrl(Routing.DownloadPackageManifestRouteName, this.HttpContext, new
188+
{
189+
id = dbpackage.IdLowercase,
190+
version = dbversion.VersionLowercase,
191+
id2 = dbpackage.IdLowercase
192+
}),
193+
// figure out symbols
194+
DependencyGroups = this.PrepareDependencyGroups(dbversion),
195+
OwnerId = dbpackage.OwnerId,
196+
AllVersions = dbpackage.Versions.Select(x => (x.Version, x.DownloadCount, new DateTimeOffset(x.PublishedAt.Value)))
197+
};
198+
199+
private IEnumerable<GalleryPackageDependencyGroupModel> PrepareDependencyGroups(PackageVersion dbversion)
200+
{
201+
foreach (var depgroup in dbversion.Dependencies.GroupBy(x => x.TargetFramework))
202+
yield return new GalleryPackageDependencyGroupModel
203+
{
204+
Framework = NuGetFramework.Parse(depgroup.Key),
205+
Dependencies = this.PrepareDependencies(depgroup)
206+
};
207+
}
208+
209+
private IEnumerable<GalleryPackageDependencyModel> PrepareDependencies(IEnumerable<PackageDependency> dbdeps)
210+
{
211+
foreach (var dbdep in dbdeps)
212+
yield return new GalleryPackageDependencyModel
213+
{
214+
Id = dbdep.Id,
215+
MinVersion = dbdep.MinVersion != null ? NuGetVersion.Parse(dbdep.MinVersion) : null,
216+
MaxVersion = dbdep.MaxVersion != null ? NuGetVersion.Parse(dbdep.MaxVersion) : null,
217+
MinInclusive = dbdep.IsMinVersionInclusive.HasValue && dbdep.IsMinVersionInclusive.HasValue,
218+
MaxInclusive = dbdep.IsMaxVersionInclusive.HasValue && dbdep.IsMaxVersionInclusive.HasValue
219+
};
220+
}
78221
}
79222
}

src/SlimGet/Controllers/IndexController.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public IActionResult Index()
4444
var registrationsBase = this.CreateResourceModels(this.Url.AbsoluteUrl(Routing.RegistrationsRouteName, this.HttpContext, new { mode = RegistrationsContentMode.Plain }).ToUri(), "RegistrationsBase", "", "3.0.0-beta", "3.0.0-rc");
4545
var registrationsBaseGz = this.CreateResourceModels(this.Url.AbsoluteUrl(Routing.RegistrationsRouteName, this.HttpContext, new { mode = RegistrationsContentMode.GZip }).ToUri(), "RegistrationsBase", "3.4.0");
4646
var registrationsBaseSemVer2 = this.CreateResourceModels(this.Url.AbsoluteUrl(Routing.RegistrationsRouteName, this.HttpContext, new { mode = RegistrationsContentMode.SemVer2 }).ToUri(), "RegistrationsBase", "3.6.0");
47-
var packageDetailsUriTemplate = this.CreateResourceModels(this.Url.AbsoluteUrl(Routing.GalleryPackageVersionRouteName, this.HttpContext, new { id = "{id}", version = "{version}" }).Replace("%7B", "{").Replace("%7D", "}").ToUri(), "PackageDetailsUriTemplate", "5.1.0");
47+
var packageDetailsUriTemplate = this.CreateResourceModels(this.Url.AbsoluteUrl(Routing.GalleryPackageRouteName, this.HttpContext, new { id = "{id}", version = "{version}" }).Replace("%7B", "{").Replace("%7D", "}").ToUri(), "PackageDetailsUriTemplate", "5.1.0");
4848
var packageBaseAddress = this.CreateResourceModels(this.Url.AbsoluteUrl(Routing.DownloadPackageRouteName, this.HttpContext).ToUri(), "PackageBaseAddress", "3.0.0");
4949

5050
var resources = packagePublish

src/SlimGet/Controllers/SearchController.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public SearchController(SlimGetContext db, RedisService redis, IFileSystemServic
4141
public async Task<IActionResult> Search([FromQuery] SearchQueryModel search, CancellationToken cancellationToken)
4242
{
4343
var semver2 = search.SemVerLevel == "2.0.0";
44-
var prerelease = search.Prerelase;
44+
var prerelease = search.Prerelease;
4545
var query = search.Query;
4646
var dbpackages = this.Database.Packages
4747
.Include(x => x.Versions)
@@ -63,7 +63,7 @@ public async Task<IActionResult> Search([FromQuery] SearchQueryModel search, Can
6363
public async Task<IActionResult> Autocomplete([FromQuery] SearchQueryModel search, CancellationToken cancellationToken)
6464
{
6565
var semver2 = search.SemVerLevel == "2.0.0";
66-
var prerelease = search.Prerelase;
66+
var prerelease = search.Prerelease;
6767
if (search.Id == null)
6868
{
6969
var query = search.Query;

src/SlimGet/Models/GalleryModels.cs

Lines changed: 107 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,15 @@
1515
// limitations under the License.
1616

1717
using System;
18+
using System.Collections.Generic;
19+
using System.ComponentModel.DataAnnotations;
20+
using Microsoft.AspNetCore.Mvc;
21+
using NuGet.Frameworks;
22+
using NuGet.Versioning;
1823

1924
namespace SlimGet.Models
2025
{
21-
public class GalleryAboutModel
26+
public sealed class GalleryAboutModel
2227
{
2328
public Uri NuGetFeedUrl { get; }
2429
public Uri SymbolsUrl { get; }
@@ -36,7 +41,7 @@ public GalleryAboutModel(Uri feedUrl, Uri symbolsUrl, Uri symbolsPushUrl, bool s
3641
}
3742
}
3843

39-
public class GalleryIndexModel
44+
public sealed class GalleryIndexModel
4045
{
4146
public int PackageCount { get; }
4247
public int VersionCount { get; }
@@ -47,4 +52,104 @@ public GalleryIndexModel(int pkgCount, int verCount)
4752
this.VersionCount = verCount;
4853
}
4954
}
55+
56+
public sealed class GallerySearchModel
57+
{
58+
[FromQuery(Name = "q"), Display(Name = "Search query"), StringLength(32767, ErrorMessage = "Package ID query is too long.")]
59+
public string Query { get; set; }
60+
61+
[FromQuery(Name = "skip"), Range(0, int.MaxValue, ErrorMessage = "Skip amount cannot be less than 0.")]
62+
public int Skip { get; set; } = 0;
63+
64+
[FromQuery(Name = "pre"), Display(Name = "Include prerelease versions")]
65+
public bool Prerelease { get; set; } = false;
66+
}
67+
68+
public sealed class GalleryPackageListModel
69+
{
70+
public int TotalCount { get; }
71+
public IEnumerable<GalleryPackageListItemModel> Items { get; }
72+
public int NextPage { get; }
73+
public int PreviousPage { get; }
74+
75+
public GalleryPackageListModel(int total, IEnumerable<GalleryPackageListItemModel> items, int next, int prev)
76+
{
77+
this.TotalCount = total;
78+
this.Items = items;
79+
this.NextPage = next;
80+
this.PreviousPage = prev;
81+
}
82+
}
83+
84+
public sealed class GalleryPackageListItemModel
85+
{
86+
public string Id { get; set; }
87+
public string Title { get; set; }
88+
public string IconUrl { get; set; }
89+
public IEnumerable<string> Authors { get; set; }
90+
public IEnumerable<string> Tags { get; set; }
91+
public long DownloadCount { get; set; }
92+
public DateTimeOffset PublishedAt { get; set; }
93+
public DateTimeOffset LastUpdatedAt { get; set; }
94+
public NuGetVersion LatestVersion { get; set; }
95+
public string Description { get; set; }
96+
}
97+
98+
public sealed class GallerySearchResultModel
99+
{
100+
public int TotalCount { get; }
101+
public IEnumerable<GalleryPackageListItemModel> Items { get; }
102+
public int NextPage { get; }
103+
public int PreviousPage { get; }
104+
public string Query { get; }
105+
public bool IncludePrerelease { get; }
106+
107+
public GallerySearchResultModel(int total, IEnumerable<GalleryPackageListItemModel> items, int next, int prev, string query, bool prerelease)
108+
{
109+
this.TotalCount = total;
110+
this.Items = items;
111+
this.NextPage = next;
112+
this.PreviousPage = prev;
113+
this.Query = query;
114+
this.IncludePrerelease = prerelease;
115+
}
116+
}
117+
118+
public sealed class GalleryPackageInfoModel
119+
{
120+
public string Id { get; set; }
121+
public string Title { get; set; }
122+
public string IconUrl { get; set; }
123+
public string ProjectUrl { get; set; }
124+
public string LicenseUrl { get; set; }
125+
public string RepositoryUrl { get; set; }
126+
public IEnumerable<string> Authors { get; set; }
127+
public IEnumerable<string> Tags { get; set; }
128+
public long DownloadCount { get; set; }
129+
public long VersionDownloadCount { get; set; }
130+
public DateTimeOffset PublishedAt { get; set; }
131+
public NuGetVersion Version { get; set; }
132+
public string Description { get; set; }
133+
public string DownloadUrl { get; set; }
134+
public string ManifestUrl { get; set; }
135+
//public string SymbolsUrl { get; set; }
136+
public IEnumerable<GalleryPackageDependencyGroupModel> DependencyGroups { get; set; }
137+
public string OwnerId { get; set; }
138+
public IEnumerable<(string version, long downloads, DateTimeOffset publishedAt)> AllVersions { get; set; }
139+
}
140+
141+
public sealed class GalleryPackageDependencyGroupModel
142+
{
143+
public NuGetFramework Framework { get; set; }
144+
public IEnumerable<GalleryPackageDependencyModel> Dependencies { get; set; }
145+
}
146+
147+
public sealed class GalleryPackageDependencyModel
148+
{
149+
public string Id { get; set; }
150+
public NuGetVersion MinVersion { get; set; }
151+
public NuGetVersion MaxVersion { get; set; }
152+
public bool MinInclusive { get; set; }
153+
public bool MaxInclusive { get; set; }
154+
}
50155
}

src/SlimGet/Models/SearchQueryModels.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public sealed class SearchQueryModel
3434
public int Take { get; set; } = 20;
3535

3636
[FromQuery(Name = "prerelease")]
37-
public bool Prerelase { get; set; } = false;
37+
public bool Prerelease { get; set; } = false;
3838

3939
[FromQuery(Name = "semVerLevel")]
4040
public string SemVerLevel { get; set; } = "2.0.0";

src/SlimGet/Routing.cs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -78,12 +78,12 @@ public static class Routing
7878
public const string GalleryRoute = "/gallery";
7979
public const string GalleryIndexRouteName = "Web,Gallery,Index";
8080
public const string GalleryIndexRoute = "/";
81-
public const string GalleryPackageIndexRouteName = "Web,Gallery,PackageIndex";
82-
public const string GalleryPackageIndexRoute = "packages";
83-
public const string GalleryPackageDetailsRouteName = "Web,Gallery,PackageDetails";
84-
public const string GalleryPackageDetailsRoute = "packages/{id}";
85-
public const string GalleryPackageVersionRouteName = "Web,Gallery,PackageVersion";
86-
public const string GalleryPackageVersionRoute = "packages/{id}/{version}";
81+
public const string GalleryListRouteName = "Web,Gallery,PackageList";
82+
public const string GalleryListRoute = "packages";
83+
public const string GalleryPackageRouteName = "Web,Gallery,Package";
84+
public const string GalleryPackageRoute = "package/{id}/{version?}";
85+
public const string GallerySearchRouteName = "Web,Gallery,PackageSearch";
86+
public const string GallerySearchRoute = "search";
8787
public const string GalleryAboutRouteName = "Web,Gallery,About";
8888
public const string GalleryAboutRoute = "about";
8989
// ENDOF: Gallery
@@ -145,9 +145,9 @@ public static class Routing
145145
// BEGIN: Gallery
146146
[GalleryRouteName] = GalleryRoute,
147147
[GalleryIndexRouteName] = GalleryIndexRoute,
148-
[GalleryPackageIndexRouteName] = GalleryPackageIndexRoute,
149-
[GalleryPackageDetailsRouteName] = GalleryPackageDetailsRoute,
150-
[GalleryPackageVersionRouteName] = GalleryPackageVersionRoute,
148+
[GalleryListRouteName] = GalleryListRoute,
149+
[GalleryPackageRouteName] = GalleryPackageRoute,
150+
[GallerySearchRouteName] = GallerySearchRoute,
151151
[GalleryAboutRouteName] = GalleryAboutRoute,
152152
// ENDOF: Gallery
153153

0 commit comments

Comments
 (0)