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]
|
[HttpGet]
|
||||||
[Authorize("identify")]
|
[Authorize("identify")]
|
||||||
[ProducesResponseType<IEnumerable<PrideFlagResponse>>(statusCode: StatusCodes.Status200OK)]
|
[ProducesResponseType<IEnumerable<UserRendererService.PrideFlagResponse>>(statusCode: StatusCodes.Status200OK)]
|
||||||
public async Task<IActionResult> GetFlagsAsync(CancellationToken ct = default)
|
public async Task<IActionResult> GetFlagsAsync(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
var flags = await db.PrideFlags.Where(f => f.UserId == CurrentUser!.Id).ToListAsync(ct);
|
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]
|
[HttpPost]
|
||||||
[Authorize("user.update")]
|
[Authorize("user.update")]
|
||||||
[ProducesResponseType<PrideFlagResponse>(statusCode: StatusCodes.Status202Accepted)]
|
[ProducesResponseType<UserRendererService.PrideFlagResponse>(statusCode: StatusCodes.Status202Accepted)]
|
||||||
public IActionResult CreateFlag([FromBody] CreateFlagRequest req)
|
public IActionResult CreateFlag([FromBody] CreateFlagRequest req)
|
||||||
{
|
{
|
||||||
ValidationUtils.Validate(ValidateFlag(req.Name, req.Description, req.Image));
|
ValidationUtils.Validate(ValidateFlag(req.Name, req.Description, req.Image));
|
||||||
|
@ -68,7 +68,7 @@ public class FlagsController(
|
||||||
db.Update(flag);
|
db.Update(flag);
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
return Ok(ToResponse(flag));
|
return Ok(userRenderer.RenderPrideFlag(flag));
|
||||||
}
|
}
|
||||||
|
|
||||||
public class UpdateFlagRequest : PatchRequest
|
public class UpdateFlagRequest : PatchRequest
|
||||||
|
@ -111,15 +111,6 @@ public class FlagsController(
|
||||||
return NoContent();
|
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)
|
private static List<(string, ValidationError?)> ValidateFlag(string? name, string? description, string? imageData)
|
||||||
{
|
{
|
||||||
var errors = new List<(string, ValidationError?)>();
|
var errors = new List<(string, ValidationError?)>();
|
||||||
|
|
|
@ -86,6 +86,12 @@ public class UsersController(
|
||||||
user.Fields = req.Fields.ToList();
|
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)))
|
if (req.HasProperty(nameof(req.Avatar)))
|
||||||
errors.Add(("avatar", ValidationUtils.ValidateAvatar(req.Avatar)));
|
errors.Add(("avatar", ValidationUtils.ValidateAvatar(req.Avatar)));
|
||||||
|
|
||||||
|
@ -182,6 +188,7 @@ public class UsersController(
|
||||||
public FieldEntry[]? Names { get; init; }
|
public FieldEntry[]? Names { get; init; }
|
||||||
public Pronoun[]? Pronouns { get; init; }
|
public Pronoun[]? Pronouns { get; init; }
|
||||||
public Field[]? Fields { 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;
|
renderAuthMethods = renderAuthMethods && tokenPrivileged;
|
||||||
|
|
||||||
IEnumerable<Member> members =
|
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.
|
// 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);
|
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
|
var authMethods = renderAuthMethods
|
||||||
? await db.AuthMethods
|
? await db.AuthMethods
|
||||||
.Where(a => a.UserId == user.Id)
|
.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.Id, user.Sid, user.Username, user.DisplayName, user.Bio, user.MemberTitle, AvatarUrlFor(user),
|
||||||
user.Links,
|
user.Links,
|
||||||
user.Names, user.Pronouns, user.Fields, user.CustomPreferences,
|
user.Names, user.Pronouns, user.Fields, user.CustomPreferences,
|
||||||
|
flags.Select(f => RenderPrideFlag(f.PrideFlag)),
|
||||||
renderMembers ? members.Select(m => memberRenderer.RenderPartialMember(m, tokenHidden)) : null,
|
renderMembers ? members.Select(m => memberRenderer.RenderPartialMember(m, tokenHidden)) : null,
|
||||||
renderAuthMethods
|
renderAuthMethods
|
||||||
? authMethods.Select(a => new AuthenticationMethodResponse(
|
? authMethods.Select(a => new AuthenticationMethodResponse(
|
||||||
|
@ -58,7 +61,7 @@ public class UserRendererService(DatabaseContext db, MemberRendererService membe
|
||||||
|
|
||||||
private string? AvatarUrlFor(User user) =>
|
private string? AvatarUrlFor(User user) =>
|
||||||
user.Avatar != null ? $"{config.MediaBaseUrl}/users/{user.Id}/avatars/{user.Avatar}.webp" : null;
|
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 string ImageUrlFor(PrideFlag flag) => $"{config.MediaBaseUrl}/flags/{flag.Hash}.webp";
|
||||||
|
|
||||||
public record UserResponse(
|
public record UserResponse(
|
||||||
|
@ -74,6 +77,7 @@ public class UserRendererService(DatabaseContext db, MemberRendererService membe
|
||||||
IEnumerable<Pronoun> Pronouns,
|
IEnumerable<Pronoun> Pronouns,
|
||||||
IEnumerable<Field> Fields,
|
IEnumerable<Field> Fields,
|
||||||
Dictionary<Snowflake, User.CustomPreference> CustomPreferences,
|
Dictionary<Snowflake, User.CustomPreference> CustomPreferences,
|
||||||
|
IEnumerable<PrideFlagResponse> Flags,
|
||||||
[property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
[property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||||
IEnumerable<MemberRendererService.PartialMember>? Members,
|
IEnumerable<MemberRendererService.PartialMember>? Members,
|
||||||
[property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
[property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||||
|
@ -105,4 +109,13 @@ public class UserRendererService(DatabaseContext db, MemberRendererService membe
|
||||||
string? AvatarUrl,
|
string? AvatarUrl,
|
||||||
Dictionary<Snowflake, User.CustomPreference> CustomPreferences
|
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…
Reference in a new issue