-
Notifications
You must be signed in to change notification settings - Fork 6
users fetch: Added an api to query multiple users in a single request #89
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,4 @@ | ||
| .github/ | ||
| .vs/ | ||
| TestResults | ||
| .vscode/ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -318,6 +318,72 @@ public async Task<ApiResponse<List<UserSearchResult>>> Search(string search, | |
| return ApiOk(users); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// get multiple users by usernames. A max of 50 usernames can be looked up at once. | ||
| /// This endpoint performs case-sensitive username matching. | ||
| /// </summary> | ||
| /// <param name="usernames">list of usernames to find</param> | ||
| /// <param name="includeSkill">if <see cref="ApiBarUser.Skill"/> will be populated. defaults to false</param> | ||
| /// <param name="includePreviousNames">will previous names be searched against as well? defaults to false</param> | ||
| /// <param name="cancel">cancellation token</param> | ||
| /// <response code="200"> | ||
| /// the response will contain a list of <see cref="ApiBarUser"/>s that match any of the usernames | ||
| /// </response> | ||
| /// <response code="400"> | ||
| /// one or more usernames are empty, or no usernames provided | ||
| /// </response> | ||
| [HttpPost("batch")] | ||
| public async Task<ApiResponse<List<ApiBarUser>>> GetUsersByUsernames( | ||
| [FromBody] List<string> usernames, | ||
| [FromQuery] bool includeSkill = false, | ||
| [FromQuery] bool includePreviousNames = false, | ||
| CancellationToken cancel = default | ||
| ) { | ||
|
|
||
| if (usernames == null || usernames.Count == 0) { | ||
| return ApiBadRequest<List<ApiBarUser>>($"usernames list cannot be empty"); | ||
| } | ||
|
|
||
| if (usernames.Count >= 50) { | ||
| return ApiBadRequest<List<ApiBarUser>>($"usernames list cannot contain more than 50 items"); | ||
| } | ||
|
|
||
| foreach (string username in usernames) { | ||
| if (string.IsNullOrWhiteSpace(username)) { | ||
| return ApiBadRequest<List<ApiBarUser>>($"all usernames must be non-empty, found empty or whitespace-only username"); | ||
| } | ||
| } | ||
|
|
||
| List<ApiBarUser> apiUsers = await _UserRepository.GetByUsernames(usernames, includePreviousNames, cancel); | ||
| _Logger.LogInformation($"Found {apiUsers.Count} users matching usernames"); | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. unneeded, please remove |
||
|
|
||
| // Get unique users by UserID (in case there are duplicates from previous name searches) | ||
| apiUsers = apiUsers | ||
| .GroupBy(u => u.UserID) | ||
| .Select(g => g.First()) | ||
| .ToList(); | ||
|
|
||
| // Fetch skills if requested | ||
| if (includeSkill == true && apiUsers.Count > 0) { | ||
| List<long> userIds = apiUsers.Select(u => u.UserID).ToList(); | ||
| Dictionary<long, List<BarUserSkill>> skillsDict = await _SkillDb.GetByUserIDs(userIds, cancel); | ||
|
|
||
| // Populate skills for each user | ||
| foreach (ApiBarUser user in apiUsers) { | ||
| user.Skill = skillsDict.GetValueOrDefault(user.UserID) ?? new List<BarUserSkill>(); | ||
| } | ||
| } | ||
|
|
||
| // Fetch previous names if requested | ||
| if (includePreviousNames == true && apiUsers.Count > 0) { | ||
| foreach (ApiBarUser user in apiUsers) { | ||
| user.PreviousNames = await _UserRepository.GetUserNames(user.UserID, cancel); | ||
| } | ||
| } | ||
|
|
||
| return ApiOk(apiUsers); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// get the start spots of a user on a specific map, either by map name or map filename | ||
| /// </summary> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -70,5 +70,31 @@ public async Task<List<BarUserSkill>> GetByUserID(long userID, CancellationToken | |
| ))).ToList(); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// get the <see cref="BarUserSkill"/> entries for multiple users | ||
| /// </summary> | ||
| /// <param name="userIDs">List of user IDs to get skills for</param> | ||
| /// <param name="cancel">cancellation token</param> | ||
| /// <returns> | ||
| /// a dictionary mapping user IDs to lists of <see cref="BarUserSkill"/>s | ||
| /// </returns> | ||
| public async Task<Dictionary<long, List<BarUserSkill>>> GetByUserIDs(List<long> userIDs, CancellationToken cancel) { | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. strictly speaking we don't need a dictionary to return this, as |
||
| if (userIDs == null || userIDs.Count == 0) { | ||
| return new Dictionary<long, List<BarUserSkill>>(); | ||
| } | ||
|
|
||
| using NpgsqlConnection conn = _DbHelper.Connection(Dbs.MAIN); | ||
| IEnumerable<BarUserSkill> skills = (await conn.QueryAsync<BarUserSkill>(new CommandDefinition( | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. use conn.QueryListAsync here |
||
| "SELECT * FROM bar_user_skill WHERE user_id = ANY(@UserIDs)", | ||
| new { UserIDs = userIDs.ToArray() }, | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. no need to convert this to an array i think |
||
| cancellationToken: cancel | ||
| ))).ToList(); | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why declare this as an |
||
|
|
||
| // Group by user ID | ||
| return skills | ||
| .GroupBy(s => s.UserID) | ||
| .ToDictionary(g => g.Key, g => g.ToList()); | ||
| } | ||
|
|
||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,10 +1,12 @@ | ||
| using gex.Models.Db; | ||
| using gex.Models.Api; | ||
| using gex.Models.Db; | ||
| using gex.Models.UserStats; | ||
| using gex.Services.Db.UserStats; | ||
| using Microsoft.Extensions.Caching.Memory; | ||
| using Microsoft.Extensions.Logging; | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Linq; | ||
| using System.Threading; | ||
| using System.Threading.Tasks; | ||
|
|
||
|
|
@@ -83,6 +85,27 @@ public Task<List<UserSearchResult>> SearchByName(string name, bool includePrevio | |
| return _UserDb.SearchByName(name, includePreviousNames, cancel); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// get users by username matches, and optionally previous names. case-insensitive | ||
| /// </summary> | ||
| /// <param name="usernames">list of usernames to find</param> | ||
| /// <param name="includePreviousNames">will previous names be searched as well</param> | ||
| /// <param name="cancel">cancellation token</param> | ||
| /// <returns>a list of <see cref="ApiBarUser"/>s</returns> | ||
| public async Task<List<ApiBarUser>> GetByUsernames(List<string> usernames, bool includePreviousNames, CancellationToken cancel) { | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Api models are only used the API controllers, have this just return BarUser please |
||
| List<BarUser> dbUsers = await _UserDb.GetByUsernames(usernames, includePreviousNames, cancel); | ||
|
|
||
| // Convert BarUser to ApiBarUser | ||
| return dbUsers | ||
| .Select(u => new ApiBarUser { | ||
| UserID = u.UserID, | ||
| Username = u.Username, | ||
| LastUpdated = u.LastUpdated, | ||
| CountryCode = u.CountryCode | ||
| }) | ||
| .ToList(); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// get all names that a user has used | ||
| /// </summary> | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is supposed to be a >