Skip to content

Commit 4e82fb1

Browse files
authored
feat(requests): add compact view, sorting and user filtering to requests list (#5424)
* feat(requests): add compact view, sorting and user filtering to requests list Addresses the feedback in #5422 about the card redesign losing the old table's capabilities: - Compact (table) view for movies, TV and albums with a cards/compact toggle persisted per media type; column headers are click-to-sort - Sort controls (field dropdown + direction toggle) in both views, persisted and applied server-side - Requested-by user filter for admins/power users, applied server-side via a new optional requestedBy query parameter on the v2 request list endpoints - Fix music request sorting: ascending order previously always sorted by requested date, and the sort property was resolved against the wrong entity type * chore: relax global.json SDK rollForward to latestMajor Allows building with any newer installed .NET SDK when the pinned 8.0.419 feature band is not available locally. * style(requests): redesign toolbar with cohesive pill controls Replace the stock Material outline selects with custom pill-style menu buttons that match the existing filter chips and dark theme: - User filter, sort field and page size become rounded pill buttons backed by mat-menu, with icons, carets and an accent highlight when a user filter is active - Sort field + direction joined into a single segmented pill group - View toggle restyled as a rounded segmented control - Dark dropdown panels with an accent check on the active option - Select All wrapped in a matching pill * style(requests): align Select All with the pill toolbar design Replace the raw Material checkbox in the toolbar with a pill toggle button matching the other controls. The icon reflects selection state (empty square / minus for partial / check for all) and the pill lights up in the accent colour while anything is selected. * fix: address CodeRabbit review — safer SDK roll-forward and stylelint block disable - global.json: revert rollForward from latestMajor to latestMinor so the build stays within .NET 8 rather than potentially picking up .NET 9/10 - _shared-card-grid.scss: replace stylelint-disable-next-line with a block-style disable/enable pair so both ::ng-deep selectors in the comma-separated rule are suppressed * fix(engines): address self-review findings — auth, null-ref, and double-query bugs - FilterByRequestedUser: add isAdmin guard so only admins/power-users can filter the request list by another user's ID; unauthenticated or non-admin callers receive the full (or HideRequestsUsers-scoped) list unchanged - Add HideResult.IsAdmin flag set by HideFromOtherUsers so the engine methods can pass the caller's privilege level without an extra IsInRole call - Fix NullReferenceException in all six sort blocks: extend the sortProperty.Contains('.') guard to also cover prop == null, which occurs when the client sends an unrecognised property name - Fix double DB round-trip in MovieRequestEngine.GetRequestsByStatus: sort the already-materialised 'requests' list instead of re-issuing allRequests.ToList(), which was a redundant second query * perf(engines): push ORDER BY to DB; fix TV paginator total after DistinctBy Movies and Music: - Replace TypeDescriptor reflection sort with static ApplySortMovies / ApplySortAlbums helpers that build typed IQueryable expressions, so ORDER BY + LIMIT/OFFSET execute server-side instead of loading the full table into memory first - Remove System.ComponentModel import (TypeDescriptor no longer used) TV (ChildRequests already loaded in-memory due to FilterChildren): - Replace TypeDescriptor reflection sort with ApplySortTv switch expression (same semantics, no reflection overhead) - Move total = allRequests.Count to AFTER DistinctBy in all three GetRequests / GetRequestsByStatus / GetUnavailableRequests methods; previously total was computed before de-duplication so the paginator showed more pages than actually existed * test: fix IAsyncQueryProvider failures and restore sort-by-id case - MovieRequestEngineTests: replace movies.AsQueryable() with movies.AsQueryable().BuildMock() on all GetWithUser() setups so that CountAsync / ToListAsync have an IAsyncQueryProvider (required after the engine methods were updated to use EF async operators) - Add "id" branch to ApplySortMovies / ApplySortAlbums / ApplySortTv; the test suite uses sortProperty="id" for deterministic ordering and the TypeDescriptor-based approach handled it implicitly; the explicit switch needs to be kept in sync * fix(cypress): align TV grid element IDs with TMDB show ID for E2E tests TV grid was using item.id (DB auto-increment PK) for element IDs but Cypress tests identify rows by the TMDB show ID (externalProviderId). Switch all card-grid ID bindings to item.parentRequest.externalProviderId so selectors like #detailsButton60735 resolve correctly. Also update the page object: requestsToDisplayDropdown now points to #gridCountButton (renamed in redesign), and getRowCheckbox now uses #adminSelectCheckbox to match the movie grid template. https://claude.ai/code/session_01GcnVpVpeRah1KpH2g77oBr
1 parent 24bcfd7 commit 4e82fb1

21 files changed

Lines changed: 1058 additions & 331 deletions

File tree

src/Ombi.Core.Tests/Engine/MovieRequestEngineTests.cs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ public async Task RemoveMovieRequest_DoesNotNotify_WhenUserCannotManageRequest()
124124
public async Task GetRequestByStatus_PendingRequests_Non4K()
125125
{
126126
var movies = RegularRequestData();
127-
_repoMock.Setup(x => x.GetWithUser()).Returns(movies.AsQueryable());
127+
_repoMock.Setup(x => x.GetWithUser()).Returns(movies.AsQueryable().BuildMock());
128128

129129
var result = await _subject.GetRequestsByStatus(10, 0, "id", "asc", Models.Requests.RequestStatus.PendingApproval);
130130

@@ -169,7 +169,7 @@ public async Task GetRequestByStatus_PendingRequests_4K()
169169
RequestedDate = DateTime.MinValue
170170
}
171171
};
172-
_repoMock.Setup(x => x.GetWithUser()).Returns(movies.AsQueryable());
172+
_repoMock.Setup(x => x.GetWithUser()).Returns(movies.AsQueryable().BuildMock());
173173

174174
var result = await _subject.GetRequestsByStatus(10, 0, "id", "asc", RequestStatus.PendingApproval);
175175

@@ -216,7 +216,7 @@ public async Task GetRequestByStatus_PendingRequests_Both4K_And_Regular()
216216
RequestedDate = DateTime.MinValue
217217
}
218218
};
219-
_repoMock.Setup(x => x.GetWithUser()).Returns(movies.AsQueryable());
219+
_repoMock.Setup(x => x.GetWithUser()).Returns(movies.AsQueryable().BuildMock());
220220

221221
var result = await _subject.GetRequestsByStatus(10, 0, "id", "asc", RequestStatus.PendingApproval);
222222

@@ -230,7 +230,7 @@ public async Task GetRequestByStatus_PendingRequests_Both4K_And_Regular()
230230
public async Task GetRequestByStatus_ProcessingRequests_Non4K()
231231
{
232232
var movies = RegularRequestData();
233-
_repoMock.Setup(x => x.GetWithUser()).Returns(movies.AsQueryable());
233+
_repoMock.Setup(x => x.GetWithUser()).Returns(movies.AsQueryable().BuildMock());
234234

235235
var result = await _subject.GetRequestsByStatus(10, 0, "id", "asc", Models.Requests.RequestStatus.ProcessingRequest);
236236

@@ -275,7 +275,7 @@ public async Task GetRequestByStatus_ProcessingRequests_4K()
275275
RequestedDate = DateTime.MinValue
276276
}
277277
};
278-
_repoMock.Setup(x => x.GetWithUser()).Returns(movies.AsQueryable());
278+
_repoMock.Setup(x => x.GetWithUser()).Returns(movies.AsQueryable().BuildMock());
279279

280280
var result = await _subject.GetRequestsByStatus(10, 0, "id", "asc", RequestStatus.ProcessingRequest);
281281

@@ -323,7 +323,7 @@ public async Task GetRequestByStatus_ProcessingRequests_Both4K_And_Regular()
323323
RequestedDate = DateTime.Now
324324
}
325325
};
326-
_repoMock.Setup(x => x.GetWithUser()).Returns(movies.AsQueryable());
326+
_repoMock.Setup(x => x.GetWithUser()).Returns(movies.AsQueryable().BuildMock());
327327

328328
var result = await _subject.GetRequestsByStatus(10, 0, "id", "asc", RequestStatus.ProcessingRequest);
329329

@@ -337,7 +337,7 @@ public async Task GetRequestByStatus_ProcessingRequests_Both4K_And_Regular()
337337
public async Task GetRequestByStatus_AvailableRequests_Non4K()
338338
{
339339
List<MovieRequests> movies = RegularRequestData();
340-
_repoMock.Setup(x => x.GetWithUser()).Returns(movies.AsQueryable());
340+
_repoMock.Setup(x => x.GetWithUser()).Returns(movies.AsQueryable().BuildMock());
341341

342342
var result = await _subject.GetRequestsByStatus(10, 0, "id", "asc", Models.Requests.RequestStatus.Available);
343343

@@ -384,7 +384,7 @@ public async Task GetRequestByStatus_AvailableRequests_4K()
384384
RequestedDate = DateTime.MinValue
385385
}
386386
};
387-
_repoMock.Setup(x => x.GetWithUser()).Returns(movies.AsQueryable());
387+
_repoMock.Setup(x => x.GetWithUser()).Returns(movies.AsQueryable().BuildMock());
388388

389389
var result = await _subject.GetRequestsByStatus(10, 0, "id", "asc", RequestStatus.Available);
390390

@@ -433,7 +433,7 @@ public async Task GetRequestByStatus_AvailableRequests_Both4K_And_Regular()
433433
RequestedDate = DateTime.Now
434434
}
435435
};
436-
_repoMock.Setup(x => x.GetWithUser()).Returns(movies.AsQueryable());
436+
_repoMock.Setup(x => x.GetWithUser()).Returns(movies.AsQueryable().BuildMock());
437437

438438
var result = await _subject.GetRequestsByStatus(10, 0, "id", "asc", RequestStatus.Available);
439439

@@ -446,7 +446,7 @@ public async Task GetRequestByStatus_AvailableRequests_Both4K_And_Regular()
446446
public async Task GetRequestByStatus_DeniedRequests_Non4K()
447447
{
448448
List<MovieRequests> movies = RegularRequestData();
449-
_repoMock.Setup(x => x.GetWithUser()).Returns(movies.AsQueryable());
449+
_repoMock.Setup(x => x.GetWithUser()).Returns(movies.AsQueryable().BuildMock());
450450

451451
var result = await _subject.GetRequestsByStatus(10, 0, "id", "asc", Models.Requests.RequestStatus.Denied);
452452

@@ -491,7 +491,7 @@ public async Task GetRequestByStatus_DeniedRequests_4K()
491491
RequestedDate = DateTime.MinValue
492492
}
493493
};
494-
_repoMock.Setup(x => x.GetWithUser()).Returns(movies.AsQueryable());
494+
_repoMock.Setup(x => x.GetWithUser()).Returns(movies.AsQueryable().BuildMock());
495495

496496
var result = await _subject.GetRequestsByStatus(10, 0, "id", "asc", RequestStatus.Denied);
497497

@@ -541,7 +541,7 @@ public async Task GetRequestByStatus_DeniedRequests_Both4K_And_Regular()
541541
RequestedDate = DateTime.Now
542542
}
543543
};
544-
_repoMock.Setup(x => x.GetWithUser()).Returns(movies.AsQueryable());
544+
_repoMock.Setup(x => x.GetWithUser()).Returns(movies.AsQueryable().BuildMock());
545545

546546
var result = await _subject.GetRequestsByStatus(10, 0, "id", "asc", RequestStatus.Denied);
547547

src/Ombi.Core/Engine/BaseMediaEngine.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,14 +152,25 @@ public RequestCountModel RequestCount()
152152
};
153153
}
154154

155+
protected static IQueryable<T> FilterByRequestedUser<T>(IQueryable<T> requests, string requestedByUserId, bool isAdmin) where T : BaseRequest
156+
{
157+
if (!isAdmin || string.IsNullOrEmpty(requestedByUserId))
158+
{
159+
return requests;
160+
}
161+
162+
return requests.Where(x => x.RequestedUserId == requestedByUserId);
163+
}
164+
155165
protected async Task<HideResult> HideFromOtherUsers()
156166
{
157167
var user = await GetUser();
158168
if (await IsInRole(OmbiRoles.Admin) || await IsInRole(OmbiRoles.PowerUser) || user.IsSystemUser)
159169
{
160170
return new HideResult
161171
{
162-
UserId = user.Id
172+
UserId = user.Id,
173+
IsAdmin = true
163174
};
164175
}
165176
var settings = await Cache.GetOrAddAsync(CacheKeys.OmbiSettings, () => OmbiSettings.GetSettingsAsync());
@@ -255,6 +266,7 @@ public class HideResult
255266
{
256267
public bool Hide { get; set; }
257268
public string UserId { get; set; }
269+
public bool IsAdmin { get; set; }
258270
}
259271
}
260272
}

src/Ombi.Core/Engine/IMusicRequestEngine.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public interface IMusicRequestEngine
2222
Task<RequestEngineResult> RequestAlbum(MusicAlbumRequestViewModel model);
2323
Task<IEnumerable<AlbumRequest>> SearchAlbumRequest(string search);
2424
Task<bool> UserHasRequest(string userId);
25-
Task<RequestsViewModel<AlbumRequest>> GetRequestsByStatus(int count, int position, string sort, string sortOrder, RequestStatus available);
26-
Task<RequestsViewModel<AlbumRequest>> GetRequests(int count, int position, string sort, string sortOrder);
25+
Task<RequestsViewModel<AlbumRequest>> GetRequestsByStatus(int count, int position, string sort, string sortOrder, RequestStatus available, string requestedByUserId = null);
26+
Task<RequestsViewModel<AlbumRequest>> GetRequests(int count, int position, string sort, string sortOrder, string requestedByUserId = null);
2727
}
2828
}

src/Ombi.Core/Engine/Interfaces/IMovieRequestEngine.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,11 @@ public interface IMovieRequestEngine : IRequestEngine<MovieRequests>
2121
Task<RequestEngineResult> ApproveMovie(MovieRequests request, bool is4K);
2222
Task<RequestEngineResult> ApproveMovieById(int requestId, bool is4K);
2323
Task<RequestEngineResult> DenyMovieById(int modelId, string denyReason, bool is4K);
24-
Task<RequestsViewModel<MovieRequests>> GetRequests(int count, int position, string sortProperty, string sortOrder);
24+
Task<RequestsViewModel<MovieRequests>> GetRequests(int count, int position, string sortProperty, string sortOrder, string requestedByUserId = null);
2525

2626
Task<RequestsViewModel<MovieRequests>> GetUnavailableRequests(int count, int position, string sortProperty,
27-
string sortOrder);
28-
Task<RequestsViewModel<MovieRequests>> GetRequestsByStatus(int count, int position, string sortProperty, string sortOrder, RequestStatus status);
27+
string sortOrder, string requestedByUserId = null);
28+
Task<RequestsViewModel<MovieRequests>> GetRequestsByStatus(int count, int position, string sortProperty, string sortOrder, RequestStatus status, string requestedByUserId = null);
2929
Task<RequestEngineResult> UpdateAdvancedOptions(MediaAdvancedOptions options);
3030
}
3131
}

src/Ombi.Core/Engine/Interfaces/ITvRequestEngine.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ namespace Ombi.Core.Engine.Interfaces
99
public interface ITvRequestEngine : IRequestEngine<TvRequests>
1010
{
1111
Task<RequestsViewModel<ChildRequests>> GetUnavailableRequests(int count, int position, string sortProperty,
12-
string sortOrder);
12+
string sortOrder, string requestedByUserId = null);
1313
Task RemoveTvRequest(int requestId);
1414
Task<TvRequests> GetTvRequest(int requestId);
1515
Task<RequestEngineResult> RequestTvShow(TvRequestViewModel tv);
@@ -25,8 +25,8 @@ Task<RequestsViewModel<ChildRequests>> GetUnavailableRequests(int count, int pos
2525
Task<IEnumerable<TvRequests>> GetRequestsLite();
2626
Task UpdateQualityProfile(int requestId, int profileId);
2727
Task UpdateRootPath(int requestId, int rootPath);
28-
Task<RequestsViewModel<ChildRequests>> GetRequests(int count, int position, string sortProperty, string sortOrder);
29-
Task<RequestsViewModel<ChildRequests>> GetRequests(int count, int position, string sortProperty, string sortOrder, RequestStatus status);
28+
Task<RequestsViewModel<ChildRequests>> GetRequests(int count, int position, string sortProperty, string sortOrder, string requestedByUserId = null);
29+
Task<RequestsViewModel<ChildRequests>> GetRequests(int count, int position, string sortProperty, string sortOrder, RequestStatus status, string requestedByUserId = null);
3030
Task<RequestEngineResult> UpdateAdvancedOptions(MediaAdvancedOptions options);
3131
}
3232
}

src/Ombi.Core/Engine/MovieRequestEngine.cs

Lines changed: 28 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
using Ombi.Store.Entities;
55
using System;
66
using System.Collections.Generic;
7-
using System.ComponentModel;
87
using System.Globalization;
98
using System.Linq;
109
using System.Security.Principal;
@@ -265,7 +264,7 @@ public async Task<RequestsViewModel<MovieRequests>> GetRequests(int count, int p
265264
};
266265
}
267266

268-
public async Task<RequestsViewModel<MovieRequests>> GetRequests(int count, int position, string sortProperty, string sortOrder)
267+
public async Task<RequestsViewModel<MovieRequests>> GetRequests(int count, int position, string sortProperty, string sortOrder, string requestedByUserId = null)
269268
{
270269
var shouldHide = await HideFromOtherUsers();
271270
IQueryable<MovieRequests> allRequests;
@@ -282,24 +281,11 @@ public async Task<RequestsViewModel<MovieRequests>> GetRequests(int count, int p
282281
.GetWithUser();
283282
}
284283

285-
var prop = TypeDescriptor.GetProperties(typeof(MovieRequests)).Find(sortProperty, true);
284+
allRequests = FilterByRequestedUser(allRequests, requestedByUserId, shouldHide.IsAdmin);
286285

287-
if (sortProperty.Contains('.'))
288-
{
289-
// This is a navigation property currently not supported
290-
prop = TypeDescriptor.GetProperties(typeof(MovieRequests)).Find("RequestedDate", true);
291-
//var properties = sortProperty.Split(new []{'.'}, StringSplitOptions.RemoveEmptyEntries);
292-
//var firstProp = TypeDescriptor.GetProperties(typeof(MovieRequests)).Find(properties[0], true);
293-
//var propType = firstProp.PropertyType;
294-
//var secondProp = TypeDescriptor.GetProperties(propType).Find(properties[1], true);
295-
}
296-
297-
// TODO fix this so we execute this on the server
298-
var requests = sortOrder.Equals("asc", StringComparison.InvariantCultureIgnoreCase)
299-
? allRequests.ToList().OrderBy(x => prop.GetValue(x)).ToList()
300-
: allRequests.ToList().OrderByDescending(x => prop.GetValue(x)).ToList();
301-
var total = requests.Count();
302-
requests = requests.Skip(position).Take(count).ToList();
286+
var total = await allRequests.CountAsync();
287+
var requests = await ApplySortMovies(allRequests, sortProperty, sortOrder)
288+
.Skip(position).Take(count).ToListAsync();
303289

304290
await FillAdditionalFields(shouldHide, requests);
305291
return new RequestsViewModel<MovieRequests>
@@ -309,7 +295,7 @@ public async Task<RequestsViewModel<MovieRequests>> GetRequests(int count, int p
309295
};
310296
}
311297

312-
public async Task<RequestsViewModel<MovieRequests>> GetRequestsByStatus(int count, int position, string sortProperty, string sortOrder, RequestStatus status)
298+
public async Task<RequestsViewModel<MovieRequests>> GetRequestsByStatus(int count, int position, string sortProperty, string sortOrder, RequestStatus status, string requestedByUserId = null)
313299
{
314300
var shouldHide = await HideFromOtherUsers();
315301
IQueryable<MovieRequests> allRequests;
@@ -326,6 +312,8 @@ public async Task<RequestsViewModel<MovieRequests>> GetRequestsByStatus(int coun
326312
.GetWithUser();
327313
}
328314

315+
allRequests = FilterByRequestedUser(allRequests, requestedByUserId, shouldHide.IsAdmin);
316+
329317
switch (status)
330318
{
331319
case RequestStatus.PendingApproval:
@@ -356,8 +344,7 @@ public async Task<RequestsViewModel<MovieRequests>> GetRequestsByStatus(int coun
356344
break;
357345
}
358346

359-
var requests = allRequests.ToList();
360-
var total = requests.Count;
347+
var total = await allRequests.CountAsync();
361348
if (total == 0)
362349
{
363350
return new RequestsViewModel<MovieRequests>
@@ -367,24 +354,8 @@ public async Task<RequestsViewModel<MovieRequests>> GetRequestsByStatus(int coun
367354
};
368355
}
369356

370-
var prop = TypeDescriptor.GetProperties(typeof(MovieRequests)).Find(sortProperty, true);
371-
372-
if (sortProperty.Contains('.'))
373-
{
374-
// This is a navigation property currently not supported
375-
prop = TypeDescriptor.GetProperties(typeof(MovieRequests)).Find("RequestedDate", true);
376-
//var properties = sortProperty.Split(new []{'.'}, StringSplitOptions.RemoveEmptyEntries);
377-
//var firstProp = TypeDescriptor.GetProperties(typeof(MovieRequests)).Find(properties[0], true);
378-
//var propType = firstProp.PropertyType;
379-
//var secondProp = TypeDescriptor.GetProperties(propType).Find(properties[1], true);
380-
}
381-
382-
requests = sortOrder.Equals("asc", StringComparison.InvariantCultureIgnoreCase)
383-
? allRequests.ToList().OrderBy(x => prop.GetValue(x)).ToList()
384-
: allRequests.ToList().OrderByDescending(x => prop.GetValue(x)).ToList();
385-
386-
// TODO fix this so we execute this on the server
387-
requests = requests.Skip(position).Take(count).ToList();
357+
var requests = await ApplySortMovies(allRequests, sortProperty, sortOrder)
358+
.Skip(position).Take(count).ToListAsync();
388359

389360
await FillAdditionalFields(shouldHide, requests);
390361
return new RequestsViewModel<MovieRequests>
@@ -394,7 +365,7 @@ public async Task<RequestsViewModel<MovieRequests>> GetRequestsByStatus(int coun
394365
};
395366
}
396367

397-
public async Task<RequestsViewModel<MovieRequests>> GetUnavailableRequests(int count, int position, string sortProperty, string sortOrder)
368+
public async Task<RequestsViewModel<MovieRequests>> GetUnavailableRequests(int count, int position, string sortProperty, string sortOrder, string requestedByUserId = null)
398369
{
399370
var shouldHide = await HideFromOtherUsers();
400371
IQueryable<MovieRequests> allRequests;
@@ -411,23 +382,11 @@ public async Task<RequestsViewModel<MovieRequests>> GetUnavailableRequests(int c
411382
.GetWithUser().Where(x => !x.Available && x.Approved);
412383
}
413384

414-
var prop = TypeDescriptor.GetProperties(typeof(MovieRequests)).Find(sortProperty, true);
385+
allRequests = FilterByRequestedUser(allRequests, requestedByUserId, shouldHide.IsAdmin);
415386

416-
if (sortProperty.Contains('.'))
417-
{
418-
// This is a navigation property currently not supported
419-
prop = TypeDescriptor.GetProperties(typeof(MovieRequests)).Find("RequestedDate", true);
420-
//var properties = sortProperty.Split(new []{'.'}, StringSplitOptions.RemoveEmptyEntries);
421-
//var firstProp = TypeDescriptor.GetProperties(typeof(MovieRequests)).Find(properties[0], true);
422-
//var propType = firstProp.PropertyType;
423-
//var secondProp = TypeDescriptor.GetProperties(propType).Find(properties[1], true);
424-
}
425-
426-
var requests = (sortOrder.Equals("asc", StringComparison.InvariantCultureIgnoreCase)
427-
? allRequests.ToList().OrderBy(x => prop.GetValue(x))
428-
: allRequests.ToList().OrderByDescending(x => prop.GetValue(x))).ToList();
429-
var total = requests.Count();
430-
requests = requests.Skip(position).Take(count).ToList();
387+
var total = await allRequests.CountAsync();
388+
var requests = await ApplySortMovies(allRequests, sortProperty, sortOrder)
389+
.Skip(position).Take(count).ToListAsync();
431390

432391
await FillAdditionalFields(shouldHide, requests);
433392
return new RequestsViewModel<MovieRequests>
@@ -460,6 +419,18 @@ public async Task<RequestEngineResult> UpdateAdvancedOptions(MediaAdvancedOption
460419
};
461420
}
462421

422+
private static IQueryable<MovieRequests> ApplySortMovies(IQueryable<MovieRequests> query, string sortProperty, string sortOrder)
423+
{
424+
var asc = sortOrder.Equals("asc", StringComparison.InvariantCultureIgnoreCase);
425+
return sortProperty.ToLowerInvariant() switch
426+
{
427+
"id" => asc ? query.OrderBy(x => x.Id) : query.OrderByDescending(x => x.Id),
428+
"title" => asc ? query.OrderBy(x => x.Title) : query.OrderByDescending(x => x.Title),
429+
"releasedate" => asc ? query.OrderBy(x => x.ReleaseDate) : query.OrderByDescending(x => x.ReleaseDate),
430+
_ => asc ? query.OrderBy(x => x.RequestedDate) : query.OrderByDescending(x => x.RequestedDate)
431+
};
432+
}
433+
463434
private IQueryable<MovieRequests> OrderMovies(IQueryable<MovieRequests> allRequests, OrderType type)
464435
{
465436
switch (type)

0 commit comments

Comments
 (0)