Skip to content

Commit 0c7b21a

Browse files
committed
Add configuration value to redirect specific curated feeds to main feed (#6489)
Address #6472
1 parent 2365944 commit 0c7b21a

5 files changed

Lines changed: 246 additions & 22 deletions

File tree

src/NuGetGallery/Configuration/AppConfiguration.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,5 +348,9 @@ public string ExternalBrandingMessage
348348
[DefaultValue(null)]
349349
[TypeConverter(typeof(StringArrayConverter))]
350350
public string[] DisabledCuratedFeeds { get; set; }
351+
352+
[DefaultValue(null)]
353+
[TypeConverter(typeof(StringArrayConverter))]
354+
public string[] RedirectedCuratedFeeds { get; set; }
351355
}
352356
}

src/NuGetGallery/Configuration/IAppConfiguration.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,5 +355,10 @@ public interface IAppConfiguration : ICoreMessageServiceConfiguration
355355
/// it doesn't exist.
356356
/// </summary>
357357
string[] DisabledCuratedFeeds { get; set; }
358+
359+
/// <summary>
360+
/// The name of zero or more curated feeds that are redirected to the main feed.
361+
/// </summary>
362+
string[] RedirectedCuratedFeeds { get; set; }
358363
}
359364
}

src/NuGetGallery/Controllers/ODataV2CuratedFeedController.cs

Lines changed: 75 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,19 @@ public class ODataV2CuratedFeedController
2525
private readonly IGalleryConfigurationService _configurationService;
2626
private readonly ISearchService _searchService;
2727
private readonly ICuratedFeedService _curatedFeedService;
28+
private readonly IEntityRepository<Package> _packagesRepository;
2829

2930
public ODataV2CuratedFeedController(
3031
IGalleryConfigurationService configurationService,
3132
ISearchService searchService,
32-
ICuratedFeedService curatedFeedService)
33+
ICuratedFeedService curatedFeedService,
34+
IEntityRepository<Package> packagesRepository)
3335
: base(configurationService)
3436
{
3537
_configurationService = configurationService;
3638
_searchService = searchService;
3739
_curatedFeedService = curatedFeedService;
40+
_packagesRepository = packagesRepository;
3841
}
3942

4043
// /api/v2/curated-feed/curatedFeedName/Packages?semVerLevel=
@@ -46,15 +49,16 @@ public IHttpActionResult Get(
4649
string curatedFeedName,
4750
[FromUri] string semVerLevel = null)
4851
{
49-
if (_curatedFeedService.GetFeedByName(curatedFeedName) == null)
52+
var result = GetCuratedFeedResult(curatedFeedName);
53+
if (result.ActionResult != null)
5054
{
51-
return NotFound();
55+
return result.ActionResult;
5256
}
5357

5458
var semVerLevelKey = SemVerLevelKey.ForSemVerLevel(semVerLevel);
5559

56-
var queryable = _curatedFeedService
57-
.GetPackages(curatedFeedName)
60+
var queryable = result
61+
.Packages
5862
.Where(p => p.PackageStatusKey == PackageStatus.Available)
5963
.Where(SemVerLevelKey.IsPackageCompliantWithSemVerLevelPredicate(semVerLevel))
6064
.ToV2FeedPackageQuery(
@@ -130,14 +134,14 @@ private async Task<IHttpActionResult> GetCore(
130134
bool return404NotFoundWhenNoResults,
131135
string semVerLevel)
132136
{
133-
var curatedFeed = _curatedFeedService.GetFeedByName(curatedFeedName);
134-
if (curatedFeed == null)
137+
var result = GetCuratedFeedResult(curatedFeedName);
138+
if (result.ActionResult != null)
135139
{
136-
return NotFound();
140+
return result.ActionResult;
137141
}
138142

139-
var packages = _curatedFeedService
140-
.GetPackages(curatedFeedName)
143+
var packages = result
144+
.Packages
141145
.Where(p => p.PackageStatusKey == PackageStatus.Available)
142146
.Where(SemVerLevelKey.IsPackageCompliantWithSemVerLevelPredicate(semVerLevel))
143147
.Where(p => p.PackageRegistration.Id.Equals(id, StringComparison.OrdinalIgnoreCase));
@@ -158,7 +162,7 @@ private async Task<IHttpActionResult> GetCore(
158162
packages,
159163
id,
160164
normalizedVersion,
161-
curatedFeed: curatedFeed,
165+
curatedFeed: result.CuratedFeed,
162166
semVerLevel: semVerLevel);
163167

164168
// If intercepted, create a paged queryresult
@@ -228,9 +232,10 @@ public async Task<IHttpActionResult> Search(
228232
[FromODataUri]bool includePrerelease = false,
229233
[FromUri]string semVerLevel = null)
230234
{
231-
if (_curatedFeedService.GetFeedByName(curatedFeedName) == null)
235+
var result = GetCuratedFeedResult(curatedFeedName);
236+
if (result.ActionResult != null)
232237
{
233-
return NotFound();
238+
return result.ActionResult;
234239
}
235240

236241
// Handle OData-style |-separated list of frameworks.
@@ -251,8 +256,8 @@ public async Task<IHttpActionResult> Search(
251256
}
252257

253258
// Perform actual search
254-
var curatedFeed = _curatedFeedService.GetFeedByName(curatedFeedName);
255-
var packages = _curatedFeedService.GetPackages(curatedFeedName)
259+
var packages = result
260+
.Packages
256261
.Where(p => p.PackageStatusKey == PackageStatus.Available)
257262
.Where(SemVerLevelKey.IsPackageCompliantWithSemVerLevelPredicate(semVerLevel))
258263
.OrderBy(p => p.PackageRegistration.Id).ThenBy(p => p.Version);
@@ -265,7 +270,7 @@ public async Task<IHttpActionResult> Search(
265270
searchTerm,
266271
targetFramework,
267272
includePrerelease,
268-
curatedFeed: curatedFeed,
273+
curatedFeed: result.CuratedFeed,
269274
semVerLevel: semVerLevel);
270275

271276
// Packages provided by search service (even when not hijacked)
@@ -326,5 +331,59 @@ public async Task<IHttpActionResult> SearchCount(
326331
var searchResults = await Search(options, curatedFeedName, searchTerm, targetFramework, includePrerelease, semVerLevel);
327332
return searchResults.FormattedAsCountResult<V2FeedPackage>();
328333
}
334+
335+
private bool IsCuratedFeedRedirected(string name)
336+
{
337+
if (_configurationService.Current.RedirectedCuratedFeeds == null)
338+
{
339+
return false;
340+
}
341+
342+
return _configurationService
343+
.Current
344+
.RedirectedCuratedFeeds
345+
.Contains(name, StringComparer.OrdinalIgnoreCase);
346+
}
347+
348+
private CuratedFeedResult GetCuratedFeedResult(string curatedFeedName)
349+
{
350+
IQueryable<Package> packages;
351+
CuratedFeed curatedFeed;
352+
if (IsCuratedFeedRedirected(curatedFeedName))
353+
{
354+
curatedFeed = null;
355+
packages = _packagesRepository.GetAll();
356+
}
357+
else
358+
{
359+
curatedFeed = _curatedFeedService.GetFeedByName(curatedFeedName);
360+
if (curatedFeed == null)
361+
{
362+
return new CuratedFeedResult(NotFound());
363+
}
364+
365+
packages = _curatedFeedService.GetPackages(curatedFeedName);
366+
}
367+
368+
return new CuratedFeedResult(packages, curatedFeed);
369+
}
370+
371+
private class CuratedFeedResult
372+
{
373+
public CuratedFeedResult(IHttpActionResult actionResult)
374+
{
375+
ActionResult = actionResult ?? throw new ArgumentNullException(nameof(actionResult));
376+
}
377+
378+
public CuratedFeedResult(IQueryable<Package> packages, CuratedFeed curatedFeed)
379+
{
380+
Packages = packages ?? throw new ArgumentNullException(nameof(packages));
381+
CuratedFeed = curatedFeed;
382+
}
383+
384+
public IQueryable<Package> Packages { get; }
385+
public CuratedFeed CuratedFeed { get; }
386+
public IHttpActionResult ActionResult { get; }
387+
}
329388
}
330389
}

tests/NuGetGallery.Facts/Controllers/ODataFeedControllerFactsBase.cs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ namespace NuGetGallery.Controllers
2424
public abstract class ODataFeedControllerFactsBase<TController>
2525
where TController : NuGetODataController
2626
{
27-
private const string _siteRoot = "https://nuget.localtest.me";
27+
protected const string _siteRoot = "https://nuget.localtest.me";
2828
protected const string TestPackageId = "Some.Awesome.Package";
2929

3030
protected readonly IReadOnlyCollection<Package> AvailablePackages;
@@ -60,7 +60,14 @@ protected TController CreateTestableODataFeedController(HttpRequestMessage reque
6060
configurationService.Current.SiteRoot = _siteRoot;
6161

6262
var controller = CreateController(PackagesRepository, configurationService, searchService);
63-
63+
64+
AddRequestToController(request, controller);
65+
66+
return controller;
67+
}
68+
69+
protected static void AddRequestToController(HttpRequestMessage request, TController controller)
70+
{
6471
var httpRequest = new HttpRequest(string.Empty, request.RequestUri.AbsoluteUri, request.RequestUri.Query);
6572
var httpResponse = new HttpResponse(new StringWriter());
6673
var httpContext = new HttpContext(httpRequest, httpResponse);
@@ -72,8 +79,6 @@ protected TController CreateTestableODataFeedController(HttpRequestMessage reque
7279

7380
controller.ControllerContext.Controller = controller;
7481
controller.ControllerContext.Configuration = new HttpConfiguration();
75-
76-
return controller;
7782
}
7883

7984
protected async Task<IReadOnlyCollection<TFeedPackage>> GetCollection<TFeedPackage>(
@@ -208,7 +213,7 @@ private static IQueryable<Package> CreatePackagesQueryable()
208213
return list.AsQueryable();
209214
}
210215

211-
private static ODataQueryContext CreateODataQueryContext<TFeedPackage>()
216+
protected static ODataQueryContext CreateODataQueryContext<TFeedPackage>()
212217
where TFeedPackage : class
213218
{
214219
var oDataModelBuilder = new ODataConventionModelBuilder();

0 commit comments

Comments
 (0)