feat(backend): ability to set profile flags, return profile flags in get user endpoint
This commit is contained in:
		
							parent
							
								
									6a4aa8064a
								
							
						
					
					
						commit
						a3cbdc1a08
					
				
					 4 changed files with 62 additions and 15 deletions
				
			
		|  | @ -24,17 +24,17 @@ public class FlagsController( | |||
| 
 | ||||
|     [HttpGet] | ||||
|     [Authorize("identify")] | ||||
|     [ProducesResponseType<IEnumerable<PrideFlagResponse>>(statusCode: StatusCodes.Status200OK)] | ||||
|     [ProducesResponseType<IEnumerable<UserRendererService.PrideFlagResponse>>(statusCode: StatusCodes.Status200OK)] | ||||
|     public async Task<IActionResult> GetFlagsAsync(CancellationToken ct = default) | ||||
|     { | ||||
|         var flags = await db.PrideFlags.Where(f => f.UserId == CurrentUser!.Id).ToListAsync(ct); | ||||
| 
 | ||||
|         return Ok(flags.Select(ToResponse)); | ||||
|         return Ok(flags.Select(userRenderer.RenderPrideFlag)); | ||||
|     } | ||||
| 
 | ||||
|     [HttpPost] | ||||
|     [Authorize("user.update")] | ||||
|     [ProducesResponseType<PrideFlagResponse>(statusCode: StatusCodes.Status202Accepted)] | ||||
|     [ProducesResponseType<UserRendererService.PrideFlagResponse>(statusCode: StatusCodes.Status202Accepted)] | ||||
|     public IActionResult CreateFlag([FromBody] CreateFlagRequest req) | ||||
|     { | ||||
|         ValidationUtils.Validate(ValidateFlag(req.Name, req.Description, req.Image)); | ||||
|  | @ -68,7 +68,7 @@ public class FlagsController( | |||
|         db.Update(flag); | ||||
|         await db.SaveChangesAsync(); | ||||
| 
 | ||||
|         return Ok(ToResponse(flag)); | ||||
|         return Ok(userRenderer.RenderPrideFlag(flag)); | ||||
|     } | ||||
| 
 | ||||
|     public class UpdateFlagRequest : PatchRequest | ||||
|  | @ -111,15 +111,6 @@ public class FlagsController( | |||
|         return NoContent(); | ||||
|     } | ||||
| 
 | ||||
|     private PrideFlagResponse ToResponse(PrideFlag flag) => | ||||
|         new(flag.Id, userRenderer.ImageUrlFor(flag), flag.Name, flag.Description); | ||||
| 
 | ||||
|     private record PrideFlagResponse( | ||||
|         Snowflake Id, | ||||
|         string ImageUrl, | ||||
|         string Name, | ||||
|         string? Description); | ||||
| 
 | ||||
|     private static List<(string, ValidationError?)> ValidateFlag(string? name, string? description, string? imageData) | ||||
|     { | ||||
|         var errors = new List<(string, ValidationError?)>(); | ||||
|  |  | |||
|  | @ -86,6 +86,12 @@ public class UsersController( | |||
|             user.Fields = req.Fields.ToList(); | ||||
|         } | ||||
| 
 | ||||
|         if (req.Flags != null) | ||||
|         { | ||||
|             var flagError = await db.SetUserFlagsAsync(CurrentUser!.Id, req.Flags); | ||||
|             if (flagError != null) errors.Add(("flags", flagError)); | ||||
|         } | ||||
| 
 | ||||
|         if (req.HasProperty(nameof(req.Avatar))) | ||||
|             errors.Add(("avatar", ValidationUtils.ValidateAvatar(req.Avatar))); | ||||
| 
 | ||||
|  | @ -182,6 +188,7 @@ public class UsersController( | |||
|         public FieldEntry[]? Names { get; init; } | ||||
|         public Pronoun[]? Pronouns { get; init; } | ||||
|         public Field[]? Fields { get; init; } | ||||
|         public Snowflake[]? Flags { get; init; } | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										36
									
								
								Foxnouns.Backend/Database/FlagQueryExtensions.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								Foxnouns.Backend/Database/FlagQueryExtensions.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,36 @@ | |||
| using Foxnouns.Backend.Database.Models; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| 
 | ||||
| namespace Foxnouns.Backend.Database; | ||||
| 
 | ||||
| public static class FlagQueryExtensions | ||||
| { | ||||
|     private static async Task<List<PrideFlag>> GetFlagsAsync(this DatabaseContext db, Snowflake userId) => | ||||
|         await db.PrideFlags.Where(f => f.UserId == userId).OrderBy(f => f.Id).ToListAsync(); | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// Sets the user's profile flags to the given IDs. Returns a validation error if any of the flag IDs are unknown | ||||
|     /// or if too many IDs are given. Duplicates are allowed. | ||||
|     /// </summary> | ||||
|     public static async Task<ValidationError?> SetUserFlagsAsync(this DatabaseContext db, Snowflake userId, | ||||
|         Snowflake[] flagIds) | ||||
|     { | ||||
|         var currentFlags = await db.UserFlags.Where(f => f.UserId == userId).ToListAsync(); | ||||
|         foreach (var flag in currentFlags) | ||||
|             db.UserFlags.Remove(flag); | ||||
| 
 | ||||
|         // If there's no new flags to set, we're done | ||||
|         if (flagIds.Length == 0) return null; | ||||
|         if (flagIds.Length > 100) return ValidationError.LengthError("Too many profile flags", 0, 100, flagIds.Length); | ||||
| 
 | ||||
|         var flags = await db.GetFlagsAsync(userId); | ||||
|         var unknownFlagIds = flagIds.Where(id => flags.All(f => f.Id != id)).ToArray(); | ||||
|         if (unknownFlagIds.Length != 0) | ||||
|             return ValidationError.GenericValidationError("Unknown flag IDs", unknownFlagIds); | ||||
| 
 | ||||
|         var userFlags = flagIds.Select(id => new UserFlag { PrideFlagId = id, UserId = userId }); | ||||
|         db.UserFlags.AddRange(userFlags); | ||||
| 
 | ||||
|         return null; | ||||
|     } | ||||
| } | ||||
|  | @ -25,10 +25,12 @@ public class UserRendererService(DatabaseContext db, MemberRendererService membe | |||
|         renderAuthMethods = renderAuthMethods && tokenPrivileged; | ||||
| 
 | ||||
|         IEnumerable<Member> members = | ||||
|             renderMembers ? await db.Members.Where(m => m.UserId == user.Id).ToListAsync(ct) : []; | ||||
|             renderMembers ? await db.Members.Where(m => m.UserId == user.Id).OrderBy(m => m.Name).ToListAsync(ct) : []; | ||||
|         // Unless the user is requesting their own members AND the token can read hidden members, we filter out unlisted members. | ||||
|         if (!(isSelfUser && tokenCanReadHiddenMembers)) members = members.Where(m => !m.Unlisted); | ||||
| 
 | ||||
|         var flags = await db.UserFlags.Where(f => f.UserId == user.Id).OrderBy(f => f.Id).ToListAsync(ct); | ||||
| 
 | ||||
|         var authMethods = renderAuthMethods | ||||
|             ? await db.AuthMethods | ||||
|                 .Where(a => a.UserId == user.Id) | ||||
|  | @ -40,6 +42,7 @@ public class UserRendererService(DatabaseContext db, MemberRendererService membe | |||
|             user.Id, user.Sid, user.Username, user.DisplayName, user.Bio, user.MemberTitle, AvatarUrlFor(user), | ||||
|             user.Links, | ||||
|             user.Names, user.Pronouns, user.Fields, user.CustomPreferences, | ||||
|             flags.Select(f => RenderPrideFlag(f.PrideFlag)), | ||||
|             renderMembers ? members.Select(m => memberRenderer.RenderPartialMember(m, tokenHidden)) : null, | ||||
|             renderAuthMethods | ||||
|                 ? authMethods.Select(a => new AuthenticationMethodResponse( | ||||
|  | @ -58,7 +61,7 @@ public class UserRendererService(DatabaseContext db, MemberRendererService membe | |||
| 
 | ||||
|     private string? AvatarUrlFor(User user) => | ||||
|         user.Avatar != null ? $"{config.MediaBaseUrl}/users/{user.Id}/avatars/{user.Avatar}.webp" : null; | ||||
|      | ||||
| 
 | ||||
|     public string ImageUrlFor(PrideFlag flag) => $"{config.MediaBaseUrl}/flags/{flag.Hash}.webp"; | ||||
| 
 | ||||
|     public record UserResponse( | ||||
|  | @ -74,6 +77,7 @@ public class UserRendererService(DatabaseContext db, MemberRendererService membe | |||
|         IEnumerable<Pronoun> Pronouns, | ||||
|         IEnumerable<Field> Fields, | ||||
|         Dictionary<Snowflake, User.CustomPreference> CustomPreferences, | ||||
|         IEnumerable<PrideFlagResponse> Flags, | ||||
|         [property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] | ||||
|         IEnumerable<MemberRendererService.PartialMember>? Members, | ||||
|         [property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] | ||||
|  | @ -105,4 +109,13 @@ public class UserRendererService(DatabaseContext db, MemberRendererService membe | |||
|         string? AvatarUrl, | ||||
|         Dictionary<Snowflake, User.CustomPreference> CustomPreferences | ||||
|     ); | ||||
| 
 | ||||
|     public PrideFlagResponse RenderPrideFlag(PrideFlag flag) => | ||||
|         new(flag.Id, ImageUrlFor(flag), flag.Name, flag.Description); | ||||
| 
 | ||||
|     public record PrideFlagResponse( | ||||
|         Snowflake Id, | ||||
|         string ImageUrl, | ||||
|         string Name, | ||||
|         string? Description); | ||||
| } | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue