Skip to content

Commit c4f6530

Browse files
committed
feat: New endpoint for searching for slugs
`/api/search/slug/<slug>`
1 parent 15c0397 commit c4f6530

File tree

3 files changed

+97
-59
lines changed

3 files changed

+97
-59
lines changed

CFLookup/ApiController.cs

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
using CurseForge.APIClient;
2+
using CurseForge.APIClient.Models;
3+
using CurseForge.APIClient.Models.Games;
4+
using CurseForge.APIClient.Models.Mods;
25
using Microsoft.AspNetCore.Mvc;
36
using StackExchange.Redis;
47

58
namespace CFLookup
69
{
7-
[Route("api/[controller]")]
810
[ApiController]
911
public class ApiController : ControllerBase
1012
{
@@ -42,6 +44,41 @@ public async Task<IActionResult> GetSlugProject(string game, string category, st
4244
}
4345
}
4446

47+
[HttpGet("/api/search/slug/{slug}")]
48+
public async Task<IActionResult> SearchSlug(string slug)
49+
{
50+
try
51+
{
52+
var searchForSlug = await SharedMethods.TryToFindSlug(_redis, _cfApiClient, slug);
53+
54+
var resultData = new List<ApiSlugResultRecord>();
55+
56+
foreach (var item in searchForSlug)
57+
{
58+
(var game, var category, var modList) = item.Value;
59+
60+
resultData.Add(new ApiSlugResultRecord(
61+
game,
62+
category,
63+
modList
64+
));
65+
}
66+
67+
var searchResult = new
68+
{
69+
data = resultData.Take(100),
70+
totalResults = resultData.Count,
71+
__help = "This endpoint will only list up to 100 results."
72+
};
73+
74+
return new JsonResult(searchResult);
75+
}
76+
catch
77+
{
78+
return Problem("An error occurred while searching for the slug");
79+
}
80+
}
81+
4582
[HttpGet("/{projectId:int}.json")]
4683
public async Task<IActionResult> GetProject(int projectId)
4784
{
@@ -87,4 +124,6 @@ public async Task<IActionResult> GetFile(int fileId)
87124
}
88125
}
89126
}
127+
128+
internal record ApiSlugResultRecord(Game Game, Category Category, List<Mod> Mods);
90129
}

CFLookup/Pages/Index.cshtml.cs

Lines changed: 1 addition & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ public async Task<IActionResult> OnPostAsync()
198198
}
199199
}
200200

201-
var slugProjects = await TryToFindSlug(ProjectSearchField);
201+
var slugProjects = await SharedMethods.TryToFindSlug(_redis, _cfApiClient, ProjectSearchField);
202202
if (slugProjects.Count == 0)
203203
{
204204
ErrorMessage = "You need to enter a valid project id or slug to lookup the project. (We found nothing)";
@@ -239,7 +239,6 @@ public async Task<IActionResult> OnPostAsync()
239239

240240
FoundMod = await SharedMethods.SearchModAsync(_redis, _cfApiClient, projectId);
241241

242-
243242
if (FoundMod == null)
244243
{
245244
ErrorMessage = "Could not find the project";
@@ -253,62 +252,6 @@ public async Task<IActionResult> OnPostAsync()
253252
return Page();
254253
}
255254

256-
private async Task<ConcurrentDictionary<string, (Game game, Category category, List<Mod> mods)>> TryToFindSlug(string slug)
257-
{
258-
var returnValue = new ConcurrentDictionary<string, (Game game, Category category, List<Mod> mods)>();
259-
var gameClasses = new ConcurrentDictionary<Game, List<Category>>();
260-
var games = await SharedMethods.GetGameInfo(_redis, _cfApiClient);
261-
262-
var gameClassTasks = games.Select(async game =>
263-
{
264-
var classes = (await SharedMethods.GetCategoryInfo(_redis, _cfApiClient, game.Id)).Where(c => c.IsClass ?? false).ToList() ?? new List<Category>();
265-
gameClasses.TryAdd(game, classes);
266-
});
267-
268-
await Task.WhenAll(gameClassTasks);
269-
270-
var sortedList = gameClasses.OrderByDescending(c => c.Key.Id == 432 || c.Key.Id == 1);
271-
272-
var cachedSlugSearch = await _redis.StringGetAsync($"cf-slug-search-{slug}");
273-
274-
if (!cachedSlugSearch.IsNullOrEmpty)
275-
{
276-
return JsonConvert.DeserializeObject<ConcurrentDictionary<string, (Game game, Category category, List<Mod> mods)>>(cachedSlugSearch);
277-
}
278-
279-
var keyTasks = sortedList.Select(async kv =>
280-
{
281-
var gameCategoryTasks = kv.Value.Select(async cat =>
282-
{
283-
try
284-
{
285-
var modSearch = await _cfApiClient.SearchModsAsync(kv.Key.Id, cat.Id, slug: slug);
286-
if (modSearch.Data.Count > 0)
287-
{
288-
if (!returnValue.ContainsKey($"{kv.Key.Id}-{cat.Id}"))
289-
{
290-
returnValue.TryAdd($"{kv.Key.Id}-{cat.Id}", (kv.Key, cat, new List<Mod>()));
291-
}
292-
293-
returnValue[$"{kv.Key.Id}-{cat.Id}"].mods.AddRange(modSearch.Data);
294-
}
295-
}
296-
catch
297-
{
298-
// Empty, because.. yeah
299-
}
300-
});
301-
302-
await Task.WhenAll(gameCategoryTasks);
303-
});
304-
305-
await Task.WhenAll(keyTasks);
306-
307-
await _redis.StringSetAsync($"cf-slug-search-{slug}", JsonConvert.SerializeObject(returnValue), TimeSpan.FromMinutes(5));
308-
309-
return returnValue;
310-
}
311-
312255
private async Task<string> GetProjectNameFromFile(string url)
313256
{
314257
return Path.GetFileName(url);

CFLookup/SharedMethods.cs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,62 @@ FROM DailyLatest
383383
return Stats;
384384
}
385385

386+
public static async Task<ConcurrentDictionary<string, (Game game, Category category, List<Mod> mods)>> TryToFindSlug(IDatabaseAsync _redis, ApiClient _cfApiClient, string slug)
387+
{
388+
var returnValue = new ConcurrentDictionary<string, (Game game, Category category, List<Mod> mods)>();
389+
var gameClasses = new ConcurrentDictionary<Game, List<Category>>();
390+
var games = await SharedMethods.GetGameInfo(_redis, _cfApiClient);
391+
392+
var gameClassTasks = games.Select(async game =>
393+
{
394+
var classes = (await SharedMethods.GetCategoryInfo(_redis, _cfApiClient, game.Id)).Where(c => c.IsClass ?? false).ToList() ?? new List<Category>();
395+
gameClasses.TryAdd(game, classes);
396+
});
397+
398+
await Task.WhenAll(gameClassTasks);
399+
400+
var sortedList = gameClasses.OrderByDescending(c => c.Key.Id == 432 || c.Key.Id == 1);
401+
402+
var cachedSlugSearch = await _redis.StringGetAsync($"cf-slug-search-{slug}");
403+
404+
//if (!cachedSlugSearch.IsNullOrEmpty)
405+
{
406+
//return JsonSerializer.Deserialize<ConcurrentDictionary<string, (Game game, Category category, List<Mod> mods)>>(cachedSlugSearch);
407+
}
408+
409+
var keyTasks = sortedList.Select(async kv =>
410+
{
411+
var gameCategoryTasks = kv.Value.Select(async cat =>
412+
{
413+
try
414+
{
415+
var modSearch = await _cfApiClient.SearchModsAsync(kv.Key.Id, cat.Id, slug: slug);
416+
if (modSearch.Data.Count > 0)
417+
{
418+
if (!returnValue.ContainsKey($"{kv.Key.Id}-{cat.Id}"))
419+
{
420+
returnValue.TryAdd($"{kv.Key.Id}-{cat.Id}", (kv.Key, cat, new List<Mod>()));
421+
}
422+
423+
returnValue[$"{kv.Key.Id}-{cat.Id}"].mods.AddRange(modSearch.Data);
424+
}
425+
}
426+
catch
427+
{
428+
// Empty, because.. yeah
429+
}
430+
});
431+
432+
await Task.WhenAll(gameCategoryTasks);
433+
});
434+
435+
await Task.WhenAll(keyTasks);
436+
437+
await _redis.StringSetAsync($"cf-slug-search-{slug}", JsonSerializer.Serialize(returnValue), TimeSpan.FromMinutes(5));
438+
439+
return returnValue;
440+
}
441+
386442
public static string GetProjectNameFromFile(string url)
387443
{
388444
return Path.GetFileName(url);

0 commit comments

Comments
 (0)