feat: GET /api/v1/members/{id}, api v1 flags
This commit is contained in:
parent
2281b3e478
commit
d182b07482
6 changed files with 229 additions and 22 deletions
|
@ -4,13 +4,36 @@ using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
namespace Foxnouns.Backend.Controllers.V1;
|
namespace Foxnouns.Backend.Controllers.V1;
|
||||||
|
|
||||||
[Route("/api/v1/users")]
|
[Route("/api/v1")]
|
||||||
public class UsersV1Controller(UsersV1Service usersV1Service) : ApiControllerBase
|
public class UsersV1Controller(UsersV1Service usersV1Service, MembersV1Service membersV1Service)
|
||||||
|
: ApiControllerBase
|
||||||
{
|
{
|
||||||
[HttpGet("{userRef}")]
|
[HttpGet("users/{userRef}")]
|
||||||
public async Task<IActionResult> GetUserAsync(string userRef, CancellationToken ct = default)
|
public async Task<IActionResult> GetUserAsync(string userRef, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
User user = await usersV1Service.ResolveUserAsync(userRef, CurrentToken, ct);
|
User user = await usersV1Service.ResolveUserAsync(userRef, CurrentToken, ct);
|
||||||
return Ok(await usersV1Service.RenderUserAsync(user));
|
return Ok(
|
||||||
|
await usersV1Service.RenderUserAsync(
|
||||||
|
user,
|
||||||
|
CurrentToken,
|
||||||
|
renderMembers: true,
|
||||||
|
renderFlags: true,
|
||||||
|
ct: ct
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("members/{id}")]
|
||||||
|
public async Task<IActionResult> GetMemberAsync(string id, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
Member member = await membersV1Service.ResolveMemberAsync(id, ct);
|
||||||
|
return Ok(
|
||||||
|
await membersV1Service.RenderMemberAsync(
|
||||||
|
member,
|
||||||
|
CurrentToken,
|
||||||
|
renderFlags: true,
|
||||||
|
ct: ct
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
44
Foxnouns.Backend/Dto/V1/Member.cs
Normal file
44
Foxnouns.Backend/Dto/V1/Member.cs
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
// ReSharper disable NotAccessedPositionalProperty.Global
|
||||||
|
using Foxnouns.Backend.Database;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace Foxnouns.Backend.Dto.V1;
|
||||||
|
|
||||||
|
public record PartialMember(
|
||||||
|
string Id,
|
||||||
|
Snowflake IdNew,
|
||||||
|
string Sid,
|
||||||
|
string Name,
|
||||||
|
string? DisplayName,
|
||||||
|
string? Bio,
|
||||||
|
string? Avatar,
|
||||||
|
string[] Links,
|
||||||
|
FieldEntry[] Names,
|
||||||
|
PronounEntry[] Pronouns
|
||||||
|
);
|
||||||
|
|
||||||
|
public record MemberResponse(
|
||||||
|
string Id,
|
||||||
|
Snowflake IdNew,
|
||||||
|
string Sid,
|
||||||
|
string Name,
|
||||||
|
string? DisplayName,
|
||||||
|
string? Bio,
|
||||||
|
string? Avatar,
|
||||||
|
string[] Links,
|
||||||
|
FieldEntry[] Names,
|
||||||
|
PronounEntry[] Pronouns,
|
||||||
|
ProfileField[] Fields,
|
||||||
|
PrideFlag[] Flags,
|
||||||
|
PartialUser User,
|
||||||
|
[property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] bool? Unlisted
|
||||||
|
);
|
||||||
|
|
||||||
|
public record PartialUser(
|
||||||
|
string Id,
|
||||||
|
Snowflake IdNew,
|
||||||
|
string Name,
|
||||||
|
string? DisplayName,
|
||||||
|
string? Avatar,
|
||||||
|
Dictionary<Guid, CustomPreference> CustomPreferences
|
||||||
|
);
|
|
@ -21,6 +21,8 @@ public record UserResponse(
|
||||||
FieldEntry[] Names,
|
FieldEntry[] Names,
|
||||||
PronounEntry[] Pronouns,
|
PronounEntry[] Pronouns,
|
||||||
ProfileField[] Fields,
|
ProfileField[] Fields,
|
||||||
|
PrideFlag[] Flags,
|
||||||
|
PartialMember[] Members,
|
||||||
int? UtcOffset,
|
int? UtcOffset,
|
||||||
Dictionary<Guid, CustomPreference> CustomPreferences
|
Dictionary<Guid, CustomPreference> CustomPreferences
|
||||||
);
|
);
|
||||||
|
@ -75,3 +77,5 @@ public record PronounEntry(string Pronouns, string? DisplayText, string Status)
|
||||||
))
|
))
|
||||||
.ToArray();
|
.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public record PrideFlag(string Id, Snowflake IdNew, string Hash, string Name, string? Description);
|
||||||
|
|
|
@ -130,7 +130,8 @@ public static class WebApplicationExtensions
|
||||||
.AddTransient<CreateFlagInvocable>()
|
.AddTransient<CreateFlagInvocable>()
|
||||||
.AddTransient<CreateDataExportInvocable>()
|
.AddTransient<CreateDataExportInvocable>()
|
||||||
// Legacy services
|
// Legacy services
|
||||||
.AddScoped<UsersV1Service>();
|
.AddScoped<UsersV1Service>()
|
||||||
|
.AddScoped<MembersV1Service>();
|
||||||
|
|
||||||
if (!config.Logging.EnableMetrics)
|
if (!config.Logging.EnableMetrics)
|
||||||
services.AddHostedService<BackgroundMetricsCollectionService>();
|
services.AddHostedService<BackgroundMetricsCollectionService>();
|
||||||
|
|
72
Foxnouns.Backend/Services/V1/MembersV1Service.cs
Normal file
72
Foxnouns.Backend/Services/V1/MembersV1Service.cs
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
using Foxnouns.Backend.Database;
|
||||||
|
using Foxnouns.Backend.Database.Models;
|
||||||
|
using Foxnouns.Backend.Dto.V1;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using FieldEntry = Foxnouns.Backend.Dto.V1.FieldEntry;
|
||||||
|
using PrideFlag = Foxnouns.Backend.Dto.V1.PrideFlag;
|
||||||
|
|
||||||
|
namespace Foxnouns.Backend.Services.V1;
|
||||||
|
|
||||||
|
public class MembersV1Service(DatabaseContext db)
|
||||||
|
{
|
||||||
|
public async Task<Member> ResolveMemberAsync(string id, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
Member? member;
|
||||||
|
if (Snowflake.TryParse(id, out Snowflake? sf))
|
||||||
|
{
|
||||||
|
member = await db
|
||||||
|
.Members.Include(m => m.User)
|
||||||
|
.FirstOrDefaultAsync(m => m.Id == sf && !m.User.Deleted, ct);
|
||||||
|
if (member != null)
|
||||||
|
return member;
|
||||||
|
}
|
||||||
|
|
||||||
|
member = await db
|
||||||
|
.Members.Include(m => m.User)
|
||||||
|
.FirstOrDefaultAsync(m => m.LegacyId == id && !m.User.Deleted, ct);
|
||||||
|
if (member != null)
|
||||||
|
return member;
|
||||||
|
|
||||||
|
throw new ApiError.NotFound("No member with that ID found.", ErrorCode.MemberNotFound);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<MemberResponse> RenderMemberAsync(
|
||||||
|
Member m,
|
||||||
|
Token? token = default,
|
||||||
|
bool renderFlags = true,
|
||||||
|
CancellationToken ct = default
|
||||||
|
)
|
||||||
|
{
|
||||||
|
bool renderUnlisted = m.UserId == token?.UserId;
|
||||||
|
|
||||||
|
List<MemberFlag> flags = renderFlags
|
||||||
|
? await db.MemberFlags.Where(f => f.MemberId == m.Id).OrderBy(f => f.Id).ToListAsync(ct)
|
||||||
|
: [];
|
||||||
|
|
||||||
|
return new MemberResponse(
|
||||||
|
m.LegacyId,
|
||||||
|
m.Id,
|
||||||
|
m.Sid,
|
||||||
|
m.Name,
|
||||||
|
m.DisplayName,
|
||||||
|
m.Bio,
|
||||||
|
m.Avatar,
|
||||||
|
m.Links,
|
||||||
|
Names: FieldEntry.FromEntries(m.Names, m.User.CustomPreferences),
|
||||||
|
Pronouns: PronounEntry.FromPronouns(m.Pronouns, m.User.CustomPreferences),
|
||||||
|
Fields: ProfileField.FromFields(m.Fields, m.User.CustomPreferences),
|
||||||
|
Flags: flags
|
||||||
|
.Where(f => f.PrideFlag.Hash != null)
|
||||||
|
.Select(f => new PrideFlag(
|
||||||
|
f.PrideFlag.LegacyId,
|
||||||
|
f.PrideFlag.Id,
|
||||||
|
f.PrideFlag.Hash!,
|
||||||
|
f.PrideFlag.Name,
|
||||||
|
f.PrideFlag.Description
|
||||||
|
))
|
||||||
|
.ToArray(),
|
||||||
|
User: UsersV1Service.RenderPartialUser(m.User),
|
||||||
|
Unlisted: renderUnlisted ? m.Unlisted : null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,6 +3,7 @@ using Foxnouns.Backend.Database.Models;
|
||||||
using Foxnouns.Backend.Dto.V1;
|
using Foxnouns.Backend.Dto.V1;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using FieldEntry = Foxnouns.Backend.Dto.V1.FieldEntry;
|
using FieldEntry = Foxnouns.Backend.Dto.V1.FieldEntry;
|
||||||
|
using PrideFlag = Foxnouns.Backend.Dto.V1.PrideFlag;
|
||||||
|
|
||||||
namespace Foxnouns.Backend.Services.V1;
|
namespace Foxnouns.Backend.Services.V1;
|
||||||
|
|
||||||
|
@ -49,8 +50,26 @@ public class UsersV1Service(DatabaseContext db)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<UserResponse> RenderUserAsync(User user)
|
public async Task<UserResponse> RenderUserAsync(
|
||||||
|
User user,
|
||||||
|
Token? token = null,
|
||||||
|
bool renderMembers = true,
|
||||||
|
bool renderFlags = true,
|
||||||
|
CancellationToken ct = default
|
||||||
|
)
|
||||||
{
|
{
|
||||||
|
bool isSelfUser = user.Id == token?.UserId;
|
||||||
|
renderMembers = renderMembers && (isSelfUser || !user.ListHidden);
|
||||||
|
|
||||||
|
// Only fetch members if we're rendering members (duh)
|
||||||
|
List<Member> members = renderMembers
|
||||||
|
? await db.Members.Where(m => m.UserId == user.Id).OrderBy(m => m.Name).ToListAsync(ct)
|
||||||
|
: [];
|
||||||
|
|
||||||
|
List<UserFlag> flags = renderFlags
|
||||||
|
? await db.UserFlags.Where(f => f.UserId == user.Id).OrderBy(f => f.Id).ToListAsync(ct)
|
||||||
|
: [];
|
||||||
|
|
||||||
int? utcOffset = null;
|
int? utcOffset = null;
|
||||||
if (
|
if (
|
||||||
user.Timezone != null
|
user.Timezone != null
|
||||||
|
@ -70,11 +89,30 @@ public class UsersV1Service(DatabaseContext db)
|
||||||
user.MemberTitle,
|
user.MemberTitle,
|
||||||
user.Avatar,
|
user.Avatar,
|
||||||
user.Links,
|
user.Links,
|
||||||
FieldEntry.FromEntries(user.Names, user.CustomPreferences),
|
Names: FieldEntry.FromEntries(user.Names, user.CustomPreferences),
|
||||||
PronounEntry.FromPronouns(user.Pronouns, user.CustomPreferences),
|
Pronouns: PronounEntry.FromPronouns(user.Pronouns, user.CustomPreferences),
|
||||||
ProfileField.FromFields(user.Fields, user.CustomPreferences),
|
Fields: ProfileField.FromFields(user.Fields, user.CustomPreferences),
|
||||||
|
Flags: flags
|
||||||
|
.Where(f => f.PrideFlag.Hash != null)
|
||||||
|
.Select(f => new PrideFlag(
|
||||||
|
f.PrideFlag.LegacyId,
|
||||||
|
f.PrideFlag.Id,
|
||||||
|
f.PrideFlag.Hash!,
|
||||||
|
f.PrideFlag.Name,
|
||||||
|
f.PrideFlag.Description
|
||||||
|
))
|
||||||
|
.ToArray(),
|
||||||
|
Members: members.Select(m => RenderPartialMember(m, user.CustomPreferences)).ToArray(),
|
||||||
utcOffset,
|
utcOffset,
|
||||||
user.CustomPreferences.Select(x =>
|
CustomPreferences: RenderCustomPreferences(user.CustomPreferences)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Dictionary<Guid, CustomPreference> RenderCustomPreferences(
|
||||||
|
Dictionary<Snowflake, User.CustomPreference> customPreferences
|
||||||
|
) =>
|
||||||
|
customPreferences
|
||||||
|
.Select(x =>
|
||||||
(
|
(
|
||||||
x.Value.LegacyId,
|
x.Value.LegacyId,
|
||||||
new CustomPreference(
|
new CustomPreference(
|
||||||
|
@ -86,7 +124,32 @@ public class UsersV1Service(DatabaseContext db)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.ToDictionary()
|
.ToDictionary();
|
||||||
|
|
||||||
|
private static PartialMember RenderPartialMember(
|
||||||
|
Member m,
|
||||||
|
Dictionary<Snowflake, User.CustomPreference> customPreferences
|
||||||
|
) =>
|
||||||
|
new(
|
||||||
|
m.LegacyId,
|
||||||
|
m.Id,
|
||||||
|
m.Sid,
|
||||||
|
m.Name,
|
||||||
|
m.DisplayName,
|
||||||
|
m.Bio,
|
||||||
|
m.Avatar,
|
||||||
|
m.Links,
|
||||||
|
Names: FieldEntry.FromEntries(m.Names, customPreferences),
|
||||||
|
Pronouns: PronounEntry.FromPronouns(m.Pronouns, customPreferences)
|
||||||
|
);
|
||||||
|
|
||||||
|
public static PartialUser RenderPartialUser(User user) =>
|
||||||
|
new(
|
||||||
|
user.LegacyId,
|
||||||
|
user.Id,
|
||||||
|
user.Username,
|
||||||
|
user.DisplayName,
|
||||||
|
user.Avatar,
|
||||||
|
CustomPreferences: RenderCustomPreferences(user.CustomPreferences)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue